#
tokens: 48064/50000 10/975 files (page 26/50)
lines: off (toggle) GitHub
raw markdown copy
This is page 26 of 50. Use http://codebase.md/eyaltoledano/claude-task-master?lines=false&page={x} to view the full context.

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/expand-all-tasks.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Tests for the expand-all-tasks.js module
 */
import { jest } from '@jest/globals';

// Mock the dependencies before importing the module under test
jest.unstable_mockModule(
	'../../../../../scripts/modules/task-manager/expand-task.js',
	() => ({
		default: jest.fn()
	})
);

jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
	readJSON: jest.fn(),
	log: jest.fn(),
	isSilentMode: jest.fn(() => false),
	findProjectRoot: jest.fn(() => '/test/project'),
	aggregateTelemetry: jest.fn()
}));

jest.unstable_mockModule(
	'../../../../../scripts/modules/config-manager.js',
	() => ({
		getDebugFlag: jest.fn(() => false)
	})
);

jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
	startLoadingIndicator: jest.fn(),
	stopLoadingIndicator: jest.fn(),
	displayAiUsageSummary: jest.fn()
}));

jest.unstable_mockModule('chalk', () => ({
	default: {
		white: { bold: jest.fn((text) => text) },
		cyan: jest.fn((text) => text),
		green: jest.fn((text) => text),
		gray: jest.fn((text) => text),
		red: jest.fn((text) => text),
		bold: jest.fn((text) => text)
	}
}));

jest.unstable_mockModule('boxen', () => ({
	default: jest.fn((text) => text)
}));

// Import the mocked modules
const { default: expandTask } = await import(
	'../../../../../scripts/modules/task-manager/expand-task.js'
);
const { readJSON, aggregateTelemetry, findProjectRoot } = await import(
	'../../../../../scripts/modules/utils.js'
);

// Import the module under test
const { default: expandAllTasks } = await import(
	'../../../../../scripts/modules/task-manager/expand-all-tasks.js'
);

const mockExpandTask = expandTask;
const mockReadJSON = readJSON;
const mockAggregateTelemetry = aggregateTelemetry;
const mockFindProjectRoot = findProjectRoot;

describe('expandAllTasks', () => {
	const mockTasksPath = '/test/tasks.json';
	const mockProjectRoot = '/test/project';
	const mockSession = { userId: 'test-user' };
	const mockMcpLog = {
		info: jest.fn(),
		warn: jest.fn(),
		error: jest.fn(),
		debug: jest.fn()
	};

	const sampleTasksData = {
		tag: 'master',
		tasks: [
			{
				id: 1,
				title: 'Pending Task 1',
				status: 'pending',
				subtasks: []
			},
			{
				id: 2,
				title: 'In Progress Task',
				status: 'in-progress',
				subtasks: []
			},
			{
				id: 3,
				title: 'Done Task',
				status: 'done',
				subtasks: []
			},
			{
				id: 4,
				title: 'Task with Subtasks',
				status: 'pending',
				subtasks: [{ id: '4.1', title: 'Existing subtask' }]
			}
		]
	};

	beforeEach(() => {
		jest.clearAllMocks();
		mockReadJSON.mockReturnValue(sampleTasksData);
		mockAggregateTelemetry.mockReturnValue({
			timestamp: '2024-01-01T00:00:00.000Z',
			commandName: 'expand-all-tasks',
			totalCost: 0.1,
			totalTokens: 2000,
			inputTokens: 1200,
			outputTokens: 800
		});
	});

	describe('successful expansion', () => {
		test('should expand all eligible pending tasks', async () => {
			// Arrange
			const mockTelemetryData = {
				timestamp: '2024-01-01T00:00:00.000Z',
				commandName: 'expand-task',
				totalCost: 0.05,
				totalTokens: 1000
			};

			mockExpandTask.mockResolvedValue({
				telemetryData: mockTelemetryData
			});

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				3, // numSubtasks
				false, // useResearch
				'test context', // additionalContext
				false, // force
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master'
				},
				'json' // outputFormat
			);

			// Assert
			expect(result.success).toBe(true);
			expect(result.expandedCount).toBe(2); // Tasks 1 and 2 (pending and in-progress)
			expect(result.failedCount).toBe(0);
			expect(result.skippedCount).toBe(0);
			expect(result.tasksToExpand).toBe(2);
			expect(result.telemetryData).toBeDefined();

			// Verify readJSON was called correctly
			expect(mockReadJSON).toHaveBeenCalledWith(
				mockTasksPath,
				mockProjectRoot,
				'master'
			);

			// Verify expandTask was called for eligible tasks
			expect(mockExpandTask).toHaveBeenCalledTimes(2);
			expect(mockExpandTask).toHaveBeenCalledWith(
				mockTasksPath,
				1,
				3,
				false,
				'test context',
				expect.objectContaining({
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master'
				}),
				false
			);
		});

		test('should handle force flag to expand tasks with existing subtasks', async () => {
			// Arrange
			mockExpandTask.mockResolvedValue({
				telemetryData: { commandName: 'expand-task', totalCost: 0.05 }
			});

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				2,
				false,
				'',
				true, // force = true
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master'
				},
				'json'
			);

			// Assert
			expect(result.expandedCount).toBe(3); // Tasks 1, 2, and 4 (including task with existing subtasks)
			expect(mockExpandTask).toHaveBeenCalledTimes(3);
		});

		test('should handle research flag', async () => {
			// Arrange
			mockExpandTask.mockResolvedValue({
				telemetryData: { commandName: 'expand-task', totalCost: 0.08 }
			});

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				undefined, // numSubtasks not specified
				true, // useResearch = true
				'research context',
				false,
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master'
				},
				'json'
			);

			// Assert
			expect(result.success).toBe(true);
			expect(mockExpandTask).toHaveBeenCalledWith(
				mockTasksPath,
				expect.any(Number),
				undefined,
				true, // research flag passed correctly
				'research context',
				expect.any(Object),
				false
			);
		});

		test('should pass complexityReportPath to expandTask when provided in context', async () => {
			// Arrange
			const mockComplexityReportPath =
				'/test/project/.taskmaster/reports/task-complexity-report.json';
			mockExpandTask.mockResolvedValue({
				telemetryData: { commandName: 'expand-task', totalCost: 0.05 }
			});

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				undefined, // numSubtasks not specified, should use complexity report
				false,
				'',
				false,
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master',
					complexityReportPath: mockComplexityReportPath
				},
				'json'
			);

			// Assert
			expect(result.success).toBe(true);
			expect(result.expandedCount).toBe(2); // Tasks 1 and 2

			// Verify expandTask was called with complexityReportPath in context
			expect(mockExpandTask).toHaveBeenCalledWith(
				mockTasksPath,
				expect.any(Number), // task id
				undefined, // numSubtasks
				false, // useResearch
				'', // additionalContext
				expect.objectContaining({
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master',
					complexityReportPath: mockComplexityReportPath
				}),
				false // force
			);
		});

		test('should return success with message when no tasks are eligible', async () => {
			// Arrange - Mock tasks data with no eligible tasks
			const noEligibleTasksData = {
				tag: 'master',
				tasks: [
					{ id: 1, status: 'done', subtasks: [] },
					{
						id: 2,
						status: 'pending',
						subtasks: [{ id: '2.1', title: 'existing' }]
					}
				]
			};
			mockReadJSON.mockReturnValue(noEligibleTasksData);

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				3,
				false,
				'',
				false, // force = false, so task with subtasks won't be expanded
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master'
				},
				'json'
			);

			// Assert
			expect(result.success).toBe(true);
			expect(result.expandedCount).toBe(0);
			expect(result.failedCount).toBe(0);
			expect(result.skippedCount).toBe(0);
			expect(result.tasksToExpand).toBe(0);
			expect(result.message).toBe('No tasks eligible for expansion.');
			expect(mockExpandTask).not.toHaveBeenCalled();
		});
	});

	describe('error handling', () => {
		test('should handle expandTask failures gracefully', async () => {
			// Arrange
			mockExpandTask
				.mockResolvedValueOnce({ telemetryData: { totalCost: 0.05 } }) // First task succeeds
				.mockRejectedValueOnce(new Error('AI service error')); // Second task fails

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				3,
				false,
				'',
				false,
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master'
				},
				'json'
			);

			// Assert
			expect(result.success).toBe(true);
			expect(result.expandedCount).toBe(1);
			expect(result.failedCount).toBe(1);
		});

		test('should throw error when tasks.json is invalid', async () => {
			// Arrange
			mockReadJSON.mockReturnValue(null);

			// Act & Assert
			await expect(
				expandAllTasks(
					mockTasksPath,
					3,
					false,
					'',
					false,
					{
						session: mockSession,
						mcpLog: mockMcpLog,
						projectRoot: mockProjectRoot,
						tag: 'master'
					},
					'json'
				)
			).rejects.toThrow('Invalid tasks data');
		});

		test('should throw error when project root cannot be determined', async () => {
			// Arrange - Mock findProjectRoot to return null for this test
			mockFindProjectRoot.mockReturnValueOnce(null);

			// Act & Assert
			await expect(
				expandAllTasks(
					mockTasksPath,
					3,
					false,
					'',
					false,
					{
						session: mockSession,
						mcpLog: mockMcpLog,
						tag: 'master'
						// No projectRoot provided, and findProjectRoot will return null
					},
					'json'
				)
			).rejects.toThrow('Could not determine project root directory');
		});
	});

	describe('telemetry aggregation', () => {
		test('should aggregate telemetry data from multiple expand operations', async () => {
			// Arrange
			const telemetryData1 = {
				commandName: 'expand-task',
				totalCost: 0.03,
				totalTokens: 600
			};
			const telemetryData2 = {
				commandName: 'expand-task',
				totalCost: 0.04,
				totalTokens: 800
			};

			mockExpandTask
				.mockResolvedValueOnce({ telemetryData: telemetryData1 })
				.mockResolvedValueOnce({ telemetryData: telemetryData2 });

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				3,
				false,
				'',
				false,
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master'
				},
				'json'
			);

			// Assert
			expect(mockAggregateTelemetry).toHaveBeenCalledWith(
				[telemetryData1, telemetryData2],
				'expand-all-tasks'
			);
			expect(result.telemetryData).toBeDefined();
			expect(result.telemetryData.commandName).toBe('expand-all-tasks');
		});

		test('should handle missing telemetry data gracefully', async () => {
			// Arrange
			mockExpandTask.mockResolvedValue({}); // No telemetryData

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				3,
				false,
				'',
				false,
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'master'
				},
				'json'
			);

			// Assert
			expect(result.success).toBe(true);
			expect(mockAggregateTelemetry).toHaveBeenCalledWith(
				[],
				'expand-all-tasks'
			);
		});
	});

	describe('output format handling', () => {
		test('should use text output format for CLI calls', async () => {
			// Arrange
			mockExpandTask.mockResolvedValue({
				telemetryData: { commandName: 'expand-task', totalCost: 0.05 }
			});

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				3,
				false,
				'',
				false,
				{
					projectRoot: mockProjectRoot,
					tag: 'master'
					// No mcpLog provided, should use CLI logger
				},
				'text' // CLI output format
			);

			// Assert
			expect(result.success).toBe(true);
			// In text mode, loading indicators and console output would be used
			// This is harder to test directly but we can verify the result structure
		});

		test('should handle context tag properly', async () => {
			// Arrange
			const taggedTasksData = {
				...sampleTasksData,
				tag: 'feature-branch'
			};
			mockReadJSON.mockReturnValue(taggedTasksData);
			mockExpandTask.mockResolvedValue({
				telemetryData: { commandName: 'expand-task', totalCost: 0.05 }
			});

			// Act
			const result = await expandAllTasks(
				mockTasksPath,
				3,
				false,
				'',
				false,
				{
					session: mockSession,
					mcpLog: mockMcpLog,
					projectRoot: mockProjectRoot,
					tag: 'feature-branch'
				},
				'json'
			);

			// Assert
			expect(mockReadJSON).toHaveBeenCalledWith(
				mockTasksPath,
				mockProjectRoot,
				'feature-branch'
			);
			expect(mockExpandTask).toHaveBeenCalledWith(
				mockTasksPath,
				expect.any(Number),
				3,
				false,
				'',
				expect.objectContaining({
					tag: 'feature-branch'
				}),
				false
			);
		});
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/integration/services/export.service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Export Service
 * Core service for exporting tasks to external systems (e.g., Hamster briefs)
 */

import {
	ERROR_CODES,
	TaskMasterError
} from '../../../common/errors/task-master-error.js';
import type { Task, TaskStatus } from '../../../common/types/index.js';
import { AuthManager } from '../../auth/managers/auth-manager.js';
import type { UserContext } from '../../auth/types.js';
import { ConfigManager } from '../../config/managers/config-manager.js';
import { FileStorage } from '../../storage/adapters/file-storage/index.js';

// Type definitions for the bulk API response
interface TaskImportResult {
	externalId?: string;
	index: number;
	success: boolean;
	taskId?: string;
	error?: string;
	validationErrors?: string[];
}

interface BulkTasksResponse {
	dryRun: boolean;
	totalTasks: number;
	successCount: number;
	failedCount: number;
	skippedCount: number;
	results: TaskImportResult[];
	summary: {
		message: string;
		duration: number;
	};
}

/**
 * Options for exporting tasks
 */
export interface ExportTasksOptions {
	/** Optional tag to export tasks from (uses active tag if not provided) */
	tag?: string;
	/** Brief ID to export to */
	briefId?: string;
	/** Organization ID (required if briefId is provided) */
	orgId?: string;
	/** Filter by task status */
	status?: TaskStatus;
	/** Exclude subtasks from export (default: false, subtasks included by default) */
	excludeSubtasks?: boolean;
}

/**
 * Result of the export operation
 */
export interface ExportResult {
	/** Whether the export was successful */
	success: boolean;
	/** Number of tasks exported */
	taskCount: number;
	/** The brief ID tasks were exported to */
	briefId: string;
	/** The organization ID */
	orgId: string;
	/** Optional message */
	message?: string;
	/** Error details if export failed */
	error?: {
		code: string;
		message: string;
	};
}

/**
 * Brief information from API
 */
export interface Brief {
	id: string;
	accountId: string;
	createdAt: string;
	name?: string;
}

/**
 * ExportService handles task export to external systems
 */
export class ExportService {
	private configManager: ConfigManager;
	private authManager: AuthManager;

	constructor(configManager: ConfigManager, authManager: AuthManager) {
		this.configManager = configManager;
		this.authManager = authManager;
	}

	/**
	 * Export tasks to a brief
	 */
	async exportTasks(options: ExportTasksOptions): Promise<ExportResult> {
		const isAuthenticated = await this.authManager.hasValidSession();
		// Validate authentication
		if (!isAuthenticated) {
			throw new TaskMasterError(
				'Authentication required for export',
				ERROR_CODES.AUTHENTICATION_ERROR
			);
		}

		// Get current context
		const context = await this.authManager.getContext();

		// Determine org and brief IDs
		let orgId = options.orgId || context?.orgId;
		let briefId = options.briefId || context?.briefId;

		// Validate we have necessary IDs
		if (!orgId) {
			throw new TaskMasterError(
				'Organization ID is required for export. Use "tm context org" to select one.',
				ERROR_CODES.MISSING_CONFIGURATION
			);
		}

		if (!briefId) {
			throw new TaskMasterError(
				'Brief ID is required for export. Use "tm context brief" or provide --brief flag.',
				ERROR_CODES.MISSING_CONFIGURATION
			);
		}

		// Get tasks from the specified or active tag
		const activeTag = this.configManager.getActiveTag();
		const tag = options.tag || activeTag;

		// Always read tasks from local file storage for export
		// (we're exporting local tasks to a remote brief)
		const fileStorage = new FileStorage(this.configManager.getProjectRoot());
		await fileStorage.initialize();

		// Load tasks with filters applied at storage layer
		const filteredTasks = await fileStorage.loadTasks(tag, {
			status: options.status,
			excludeSubtasks: options.excludeSubtasks
		});

		// Get total count (without filters) for comparison
		const allTasks = await fileStorage.loadTasks(tag);

		const taskListResult = {
			tasks: filteredTasks,
			total: allTasks.length,
			filtered: filteredTasks.length,
			tag,
			storageType: 'file' as const
		};

		if (taskListResult.tasks.length === 0) {
			return {
				success: false,
				taskCount: 0,
				briefId,
				orgId,
				message: 'No tasks found to export',
				error: {
					code: 'NO_TASKS',
					message: 'No tasks match the specified criteria'
				}
			};
		}

		try {
			// Call the export API with the original tasks
			// performExport will handle the transformation based on the method used
			await this.performExport(orgId, briefId, taskListResult.tasks);

			return {
				success: true,
				taskCount: taskListResult.tasks.length,
				briefId,
				orgId,
				message: `Successfully exported ${taskListResult.tasks.length} task(s) to brief`
			};
		} catch (error) {
			const errorMessage =
				error instanceof Error ? error.message : String(error);

			return {
				success: false,
				taskCount: 0,
				briefId,
				orgId,
				error: {
					code: 'EXPORT_FAILED',
					message: errorMessage
				}
			};
		}
	}

	/**
	 * Export tasks from a brief ID or URL
	 */
	async exportFromBriefInput(briefInput: string): Promise<ExportResult> {
		// Extract brief ID from input
		const briefId = this.extractBriefId(briefInput);
		if (!briefId) {
			throw new TaskMasterError(
				'Invalid brief ID or URL provided',
				ERROR_CODES.VALIDATION_ERROR
			);
		}

		// Fetch brief to get organization
		const brief = await this.authManager.getBrief(briefId);
		if (!brief) {
			throw new TaskMasterError(
				'Brief not found or you do not have access',
				ERROR_CODES.NOT_FOUND
			);
		}

		// Export with the resolved org and brief
		return this.exportTasks({
			orgId: brief.accountId,
			briefId: brief.id
		});
	}

	/**
	 * Validate export context before prompting
	 */
	async validateContext(): Promise<{
		hasOrg: boolean;
		hasBrief: boolean;
		context: UserContext | null;
	}> {
		const context = await this.authManager.getContext();

		return {
			hasOrg: !!context?.orgId,
			hasBrief: !!context?.briefId,
			context
		};
	}

	/**
	 * Transform tasks for API bulk import format (flat structure)
	 */
	private transformTasksForBulkImport(tasks: Task[]): any[] {
		const flatTasks: any[] = [];

		// Process each task and its subtasks
		tasks.forEach((task) => {
			// Add parent task
			flatTasks.push({
				externalId: String(task.id),
				title: task.title,
				description: this.enrichDescription(task),
				status: this.mapStatusForAPI(task.status),
				priority: task.priority || 'medium',
				dependencies: task.dependencies?.map(String) || [],
				details: task.details,
				testStrategy: task.testStrategy,
				complexity: task.complexity,
				metadata: {
					complexity: task.complexity,
					originalId: task.id,
					originalDescription: task.description,
					originalDetails: task.details,
					originalTestStrategy: task.testStrategy
				}
			});

			// Add subtasks if they exist
			if (task.subtasks && task.subtasks.length > 0) {
				task.subtasks.forEach((subtask) => {
					flatTasks.push({
						externalId: `${task.id}.${subtask.id}`,
						parentExternalId: String(task.id),
						title: subtask.title,
						description: this.enrichDescription(subtask),
						status: this.mapStatusForAPI(subtask.status),
						priority: subtask.priority || 'medium',
						dependencies:
							subtask.dependencies?.map((dep) => {
								// Convert subtask dependencies to full ID format
								if (String(dep).includes('.')) {
									return String(dep);
								}
								return `${task.id}.${dep}`;
							}) || [],
						details: subtask.details,
						testStrategy: subtask.testStrategy,
						complexity: subtask.complexity,
						metadata: {
							complexity: subtask.complexity,
							originalId: subtask.id,
							originalDescription: subtask.description,
							originalDetails: subtask.details,
							originalTestStrategy: subtask.testStrategy
						}
					});
				});
			}
		});

		return flatTasks;
	}

	/**
	 * Enrich task/subtask description with implementation details and test strategy
	 * Creates a comprehensive markdown-formatted description
	 */
	private enrichDescription(taskOrSubtask: Task | any): string {
		const sections: string[] = [];

		// Start with original description if it exists
		if (taskOrSubtask.description) {
			sections.push(taskOrSubtask.description);
		}

		// Add implementation details section
		if (taskOrSubtask.details) {
			sections.push('## Implementation Details\n');
			sections.push(taskOrSubtask.details);
		}

		// Add test strategy section
		if (taskOrSubtask.testStrategy) {
			sections.push('## Test Strategy\n');
			sections.push(taskOrSubtask.testStrategy);
		}

		// Join sections with double newlines for better markdown formatting
		return sections.join('\n\n').trim() || 'No description provided';
	}

	/**
	 * Map internal status to API status format
	 */
	private mapStatusForAPI(status?: string): string {
		switch (status) {
			case 'pending':
				return 'todo';
			case 'in-progress':
				return 'in_progress';
			case 'done':
				return 'done';
			default:
				return 'todo';
		}
	}

	/**
	 * Perform the actual export API call
	 */
	private async performExport(
		orgId: string,
		briefId: string,
		tasks: any[]
	): Promise<void> {
		// Check if we should use the API endpoint or direct Supabase
		const apiEndpoint =
			process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN;

		if (apiEndpoint) {
			// Use the new bulk import API endpoint
			const apiUrl = `${apiEndpoint}/ai/api/v1/briefs/${briefId}/tasks`;

			// Transform tasks to flat structure for API
			const flatTasks = this.transformTasksForBulkImport(tasks);

			// Prepare request body
			const requestBody = {
				source: 'task-master-cli',
				options: {
					dryRun: false,
					stopOnError: false
				},
				accountId: orgId,
				tasks: flatTasks
			};

			// Get auth token
			const accessToken = await this.authManager.getAccessToken();
			if (!accessToken) {
				throw new Error('Not authenticated');
			}

			// Make API request
			const response = await fetch(apiUrl, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
					Authorization: `Bearer ${accessToken}`
				},
				body: JSON.stringify(requestBody)
			});

			if (!response.ok) {
				const errorText = await response.text();
				throw new Error(
					`API request failed: ${response.status} - ${errorText}`
				);
			}

			const result = (await response.json()) as BulkTasksResponse;

			if (result.failedCount > 0) {
				const failedTasks = result.results
					.filter((r) => !r.success)
					.map((r) => `${r.externalId}: ${r.error}`)
					.join(', ');
				console.warn(
					`Warning: ${result.failedCount} tasks failed to import: ${failedTasks}`
				);
			}

			console.log(
				`Successfully exported ${result.successCount} of ${result.totalTasks} tasks to brief ${briefId}`
			);
		} else {
			// Direct Supabase approach is no longer supported
			// The extractTasks method has been removed from SupabaseRepository
			// as we now exclusively use the API endpoint for exports
			throw new Error(
				'Export API endpoint not configured. Please set TM_PUBLIC_BASE_DOMAIN environment variable to enable task export.'
			);
		}
	}

	/**
	 * Extract a brief ID from raw input (ID or URL)
	 */
	private extractBriefId(input: string): string | null {
		const raw = input?.trim() ?? '';
		if (!raw) return null;

		const parseUrl = (s: string): URL | null => {
			try {
				return new URL(s);
			} catch {}
			try {
				return new URL(`https://${s}`);
			} catch {}
			return null;
		};

		const fromParts = (path: string): string | null => {
			const parts = path.split('/').filter(Boolean);
			const briefsIdx = parts.lastIndexOf('briefs');
			const candidate =
				briefsIdx >= 0 && parts.length > briefsIdx + 1
					? parts[briefsIdx + 1]
					: parts[parts.length - 1];
			return candidate?.trim() || null;
		};

		// Try to parse as URL
		const url = parseUrl(raw);
		if (url) {
			const qId = url.searchParams.get('id') || url.searchParams.get('briefId');
			const candidate = (qId || fromParts(url.pathname)) ?? null;
			if (candidate) {
				if (this.isLikelyId(candidate) || candidate.length >= 8) {
					return candidate;
				}
			}
		}

		// Check if it looks like a path without scheme
		if (raw.includes('/')) {
			const candidate = fromParts(raw);
			if (candidate && (this.isLikelyId(candidate) || candidate.length >= 8)) {
				return candidate;
			}
		}

		// Return as-is if it looks like an ID
		if (this.isLikelyId(raw) || raw.length >= 8) {
			return raw;
		}

		return null;
	}

	/**
	 * Check if a string looks like a brief ID (UUID-like)
	 */
	private isLikelyId(value: string): boolean {
		const uuidRegex =
			/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
		const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
		const slugRegex = /^[A-Za-z0-9_-]{16,}$/;
		return (
			uuidRegex.test(value) || ulidRegex.test(value) || slugRegex.test(value)
		);
	}
}

```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/expand-task.js:
--------------------------------------------------------------------------------

```javascript
import fs from 'fs';

import { readJSON, writeJSON } from '../utils.js';

import {
	displayAiUsageSummary,
	startLoadingIndicator,
	stopLoadingIndicator
} from '../ui.js';

import { COMMAND_SCHEMAS } from '../../../src/schemas/registry.js';
import { generateObjectService } from '../ai-services-unified.js';

import {
	getDefaultSubtasks,
	hasCodebaseAnalysis,
	getDebugFlag
} from '../config-manager.js';
import { getPromptManager } from '../prompt-manager.js';
import { findProjectRoot, flattenTasksWithSubtasks } from '../utils.js';
import { ContextGatherer } from '../utils/contextGatherer.js';
import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
import { tryExpandViaRemote } from '@tm/bridge';
import { createBridgeLogger } from '../bridge-utils.js';

/**
 * Expand a task into subtasks using the unified AI service (generateObjectService).
 * Appends new subtasks by default. Replaces existing subtasks if force=true.
 * Integrates complexity report to determine subtask count and prompt if available,
 * unless numSubtasks is explicitly provided.
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {number} taskId - Task ID to expand
 * @param {number | null | undefined} [numSubtasks] - Optional: Explicit target number of subtasks. If null/undefined, check complexity report or config default.
 * @param {boolean} [useResearch=false] - Whether to use the research AI role.
 * @param {string} [additionalContext=''] - Optional additional context.
 * @param {Object} context - Context object containing session and mcpLog.
 * @param {Object} [context.session] - Session object from MCP.
 * @param {Object} [context.mcpLog] - MCP logger object.
 * @param {string} [context.projectRoot] - Project root path
 * @param {string} [context.tag] - Tag for the task
 * @param {boolean} [force=false] - If true, replace existing subtasks; otherwise, append.
 * @returns {Promise<Object>} The updated parent task object with new subtasks.
 * @throws {Error} If task not found, AI service fails, or parsing fails.
 */
async function expandTask(
	tasksPath,
	taskId,
	numSubtasks,
	useResearch = false,
	additionalContext = '',
	context = {},
	force = false
) {
	const {
		session,
		mcpLog,
		projectRoot: contextProjectRoot,
		tag,
		complexityReportPath
	} = context;
	const outputFormat = mcpLog ? 'json' : 'text';

	// Determine projectRoot: Use from context if available, otherwise derive from tasksPath
	const projectRoot = contextProjectRoot || findProjectRoot(tasksPath);

	// Create unified logger and report function
	const { logger, report, isMCP } = createBridgeLogger(mcpLog, session);

	if (isMCP) {
		logger.info(`expandTask called with context: session=${!!session}`);
	}

	try {
		// --- BRIDGE: Try remote expansion first (API storage) ---
		const remoteResult = await tryExpandViaRemote({
			taskId,
			numSubtasks,
			useResearch,
			additionalContext,
			force,
			projectRoot,
			tag,
			isMCP,
			outputFormat,
			report
		});

		// If remote handled it, return the result
		if (remoteResult) {
			return remoteResult;
		}
		// Otherwise fall through to file-based logic below
		// --- End BRIDGE ---

		// --- Task Loading/Filtering (Unchanged) ---
		logger.info(`Reading tasks from ${tasksPath}`);
		const data = readJSON(tasksPath, projectRoot, tag);
		if (!data || !data.tasks)
			throw new Error(`Invalid tasks data in ${tasksPath}`);
		const taskIndex = data.tasks.findIndex(
			(t) => t.id === parseInt(taskId, 10)
		);
		if (taskIndex === -1) throw new Error(`Task ${taskId} not found`);
		const task = data.tasks[taskIndex];
		logger.info(
			`Expanding task ${taskId}: ${task.title}${useResearch ? ' with research' : ''}`
		);
		// --- End Task Loading/Filtering ---

		// --- Handle Force Flag: Clear existing subtasks if force=true ---
		if (force && Array.isArray(task.subtasks) && task.subtasks.length > 0) {
			logger.info(
				`Force flag set. Clearing existing ${task.subtasks.length} subtasks for task ${taskId}.`
			);
			task.subtasks = []; // Clear existing subtasks
		}
		// --- End Force Flag Handling ---

		// --- Context Gathering ---
		let gatheredContext = '';
		try {
			const contextGatherer = new ContextGatherer(projectRoot, tag);
			const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
			const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'expand-task');
			const searchQuery = `${task.title} ${task.description}`;
			const searchResults = fuzzySearch.findRelevantTasks(searchQuery, {
				maxResults: 5,
				includeSelf: true
			});
			const relevantTaskIds = fuzzySearch.getTaskIds(searchResults);

			const finalTaskIds = [
				...new Set([taskId.toString(), ...relevantTaskIds])
			];

			if (finalTaskIds.length > 0) {
				const contextResult = await contextGatherer.gather({
					tasks: finalTaskIds,
					format: 'research'
				});
				gatheredContext = contextResult.context || '';
			}
		} catch (contextError) {
			logger.warn(`Could not gather context: ${contextError.message}`);
		}
		// --- End Context Gathering ---

		// --- Complexity Report Integration ---
		let finalSubtaskCount;
		let complexityReasoningContext = '';
		let taskAnalysis = null;

		logger.info(
			`Looking for complexity report at: ${complexityReportPath}${tag !== 'master' ? ` (tag-specific for '${tag}')` : ''}`
		);

		try {
			if (fs.existsSync(complexityReportPath)) {
				const complexityReport = readJSON(complexityReportPath);
				taskAnalysis = complexityReport?.complexityAnalysis?.find(
					(a) => a.taskId === task.id
				);
				if (taskAnalysis) {
					logger.info(
						`Found complexity analysis for task ${task.id}: Score ${taskAnalysis.complexityScore}`
					);
					if (taskAnalysis.reasoning) {
						complexityReasoningContext = `\nComplexity Analysis Reasoning: ${taskAnalysis.reasoning}`;
					}
				} else {
					logger.info(
						`No complexity analysis found for task ${task.id} in report.`
					);
				}
			} else {
				logger.info(
					`Complexity report not found at ${complexityReportPath}. Skipping complexity check.`
				);
			}
		} catch (reportError) {
			logger.warn(
				`Could not read or parse complexity report: ${reportError.message}. Proceeding without it.`
			);
		}

		// Determine final subtask count
		const explicitNumSubtasks = parseInt(numSubtasks, 10);
		if (!Number.isNaN(explicitNumSubtasks) && explicitNumSubtasks >= 0) {
			finalSubtaskCount = explicitNumSubtasks;
			logger.info(
				`Using explicitly provided subtask count: ${finalSubtaskCount}`
			);
		} else if (taskAnalysis?.recommendedSubtasks) {
			finalSubtaskCount = parseInt(taskAnalysis.recommendedSubtasks, 10);
			logger.info(
				`Using subtask count from complexity report: ${finalSubtaskCount}`
			);
		} else {
			finalSubtaskCount = getDefaultSubtasks(session);
			logger.info(`Using default number of subtasks: ${finalSubtaskCount}`);
		}
		if (Number.isNaN(finalSubtaskCount) || finalSubtaskCount < 0) {
			logger.warn(
				`Invalid subtask count determined (${finalSubtaskCount}), defaulting to 3.`
			);
			finalSubtaskCount = 3;
		}

		// Determine prompt content AND system prompt
		// Calculate the next subtask ID to match current behavior:
		// - Start from the number of existing subtasks + 1
		// - This creates sequential IDs: 1, 2, 3, 4...
		// - Display format shows as parentTaskId.subtaskId (e.g., "1.1", "1.2", "2.1")
		const nextSubtaskId = (task.subtasks?.length || 0) + 1;

		// Load prompts using PromptManager
		const promptManager = getPromptManager();

		// Check if a codebase analysis provider is being used
		const hasCodebaseAnalysisCapability = hasCodebaseAnalysis(
			useResearch,
			projectRoot,
			session
		);

		// Combine all context sources into a single additionalContext parameter
		let combinedAdditionalContext = '';
		if (additionalContext || complexityReasoningContext) {
			combinedAdditionalContext =
				`\n\n${additionalContext}${complexityReasoningContext}`.trim();
		}
		if (gatheredContext) {
			combinedAdditionalContext =
				`${combinedAdditionalContext}\n\n# Project Context\n\n${gatheredContext}`.trim();
		}

		// Ensure expansionPrompt is a string (handle both string and object formats)
		let expansionPromptText = undefined;
		if (taskAnalysis?.expansionPrompt) {
			if (typeof taskAnalysis.expansionPrompt === 'string') {
				expansionPromptText = taskAnalysis.expansionPrompt;
			} else if (
				typeof taskAnalysis.expansionPrompt === 'object' &&
				taskAnalysis.expansionPrompt.text
			) {
				expansionPromptText = taskAnalysis.expansionPrompt.text;
			}
		}

		// Ensure gatheredContext is a string (handle both string and object formats)
		let gatheredContextText = gatheredContext;
		if (typeof gatheredContext === 'object' && gatheredContext !== null) {
			if (gatheredContext.data) {
				gatheredContextText = gatheredContext.data;
			} else if (gatheredContext.text) {
				gatheredContextText = gatheredContext.text;
			} else {
				gatheredContextText = JSON.stringify(gatheredContext);
			}
		}

		const promptParams = {
			task: task,
			subtaskCount: finalSubtaskCount,
			nextSubtaskId: nextSubtaskId,
			additionalContext: additionalContext,
			complexityReasoningContext: complexityReasoningContext,
			gatheredContext: gatheredContextText || '',
			useResearch: useResearch,
			expansionPrompt: expansionPromptText || undefined,
			hasCodebaseAnalysis: hasCodebaseAnalysisCapability,
			projectRoot: projectRoot || ''
		};
		let variantKey = 'default';
		if (expansionPromptText) {
			variantKey = 'complexity-report';
			logger.info(
				`Using expansion prompt from complexity report for task ${task.id}.`
			);
		} else if (useResearch) {
			variantKey = 'research';
			logger.info(`Using research variant for task ${task.id}.`);
		} else {
			logger.info(`Using standard prompt generation for task ${task.id}.`);
		}

		const { systemPrompt, userPrompt: promptContent } =
			promptManager.loadPrompt('expand-task', promptParams, variantKey);

		// Debug logging to identify the issue
		logger.debug(`Selected variant: ${variantKey}`);
		logger.debug(
			`Prompt params passed: ${JSON.stringify(promptParams, null, 2)}`
		);
		logger.debug(
			`System prompt (first 500 chars): ${systemPrompt.substring(0, 500)}...`
		);
		logger.debug(
			`User prompt (first 500 chars): ${promptContent.substring(0, 500)}...`
		);
		// --- End Complexity Report / Prompt Logic ---

		// --- AI Subtask Generation using generateObjectService ---
		let generatedSubtasks = [];
		let loadingIndicator = null;
		if (outputFormat === 'text') {
			loadingIndicator = startLoadingIndicator(
				`Generating ${finalSubtaskCount || 'appropriate number of'} subtasks...\n`
			);
		}

		let aiServiceResponse = null;
		try {
			const role = useResearch ? 'research' : 'main';

			// Call generateObjectService with the determined prompts and telemetry params
			aiServiceResponse = await generateObjectService({
				prompt: promptContent,
				systemPrompt: systemPrompt,
				role,
				session,
				projectRoot,
				schema: COMMAND_SCHEMAS['expand-task'],
				objectName: 'subtasks',
				commandName: 'expand-task',
				outputType: outputFormat
			});

			// With generateObject, we expect structured data – verify it before use
			const mainResult = aiServiceResponse?.mainResult;
			if (!mainResult || !Array.isArray(mainResult.subtasks)) {
				throw new Error('AI response did not include a valid subtasks array.');
			}
			generatedSubtasks = mainResult.subtasks;
			logger.info(`Received ${generatedSubtasks.length} subtasks from AI.`);
		} catch (error) {
			if (loadingIndicator) stopLoadingIndicator(loadingIndicator);
			logger.error(
				`Error during AI call or parsing for task ${taskId}: ${error.message}`, // Added task ID context
				'error'
			);
			throw error;
		} finally {
			if (loadingIndicator) stopLoadingIndicator(loadingIndicator);
		}

		// --- Task Update & File Writing ---
		// Ensure task.subtasks is an array before appending
		if (!Array.isArray(task.subtasks)) {
			task.subtasks = [];
		}
		// Append the newly generated and validated subtasks
		task.subtasks.push(...generatedSubtasks);
		// --- End Change: Append instead of replace ---

		data.tasks[taskIndex] = task; // Assign the modified task back
		writeJSON(tasksPath, data, projectRoot, tag);
		// await generateTaskFiles(tasksPath, path.dirname(tasksPath));

		// Display AI Usage Summary for CLI
		if (
			outputFormat === 'text' &&
			aiServiceResponse &&
			aiServiceResponse.telemetryData
		) {
			displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli');
		}

		// Return the updated task object AND telemetry data
		return {
			task,
			telemetryData: aiServiceResponse?.telemetryData,
			tagInfo: aiServiceResponse?.tagInfo
		};
	} catch (error) {
		// Catches errors from file reading, parsing, AI call etc.
		logger.error(`Error expanding task ${taskId}: ${error.message}`, 'error');
		if (outputFormat === 'text' && getDebugFlag(session)) {
			console.error(error); // Log full stack in debug CLI mode
		}
		throw error; // Re-throw for the caller
	}
}

export default expandTask;

```

--------------------------------------------------------------------------------
/apps/docs/best-practices/configuration-advanced.mdx:
--------------------------------------------------------------------------------

```markdown
---
title: Advanced Configuration
sidebarTitle: "Advanced Configuration"
---


Taskmaster uses two primary methods for configuration:

1.  **`.taskmaster/config.json` File (Recommended - New Structure)**

    - This JSON file stores most configuration settings, including AI model selections, parameters, logging levels, and project defaults.
    - **Location:** This file is created in the `.taskmaster/` directory when you run the `task-master models --setup` interactive setup or initialize a new project with `task-master init`.
    - **Migration:** Existing projects with `.taskmasterconfig` in the root will continue to work, but should be migrated to the new structure using `task-master migrate`.
    - **Management:** Use the `task-master models --setup` command (or `models` MCP tool) to interactively create and manage this file. You can also set specific models directly using `task-master models --set-<role>=<model_id>`, adding `--ollama` or `--openrouter` flags for custom models. Manual editing is possible but not recommended unless you understand the structure.
    - **Example Structure:**
      ```json
      {
        "models": {
          "main": {
            "provider": "anthropic",
            "modelId": "claude-3-7-sonnet-20250219",
            "maxTokens": 64000,
            "temperature": 0.2,
            "baseURL": "https://api.anthropic.com/v1"
          },
          "research": {
            "provider": "perplexity",
            "modelId": "sonar-pro",
            "maxTokens": 8700,
            "temperature": 0.1,
            "baseURL": "https://api.perplexity.ai/v1"
          },
          "fallback": {
            "provider": "anthropic",
            "modelId": "claude-3-5-sonnet",
            "maxTokens": 64000,
            "temperature": 0.2
          }
        },
        "global": {
          "logLevel": "info",
          "debug": false,
          "defaultSubtasks": 5,
          "defaultPriority": "medium",
          "defaultTag": "master",
          "projectName": "Your Project Name",
          "ollamaBaseURL": "http://localhost:11434/api",
          "azureBaseURL": "https://your-endpoint.azure.com/openai/deployments",
          "vertexProjectId": "your-gcp-project-id",
          "vertexLocation": "us-central1"
        }
      }
      ```


2.  **Legacy `.taskmasterconfig` File (Backward Compatibility)**

    - For projects that haven't migrated to the new structure yet.
    - **Location:** Project root directory.
    - **Migration:** Use `task-master migrate` to move this to `.taskmaster/config.json`.
    - **Deprecation:** While still supported, you'll see warnings encouraging migration to the new structure.

## Environment Variables (`.env` file or MCP `env` block - For API Keys Only)

- Used **exclusively** for sensitive API keys and specific endpoint URLs.
- **Location:**
  - For CLI usage: Create a `.env` file in your project root.
  - For MCP/Cursor usage: Configure keys in the `env` section of your `.cursor/mcp.json` file.
- **Required API Keys (Depending on configured providers):**
  - `ANTHROPIC_API_KEY`: Your Anthropic API key.
  - `PERPLEXITY_API_KEY`: Your Perplexity API key.
  - `OPENAI_API_KEY`: Your OpenAI API key.
  - `GOOGLE_API_KEY`: Your Google API key (also used for Vertex AI provider).
  - `MISTRAL_API_KEY`: Your Mistral API key.
  - `AZURE_OPENAI_API_KEY`: Your Azure OpenAI API key (also requires `AZURE_OPENAI_ENDPOINT`).
  - `OPENROUTER_API_KEY`: Your OpenRouter API key.
  - `XAI_API_KEY`: Your X-AI API key.
- **Optional Endpoint Overrides:**
  - **Per-role `baseURL` in `.taskmasterconfig`:** You can add a `baseURL` property to any model role (`main`, `research`, `fallback`) to override the default API endpoint for that provider. If omitted, the provider's standard endpoint is used.
  - **Environment Variable Overrides (`<PROVIDER>_BASE_URL`):** For greater flexibility, especially with third-party services, you can set an environment variable like `OPENAI_BASE_URL` or `MISTRAL_BASE_URL`. This will override any `baseURL` set in the configuration file for that provider. This is the recommended way to connect to OpenAI-compatible APIs.
  - `AZURE_OPENAI_ENDPOINT`: Required if using Azure OpenAI key (can also be set as `baseURL` for the Azure model role).
  - `OLLAMA_BASE_URL`: Override the default Ollama API URL (Default: `http://localhost:11434/api`).
  - `VERTEX_PROJECT_ID`: Your Google Cloud project ID for Vertex AI. Required when using the 'vertex' provider.
  - `VERTEX_LOCATION`: Google Cloud region for Vertex AI (e.g., 'us-central1'). Default is 'us-central1'.
  - `GOOGLE_APPLICATION_CREDENTIALS`: Path to service account credentials JSON file for Google Cloud auth (alternative to API key for Vertex AI).
- **Optional Auto-Update Control:**
  - `TASKMASTER_SKIP_AUTO_UPDATE`: Set to '1' to disable automatic updates. Also automatically disabled in CI environments (when `CI` environment variable is set).

**Important:** Settings like model ID selections (`main`, `research`, `fallback`), `maxTokens`, `temperature`, `logLevel`, `defaultSubtasks`, `defaultPriority`, and `projectName` are **managed in `.taskmaster/config.json`** (or `.taskmasterconfig` for unmigrated projects), not environment variables.

## Tagged Task Lists Configuration (v0.17+)

Taskmaster includes a tagged task lists system for multi-context task management.

### Global Tag Settings

```json
"global": {
  "defaultTag": "master"
}
```

- **`defaultTag`** (string): Default tag context for new operations (default: "master")

### Git Integration

Task Master provides manual git integration through the `--from-branch` option:

- **Manual Tag Creation**: Use `task-master add-tag --from-branch` to create a tag based on your current git branch name
- **User Control**: No automatic tag switching - you control when and how tags are created
- **Flexible Workflow**: Supports any git workflow without imposing rigid branch-tag mappings

## State Management File

Taskmaster uses `.taskmaster/state.json` to track tagged system runtime information:

```json
{
  "currentTag": "master",
  "lastSwitched": "2025-06-11T20:26:12.598Z",
  "migrationNoticeShown": true
}
```

- **`currentTag`**: Currently active tag context
- **`lastSwitched`**: Timestamp of last tag switch
- **`migrationNoticeShown`**: Whether migration notice has been displayed

This file is automatically created during tagged system migration and should not be manually edited.

## Example `.env` File (for API Keys)

```
# Required API keys for providers configured in .taskmaster/config.json
ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
PERPLEXITY_API_KEY=pplx-your-key-here
# OPENAI_API_KEY=sk-your-key-here
# GOOGLE_API_KEY=AIzaSy...
# AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
# etc.

# Optional Endpoint Overrides
# Use a specific provider's base URL, e.g., for an OpenAI-compatible API
# OPENAI_BASE_URL=https://api.third-party.com/v1
#
# Azure OpenAI Configuration
# AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/ or https://your-endpoint-name.cognitiveservices.azure.com/openai/deployments
# OLLAMA_BASE_URL=http://custom-ollama-host:11434/api

# Google Vertex AI Configuration (Required if using 'vertex' provider)
# VERTEX_PROJECT_ID=your-gcp-project-id
```

## Troubleshooting

### Configuration Errors

- If Task Master reports errors about missing configuration or cannot find the config file, run `task-master models --setup` in your project root to create or repair the file.
- For new projects, config will be created at `.taskmaster/config.json`. For legacy projects, you may want to use `task-master migrate` to move to the new structure.
- Ensure API keys are correctly placed in your `.env` file (for CLI) or `.cursor/mcp.json` (for MCP) and are valid for the providers selected in your config file.

### If `task-master init` doesn't respond:

Try running it with Node directly:

```bash
node node_modules/claude-task-master/scripts/init.js
```

Or clone the repository and run:

```bash
git clone https://github.com/eyaltoledano/claude-task-master.git
cd claude-task-master
node scripts/init.js
```

## Provider-Specific Configuration

### Google Vertex AI Configuration

Google Vertex AI is Google Cloud's enterprise AI platform and requires specific configuration:

1. **Prerequisites**:
   - A Google Cloud account with Vertex AI API enabled
   - Either a Google API key with Vertex AI permissions OR a service account with appropriate roles
   - A Google Cloud project ID
2. **Authentication Options**:
   - **API Key**: Set the `GOOGLE_API_KEY` environment variable
   - **Service Account**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to your service account JSON file
3. **Required Configuration**:
   - Set `VERTEX_PROJECT_ID` to your Google Cloud project ID
   - Set `VERTEX_LOCATION` to your preferred Google Cloud region (default: us-central1)
4. **Example Setup**:

   ```bash
   # In .env file
   GOOGLE_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXX
   VERTEX_PROJECT_ID=my-gcp-project-123
   VERTEX_LOCATION=us-central1
   ```

   Or using service account:

   ```bash
   # In .env file
   GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
   VERTEX_PROJECT_ID=my-gcp-project-123
   VERTEX_LOCATION=us-central1
   ```

5. **In .taskmaster/config.json**:
   ```json
   "global": {
     "vertexProjectId": "my-gcp-project-123",
     "vertexLocation": "us-central1"
   }
   ```

### Azure OpenAI Configuration

Azure OpenAI provides enterprise-grade OpenAI models through Microsoft's Azure cloud platform and requires specific configuration:

1. **Prerequisites**:
   - An Azure account with an active subscription
   - Azure OpenAI service resource created in the Azure portal
   - Azure OpenAI API key and endpoint URL
   - Deployed models (e.g., gpt-4o, gpt-4o-mini, gpt-4.1, etc) in your Azure OpenAI resource

2. **Authentication**:
   - Set the `AZURE_OPENAI_API_KEY` environment variable with your Azure OpenAI API key
   - Configure the endpoint URL using one of the methods below

3. **Configuration Options**:

   **Option 1: Using Global Azure Base URL (affects all Azure models)**
   ```json
   // In .taskmaster/config.json
   {
     "models": {
       "main": {
         "provider": "azure",
         "modelId": "gpt-4o",
         "maxTokens": 16000,
         "temperature": 0.7
       },
       "fallback": {
         "provider": "azure", 
         "modelId": "gpt-4o-mini",
         "maxTokens": 10000,
         "temperature": 0.7
       }
     },
     "global": {
       "azureBaseURL": "https://your-resource-name.azure.com/openai/deployments"
     }
   }
   ```

   **Option 2: Using Per-Model Base URLs (recommended for flexibility)**
   ```json
   // In .taskmaster/config.json
   {
     "models": {
       "main": {
         "provider": "azure",
         "modelId": "gpt-4o", 
         "maxTokens": 16000,
         "temperature": 0.7,
         "baseURL": "https://your-resource-name.azure.com/openai/deployments"
       },
       "research": {
         "provider": "perplexity",
         "modelId": "sonar-pro",
         "maxTokens": 8700,
         "temperature": 0.1
       },
       "fallback": {
         "provider": "azure",
         "modelId": "gpt-4o-mini",
         "maxTokens": 10000, 
         "temperature": 0.7,
         "baseURL": "https://your-resource-name.azure.com/openai/deployments"
       }
     }
   }
   ```

4. **Environment Variables**:
   ```bash
   # In .env file
   AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
   
   # Optional: Override endpoint for all Azure models
   AZURE_OPENAI_ENDPOINT=https://your-resource-name.azure.com/openai/deployments
   ```

5. **Important Notes**:
   - **Model Deployment Names**: The `modelId` in your configuration should match the **deployment name** you created in Azure OpenAI Studio, not the underlying model name
   - **Base URL Priority**: Per-model `baseURL` settings override the global `azureBaseURL` setting
   - **Endpoint Format**: When using per-model `baseURL`, use the full path including `/openai/deployments`

6. **Troubleshooting**:

   **"Resource not found" errors:**
   - Ensure your `baseURL` includes the full path: `https://your-resource-name.openai.azure.com/openai/deployments`
   - Verify that your deployment name in `modelId` exactly matches what's configured in Azure OpenAI Studio
   - Check that your Azure OpenAI resource is in the correct region and properly deployed

   **Authentication errors:**
   - Verify your `AZURE_OPENAI_API_KEY` is correct and has not expired
   - Ensure your Azure OpenAI resource has the necessary permissions
   - Check that your subscription has not been suspended or reached quota limits

   **Model availability errors:**
   - Confirm the model is deployed in your Azure OpenAI resource
   - Verify the deployment name matches your configuration exactly (case-sensitive)
   - Ensure the model deployment is in a "Succeeded" state in Azure OpenAI Studio
   - Ensure youre not getting rate limited by `maxTokens` maintain appropriate Tokens per Minute Rate Limit (TPM) in your deployment.
```

--------------------------------------------------------------------------------
/tests/unit/task-master.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Tests for task-master.js initTaskMaster function
 */

import { jest } from '@jest/globals';
import path from 'path';
import fs from 'fs';
import os from 'os';
import { initTaskMaster, TaskMaster } from '../../src/task-master.js';
import {
	TASKMASTER_DIR,
	TASKMASTER_TASKS_FILE,
	LEGACY_CONFIG_FILE,
	TASKMASTER_CONFIG_FILE,
	LEGACY_TASKS_FILE
} from '../../src/constants/paths.js';

// Mock the console to prevent noise during tests
jest.spyOn(console, 'error').mockImplementation(() => {});

describe('initTaskMaster', () => {
	let tempDir;
	let originalCwd;

	beforeEach(() => {
		// Create a temporary directory for testing
		tempDir = fs.realpathSync(
			fs.mkdtempSync(path.join(os.tmpdir(), 'taskmaster-test-'))
		);
		originalCwd = process.cwd();

		// Clear all mocks
		jest.clearAllMocks();
	});

	afterEach(() => {
		// Restore original working directory
		process.chdir(originalCwd);

		// Clean up temporary directory
		if (fs.existsSync(tempDir)) {
			fs.rmSync(tempDir, { recursive: true, force: true });
		}
	});

	describe('Project root detection', () => {
		test('should find project root when .taskmaster directory exists', () => {
			// Arrange - Create .taskmaster directory in temp dir
			const taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });

			// Change to temp directory
			process.chdir(tempDir);

			// Act
			const taskMaster = initTaskMaster({});

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
			expect(taskMaster).toBeInstanceOf(TaskMaster);
		});

		test('should find project root when legacy config file exists', () => {
			// Arrange - Create legacy config file in temp dir
			const legacyConfigPath = path.join(tempDir, LEGACY_CONFIG_FILE);
			fs.writeFileSync(legacyConfigPath, '{}');

			// Change to temp directory
			process.chdir(tempDir);

			// Act
			const taskMaster = initTaskMaster({});

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
		});

		test('should find project root from subdirectory', () => {
			// Arrange - Create .taskmaster directory in temp dir
			const taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });

			// Create a subdirectory and change to it
			const srcDir = path.join(tempDir, 'src');
			fs.mkdirSync(srcDir, { recursive: true });
			process.chdir(srcDir);

			// Act
			const taskMaster = initTaskMaster({});

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
		});

		test('should find project root from deeply nested subdirectory', () => {
			// Arrange - Create .taskmaster directory in temp dir
			const taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });

			// Create deeply nested subdirectory and change to it
			const deepDir = path.join(tempDir, 'src', 'components', 'ui');
			fs.mkdirSync(deepDir, { recursive: true });
			process.chdir(deepDir);

			// Act
			const taskMaster = initTaskMaster({});

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
		});

		test('should return cwd when no project markers found cuz we changed the behavior of this function', () => {
			// Arrange - Empty temp directory, no project markers
			process.chdir(tempDir);

			// Act
			const taskMaster = initTaskMaster({});

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
		});
	});

	describe('Project root override validation', () => {
		test('should accept valid project root override with .taskmaster directory', () => {
			// Arrange - Create .taskmaster directory in temp dir
			const taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });

			// Act
			const taskMaster = initTaskMaster({ projectRoot: tempDir });

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
		});

		test('should accept valid project root override with legacy config', () => {
			// Arrange - Create legacy config file in temp dir
			const legacyConfigPath = path.join(tempDir, LEGACY_CONFIG_FILE);
			fs.writeFileSync(legacyConfigPath, '{}');

			// Act
			const taskMaster = initTaskMaster({ projectRoot: tempDir });

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
		});

		test('should throw error when project root override does not exist', () => {
			// Arrange - Non-existent path
			const nonExistentPath = path.join(tempDir, 'does-not-exist');

			// Act & Assert
			expect(() => {
				initTaskMaster({ projectRoot: nonExistentPath });
			}).toThrow(
				`Project root override path does not exist: ${nonExistentPath}`
			);
		});

		test('should throw error when project root override has no project markers', () => {
			// Arrange - Empty temp directory (no project markers)

			// Act & Assert
			expect(() => {
				initTaskMaster({ projectRoot: tempDir });
			}).toThrow(
				`Project root override is not a valid taskmaster project: ${tempDir}`
			);
		});

		test('should resolve relative project root override', () => {
			// Arrange - Create .taskmaster directory in temp dir
			const taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });

			// Create subdirectory and change to it
			const srcDir = path.join(tempDir, 'src');
			fs.mkdirSync(srcDir, { recursive: true });
			process.chdir(srcDir);

			// Act - Use relative path '../' to go back to project root
			const taskMaster = initTaskMaster({ projectRoot: '../' });

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
		});
	});

	describe('Path resolution with boolean logic', () => {
		let taskMasterDir, tasksPath, configPath, statePath;

		beforeEach(() => {
			// Setup a valid project structure
			taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });

			tasksPath = path.join(tempDir, TASKMASTER_TASKS_FILE);
			fs.mkdirSync(path.dirname(tasksPath), { recursive: true });
			fs.writeFileSync(tasksPath, '[]');

			configPath = path.join(tempDir, TASKMASTER_CONFIG_FILE);
			fs.writeFileSync(configPath, '{}');

			statePath = path.join(taskMasterDir, 'state.json');
			fs.writeFileSync(statePath, '{}');

			process.chdir(tempDir);
		});

		test('should return paths when required (true) and files exist', () => {
			// Act
			const taskMaster = initTaskMaster({
				tasksPath: true,
				configPath: true,
				statePath: true
			});

			// Assert
			expect(taskMaster.getTasksPath()).toBe(tasksPath);
			expect(taskMaster.getConfigPath()).toBe(configPath);
			expect(taskMaster.getStatePath()).toBe(statePath);
		});

		test('should throw error when required (true) files do not exist', () => {
			// Arrange - Remove tasks file
			fs.unlinkSync(tasksPath);

			// Act & Assert
			expect(() => {
				initTaskMaster({ tasksPath: true });
			}).toThrow(
				'Required tasks file not found. Searched: .taskmaster/tasks/tasks.json, tasks/tasks.json'
			);
		});

		test('should return null when optional (false/undefined) files do not exist', () => {
			// Arrange - Remove tasks file
			fs.unlinkSync(tasksPath);

			// Act
			const taskMaster = initTaskMaster({
				tasksPath: false
			});

			// Assert
			expect(taskMaster.getTasksPath()).toBeNull();
		});

		test('should return default paths when optional files not specified in overrides', () => {
			// Arrange - Remove all optional files
			fs.unlinkSync(tasksPath);
			fs.unlinkSync(configPath);
			fs.unlinkSync(statePath);

			// Act - Don't specify any optional paths
			const taskMaster = initTaskMaster({});

			// Assert - Should return absolute paths with default locations
			expect(taskMaster.getTasksPath()).toBe(
				path.join(tempDir, TASKMASTER_TASKS_FILE)
			);
			expect(taskMaster.getConfigPath()).toBe(
				path.join(tempDir, TASKMASTER_CONFIG_FILE)
			);
			expect(taskMaster.getStatePath()).toBe(
				path.join(tempDir, TASKMASTER_DIR, 'state.json')
			);
		});
	});

	describe('String path overrides', () => {
		let taskMasterDir;

		beforeEach(() => {
			taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });
			process.chdir(tempDir);
		});

		test('should accept valid absolute path override', () => {
			// Arrange - Create custom tasks file
			const customTasksPath = path.join(tempDir, 'custom-tasks.json');
			fs.writeFileSync(customTasksPath, '[]');

			// Act
			const taskMaster = initTaskMaster({
				tasksPath: customTasksPath
			});

			// Assert
			expect(taskMaster.getTasksPath()).toBe(customTasksPath);
		});

		test('should accept valid relative path override', () => {
			// Arrange - Create custom tasks file
			const customTasksPath = path.join(tempDir, 'custom-tasks.json');
			fs.writeFileSync(customTasksPath, '[]');

			// Act
			const taskMaster = initTaskMaster({
				tasksPath: './custom-tasks.json'
			});

			// Assert
			expect(taskMaster.getTasksPath()).toBe(customTasksPath);
		});

		test('should throw error when string path override does not exist', () => {
			// Arrange - Non-existent file path
			const nonExistentPath = path.join(tempDir, 'does-not-exist.json');

			// Act & Assert
			expect(() => {
				initTaskMaster({ tasksPath: nonExistentPath });
			}).toThrow(`tasks file override path does not exist: ${nonExistentPath}`);
		});
	});

	describe('Legacy file support', () => {
		beforeEach(() => {
			// Setup basic project structure
			const taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });
			process.chdir(tempDir);
		});

		test('should find legacy tasks file when new format does not exist', () => {
			// Arrange - Create legacy tasks file
			const legacyTasksDir = path.join(tempDir, 'tasks');
			fs.mkdirSync(legacyTasksDir, { recursive: true });
			const legacyTasksPath = path.join(tempDir, LEGACY_TASKS_FILE);
			fs.writeFileSync(legacyTasksPath, '[]');

			// Act
			const taskMaster = initTaskMaster({ tasksPath: true });

			// Assert
			expect(taskMaster.getTasksPath()).toBe(legacyTasksPath);
		});

		test('should prefer new format over legacy when both exist', () => {
			// Arrange - Create both new and legacy files
			const newTasksPath = path.join(tempDir, TASKMASTER_TASKS_FILE);
			fs.mkdirSync(path.dirname(newTasksPath), { recursive: true });
			fs.writeFileSync(newTasksPath, '[]');

			const legacyTasksDir = path.join(tempDir, 'tasks');
			fs.mkdirSync(legacyTasksDir, { recursive: true });
			const legacyTasksPath = path.join(tempDir, LEGACY_TASKS_FILE);
			fs.writeFileSync(legacyTasksPath, '[]');

			// Act
			const taskMaster = initTaskMaster({ tasksPath: true });

			// Assert
			expect(taskMaster.getTasksPath()).toBe(newTasksPath);
		});

		test('should find legacy config file when new format does not exist', () => {
			// Arrange - Create legacy config file
			const legacyConfigPath = path.join(tempDir, LEGACY_CONFIG_FILE);
			fs.writeFileSync(legacyConfigPath, '{}');

			// Act
			const taskMaster = initTaskMaster({ configPath: true });

			// Assert
			expect(taskMaster.getConfigPath()).toBe(legacyConfigPath);
		});
	});

	describe('TaskMaster class methods', () => {
		test('should return all paths via getAllPaths method', () => {
			// Arrange - Setup project with all files
			const taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });

			const tasksPath = path.join(tempDir, TASKMASTER_TASKS_FILE);
			fs.mkdirSync(path.dirname(tasksPath), { recursive: true });
			fs.writeFileSync(tasksPath, '[]');

			const configPath = path.join(tempDir, TASKMASTER_CONFIG_FILE);
			fs.writeFileSync(configPath, '{}');

			process.chdir(tempDir);

			// Act
			const taskMaster = initTaskMaster({
				tasksPath: true,
				configPath: true
			});

			// Assert
			const allPaths = taskMaster.getAllPaths();
			expect(allPaths).toEqual(
				expect.objectContaining({
					projectRoot: tempDir,
					taskMasterDir: taskMasterDir,
					tasksPath: tasksPath,
					configPath: configPath
				})
			);

			// Verify paths object is frozen
			expect(() => {
				allPaths.projectRoot = '/different/path';
			}).toThrow();
		});

		test('should return correct individual paths', () => {
			// Arrange
			const taskMasterDir = path.join(tempDir, TASKMASTER_DIR);
			fs.mkdirSync(taskMasterDir, { recursive: true });
			process.chdir(tempDir);

			// Act
			const taskMaster = initTaskMaster({});

			// Assert
			expect(taskMaster.getProjectRoot()).toBe(tempDir);
			expect(taskMaster.getTaskMasterDir()).toBe(taskMasterDir);
			// Default paths are always set for tasks, config, and state
			expect(taskMaster.getTasksPath()).toBe(
				path.join(tempDir, TASKMASTER_TASKS_FILE)
			);
			expect(taskMaster.getConfigPath()).toBe(
				path.join(tempDir, TASKMASTER_CONFIG_FILE)
			);
			expect(taskMaster.getStatePath()).toBe(
				path.join(taskMasterDir, 'state.json')
			);
			// PRD and complexity report paths are undefined when not provided
			expect(typeof taskMaster.getComplexityReportPath()).toBe('string');
			expect(taskMaster.getComplexityReportPath()).toMatch(
				/task-complexity-report\.json$/
			);
		});
	});
});

```

--------------------------------------------------------------------------------
/src/ui/parse-prd.js:
--------------------------------------------------------------------------------

```javascript
/**
 * parse-prd.js
 * UI functions specifically for PRD parsing operations
 */

import chalk from 'chalk';
import boxen from 'boxen';
import Table from 'cli-table3';
import { formatElapsedTime } from '../utils/format.js';

// Constants
const CONSTANTS = {
	BAR_WIDTH: 40,
	TABLE_COL_WIDTHS: [28, 50],
	DEFAULT_MODEL: 'Default',
	DEFAULT_TEMPERATURE: 0.7
};

const PRIORITIES = {
	HIGH: 'high',
	MEDIUM: 'medium',
	LOW: 'low'
};

const PRIORITY_COLORS = {
	[PRIORITIES.HIGH]: '#CC0000',
	[PRIORITIES.MEDIUM]: '#FF8800',
	[PRIORITIES.LOW]: '#FFCC00'
};

// Reusable box styles
const BOX_STYLES = {
	main: {
		padding: { top: 1, bottom: 1, left: 2, right: 2 },
		margin: { top: 0, bottom: 0 },
		borderColor: 'blue',
		borderStyle: 'round'
	},
	summary: {
		padding: { top: 1, right: 1, bottom: 1, left: 1 },
		borderColor: 'blue',
		borderStyle: 'round',
		margin: { top: 1, right: 1, bottom: 1, left: 0 }
	},
	warning: {
		padding: 1,
		borderColor: 'yellow',
		borderStyle: 'round',
		margin: { top: 1, bottom: 1 }
	},
	nextSteps: {
		padding: 1,
		borderColor: 'cyan',
		borderStyle: 'round',
		margin: { top: 1, right: 0, bottom: 1, left: 0 }
	}
};

/**
 * Helper function for building main message content
 * @param {Object} params - Message parameters
 * @param {string} params.prdFilePath - Path to the PRD file
 * @param {string} params.outputPath - Path where tasks will be saved
 * @param {number} params.numTasks - Number of tasks to generate
 * @param {string} params.model - AI model name
 * @param {number} params.temperature - AI temperature setting
 * @param {boolean} params.append - Whether appending to existing tasks
 * @param {boolean} params.research - Whether research mode is enabled
 * @returns {string} The formatted message content
 */
function buildMainMessage({
	prdFilePath,
	outputPath,
	numTasks,
	model,
	temperature,
	append,
	research
}) {
	const actionVerb = append ? 'Appending' : 'Generating';

	let modelLine = `Model: ${model} | Temperature: ${temperature}`;
	if (research) {
		modelLine += ` | ${chalk.cyan.bold('🔬 Research Mode')}`;
	}

	return (
		chalk.bold(`🤖 Parsing PRD and ${actionVerb} Tasks`) +
		'\n' +
		chalk.dim(modelLine) +
		'\n\n' +
		chalk.blue(`Input: ${prdFilePath}`) +
		'\n' +
		chalk.blue(`Output: ${outputPath}`) +
		'\n' +
		chalk.blue(`Tasks to ${append ? 'Append' : 'Generate'}: ${numTasks}`)
	);
}

/**
 * Helper function for displaying the main message box
 * @param {string} message - The message content to display in the box
 */
function displayMainMessageBox(message) {
	console.log(boxen(message, BOX_STYLES.main));
}

/**
 * Helper function for displaying append mode notice
 * @param {number} existingTasksCount - Number of existing tasks
 * @param {number} nextId - Next ID to be used
 */
function displayAppendModeNotice(existingTasksCount, nextId) {
	console.log(
		chalk.yellow.bold('📝 Append mode') +
			` - Adding to ${existingTasksCount} existing tasks (next ID: ${nextId})`
	);
}

/**
 * Helper function for force mode messages
 * @param {boolean} append - Whether in append mode
 * @returns {string} The formatted force mode message
 */
function createForceMessage(append) {
	const baseMessage = chalk.red.bold('⚠️  Force flag enabled');
	return append
		? `${baseMessage} - Will overwrite if conflicts occur`
		: `${baseMessage} - Overwriting existing tasks`;
}

/**
 * Display the start of PRD parsing with a boxen announcement
 * @param {Object} options - Options for PRD parsing start
 * @param {string} options.prdFilePath - Path to the PRD file being parsed
 * @param {string} options.outputPath - Path where the tasks will be saved
 * @param {number} options.numTasks - Number of tasks to generate
 * @param {string} [options.model] - AI model name
 * @param {number} [options.temperature] - AI temperature setting
 * @param {boolean} [options.append=false] - Whether to append to existing tasks
 * @param {boolean} [options.research=false] - Whether research mode is enabled
 * @param {boolean} [options.force=false] - Whether force mode is enabled
 * @param {Array} [options.existingTasks=[]] - Existing tasks array
 * @param {number} [options.nextId=1] - Next ID to be used
 */
function displayParsePrdStart({
	prdFilePath,
	outputPath,
	numTasks,
	model = CONSTANTS.DEFAULT_MODEL,
	temperature = CONSTANTS.DEFAULT_TEMPERATURE,
	append = false,
	research = false,
	force = false,
	existingTasks = [],
	nextId = 1
}) {
	// Input validation
	if (
		!prdFilePath ||
		typeof prdFilePath !== 'string' ||
		prdFilePath.trim() === ''
	) {
		throw new Error('prdFilePath is required and must be a non-empty string');
	}
	if (
		!outputPath ||
		typeof outputPath !== 'string' ||
		outputPath.trim() === ''
	) {
		throw new Error('outputPath is required and must be a non-empty string');
	}

	// Build and display the main message box
	const message = buildMainMessage({
		prdFilePath,
		outputPath,
		numTasks,
		model,
		temperature,
		append,
		research
	});
	displayMainMessageBox(message);

	// Display append/force notices beneath the boxen if either flag is set
	if (append || force) {
		// Add append mode details if enabled
		if (append) {
			displayAppendModeNotice(existingTasks.length, nextId);
		}

		// Add force mode details if enabled
		if (force) {
			console.log(createForceMessage(append));
		}

		// Add a blank line after notices for spacing
		console.log();
	}
}

/**
 * Calculate priority statistics
 * @param {Object} taskPriorities - Priority counts object
 * @param {number} totalTasks - Total number of tasks
 * @returns {Object} Priority statistics with counts and percentages
 */
function calculatePriorityStats(taskPriorities, totalTasks) {
	const stats = {};

	Object.values(PRIORITIES).forEach((priority) => {
		const count = taskPriorities[priority] || 0;
		stats[priority] = {
			count,
			percentage: totalTasks > 0 ? Math.round((count / totalTasks) * 100) : 0
		};
	});

	return stats;
}

/**
 * Calculate bar character distribution for priorities
 * @param {Object} priorityStats - Priority statistics
 * @param {number} totalTasks - Total number of tasks
 * @returns {Object} Character counts for each priority
 */
function calculateBarDistribution(priorityStats, totalTasks) {
	const barWidth = CONSTANTS.BAR_WIDTH;
	const distribution = {};

	if (totalTasks === 0) {
		Object.values(PRIORITIES).forEach((priority) => {
			distribution[priority] = 0;
		});
		return distribution;
	}

	// Calculate raw proportions
	const rawChars = {};
	Object.values(PRIORITIES).forEach((priority) => {
		rawChars[priority] =
			(priorityStats[priority].count / totalTasks) * barWidth;
	});

	// Initial distribution - floor values
	Object.values(PRIORITIES).forEach((priority) => {
		distribution[priority] = Math.floor(rawChars[priority]);
	});

	// Ensure non-zero priorities get at least 1 character
	Object.values(PRIORITIES).forEach((priority) => {
		if (priorityStats[priority].count > 0 && distribution[priority] === 0) {
			distribution[priority] = 1;
		}
	});

	// Distribute remaining characters based on decimal parts
	const currentTotal = Object.values(distribution).reduce(
		(sum, val) => sum + val,
		0
	);
	const remainingChars = barWidth - currentTotal;

	if (remainingChars > 0) {
		const decimals = Object.values(PRIORITIES)
			.map((priority) => ({
				priority,
				decimal: rawChars[priority] - Math.floor(rawChars[priority])
			}))
			.sort((a, b) => b.decimal - a.decimal);

		for (let i = 0; i < remainingChars && i < decimals.length; i++) {
			distribution[decimals[i].priority]++;
		}
	}

	return distribution;
}

/**
 * Create priority distribution bar visual
 * @param {Object} barDistribution - Character distribution for priorities
 * @returns {string} Visual bar string
 */
function createPriorityBar(barDistribution) {
	let bar = '';

	bar += chalk.hex(PRIORITY_COLORS[PRIORITIES.HIGH])(
		'█'.repeat(barDistribution[PRIORITIES.HIGH])
	);
	bar += chalk.hex(PRIORITY_COLORS[PRIORITIES.MEDIUM])(
		'█'.repeat(barDistribution[PRIORITIES.MEDIUM])
	);
	bar += chalk.yellow('█'.repeat(barDistribution[PRIORITIES.LOW]));

	const totalChars = Object.values(barDistribution).reduce(
		(sum, val) => sum + val,
		0
	);
	if (totalChars < CONSTANTS.BAR_WIDTH) {
		bar += chalk.gray('░'.repeat(CONSTANTS.BAR_WIDTH - totalChars));
	}

	return bar;
}

/**
 * Build priority distribution row for table
 * @param {Object} priorityStats - Priority statistics
 * @returns {Array} Table row for priority distribution
 */
function buildPriorityRow(priorityStats) {
	const parts = [];

	Object.entries(PRIORITIES).forEach(([key, priority]) => {
		const stats = priorityStats[priority];
		const color =
			priority === PRIORITIES.HIGH
				? chalk.hex(PRIORITY_COLORS[PRIORITIES.HIGH])
				: priority === PRIORITIES.MEDIUM
					? chalk.hex(PRIORITY_COLORS[PRIORITIES.MEDIUM])
					: chalk.yellow;

		const label = key.charAt(0) + key.slice(1).toLowerCase();
		parts.push(
			`${color.bold(stats.count)} ${color(label)} (${stats.percentage}%)`
		);
	});

	return [chalk.cyan('Priority distribution:'), parts.join(' · ')];
}

/**
 * Display a summary of the PRD parsing results
 * @param {Object} summary - Summary of the parsing results
 * @param {number} summary.totalTasks - Total number of tasks generated
 * @param {string} summary.prdFilePath - Path to the PRD file
 * @param {string} summary.outputPath - Path where the tasks were saved
 * @param {number} summary.elapsedTime - Total elapsed time in seconds
 * @param {Object} summary.taskPriorities - Breakdown of tasks by category/priority
 * @param {boolean} summary.usedFallback - Whether fallback parsing was used
 * @param {string} summary.actionVerb - Whether tasks were 'generated' or 'appended'
 */
function displayParsePrdSummary(summary) {
	const {
		totalTasks,
		taskPriorities = {},
		prdFilePath,
		outputPath,
		elapsedTime,
		usedFallback = false,
		actionVerb = 'generated'
	} = summary;

	// Format the elapsed time
	const timeDisplay = formatElapsedTime(elapsedTime);

	// Create a table for better alignment
	const table = new Table({
		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': 2 },
		colWidths: CONSTANTS.TABLE_COL_WIDTHS
	});

	// Basic info
	// Use the action verb to properly display if tasks were generated or appended
	table.push(
		[chalk.cyan(`Total tasks ${actionVerb}:`), chalk.bold(totalTasks)],
		[chalk.cyan('Processing time:'), chalk.bold(timeDisplay)]
	);

	// Priority distribution if available
	if (taskPriorities && Object.keys(taskPriorities).length > 0) {
		const priorityStats = calculatePriorityStats(taskPriorities, totalTasks);
		const priorityRow = buildPriorityRow(priorityStats);
		table.push(priorityRow);

		// Visual bar representation
		const barDistribution = calculateBarDistribution(priorityStats, totalTasks);
		const distributionBar = createPriorityBar(barDistribution);
		table.push([chalk.cyan('Distribution:'), distributionBar]);
	}

	// Add file paths
	table.push(
		[chalk.cyan('PRD source:'), chalk.italic(prdFilePath)],
		[chalk.cyan('Tasks file:'), chalk.italic(outputPath)]
	);

	// Add fallback parsing indicator if applicable
	if (usedFallback) {
		table.push([
			chalk.yellow('Fallback parsing:'),
			chalk.yellow('✓ Used fallback parsing')
		]);
	}

	// Final string output with title and footer
	const output = [
		chalk.bold.underline(
			`PRD Parsing Complete - Tasks ${actionVerb.charAt(0).toUpperCase() + actionVerb.slice(1)}`
		),
		'',
		table.toString()
	].join('\n');

	// Display the summary box
	console.log(boxen(output, BOX_STYLES.summary));

	// Show fallback parsing warning if needed
	if (usedFallback) {
		displayFallbackWarning();
	}

	// Show next steps
	displayNextSteps();
}

/**
 * Display fallback parsing warning
 */
function displayFallbackWarning() {
	const warningContent =
		chalk.yellow.bold('⚠️ Fallback Parsing Used') +
		'\n\n' +
		chalk.white(
			'The system used fallback parsing to complete task generation.'
		) +
		'\n' +
		chalk.white(
			'This typically happens when streaming JSON parsing is incomplete.'
		) +
		'\n' +
		chalk.white('Your tasks were successfully generated, but consider:') +
		'\n' +
		chalk.white('• Reviewing task completeness') +
		'\n' +
		chalk.white('• Checking for any missing details') +
		'\n\n' +
		chalk.white("This is normal and usually doesn't indicate any issues.");

	console.log(boxen(warningContent, BOX_STYLES.warning));
}

/**
 * Display next steps after parsing
 */
function displayNextSteps() {
	const stepsContent =
		chalk.white.bold('Next Steps:') +
		'\n\n' +
		`${chalk.cyan('1.')} Run ${chalk.yellow('task-master list')} to view all tasks\n` +
		`${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=<id>')} to break down a task into subtasks\n` +
		`${chalk.cyan('3.')} Run ${chalk.yellow('task-master analyze-complexity')} to analyze task complexity`;

	console.log(boxen(stepsContent, BOX_STYLES.nextSteps));
}

export { displayParsePrdStart, displayParsePrdSummary, formatElapsedTime };

```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/commands/move-cross-tag.test.js:
--------------------------------------------------------------------------------

```javascript
import { jest } from '@jest/globals';
import chalk from 'chalk';

// ============================================================================
// MOCK FACTORY & CONFIGURATION SYSTEM
// ============================================================================

/**
 * Mock configuration object to enable/disable specific mocks per test
 */
const mockConfig = {
	// Core functionality mocks (always needed)
	core: {
		moveTasksBetweenTags: true,
		generateTaskFiles: true,
		readJSON: true,
		initTaskMaster: true,
		findProjectRoot: true
	},
	// Console and process mocks
	console: {
		error: true,
		log: true,
		exit: true
	},
	// TaskMaster instance mocks
	taskMaster: {
		getCurrentTag: true,
		getTasksPath: true,
		getProjectRoot: true
	}
};

/**
 * Creates mock functions with consistent naming
 */
function createMock(name) {
	return jest.fn().mockName(name);
}

/**
 * Mock factory for creating focused mocks based on configuration
 */
function createMockFactory(config = mockConfig) {
	const mocks = {};

	// Core functionality mocks
	if (config.core?.moveTasksBetweenTags) {
		mocks.moveTasksBetweenTags = createMock('moveTasksBetweenTags');
	}
	if (config.core?.generateTaskFiles) {
		mocks.generateTaskFiles = createMock('generateTaskFiles');
	}
	if (config.core?.readJSON) {
		mocks.readJSON = createMock('readJSON');
	}
	if (config.core?.initTaskMaster) {
		mocks.initTaskMaster = createMock('initTaskMaster');
	}
	if (config.core?.findProjectRoot) {
		mocks.findProjectRoot = createMock('findProjectRoot');
	}

	return mocks;
}

/**
 * Sets up mocks based on configuration
 */
function setupMocks(config = mockConfig) {
	const mocks = createMockFactory(config);

	// Only mock the modules that are actually used in cross-tag move functionality
	if (config.core?.moveTasksBetweenTags) {
		jest.mock(
			'../../../../../scripts/modules/task-manager/move-task.js',
			() => ({
				moveTasksBetweenTags: mocks.moveTasksBetweenTags
			})
		);
	}

	if (
		config.core?.generateTaskFiles ||
		config.core?.readJSON ||
		config.core?.findProjectRoot
	) {
		jest.mock('../../../../../scripts/modules/utils.js', () => ({
			findProjectRoot: mocks.findProjectRoot,
			generateTaskFiles: mocks.generateTaskFiles,
			readJSON: mocks.readJSON,
			// Minimal set of utils that might be used
			log: jest.fn(),
			writeJSON: jest.fn(),
			getCurrentTag: jest.fn(() => 'master')
		}));
	}

	if (config.core?.initTaskMaster) {
		jest.mock('../../../../../scripts/modules/config-manager.js', () => ({
			initTaskMaster: mocks.initTaskMaster,
			isApiKeySet: jest.fn(() => true),
			getConfig: jest.fn(() => ({}))
		}));
	}

	// Mock chalk for consistent output testing
	jest.mock('chalk', () => ({
		red: jest.fn((text) => text),
		blue: jest.fn((text) => text),
		green: jest.fn((text) => text),
		yellow: jest.fn((text) => text),
		white: jest.fn((text) => ({
			bold: jest.fn((text) => text)
		})),
		reset: jest.fn((text) => text)
	}));

	return mocks;
}

// ============================================================================
// TEST SETUP
// ============================================================================

// Set up mocks with default configuration
const mocks = setupMocks();

// Import the actual command handler functions
import { registerCommands } from '../../../../../scripts/modules/commands.js';

// Extract the handleCrossTagMove function from the commands module
// This is a simplified version of the actual function for testing
async function handleCrossTagMove(moveContext, options) {
	const { sourceId, sourceTag, toTag, taskMaster } = moveContext;

	if (!sourceId) {
		console.error('Error: --from parameter is required for cross-tag moves');
		process.exit(1);
		throw new Error('--from parameter is required for cross-tag moves');
	}

	if (sourceTag === toTag) {
		console.error(
			`Error: Source and target tags are the same ("${sourceTag}")`
		);
		process.exit(1);
		throw new Error(`Source and target tags are the same ("${sourceTag}")`);
	}

	const sourceIds = sourceId.split(',').map((id) => id.trim());
	const moveOptions = {
		withDependencies: options.withDependencies || false,
		ignoreDependencies: options.ignoreDependencies || false
	};

	const result = await mocks.moveTasksBetweenTags(
		taskMaster.getTasksPath(),
		sourceIds,
		sourceTag,
		toTag,
		moveOptions,
		{ projectRoot: taskMaster.getProjectRoot() }
	);

	// Check if source tag still contains tasks before regenerating files
	const tasksData = mocks.readJSON(
		taskMaster.getTasksPath(),
		taskMaster.getProjectRoot(),
		sourceTag
	);
	const sourceTagHasTasks =
		tasksData && Array.isArray(tasksData.tasks) && tasksData.tasks.length > 0;

	// Generate task files for the affected tags
	await mocks.generateTaskFiles(taskMaster.getTasksPath(), 'tasks', {
		tag: toTag,
		projectRoot: taskMaster.getProjectRoot()
	});

	// Only regenerate source tag files if it still contains tasks
	if (sourceTagHasTasks) {
		await mocks.generateTaskFiles(taskMaster.getTasksPath(), 'tasks', {
			tag: sourceTag,
			projectRoot: taskMaster.getProjectRoot()
		});
	}

	return result;
}

// ============================================================================
// TEST SUITE
// ============================================================================

describe('CLI Move Command Cross-Tag Functionality', () => {
	let mockTaskMaster;
	let mockConsoleError;
	let mockConsoleLog;
	let mockProcessExit;

	beforeEach(() => {
		jest.clearAllMocks();

		// Mock console methods
		mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
		mockConsoleLog = jest.spyOn(console, 'log').mockImplementation();
		mockProcessExit = jest.spyOn(process, 'exit').mockImplementation();

		// Mock TaskMaster instance
		mockTaskMaster = {
			getCurrentTag: jest.fn().mockReturnValue('master'),
			getTasksPath: jest.fn().mockReturnValue('/test/path/tasks.json'),
			getProjectRoot: jest.fn().mockReturnValue('/test/project')
		};

		mocks.initTaskMaster.mockReturnValue(mockTaskMaster);
		mocks.findProjectRoot.mockReturnValue('/test/project');
		mocks.generateTaskFiles.mockResolvedValue();
		mocks.readJSON.mockReturnValue({
			tasks: [
				{ id: 1, title: 'Test Task 1' },
				{ id: 2, title: 'Test Task 2' }
			]
		});
	});

	afterEach(() => {
		jest.restoreAllMocks();
	});

	describe('Cross-Tag Move Logic', () => {
		it('should handle basic cross-tag move', async () => {
			const options = {
				from: '1',
				fromTag: 'backlog',
				toTag: 'in-progress',
				withDependencies: false,
				ignoreDependencies: false
			};

			const moveContext = {
				sourceId: options.from,
				sourceTag: options.fromTag,
				toTag: options.toTag,
				taskMaster: mockTaskMaster
			};

			mocks.moveTasksBetweenTags.mockResolvedValue({
				message: 'Successfully moved 1 tasks from "backlog" to "in-progress"'
			});

			await handleCrossTagMove(moveContext, options);

			expect(mocks.moveTasksBetweenTags).toHaveBeenCalledWith(
				'/test/path/tasks.json',
				['1'],
				'backlog',
				'in-progress',
				{
					withDependencies: false,
					ignoreDependencies: false
				},
				{ projectRoot: '/test/project' }
			);
		});

		it('should handle --with-dependencies flag', async () => {
			const options = {
				from: '1',
				fromTag: 'backlog',
				toTag: 'in-progress',
				withDependencies: true,
				ignoreDependencies: false
			};

			const moveContext = {
				sourceId: options.from,
				sourceTag: options.fromTag,
				toTag: options.toTag,
				taskMaster: mockTaskMaster
			};

			mocks.moveTasksBetweenTags.mockResolvedValue({
				message: 'Successfully moved 2 tasks from "backlog" to "in-progress"'
			});

			await handleCrossTagMove(moveContext, options);

			expect(mocks.moveTasksBetweenTags).toHaveBeenCalledWith(
				'/test/path/tasks.json',
				['1'],
				'backlog',
				'in-progress',
				{
					withDependencies: true,
					ignoreDependencies: false
				},
				{ projectRoot: '/test/project' }
			);
		});

		it('should handle --ignore-dependencies flag', async () => {
			const options = {
				from: '1',
				fromTag: 'backlog',
				toTag: 'in-progress',
				withDependencies: false,
				ignoreDependencies: true
			};

			const moveContext = {
				sourceId: options.from,
				sourceTag: options.fromTag,
				toTag: options.toTag,
				taskMaster: mockTaskMaster
			};

			mocks.moveTasksBetweenTags.mockResolvedValue({
				message: 'Successfully moved 1 tasks from "backlog" to "in-progress"'
			});

			await handleCrossTagMove(moveContext, options);

			expect(mocks.moveTasksBetweenTags).toHaveBeenCalledWith(
				'/test/path/tasks.json',
				['1'],
				'backlog',
				'in-progress',
				{
					withDependencies: false,
					ignoreDependencies: true
				},
				{ projectRoot: '/test/project' }
			);
		});
	});

	describe('Error Handling', () => {
		it('should handle missing --from parameter', async () => {
			const options = {
				from: undefined,
				fromTag: 'backlog',
				toTag: 'in-progress'
			};

			const moveContext = {
				sourceId: options.from,
				sourceTag: options.fromTag,
				toTag: options.toTag,
				taskMaster: mockTaskMaster
			};

			await expect(handleCrossTagMove(moveContext, options)).rejects.toThrow();

			expect(mockConsoleError).toHaveBeenCalledWith(
				'Error: --from parameter is required for cross-tag moves'
			);
			expect(mockProcessExit).toHaveBeenCalledWith(1);
		});

		it('should handle same source and target tags', async () => {
			const options = {
				from: '1',
				fromTag: 'backlog',
				toTag: 'backlog'
			};

			const moveContext = {
				sourceId: options.from,
				sourceTag: options.fromTag,
				toTag: options.toTag,
				taskMaster: mockTaskMaster
			};

			await expect(handleCrossTagMove(moveContext, options)).rejects.toThrow();

			expect(mockConsoleError).toHaveBeenCalledWith(
				'Error: Source and target tags are the same ("backlog")'
			);
			expect(mockProcessExit).toHaveBeenCalledWith(1);
		});
	});

	describe('Fallback to Current Tag', () => {
		it('should use current tag when --from-tag is not provided', async () => {
			const options = {
				from: '1',
				fromTag: undefined,
				toTag: 'in-progress'
			};

			const moveContext = {
				sourceId: options.from,
				sourceTag: 'master', // Should use current tag
				toTag: options.toTag,
				taskMaster: mockTaskMaster
			};

			mocks.moveTasksBetweenTags.mockResolvedValue({
				message: 'Successfully moved 1 tasks from "master" to "in-progress"'
			});

			await handleCrossTagMove(moveContext, options);

			expect(mocks.moveTasksBetweenTags).toHaveBeenCalledWith(
				'/test/path/tasks.json',
				['1'],
				'master',
				'in-progress',
				expect.any(Object),
				{ projectRoot: '/test/project' }
			);
		});
	});

	describe('Multiple Task Movement', () => {
		it('should handle comma-separated task IDs', async () => {
			const options = {
				from: '1,2,3',
				fromTag: 'backlog',
				toTag: 'in-progress'
			};

			const moveContext = {
				sourceId: options.from,
				sourceTag: options.fromTag,
				toTag: options.toTag,
				taskMaster: mockTaskMaster
			};

			mocks.moveTasksBetweenTags.mockResolvedValue({
				message: 'Successfully moved 3 tasks from "backlog" to "in-progress"'
			});

			await handleCrossTagMove(moveContext, options);

			expect(mocks.moveTasksBetweenTags).toHaveBeenCalledWith(
				'/test/path/tasks.json',
				['1', '2', '3'],
				'backlog',
				'in-progress',
				expect.any(Object),
				{ projectRoot: '/test/project' }
			);
		});

		it('should handle whitespace in comma-separated task IDs', async () => {
			const options = {
				from: '1, 2, 3',
				fromTag: 'backlog',
				toTag: 'in-progress'
			};

			const moveContext = {
				sourceId: options.from,
				sourceTag: options.fromTag,
				toTag: options.toTag,
				taskMaster: mockTaskMaster
			};

			mocks.moveTasksBetweenTags.mockResolvedValue({
				message: 'Successfully moved 3 tasks from "backlog" to "in-progress"'
			});

			await handleCrossTagMove(moveContext, options);

			expect(mocks.moveTasksBetweenTags).toHaveBeenCalledWith(
				'/test/path/tasks.json',
				['1', '2', '3'],
				'backlog',
				'in-progress',
				expect.any(Object),
				{ projectRoot: '/test/project' }
			);
		});
	});

	describe('Mock Configuration Tests', () => {
		it('should work with minimal mock configuration', async () => {
			// Test that the mock factory works with minimal config
			const minimalConfig = {
				core: {
					moveTasksBetweenTags: true,
					generateTaskFiles: true,
					readJSON: true
				}
			};

			const minimalMocks = createMockFactory(minimalConfig);
			expect(minimalMocks.moveTasksBetweenTags).toBeDefined();
			expect(minimalMocks.generateTaskFiles).toBeDefined();
			expect(minimalMocks.readJSON).toBeDefined();
		});

		it('should allow disabling specific mocks', async () => {
			// Test that mocks can be selectively disabled
			const selectiveConfig = {
				core: {
					moveTasksBetweenTags: true,
					generateTaskFiles: false, // Disabled
					readJSON: true
				}
			};

			const selectiveMocks = createMockFactory(selectiveConfig);
			expect(selectiveMocks.moveTasksBetweenTags).toBeDefined();
			expect(selectiveMocks.generateTaskFiles).toBeUndefined();
			expect(selectiveMocks.readJSON).toBeDefined();
		});
	});
});

```

--------------------------------------------------------------------------------
/tests/unit/mcp/tools/remove-task.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Tests for the remove-task MCP tool
 *
 * Note: This test does NOT test the actual implementation. It tests that:
 * 1. The tool is registered correctly with the correct parameters
 * 2. Arguments are passed correctly to removeTaskDirect
 * 3. Error handling works as expected
 * 4. Tag parameter is properly handled and passed through
 *
 * We do NOT import the real implementation - everything is mocked
 */

import { jest } from '@jest/globals';

// Mock EVERYTHING
const mockRemoveTaskDirect = jest.fn();
jest.mock('../../../../mcp-server/src/core/task-master-core.js', () => ({
	removeTaskDirect: mockRemoveTaskDirect
}));

const mockHandleApiResult = jest.fn((result) => result);
const mockWithNormalizedProjectRoot = jest.fn((fn) => fn);
const mockCreateErrorResponse = jest.fn((msg) => ({
	success: false,
	error: { code: 'ERROR', message: msg }
}));
const mockFindTasksPath = jest.fn(() => '/mock/project/tasks.json');

jest.mock('../../../../mcp-server/src/tools/utils.js', () => ({
	handleApiResult: mockHandleApiResult,
	createErrorResponse: mockCreateErrorResponse,
	withNormalizedProjectRoot: mockWithNormalizedProjectRoot
}));

jest.mock('../../../../mcp-server/src/core/utils/path-utils.js', () => ({
	findTasksPath: mockFindTasksPath
}));

// Mock the z object from zod
const mockZod = {
	object: jest.fn(() => mockZod),
	string: jest.fn(() => mockZod),
	boolean: jest.fn(() => mockZod),
	optional: jest.fn(() => mockZod),
	describe: jest.fn(() => mockZod),
	_def: {
		shape: () => ({
			id: {},
			file: {},
			projectRoot: {},
			confirm: {},
			tag: {}
		})
	}
};

jest.mock('zod', () => ({
	z: mockZod
}));

// DO NOT import the real module - create a fake implementation
// This is the fake implementation of registerRemoveTaskTool
const registerRemoveTaskTool = (server) => {
	// Create simplified version of the tool config
	const toolConfig = {
		name: 'remove_task',
		description: 'Remove a task or subtask permanently from the tasks list',
		parameters: mockZod,

		// Create a simplified mock of the execute function
		execute: mockWithNormalizedProjectRoot(async (args, context) => {
			const { log, session } = context;

			try {
				log.info && log.info(`Removing task(s) with ID(s): ${args.id}`);

				// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
				let tasksJsonPath;
				try {
					tasksJsonPath = mockFindTasksPath(
						{ projectRoot: args.projectRoot, file: args.file },
						log
					);
				} catch (error) {
					log.error && log.error(`Error finding tasks.json: ${error.message}`);
					return mockCreateErrorResponse(
						`Failed to find tasks.json: ${error.message}`
					);
				}

				log.info && log.info(`Using tasks file path: ${tasksJsonPath}`);

				const result = await mockRemoveTaskDirect(
					{
						tasksJsonPath: tasksJsonPath,
						id: args.id,
						projectRoot: args.projectRoot,
						tag: args.tag
					},
					log,
					{ session }
				);

				if (result.success) {
					log.info && log.info(`Successfully removed task: ${args.id}`);
				} else {
					log.error &&
						log.error(`Failed to remove task: ${result.error.message}`);
				}

				return mockHandleApiResult(
					result,
					log,
					'Error removing task',
					undefined,
					args.projectRoot
				);
			} catch (error) {
				log.error && log.error(`Error in remove-task tool: ${error.message}`);
				return mockCreateErrorResponse(error.message);
			}
		})
	};

	// Register the tool with the server
	server.addTool(toolConfig);
};

describe('MCP Tool: remove-task', () => {
	// Create mock server
	let mockServer;
	let executeFunction;

	// Create mock logger
	const mockLogger = {
		debug: jest.fn(),
		info: jest.fn(),
		warn: jest.fn(),
		error: jest.fn()
	};

	// Test data
	const validArgs = {
		id: '5',
		projectRoot: '/mock/project/root',
		file: '/mock/project/tasks.json',
		confirm: true,
		tag: 'feature-branch'
	};

	const multipleTaskArgs = {
		id: '5,6.1,7',
		projectRoot: '/mock/project/root',
		tag: 'master'
	};

	// Standard responses
	const successResponse = {
		success: true,
		data: {
			totalTasks: 1,
			successful: 1,
			failed: 0,
			removedTasks: [
				{
					id: 5,
					title: 'Removed Task',
					status: 'pending'
				}
			],
			messages: ["Successfully removed task 5 from tag 'feature-branch'"],
			errors: [],
			tasksPath: '/mock/project/tasks.json',
			tag: 'feature-branch'
		}
	};

	const multipleTasksSuccessResponse = {
		success: true,
		data: {
			totalTasks: 3,
			successful: 3,
			failed: 0,
			removedTasks: [
				{ id: 5, title: 'Task 5', status: 'pending' },
				{ id: 1, title: 'Subtask 6.1', status: 'done', parentTaskId: 6 },
				{ id: 7, title: 'Task 7', status: 'in-progress' }
			],
			messages: [
				"Successfully removed task 5 from tag 'master'",
				"Successfully removed subtask 6.1 from tag 'master'",
				"Successfully removed task 7 from tag 'master'"
			],
			errors: [],
			tasksPath: '/mock/project/tasks.json',
			tag: 'master'
		}
	};

	const errorResponse = {
		success: false,
		error: {
			code: 'INVALID_TASK_ID',
			message: "The following tasks were not found in tag 'feature-branch': 999"
		}
	};

	const pathErrorResponse = {
		success: false,
		error: {
			code: 'PATH_ERROR',
			message: 'Failed to find tasks.json: No tasks.json found'
		}
	};

	beforeEach(() => {
		// Reset all mocks
		jest.clearAllMocks();

		// Create mock server
		mockServer = {
			addTool: jest.fn((config) => {
				executeFunction = config.execute;
			})
		};

		// Setup default successful response
		mockRemoveTaskDirect.mockResolvedValue(successResponse);
		mockFindTasksPath.mockReturnValue('/mock/project/tasks.json');

		// Register the tool
		registerRemoveTaskTool(mockServer);
	});

	test('should register the tool correctly', () => {
		// Verify tool was registered
		expect(mockServer.addTool).toHaveBeenCalledWith(
			expect.objectContaining({
				name: 'remove_task',
				description: 'Remove a task or subtask permanently from the tasks list',
				parameters: expect.any(Object),
				execute: expect.any(Function)
			})
		);

		// Verify the tool config was passed
		const toolConfig = mockServer.addTool.mock.calls[0][0];
		expect(toolConfig).toHaveProperty('parameters');
		expect(toolConfig).toHaveProperty('execute');
	});

	test('should execute the tool with valid parameters including tag', async () => {
		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		await executeFunction(validArgs, mockContext);

		// Verify findTasksPath was called with correct arguments
		expect(mockFindTasksPath).toHaveBeenCalledWith(
			{
				projectRoot: validArgs.projectRoot,
				file: validArgs.file
			},
			mockLogger
		);

		// Verify removeTaskDirect was called with correct arguments including tag
		expect(mockRemoveTaskDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				tasksJsonPath: '/mock/project/tasks.json',
				id: validArgs.id,
				projectRoot: validArgs.projectRoot,
				tag: validArgs.tag // This is the key test - tag parameter should be passed through
			}),
			mockLogger,
			{
				session: mockContext.session
			}
		);

		// Verify handleApiResult was called
		expect(mockHandleApiResult).toHaveBeenCalledWith(
			successResponse,
			mockLogger,
			'Error removing task',
			undefined,
			validArgs.projectRoot
		);
	});

	test('should handle multiple task IDs with tag context', async () => {
		// Setup multiple tasks response
		mockRemoveTaskDirect.mockResolvedValueOnce(multipleTasksSuccessResponse);

		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		await executeFunction(multipleTaskArgs, mockContext);

		// Verify removeTaskDirect was called with comma-separated IDs and tag
		expect(mockRemoveTaskDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				id: '5,6.1,7',
				tag: 'master'
			}),
			mockLogger,
			expect.any(Object)
		);

		// Verify successful handling of multiple tasks
		expect(mockHandleApiResult).toHaveBeenCalledWith(
			multipleTasksSuccessResponse,
			mockLogger,
			'Error removing task',
			undefined,
			multipleTaskArgs.projectRoot
		);
	});

	test('should handle missing tag parameter (defaults to current tag)', async () => {
		const argsWithoutTag = {
			id: '5',
			projectRoot: '/mock/project/root'
		};

		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		await executeFunction(argsWithoutTag, mockContext);

		// Verify removeTaskDirect was called with undefined tag (should default to current tag)
		expect(mockRemoveTaskDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				id: '5',
				projectRoot: '/mock/project/root',
				tag: undefined // Should be undefined when not provided
			}),
			mockLogger,
			expect.any(Object)
		);
	});

	test('should handle errors from removeTaskDirect', async () => {
		// Setup error response
		mockRemoveTaskDirect.mockResolvedValueOnce(errorResponse);

		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		await executeFunction(validArgs, mockContext);

		// Verify removeTaskDirect was called
		expect(mockRemoveTaskDirect).toHaveBeenCalled();

		// Verify error logging
		expect(mockLogger.error).toHaveBeenCalledWith(
			"Failed to remove task: The following tasks were not found in tag 'feature-branch': 999"
		);

		// Verify handleApiResult was called with error response
		expect(mockHandleApiResult).toHaveBeenCalledWith(
			errorResponse,
			mockLogger,
			'Error removing task',
			undefined,
			validArgs.projectRoot
		);
	});

	test('should handle path finding errors', async () => {
		// Setup path finding error
		mockFindTasksPath.mockImplementationOnce(() => {
			throw new Error('No tasks.json found');
		});

		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		const result = await executeFunction(validArgs, mockContext);

		// Verify error logging
		expect(mockLogger.error).toHaveBeenCalledWith(
			'Error finding tasks.json: No tasks.json found'
		);

		// Verify error response was returned
		expect(mockCreateErrorResponse).toHaveBeenCalledWith(
			'Failed to find tasks.json: No tasks.json found'
		);

		// Verify removeTaskDirect was NOT called
		expect(mockRemoveTaskDirect).not.toHaveBeenCalled();
	});

	test('should handle unexpected errors in execute function', async () => {
		// Setup unexpected error
		mockRemoveTaskDirect.mockImplementationOnce(() => {
			throw new Error('Unexpected error');
		});

		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		await executeFunction(validArgs, mockContext);

		// Verify error logging
		expect(mockLogger.error).toHaveBeenCalledWith(
			'Error in remove-task tool: Unexpected error'
		);

		// Verify error response was returned
		expect(mockCreateErrorResponse).toHaveBeenCalledWith('Unexpected error');
	});

	test('should properly handle withNormalizedProjectRoot wrapper', () => {
		// Verify that withNormalizedProjectRoot was called with the execute function
		expect(mockWithNormalizedProjectRoot).toHaveBeenCalledWith(
			expect.any(Function)
		);
	});

	test('should log appropriate info messages for successful operations', async () => {
		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		await executeFunction(validArgs, mockContext);

		// Verify appropriate logging
		expect(mockLogger.info).toHaveBeenCalledWith(
			'Removing task(s) with ID(s): 5'
		);
		expect(mockLogger.info).toHaveBeenCalledWith(
			'Using tasks file path: /mock/project/tasks.json'
		);
		expect(mockLogger.info).toHaveBeenCalledWith(
			'Successfully removed task: 5'
		);
	});

	test('should handle subtask removal with proper tag context', async () => {
		const subtaskArgs = {
			id: '5.2',
			projectRoot: '/mock/project/root',
			tag: 'feature-branch'
		};

		const subtaskSuccessResponse = {
			success: true,
			data: {
				totalTasks: 1,
				successful: 1,
				failed: 0,
				removedTasks: [
					{
						id: 2,
						title: 'Removed Subtask',
						status: 'pending',
						parentTaskId: 5
					}
				],
				messages: [
					"Successfully removed subtask 5.2 from tag 'feature-branch'"
				],
				errors: [],
				tasksPath: '/mock/project/tasks.json',
				tag: 'feature-branch'
			}
		};

		mockRemoveTaskDirect.mockResolvedValueOnce(subtaskSuccessResponse);

		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		await executeFunction(subtaskArgs, mockContext);

		// Verify removeTaskDirect was called with subtask ID and tag
		expect(mockRemoveTaskDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				id: '5.2',
				tag: 'feature-branch'
			}),
			mockLogger,
			expect.any(Object)
		);

		// Verify successful handling
		expect(mockHandleApiResult).toHaveBeenCalledWith(
			subtaskSuccessResponse,
			mockLogger,
			'Error removing task',
			undefined,
			subtaskArgs.projectRoot
		);
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/workflow/services/workflow.service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview WorkflowService - High-level facade for TDD workflow operations
 * Provides a simplified API for MCP tools while delegating to WorkflowOrchestrator
 */

import { GitAdapter } from '../../git/adapters/git-adapter.js';
import { WorkflowStateManager } from '../managers/workflow-state-manager.js';
import { WorkflowOrchestrator } from '../orchestrators/workflow-orchestrator.js';
import type {
	SubtaskInfo,
	TDDPhase,
	TestResult,
	WorkflowContext,
	WorkflowPhase,
	WorkflowState
} from '../types.js';
import { WorkflowActivityLogger } from './workflow-activity-logger.js';

/**
 * Options for starting a new workflow
 */
export interface StartWorkflowOptions {
	taskId: string;
	taskTitle: string;
	subtasks: Array<{
		id: string;
		title: string;
		status: string;
		maxAttempts?: number;
	}>;
	maxAttempts?: number;
	force?: boolean;
	tag?: string; // Optional tag for branch naming
}

/**
 * Simplified workflow status for MCP responses
 */
export interface WorkflowStatus {
	taskId: string;
	phase: WorkflowPhase;
	tddPhase?: TDDPhase;
	branchName?: string;
	currentSubtask?: {
		id: string;
		title: string;
		attempts: number;
		maxAttempts: number;
	};
	progress: {
		completed: number;
		total: number;
		current: number;
		percentage: number;
	};
}

/**
 * Next action recommendation for AI agent
 */
export interface NextAction {
	action: string;
	description: string;
	nextSteps: string;
	phase: WorkflowPhase;
	tddPhase?: TDDPhase;
	subtask?: {
		id: string;
		title: string;
	};
}

/**
 * WorkflowService - Facade for workflow operations
 * Manages WorkflowOrchestrator lifecycle and state persistence
 */
export class WorkflowService {
	private readonly projectRoot: string;
	private readonly stateManager: WorkflowStateManager;
	private orchestrator?: WorkflowOrchestrator;
	private activityLogger?: WorkflowActivityLogger;

	constructor(projectRoot: string) {
		this.projectRoot = projectRoot;
		this.stateManager = new WorkflowStateManager(projectRoot);
	}

	/**
	 * Check if workflow state exists
	 */
	async hasWorkflow(): Promise<boolean> {
		return await this.stateManager.exists();
	}

	/**
	 * Start a new TDD workflow
	 */
	async startWorkflow(options: StartWorkflowOptions): Promise<WorkflowStatus> {
		const {
			taskId,
			taskTitle,
			subtasks,
			maxAttempts = 3,
			force,
			tag
		} = options;

		// Check for existing workflow
		if ((await this.hasWorkflow()) && !force) {
			throw new Error(
				'Workflow already exists. Use force=true to override or resume existing workflow.'
			);
		}

		// Initialize git adapter and ensure clean state
		const gitAdapter = new GitAdapter(this.projectRoot);
		await gitAdapter.ensureGitRepository();
		await gitAdapter.ensureCleanWorkingTree();

		// Parse subtasks to WorkflowContext format
		const workflowSubtasks: SubtaskInfo[] = subtasks.map((st) => ({
			id: st.id,
			title: st.title,
			status: st.status === 'done' ? 'completed' : 'pending',
			attempts: 0,
			maxAttempts: st.maxAttempts || maxAttempts
		}));

		// Find the first incomplete subtask to resume from
		const firstIncompleteIndex = workflowSubtasks.findIndex(
			(st) => st.status !== 'completed'
		);

		// If all subtasks are already completed, throw an error
		if (firstIncompleteIndex === -1) {
			throw new Error(
				`All subtasks for task ${taskId} are already completed. Nothing to do.`
			);
		}

		// Create workflow context, starting from first incomplete subtask
		const context: WorkflowContext = {
			taskId,
			subtasks: workflowSubtasks,
			currentSubtaskIndex: firstIncompleteIndex,
			errors: [],
			metadata: {
				startedAt: new Date().toISOString(),
				taskTitle,
				resumedFromSubtask:
					firstIncompleteIndex > 0
						? workflowSubtasks[firstIncompleteIndex].id
						: undefined
			}
		};

		// Create orchestrator with auto-persistence
		this.orchestrator = new WorkflowOrchestrator(context);
		this.orchestrator.enableAutoPersist(async (state: WorkflowState) => {
			await this.stateManager.save(state);
		});

		// Initialize activity logger to track all workflow events
		this.activityLogger = new WorkflowActivityLogger(
			this.orchestrator,
			this.stateManager.getActivityLogPath()
		);
		this.activityLogger.start();

		// Transition through PREFLIGHT and BRANCH_SETUP phases
		await this.orchestrator.transition({ type: 'PREFLIGHT_COMPLETE' });

		// Create git branch with descriptive name
		const branchName = this.generateBranchName(taskId, taskTitle, tag);

		// Check if we're already on the target branch
		const currentBranch = await gitAdapter.getCurrentBranch();
		if (currentBranch !== branchName) {
			// Only create branch if we're not already on it
			await gitAdapter.createAndCheckoutBranch(branchName);
		}

		// Transition to SUBTASK_LOOP with RED phase
		await this.orchestrator.transition({
			type: 'BRANCH_CREATED',
			branchName
		});

		return this.getStatus();
	}

	/**
	 * Resume an existing workflow
	 */
	async resumeWorkflow(): Promise<WorkflowStatus> {
		// Load state
		const state = await this.stateManager.load();

		// Create new orchestrator with loaded context
		this.orchestrator = new WorkflowOrchestrator(state.context);

		// Validate and restore state
		if (!this.orchestrator.canResumeFromState(state)) {
			throw new Error(
				'Invalid workflow state. State may be corrupted. Consider starting a new workflow.'
			);
		}

		this.orchestrator.restoreState(state);

		// Re-enable auto-persistence
		this.orchestrator.enableAutoPersist(async (newState: WorkflowState) => {
			await this.stateManager.save(newState);
		});

		// Initialize activity logger to continue tracking events
		this.activityLogger = new WorkflowActivityLogger(
			this.orchestrator,
			this.stateManager.getActivityLogPath()
		);
		this.activityLogger.start();

		return this.getStatus();
	}

	/**
	 * Get current workflow status
	 */
	getStatus(): WorkflowStatus {
		if (!this.orchestrator) {
			throw new Error('No active workflow. Start or resume a workflow first.');
		}

		const context = this.orchestrator.getContext();
		const progress = this.orchestrator.getProgress();
		const currentSubtask = this.orchestrator.getCurrentSubtask();

		return {
			taskId: context.taskId,
			phase: this.orchestrator.getCurrentPhase(),
			tddPhase: this.orchestrator.getCurrentTDDPhase(),
			branchName: context.branchName,
			currentSubtask: currentSubtask
				? {
						id: currentSubtask.id,
						title: currentSubtask.title,
						attempts: currentSubtask.attempts,
						maxAttempts: currentSubtask.maxAttempts || 3
					}
				: undefined,
			progress
		};
	}

	/**
	 * Get workflow context (for accessing full state details)
	 */
	getContext(): WorkflowContext {
		if (!this.orchestrator) {
			throw new Error('No active workflow. Start or resume a workflow first.');
		}

		return this.orchestrator.getContext();
	}

	/**
	 * Get next recommended action for AI agent
	 */
	getNextAction(): NextAction {
		if (!this.orchestrator) {
			throw new Error('No active workflow. Start or resume a workflow first.');
		}

		const phase = this.orchestrator.getCurrentPhase();
		const tddPhase = this.orchestrator.getCurrentTDDPhase();
		const currentSubtask = this.orchestrator.getCurrentSubtask();

		// Determine action based on current phase
		if (phase === 'COMPLETE') {
			return {
				action: 'workflow_complete',
				description: 'All subtasks completed',
				nextSteps:
					'All subtasks completed! Review the entire implementation and merge your branch when ready.',
				phase
			};
		}

		if (phase === 'FINALIZE') {
			return {
				action: 'finalize_workflow',
				description: 'Finalize and complete the workflow',
				nextSteps:
					'All subtasks are complete! Use autopilot_finalize to verify no uncommitted changes remain and mark the workflow as complete.',
				phase
			};
		}

		if (phase !== 'SUBTASK_LOOP' || !tddPhase || !currentSubtask) {
			return {
				action: 'unknown',
				description: 'Workflow is not in active state',
				nextSteps: 'Use autopilot_status to check workflow state.',
				phase
			};
		}

		const baseAction = {
			phase,
			tddPhase,
			subtask: {
				id: currentSubtask.id,
				title: currentSubtask.title
			}
		};

		switch (tddPhase) {
			case 'RED':
				return {
					...baseAction,
					action: 'generate_test',
					description: 'Generate failing test for current subtask',
					nextSteps: `Write failing tests for subtask ${currentSubtask.id}: "${currentSubtask.title}". Create test file(s) that validate the expected behavior. Run tests and use autopilot_complete_phase with results. Note: If all tests pass (0 failures), the feature is already implemented and the subtask will be auto-completed.`
				};
			case 'GREEN':
				return {
					...baseAction,
					action: 'implement_code',
					description: 'Implement feature to make tests pass',
					nextSteps: `Implement code to make tests pass for subtask ${currentSubtask.id}: "${currentSubtask.title}". Write the minimal code needed to pass all tests (GREEN phase), then use autopilot_complete_phase with test results.`
				};
			case 'COMMIT':
				return {
					...baseAction,
					action: 'commit_changes',
					description: 'Commit RED-GREEN cycle changes',
					nextSteps: `Review and commit your changes for subtask ${currentSubtask.id}: "${currentSubtask.title}". Use autopilot_commit to create the commit and advance to the next subtask.`
				};
			default:
				return {
					...baseAction,
					action: 'unknown',
					description: 'Unknown TDD phase',
					nextSteps: 'Use autopilot_status to check workflow state.'
				};
		}
	}

	/**
	 * Complete current TDD phase with test results
	 */
	async completePhase(testResults: TestResult): Promise<WorkflowStatus> {
		if (!this.orchestrator) {
			throw new Error('No active workflow. Start or resume a workflow first.');
		}

		const tddPhase = this.orchestrator.getCurrentTDDPhase();

		if (!tddPhase) {
			throw new Error('Not in active TDD phase');
		}

		// Transition based on current phase
		switch (tddPhase) {
			case 'RED':
				await this.orchestrator.transition({
					type: 'RED_PHASE_COMPLETE',
					testResults
				});
				break;
			case 'GREEN':
				await this.orchestrator.transition({
					type: 'GREEN_PHASE_COMPLETE',
					testResults
				});
				break;
			case 'COMMIT':
				throw new Error(
					'Cannot complete COMMIT phase with test results. Use commit() instead.'
				);
			default:
				throw new Error(`Unknown TDD phase: ${tddPhase}`);
		}

		return this.getStatus();
	}

	/**
	 * Commit current changes and advance workflow
	 */
	async commit(): Promise<WorkflowStatus> {
		if (!this.orchestrator) {
			throw new Error('No active workflow. Start or resume a workflow first.');
		}

		const tddPhase = this.orchestrator.getCurrentTDDPhase();

		if (tddPhase !== 'COMMIT') {
			throw new Error(
				`Cannot commit in ${tddPhase} phase. Complete RED and GREEN phases first.`
			);
		}

		// Transition COMMIT phase complete
		await this.orchestrator.transition({
			type: 'COMMIT_COMPLETE'
		});

		// Check if should advance to next subtask
		const progress = this.orchestrator.getProgress();
		if (progress.current < progress.total) {
			await this.orchestrator.transition({ type: 'SUBTASK_COMPLETE' });
		} else {
			// All subtasks complete
			await this.orchestrator.transition({ type: 'ALL_SUBTASKS_COMPLETE' });
		}

		return this.getStatus();
	}

	/**
	 * Finalize and complete the workflow
	 * Validates working tree is clean before marking complete
	 */
	async finalizeWorkflow(): Promise<WorkflowStatus> {
		if (!this.orchestrator) {
			throw new Error('No active workflow. Start or resume a workflow first.');
		}

		const phase = this.orchestrator.getCurrentPhase();
		if (phase !== 'FINALIZE') {
			throw new Error(
				`Cannot finalize workflow in ${phase} phase. Complete all subtasks first.`
			);
		}

		// Check working tree is clean
		const gitAdapter = new GitAdapter(this.projectRoot);
		const statusSummary = await gitAdapter.getStatusSummary();

		if (!statusSummary.isClean) {
			throw new Error(
				`Cannot finalize workflow: working tree has uncommitted changes.\n` +
					`Staged: ${statusSummary.staged}, Modified: ${statusSummary.modified}, ` +
					`Deleted: ${statusSummary.deleted}, Untracked: ${statusSummary.untracked}\n` +
					`Please commit all changes before finalizing the workflow.`
			);
		}

		// Transition to COMPLETE
		await this.orchestrator.transition({ type: 'FINALIZE_COMPLETE' });

		return this.getStatus();
	}

	/**
	 * Abort current workflow
	 */
	async abortWorkflow(): Promise<void> {
		if (this.orchestrator) {
			await this.orchestrator.transition({ type: 'ABORT' });
		}

		// Delete state file
		await this.stateManager.delete();

		this.orchestrator = undefined;
	}

	/**
	 * Generate a descriptive git branch name
	 * Format: tag-name/task-id-task-title or task-id-task-title
	 */
	private generateBranchName(
		taskId: string,
		taskTitle: string,
		tag?: string
	): string {
		// Sanitize task title for branch name
		const sanitizedTitle = taskTitle
			.toLowerCase()
			.replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with dash
			.replace(/^-+|-+$/g, '') // Remove leading/trailing dashes
			.substring(0, 50); // Limit length

		// Format task ID for branch name
		const formattedTaskId = taskId.replace(/\./g, '-');

		// Add tag prefix if tag is provided
		const tagPrefix = tag ? `${tag}/` : '';

		return `${tagPrefix}task-${formattedTaskId}-${sanitizedTitle}`;
	}
}

```

--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/grok-cli-language-model.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Grok CLI Language Model implementation for AI SDK v5
 */

import { spawn } from 'child_process';
import { promises as fs } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
import type {
	LanguageModelV2,
	LanguageModelV2CallOptions,
	LanguageModelV2CallWarning
} from '@ai-sdk/provider';
import { NoSuchModelError } from '@ai-sdk/provider';
import { generateId } from '@ai-sdk/provider-utils';

import {
	createAPICallError,
	createAuthenticationError,
	createInstallationError,
	createTimeoutError
} from './errors.js';
import { extractJson } from './json-extractor.js';
import {
	convertFromGrokCliResponse,
	createPromptFromMessages,
	escapeShellArg
} from './message-converter.js';
import type {
	GrokCliLanguageModelOptions,
	GrokCliModelId,
	GrokCliSettings
} from './types.js';

/**
 * Grok CLI Language Model implementation for AI SDK v5
 */
export class GrokCliLanguageModel implements LanguageModelV2 {
	readonly specificationVersion = 'v2' as const;
	readonly defaultObjectGenerationMode = 'json' as const;
	readonly supportsImageUrls = false;
	readonly supportsStructuredOutputs = false;
	readonly supportedUrls: Record<string, RegExp[]> = {};

	readonly modelId: GrokCliModelId;
	readonly settings: GrokCliSettings;

	constructor(options: GrokCliLanguageModelOptions) {
		this.modelId = options.id;
		this.settings = options.settings ?? {};

		// Validate model ID format
		if (
			!this.modelId ||
			typeof this.modelId !== 'string' ||
			this.modelId.trim() === ''
		) {
			throw new NoSuchModelError({
				modelId: this.modelId,
				modelType: 'languageModel'
			});
		}
	}

	get provider(): string {
		return 'grok-cli';
	}

	/**
	 * Check if Grok CLI is installed and available
	 */
	private async checkGrokCliInstallation(): Promise<boolean> {
		return new Promise((resolve) => {
			const child = spawn('grok', ['--version'], {
				stdio: 'pipe'
			});

			child.on('error', () => resolve(false));
			child.on('exit', (code) => resolve(code === 0));
		});
	}

	/**
	 * Get API key from settings or environment
	 */
	private async getApiKey(): Promise<string | null> {
		// Check settings first
		if (this.settings.apiKey) {
			return this.settings.apiKey;
		}

		// Check environment variable
		if (process.env.GROK_CLI_API_KEY) {
			return process.env.GROK_CLI_API_KEY;
		}

		// Check grok-cli config file
		try {
			const configPath = join(homedir(), '.grok', 'user-settings.json');
			const configContent = await fs.readFile(configPath, 'utf8');
			const config = JSON.parse(configContent);
			return config.apiKey || null;
		} catch (error) {
			return null;
		}
	}

	/**
	 * Execute Grok CLI command
	 */
	private async executeGrokCli(
		args: string[],
		options: { timeout?: number; apiKey?: string } = {}
	): Promise<{ stdout: string; stderr: string; exitCode: number }> {
		// Default timeout based on model type
		let defaultTimeout = 120000; // 2 minutes default
		if (this.modelId.includes('grok-4')) {
			defaultTimeout = 600000; // 10 minutes for grok-4 models (they seem to hang during setup)
		}

		const timeout = options.timeout ?? this.settings.timeout ?? defaultTimeout;

		return new Promise((resolve, reject) => {
			const child = spawn('grok', args, {
				stdio: 'pipe',
				cwd: this.settings.workingDirectory || process.cwd(),
				env:
					options.apiKey === undefined
						? process.env
						: { ...process.env, GROK_CLI_API_KEY: options.apiKey }
			});

			let stdout = '';
			let stderr = '';
			let timeoutId: NodeJS.Timeout | undefined;

			// Set up timeout
			if (timeout > 0) {
				timeoutId = setTimeout(() => {
					child.kill('SIGTERM');
					reject(
						createTimeoutError({
							message: `Grok CLI command timed out after ${timeout}ms`,
							timeoutMs: timeout,
							promptExcerpt: args.join(' ').substring(0, 200)
						})
					);
				}, timeout);
			}

			child.stdout?.on('data', (data) => {
				const chunk = data.toString();
				stdout += chunk;
			});

			child.stderr?.on('data', (data) => {
				const chunk = data.toString();
				stderr += chunk;
			});

			child.on('error', (error) => {
				if (timeoutId) clearTimeout(timeoutId);

				if ((error as any).code === 'ENOENT') {
					reject(createInstallationError({}));
				} else {
					reject(
						createAPICallError({
							message: `Failed to execute Grok CLI: ${error.message}`,
							code: (error as any).code,
							stderr: error.message,
							isRetryable: false
						})
					);
				}
			});

			child.on('exit', (exitCode) => {
				if (timeoutId) clearTimeout(timeoutId);

				resolve({
					stdout: stdout.trim(),
					stderr: stderr.trim(),
					exitCode: exitCode || 0
				});
			});
		});
	}

	/**
	 * Generate comprehensive warnings for unsupported parameters and validation issues
	 */
	private generateAllWarnings(
		options: LanguageModelV2CallOptions,
		prompt: string
	): LanguageModelV2CallWarning[] {
		const warnings: LanguageModelV2CallWarning[] = [];
		const unsupportedParams: string[] = [];

		// Check for unsupported parameters
		if (options.temperature !== undefined)
			unsupportedParams.push('temperature');
		if (options.topP !== undefined) unsupportedParams.push('topP');
		if (options.topK !== undefined) unsupportedParams.push('topK');
		if (options.presencePenalty !== undefined)
			unsupportedParams.push('presencePenalty');
		if (options.frequencyPenalty !== undefined)
			unsupportedParams.push('frequencyPenalty');
		if (options.stopSequences !== undefined && options.stopSequences.length > 0)
			unsupportedParams.push('stopSequences');
		if (options.seed !== undefined) unsupportedParams.push('seed');

		if (unsupportedParams.length > 0) {
			// Add a warning for each unsupported parameter
			for (const param of unsupportedParams) {
				warnings.push({
					type: 'unsupported-setting',
					setting: param as
						| 'temperature'
						| 'topP'
						| 'topK'
						| 'presencePenalty'
						| 'frequencyPenalty'
						| 'stopSequences'
						| 'seed',
					details: `Grok CLI does not support the ${param} parameter. It will be ignored.`
				});
			}
		}

		// Add model validation warnings if needed
		if (!this.modelId || this.modelId.trim() === '') {
			warnings.push({
				type: 'other',
				message: 'Model ID is empty or invalid'
			});
		}

		// Add prompt validation
		if (!prompt || prompt.trim() === '') {
			warnings.push({
				type: 'other',
				message: 'Prompt is empty'
			});
		}

		return warnings;
	}

	/**
	 * Generate text using Grok CLI
	 */
	async doGenerate(options: LanguageModelV2CallOptions) {
		// Handle abort signal early
		if (options.abortSignal?.aborted) {
			throw options.abortSignal.reason || new Error('Request aborted');
		}

		// Check CLI installation
		const isInstalled = await this.checkGrokCliInstallation();
		if (!isInstalled) {
			throw createInstallationError({});
		}

		// Get API key
		const apiKey = await this.getApiKey();
		if (!apiKey) {
			throw createAuthenticationError({
				message:
					'Grok CLI API key not found. Set GROK_CLI_API_KEY environment variable or configure grok-cli.'
			});
		}

		const prompt = createPromptFromMessages(options.prompt);
		const warnings = this.generateAllWarnings(options, prompt);

		// Build command arguments
		const args = ['--prompt', escapeShellArg(prompt)];

		// Add model if specified
		if (this.modelId && this.modelId !== 'default') {
			args.push('--model', this.modelId);
		}

		// Skip API key parameter if it's likely already configured to avoid hanging
		// The CLI seems to hang when trying to save API keys for grok-4 models
		// if (apiKey) {
		//	args.push('--api-key', apiKey);
		// }

		// Add base URL if provided in settings
		if (this.settings.baseURL) {
			args.push('--base-url', this.settings.baseURL);
		}

		// Add working directory if specified
		if (this.settings.workingDirectory) {
			args.push('--directory', this.settings.workingDirectory);
		}

		try {
			const result = await this.executeGrokCli(args, { apiKey });

			if (result.exitCode !== 0) {
				// Handle authentication errors
				if (
					result.stderr.toLowerCase().includes('unauthorized') ||
					result.stderr.toLowerCase().includes('authentication')
				) {
					throw createAuthenticationError({
						message: `Grok CLI authentication failed: ${result.stderr}`
					});
				}

				throw createAPICallError({
					message: `Grok CLI failed with exit code ${result.exitCode}: ${result.stderr || 'Unknown error'}`,
					exitCode: result.exitCode,
					stderr: result.stderr,
					stdout: result.stdout,
					promptExcerpt: prompt.substring(0, 200),
					isRetryable: false
				});
			}

			// Parse response
			const response = convertFromGrokCliResponse(result.stdout);
			let text = response.text || '';

			// Extract JSON if in object-json mode
			const isObjectJson = (
				o: unknown
			): o is { mode: { type: 'object-json' } } =>
				!!o &&
				typeof o === 'object' &&
				'mode' in o &&
				(o as any).mode?.type === 'object-json';
			if (isObjectJson(options) && text) {
				text = extractJson(text);
			}

			return {
				content: [
					{
						type: 'text' as const,
						text: text || ''
					}
				],
				usage: response.usage
					? {
							inputTokens: response.usage.promptTokens,
							outputTokens: response.usage.completionTokens,
							totalTokens: response.usage.totalTokens
						}
					: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
				finishReason: 'stop' as const,
				rawCall: {
					rawPrompt: prompt,
					rawSettings: args
				},
				warnings: warnings,
				response: {
					id: generateId(),
					timestamp: new Date(),
					modelId: this.modelId
				},
				request: {
					body: prompt
				},
				providerMetadata: {
					'grok-cli': {
						exitCode: result.exitCode,
						...(result.stderr && { stderr: result.stderr })
					}
				}
			};
		} catch (error) {
			// Re-throw our custom errors
			if (
				(error as any).name === 'APICallError' ||
				(error as any).name === 'LoadAPIKeyError'
			) {
				throw error;
			}

			// Wrap other errors
			throw createAPICallError({
				message: `Grok CLI execution failed: ${(error as Error).message}`,
				code: (error as any).code,
				promptExcerpt: prompt.substring(0, 200),
				isRetryable: false
			});
		}
	}

	/**
	 * Stream text using Grok CLI
	 * Note: Grok CLI doesn't natively support streaming, so this simulates streaming
	 * by generating the full response and then streaming it in chunks
	 */
	async doStream(options: LanguageModelV2CallOptions) {
		const prompt = createPromptFromMessages(options.prompt);
		const warnings = this.generateAllWarnings(options, prompt);

		const stream = new ReadableStream({
			start: async (controller) => {
				let abortListener: (() => void) | undefined;

				try {
					// Handle abort signal
					if (options.abortSignal?.aborted) {
						throw options.abortSignal.reason || new Error('Request aborted');
					}

					// Set up abort listener
					if (options.abortSignal) {
						abortListener = () => {
							controller.enqueue({
								type: 'error',
								error:
									options.abortSignal?.reason || new Error('Request aborted')
							});
							controller.close();
						};
						options.abortSignal.addEventListener('abort', abortListener, {
							once: true
						});
					}

					// Emit stream-start with warnings
					controller.enqueue({ type: 'stream-start', warnings });

					// Generate the full response first
					const result = await this.doGenerate(options);

					// Emit response metadata
					controller.enqueue({
						type: 'response-metadata',
						id: result.response.id,
						timestamp: result.response.timestamp,
						modelId: result.response.modelId
					});

					// Simulate streaming by chunking the text
					const content = result.content || [];
					const text =
						content.length > 0 && content[0].type === 'text'
							? content[0].text
							: '';
					const chunkSize = 50; // Characters per chunk
					let textPartId: string | undefined;

					// Emit text-start if we have content
					if (text.length > 0) {
						textPartId = generateId();
						controller.enqueue({
							type: 'text-start',
							id: textPartId
						});
					}

					for (let i = 0; i < text.length; i += chunkSize) {
						// Check for abort during streaming
						if (options.abortSignal?.aborted) {
							throw options.abortSignal.reason || new Error('Request aborted');
						}

						const chunk = text.slice(i, i + chunkSize);
						controller.enqueue({
							type: 'text-delta',
							id: textPartId!,
							delta: chunk
						});

						// Add small delay to simulate streaming
						await new Promise((resolve) => setTimeout(resolve, 20));
					}

					// Close text part if opened
					if (textPartId) {
						controller.enqueue({
							type: 'text-end',
							id: textPartId
						});
					}

					// Emit finish event
					controller.enqueue({
						type: 'finish',
						finishReason: result.finishReason,
						usage: result.usage,
						providerMetadata: result.providerMetadata
					});

					controller.close();
				} catch (error) {
					controller.enqueue({
						type: 'error',
						error
					});
					controller.close();
				} finally {
					// Clean up abort listener
					if (options.abortSignal && abortListener) {
						options.abortSignal.removeEventListener('abort', abortListener);
					}
				}
			},
			cancel: () => {
				// Clean up if stream is cancelled
			}
		});

		return {
			stream,
			request: {
				body: prompt
			}
		};
	}
}

```
Page 26/50FirstPrevNextLast