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

# Directory Structure

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

# Files

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

```javascript
import { log, readJSON, isSilentMode, findProjectRoot } from '../utils.js';
import {
	startLoadingIndicator,
	stopLoadingIndicator,
	displayAiUsageSummary
} from '../ui.js';
import expandTask from './expand-task.js';
import { getDebugFlag } from '../config-manager.js';
import { aggregateTelemetry } from '../utils.js';
import chalk from 'chalk';
import boxen from 'boxen';

/**
 * Expand all eligible pending or in-progress tasks using the expandTask function.
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {number} [numSubtasks] - Optional: Target number of subtasks per task.
 * @param {boolean} [useResearch=false] - Whether to use the research AI role.
 * @param {string} [additionalContext=''] - Optional additional context.
 * @param {boolean} [force=false] - Force expansion even if tasks already have subtasks.
 * @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 {string} [context.complexityReportPath] - Path to the complexity report file
 * @param {string} [outputFormat='text'] - Output format ('text' or 'json'). MCP calls should use 'json'.
 * @returns {Promise<{success: boolean, expandedCount: number, failedCount: number, skippedCount: number, tasksToExpand: number, telemetryData: Array<Object>}>} - Result summary.
 */
async function expandAllTasks(
	tasksPath,
	numSubtasks, // Keep this signature, expandTask handles defaults
	useResearch = false,
	additionalContext = '',
	force = false, // Keep force here for the filter logic
	context = {},
	outputFormat = 'text' // Assume text default for CLI
) {
	const {
		session,
		mcpLog,
		projectRoot: providedProjectRoot,
		tag,
		complexityReportPath
	} = context;
	const isMCPCall = !!mcpLog; // Determine if called from MCP

	const projectRoot = providedProjectRoot || findProjectRoot();
	if (!projectRoot) {
		throw new Error('Could not determine project root directory');
	}

	// Use mcpLog if available, otherwise use the default console log wrapper respecting silent mode
	const logger =
		mcpLog ||
		(outputFormat === 'json'
			? {
					// Basic logger for JSON output mode
					info: (msg) => {},
					warn: (msg) => {},
					error: (msg) => console.error(`ERROR: ${msg}`), // Still log errors
					debug: (msg) => {}
				}
			: {
					// CLI logger respecting silent mode
					info: (msg) => !isSilentMode() && log('info', msg),
					warn: (msg) => !isSilentMode() && log('warn', msg),
					error: (msg) => !isSilentMode() && log('error', msg),
					debug: (msg) =>
						!isSilentMode() && getDebugFlag(session) && log('debug', msg)
				});

	let loadingIndicator = null;
	let expandedCount = 0;
	let failedCount = 0;
	let tasksToExpandCount = 0;
	const allTelemetryData = []; // Still collect individual data first

	if (!isMCPCall && outputFormat === 'text') {
		loadingIndicator = startLoadingIndicator(
			'Analyzing tasks for expansion...'
		);
	}

	try {
		logger.info(`Reading tasks from ${tasksPath}`);
		const data = readJSON(tasksPath, projectRoot, tag);
		if (!data || !data.tasks) {
			throw new Error(`Invalid tasks data in ${tasksPath}`);
		}

		// --- Restore Original Filtering Logic ---
		const tasksToExpand = data.tasks.filter(
			(task) =>
				(task.status === 'pending' || task.status === 'in-progress') && // Include 'in-progress'
				(!task.subtasks || task.subtasks.length === 0 || force) // Check subtasks/force here
		);
		tasksToExpandCount = tasksToExpand.length; // Get the count from the filtered array
		logger.info(`Found ${tasksToExpandCount} tasks eligible for expansion.`);
		// --- End Restored Filtering Logic ---

		if (loadingIndicator) {
			stopLoadingIndicator(loadingIndicator, 'Analysis complete.');
		}

		if (tasksToExpandCount === 0) {
			logger.info('No tasks eligible for expansion.');
			// --- Fix: Restore success: true and add message ---
			return {
				success: true, // Indicate overall success despite no action
				expandedCount: 0,
				failedCount: 0,
				skippedCount: 0,
				tasksToExpand: 0,
				telemetryData: allTelemetryData,
				message: 'No tasks eligible for expansion.'
			};
			// --- End Fix ---
		}

		// Iterate over the already filtered tasks
		for (const task of tasksToExpand) {
			// Start indicator for individual task expansion in CLI mode
			let taskIndicator = null;
			if (!isMCPCall && outputFormat === 'text') {
				taskIndicator = startLoadingIndicator(`Expanding task ${task.id}...`);
			}

			try {
				// Call the refactored expandTask function AND capture result
				const result = await expandTask(
					tasksPath,
					task.id,
					numSubtasks,
					useResearch,
					additionalContext,
					{
						...context,
						projectRoot,
						tag: data.tag || tag,
						complexityReportPath
					}, // Pass the whole context object with projectRoot and resolved tag
					force
				);
				expandedCount++;

				// Collect individual telemetry data
				if (result && result.telemetryData) {
					allTelemetryData.push(result.telemetryData);
				}

				if (taskIndicator) {
					stopLoadingIndicator(taskIndicator, `Task ${task.id} expanded.`);
				}
				logger.info(`Successfully expanded task ${task.id}.`);
			} catch (error) {
				failedCount++;
				if (taskIndicator) {
					stopLoadingIndicator(
						taskIndicator,
						`Failed to expand task ${task.id}.`,
						false
					);
				}
				logger.error(`Failed to expand task ${task.id}: ${error.message}`);
				// Continue to the next task
			}
		}

		// --- AGGREGATION AND DISPLAY ---
		logger.info(
			`Expansion complete: ${expandedCount} expanded, ${failedCount} failed.`
		);

		// Aggregate the collected telemetry data
		const aggregatedTelemetryData = aggregateTelemetry(
			allTelemetryData,
			'expand-all-tasks'
		);

		if (outputFormat === 'text') {
			const summaryContent =
				`${chalk.white.bold('Expansion Summary:')}\n\n` +
				`${chalk.cyan('-')} Attempted: ${chalk.bold(tasksToExpandCount)}\n` +
				`${chalk.green('-')} Expanded:  ${chalk.bold(expandedCount)}\n` +
				// Skipped count is always 0 now due to pre-filtering
				`${chalk.gray('-')} Skipped:   ${chalk.bold(0)}\n` +
				`${chalk.red('-')} Failed:    ${chalk.bold(failedCount)}`;

			console.log(
				boxen(summaryContent, {
					padding: 1,
					margin: { top: 1 },
					borderColor: failedCount > 0 ? 'red' : 'green', // Red if failures, green otherwise
					borderStyle: 'round'
				})
			);
		}

		if (outputFormat === 'text' && aggregatedTelemetryData) {
			displayAiUsageSummary(aggregatedTelemetryData, 'cli');
		}

		// Return summary including the AGGREGATED telemetry data
		return {
			success: true,
			expandedCount,
			failedCount,
			skippedCount: 0,
			tasksToExpand: tasksToExpandCount,
			telemetryData: aggregatedTelemetryData
		};
	} catch (error) {
		if (loadingIndicator)
			stopLoadingIndicator(loadingIndicator, 'Error.', false);
		logger.error(`Error during expand all operation: ${error.message}`);
		if (!isMCPCall && getDebugFlag(session)) {
			console.error(error); // Log full stack in debug CLI mode
		}
		// Re-throw error for the caller to handle, the direct function will format it
		throw error; // Let direct function wrapper handle formatting
		/* Original re-throw:
		throw new Error(`Failed to expand all tasks: ${error.message}`);
		*/
	}
}

export default expandAllTasks;

```

--------------------------------------------------------------------------------
/apps/cli/src/commands/models/custom-providers.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Custom provider handlers for model setup
 */

import { CUSTOM_PROVIDERS } from '@tm/core';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { validateOllamaModel, validateOpenRouterModel } from './fetchers.js';
import { CUSTOM_PROVIDER_IDS } from './types.js';
import type {
	CustomProviderConfig,
	CustomProviderId,
	ModelRole
} from './types.js';

/**
 * Configuration for all custom providers
 */
export const customProviderConfigs: Record<
	keyof typeof CUSTOM_PROVIDER_IDS,
	CustomProviderConfig
> = {
	OPENROUTER: {
		id: '__CUSTOM_OPENROUTER__',
		name: '* Custom OpenRouter model',
		provider: CUSTOM_PROVIDERS.OPENROUTER,
		promptMessage: (role) =>
			`Enter the custom OpenRouter Model ID for the ${role} role:`,
		validate: async (modelId) => {
			const isValid = await validateOpenRouterModel(modelId);
			if (!isValid) {
				console.error(
					chalk.red(
						`Error: Model ID "${modelId}" not found in the live OpenRouter model list. Please check the ID.`
					)
				);
			}
			return isValid;
		}
	},
	OLLAMA: {
		id: '__CUSTOM_OLLAMA__',
		name: '* Custom Ollama model',
		provider: CUSTOM_PROVIDERS.OLLAMA,
		requiresBaseURL: true,
		defaultBaseURL: 'http://localhost:11434/api',
		promptMessage: (role) =>
			`Enter the custom Ollama Model ID for the ${role} role:`,
		validate: async (modelId, baseURL) => {
			const urlToCheck = baseURL || 'http://localhost:11434/api';
			const isValid = await validateOllamaModel(modelId, urlToCheck);
			if (!isValid) {
				console.error(
					chalk.red(
						`Error: Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available.`
					)
				);
				console.log(
					chalk.yellow(
						`You can check available models with: curl ${urlToCheck}/tags`
					)
				);
			}
			return isValid;
		}
	},
	BEDROCK: {
		id: '__CUSTOM_BEDROCK__',
		name: '* Custom Bedrock model',
		provider: CUSTOM_PROVIDERS.BEDROCK,
		promptMessage: (role) =>
			`Enter the custom Bedrock Model ID for the ${role} role (e.g., anthropic.claude-3-sonnet-20240229-v1:0):`,
		checkEnvVars: () => {
			if (
				!process.env.AWS_ACCESS_KEY_ID ||
				!process.env.AWS_SECRET_ACCESS_KEY
			) {
				console.warn(
					chalk.yellow(
						'Warning: AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY environment variables are missing. Will fallback to system configuration (ex: aws config files or ec2 instance profiles).'
					)
				);
			}
			return true;
		}
	},
	AZURE: {
		id: '__CUSTOM_AZURE__',
		name: '* Custom Azure model',
		provider: CUSTOM_PROVIDERS.AZURE,
		promptMessage: (role) =>
			`Enter the custom Azure OpenAI Model ID for the ${role} role (e.g., gpt-4o):`,
		checkEnvVars: () => {
			if (
				!process.env.AZURE_OPENAI_API_KEY ||
				!process.env.AZURE_OPENAI_ENDPOINT
			) {
				console.error(
					chalk.red(
						'Error: AZURE_OPENAI_API_KEY and/or AZURE_OPENAI_ENDPOINT environment variables are missing. Please set them before using custom Azure models.'
					)
				);
				return false;
			}
			return true;
		}
	},
	VERTEX: {
		id: '__CUSTOM_VERTEX__',
		name: '* Custom Vertex model',
		provider: CUSTOM_PROVIDERS.VERTEX,
		promptMessage: (role) =>
			`Enter the custom Vertex AI Model ID for the ${role} role (e.g., gemini-1.5-pro-002):`,
		checkEnvVars: () => {
			if (
				!process.env.GOOGLE_API_KEY &&
				!process.env.GOOGLE_APPLICATION_CREDENTIALS
			) {
				console.error(
					chalk.red(
						'Error: Either GOOGLE_API_KEY or GOOGLE_APPLICATION_CREDENTIALS environment variable is required. Please set one before using custom Vertex models.'
					)
				);
				return false;
			}
			return true;
		}
	},
	LMSTUDIO: {
		id: '__CUSTOM_LMSTUDIO__',
		name: '* Custom LMStudio model',
		provider: CUSTOM_PROVIDERS.LMSTUDIO,
		requiresBaseURL: true,
		defaultBaseURL: 'http://localhost:1234/v1',
		promptMessage: (role) =>
			`Enter the custom LM Studio Model ID for the ${role} role:`,
		checkEnvVars: () => {
			console.log(
				chalk.blue(
					'Note: LM Studio runs locally. Make sure the LM Studio server is running.'
				)
			);
			return true;
		}
	},
	OPENAI_COMPATIBLE: {
		id: '__CUSTOM_OPENAI_COMPATIBLE__',
		name: '* Custom OpenAI-compatible model',
		provider: CUSTOM_PROVIDERS.OPENAI_COMPATIBLE,
		promptMessage: (role) =>
			`Enter the custom OpenAI-compatible Model ID for the ${role} role:`,
		requiresBaseURL: true,
		checkEnvVars: () => {
			console.log(
				chalk.blue(
					'Note: This will configure a generic OpenAI-compatible provider. Make sure your API endpoint is accessible.'
				)
			);
			return true;
		}
	}
};

/**
 * Handle custom provider selection
 */
export async function handleCustomProvider(
	providerId: CustomProviderId,
	role: ModelRole,
	currentModel: {
		modelId?: string | null;
		provider?: string | null;
		baseURL?: string | null;
	} | null = null
): Promise<{
	modelId: string | null;
	provider: string | null;
	baseURL?: string | null;
	success: boolean;
}> {
	// Find the matching config
	const configEntry = Object.entries(customProviderConfigs).find(
		([_, config]) => config.id === providerId
	);

	if (!configEntry) {
		console.error(chalk.red(`Unknown custom provider: ${providerId}`));
		return { modelId: null, provider: null, success: false };
	}

	const config = configEntry[1];

	// Check environment variables if needed
	if (config.checkEnvVars && !config.checkEnvVars()) {
		return { modelId: null, provider: null, success: false };
	}

	// Prompt for baseURL if required
	let baseURL: string | null = null;
	if (config.requiresBaseURL) {
		// Determine the appropriate default baseURL
		let defaultBaseURL: string;
		if (currentModel?.provider === config.provider && currentModel?.baseURL) {
			// Already using this provider - preserve existing baseURL
			defaultBaseURL = currentModel.baseURL;
		} else {
			// Switching providers or no existing baseURL - use fallback default
			defaultBaseURL = config.defaultBaseURL || '';
		}

		const baseURLAnswer = await inquirer.prompt([
			{
				type: 'input',
				name: 'baseURL',
				message: `Enter the base URL for the ${role} role:`,
				default: defaultBaseURL,
				validate: (input: string) => {
					if (!input || input.trim() === '') {
						return `Base URL is required for ${config.provider} providers`;
					}
					try {
						new URL(input);
						return true;
					} catch {
						return 'Please enter a valid URL';
					}
				}
			}
		]);
		baseURL = baseURLAnswer.baseURL;
	}

	// Prompt for custom ID
	const { customId } = await inquirer.prompt([
		{
			type: 'input',
			name: 'customId',
			message: config.promptMessage(role)
		}
	]);

	if (!customId) {
		console.log(chalk.yellow('No custom ID entered. Skipping role.'));
		return { modelId: null, provider: null, success: true };
	}

	// Validate if validation function exists
	if (config.validate) {
		const isValid = await config.validate(customId, baseURL || undefined);
		if (!isValid) {
			return { modelId: null, provider: null, success: false };
		}
	} else {
		console.log(
			chalk.blue(
				`Custom ${config.provider} model "${customId}" will be used. No validation performed.`
			)
		);
	}

	return {
		modelId: customId,
		provider: config.provider,
		baseURL: baseURL,
		success: true
	};
}

/**
 * Get all custom provider options for display
 */
export function getCustomProviderOptions(): Array<{
	name: string;
	value: CustomProviderId;
	short: string;
}> {
	return Object.values(customProviderConfigs).map((config) => ({
		name: config.name,
		value: config.id,
		short: config.name
	}));
}

```

--------------------------------------------------------------------------------
/docs/cross-tag-task-movement.md:
--------------------------------------------------------------------------------

```markdown
# Cross-Tag Task Movement

Task Master now supports moving tasks between different tag contexts, allowing you to organize your work across multiple project contexts, feature branches, or development phases.

## Overview

Cross-tag task movement enables you to:
- Move tasks between different tag contexts (e.g., from "backlog" to "in-progress")
- Handle cross-tag dependencies intelligently
- Maintain task relationships across different contexts
- Organize work across multiple project phases

## Basic Usage

### Within-Tag Moves

Move tasks within the same tag context:

```bash
# Move a single task
task-master move --from=5 --to=7

# Move a subtask
task-master move --from=5.2 --to=7.3

# Move multiple tasks
task-master move --from=5,6,7 --to=10,11,12
```

### Cross-Tag Moves

Move tasks between different tag contexts:

```bash
# Basic cross-tag move
task-master move --from=5 --from-tag=backlog --to-tag=in-progress

# Move multiple tasks
task-master move --from=5,6,7 --from-tag=backlog --to-tag=done
```

## Dependency Resolution

When moving tasks between tags, you may encounter cross-tag dependencies. Task Master provides several options to handle these:

### Move with Dependencies

Move the main task along with all its dependent tasks:

```bash
task-master move --from=5 --from-tag=backlog --to-tag=in-progress --with-dependencies
```

This ensures that all dependent tasks are moved together, maintaining the task relationships.

### Break Dependencies

Break cross-tag dependencies and move only the specified task:

```bash
task-master move --from=5 --from-tag=backlog --to-tag=in-progress --ignore-dependencies
```

This removes the dependency relationships and moves only the specified task.

### Force Move

Note: Force moves are no longer supported. Instead, use one of these options:

- `--with-dependencies` — move dependents together
- `--ignore-dependencies` — break cross-tag dependencies

⚠️ **Warning**: This may break dependency relationships and should be used with caution.

## Error Handling

Task Master provides enhanced error messages with specific resolution suggestions:

### Cross-Tag Dependency Conflicts

When you encounter dependency conflicts, you'll see:

```text
❌ Cannot move tasks from "backlog" to "in-progress"

Cross-tag dependency conflicts detected:
  • Task 5 depends on 2 (in backlog)
  • Task 6 depends on 3 (in done)

Resolution options:
  1. Move with dependencies: task-master move --from=5,6 --from-tag=backlog --to-tag=in-progress --with-dependencies
  2. Break dependencies: task-master move --from=5,6 --from-tag=backlog --to-tag=in-progress --ignore-dependencies
  3. Validate and fix dependencies: task-master validate-dependencies && task-master fix-dependencies
  4. Move dependencies first: task-master move --from=2,3 --from-tag=backlog --to-tag=in-progress
  5. After deciding, re-run the move with either --with-dependencies or --ignore-dependencies
```

### Subtask Movement Restrictions

Subtasks cannot be moved directly between tags:

```text
❌ Cannot move subtask 5.2 directly between tags

Subtask movement restriction:
  • Subtasks cannot be moved directly between tags
  • They must be promoted to full tasks first

Resolution options:
  1. Promote subtask to full task: task-master remove-subtask --id=5.2 --convert
  2. Then move the promoted task: task-master move --from=5 --from-tag=backlog --to-tag=in-progress
  3. Or move the parent task with all subtasks: task-master move --from=5 --from-tag=backlog --to-tag=in-progress --with-dependencies
```

### Invalid Tag Combinations

When source and target tags are the same:

```text
❌ Invalid tag combination

Error details:
  • Source tag: "backlog"
  • Target tag: "backlog"
  • Reason: Source and target tags are identical

Resolution options:
  1. Use different tags for cross-tag moves
  2. Use within-tag move: task-master move --from=<id> --to=<id> --tag=backlog
  3. Check available tags: task-master tags
```

## Best Practices

### 1. Check Dependencies First

Before moving tasks, validate your dependencies:

```bash
# Check for dependency issues
task-master validate-dependencies

# Fix common dependency problems
task-master fix-dependencies
```

### 2. Use Appropriate Flags

- **`--with-dependencies`**: When you want to maintain task relationships
- **`--ignore-dependencies`**: When you want to break cross-tag dependencies

### 3. Organize by Context

Use tags to organize work by:
- **Development phases**: `backlog`, `in-progress`, `review`, `done`
- **Feature branches**: `feature-auth`, `feature-dashboard`
- **Team members**: `alice-tasks`, `bob-tasks`
- **Project versions**: `v1.0`, `v2.0`

### 4. Handle Subtasks Properly

For subtasks, either:
1. Promote the subtask to a full task first
2. Move the parent task with all subtasks using `--with-dependencies`

## Advanced Usage

### Multiple Task Movement

Move multiple tasks at once:

```bash
# Move multiple tasks with dependencies
task-master move --from=5,6,7 --from-tag=backlog --to-tag=in-progress --with-dependencies

# Move multiple tasks, breaking dependencies
task-master move --from=5,6,7 --from-tag=backlog --to-tag=in-progress --ignore-dependencies
```

### Tag Creation

Target tags are created automatically if they don't exist:

```bash
# This will create the "new-feature" tag if it doesn't exist
task-master move --from=5 --from-tag=backlog --to-tag=new-feature
```

### Current Tag Fallback

If `--from-tag` is not provided, the current tag is used:

```bash
# Uses current tag as source
task-master move --from=5 --to-tag=in-progress
```

## MCP Integration

The cross-tag move functionality is also available through MCP tools:

```javascript
// Move task with dependencies
await moveTask({
  from: "5",
  fromTag: "backlog", 
  toTag: "in-progress",
  withDependencies: true
});

// Break dependencies
await moveTask({
  from: "5",
  fromTag: "backlog",
  toTag: "in-progress", 
  ignoreDependencies: true
});
```

## Troubleshooting

### Common Issues

1. **"Source tag not found"**: Check available tags with `task-master tags`
2. **"Task not found"**: Verify task IDs with `task-master list`
3. **"Cross-tag dependency conflicts"**: Use dependency resolution flags
4. **"Cannot move subtask"**: Promote subtask first or move parent task

### Getting Help

```bash
# Show move command help
task-master move --help

# Check available tags
task-master tags

# Validate dependencies
task-master validate-dependencies

# Fix dependency issues
task-master fix-dependencies
```

## Examples

### Scenario 1: Moving from Backlog to In-Progress

```bash
# Check for dependencies first
task-master validate-dependencies

# Move with dependencies
task-master move --from=5 --from-tag=backlog --to-tag=in-progress --with-dependencies
```

### Scenario 2: Breaking Dependencies

```bash
# Move task, breaking cross-tag dependencies
task-master move --from=5 --from-tag=backlog --to-tag=done --ignore-dependencies
```

### Scenario 3: Force Move

Choose one of these options explicitly:

```bash
# Move together with dependencies
task-master move --from=5 --from-tag=backlog --to-tag=in-progress --with-dependencies

# Or break dependencies
task-master move --from=5 --from-tag=backlog --to-tag=in-progress --ignore-dependencies
```

### Scenario 4: Moving Subtasks

```bash
# Option 1: Promote subtask first
task-master remove-subtask --id=5.2 --convert
task-master move --from=5 --from-tag=backlog --to-tag=in-progress

# Option 2: Move parent with all subtasks
task-master move --from=5 --from-tag=backlog --to-tag=in-progress --with-dependencies
```

```

--------------------------------------------------------------------------------
/src/prompts/parse-prd.json:
--------------------------------------------------------------------------------

```json
{
	"id": "parse-prd",
	"version": "1.0.0",
	"description": "Parse a Product Requirements Document into structured tasks",
	"metadata": {
		"author": "system",
		"created": "2024-01-01T00:00:00Z",
		"updated": "2024-01-01T00:00:00Z",
		"tags": ["prd", "parsing", "initialization"]
	},
	"parameters": {
		"numTasks": {
			"type": "number",
			"required": true,
			"description": "Target number of tasks to generate"
		},
		"nextId": {
			"type": "number",
			"required": true,
			"description": "Starting ID for tasks"
		},
		"research": {
			"type": "boolean",
			"default": false,
			"description": "Enable research mode for latest best practices"
		},
		"prdContent": {
			"type": "string",
			"required": true,
			"description": "Content of the PRD file"
		},
		"prdPath": {
			"type": "string",
			"required": true,
			"description": "Path to the PRD file"
		},
		"defaultTaskPriority": {
			"type": "string",
			"required": false,
			"default": "medium",
			"enum": ["high", "medium", "low"],
			"description": "Default priority for generated tasks"
		},
		"hasCodebaseAnalysis": {
			"type": "boolean",
			"required": false,
			"default": false,
			"description": "Whether codebase analysis is available"
		},
		"projectRoot": {
			"type": "string",
			"required": false,
			"default": "",
			"description": "Project root path for context"
		}
	},
	"prompts": {
		"default": {
			"system": "You are an AI assistant specialized in analyzing Product Requirements Documents (PRDs) and generating a structured, logically ordered, dependency-aware and sequenced list of development tasks in JSON format.{{#if research}}\nBefore breaking down the PRD into tasks, you will:\n1. Research and analyze the latest technologies, libraries, frameworks, and best practices that would be appropriate for this project\n2. Identify any potential technical challenges, security concerns, or scalability issues not explicitly mentioned in the PRD without discarding any explicit requirements or going overboard with complexity -- always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches\n3. Consider current industry standards and evolving trends relevant to this project (this step aims to solve LLM hallucinations and out of date information due to training data cutoff dates)\n4. Evaluate alternative implementation approaches and recommend the most efficient path\n5. Include specific library versions, helpful APIs, and concrete implementation guidance based on your research\n6. Always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches\n\nYour task breakdown should incorporate this research, resulting in more detailed implementation guidance, more accurate dependency mapping, and more precise technology recommendations than would be possible from the PRD text alone, while maintaining all explicit requirements and best practices and all details and nuances of the PRD.{{/if}}\n\nAnalyze the provided PRD content and generate {{#if (gt numTasks 0)}}approximately {{numTasks}}{{else}}an appropriate number of{{/if}} top-level development tasks. If the complexity or the level of detail of the PRD is high, generate more tasks relative to the complexity of the PRD\nEach task should represent a logical unit of work needed to implement the requirements and focus on the most direct and effective way to implement the requirements without unnecessary complexity or overengineering. Include pseudo-code, implementation details, and test strategy for each task. Find the most up to date information to implement each task.\nAssign sequential IDs starting from {{nextId}}. Infer title, description, details, and test strategy for each task based *only* on the PRD content.\nSet status to 'pending', dependencies to an empty array [], and priority to '{{defaultTaskPriority}}' initially for all tasks.\nGenerate a response containing a single key \"tasks\", where the value is an array of task objects adhering to the provided schema.\n\nEach task should follow this JSON structure:\n{\n\t\"id\": number,\n\t\"title\": string,\n\t\"description\": string,\n\t\"status\": \"pending\",\n\t\"dependencies\": number[] (IDs of tasks this depends on),\n\t\"priority\": \"high\" | \"medium\" | \"low\",\n\t\"details\": string (implementation details),\n\t\"testStrategy\": string (validation approach)\n}\n\nGuidelines:\n1. {{#if (gt numTasks 0)}}Unless complexity warrants otherwise{{else}}Depending on the complexity{{/if}}, create {{#if (gt numTasks 0)}}exactly {{numTasks}}{{else}}an appropriate number of{{/if}} tasks, numbered sequentially starting from {{nextId}}\n2. Each task should be atomic and focused on a single responsibility following the most up to date best practices and standards\n3. Order tasks logically - consider dependencies and implementation sequence\n4. Early tasks should focus on setup, core functionality first, then advanced features\n5. Include clear validation/testing approach for each task\n6. Set appropriate dependency IDs (a task can only depend on tasks with lower IDs, potentially including existing tasks with IDs less than {{nextId}} if applicable)\n7. Assign priority (high/medium/low) based on criticality and dependency order\n8. Include detailed implementation guidance in the \"details\" field{{#if research}}, with specific libraries and version recommendations based on your research{{/if}}\n9. If the PRD contains specific requirements for libraries, database schemas, frameworks, tech stacks, or any other implementation details, STRICTLY ADHERE to these requirements in your task breakdown and do not discard them under any circumstance\n10. Focus on filling in any gaps left by the PRD or areas that aren't fully specified, while preserving all explicit requirements\n11. Always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches{{#if research}}\n12. For each task, include specific, actionable guidance based on current industry standards and best practices discovered through research{{/if}}",
			"user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before generating tasks:\n\n1. Use the Glob tool to explore the project structure (e.g., \"**/*.js\", \"**/*.json\", \"**/README.md\")\n2. Use the Grep tool to search for existing implementations, patterns, and technologies\n3. Use the Read tool to examine key files like package.json, README.md, and main entry points\n4. Analyze the current state of implementation to understand what already exists\n\nBased on your analysis:\n- Identify what components/features are already implemented\n- Understand the technology stack, frameworks, and patterns in use\n- Generate tasks that build upon the existing codebase rather than duplicating work\n- Ensure tasks align with the project's current architecture and conventions\n\nProject Root: {{projectRoot}}\n\n{{/if}}Here's the Product Requirements Document (PRD) to break down into {{#if (gt numTasks 0)}}approximately {{numTasks}}{{else}}an appropriate number of{{/if}} tasks, starting IDs from {{nextId}}:{{#if research}}\n\nRemember to thoroughly research current best practices and technologies before task breakdown to provide specific, actionable implementation details.{{/if}}\n\n{{prdContent}}\n\nIMPORTANT: Your response must be a JSON object with a \"tasks\" property containing an array of task objects. You may optionally include a \"metadata\" object. Do not include any other properties."
		}
	}
}

```

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

```javascript
import path from 'path';
import * as fs from 'fs';
import { readJSON, writeJSON, log, findTaskById } from '../utils.js';
import generateTaskFiles from './generate-task-files.js';
import taskExists from './task-exists.js';

/**
 * Removes one or more tasks or subtasks from the tasks file
 * @param {string} tasksPath - Path to the tasks file
 * @param {string} taskIds - Comma-separated string of task/subtask IDs to remove (e.g., '5,6.1,7')
 * @param {Object} context - Context object containing projectRoot and tag information
 * @param {string} [context.projectRoot] - Project root path
 * @param {string} [context.tag] - Tag for the task
 * @returns {Object} Result object with success status, messages, and removed task info
 */
async function removeTask(tasksPath, taskIds, context = {}) {
	const { projectRoot, tag } = context;
	const results = {
		success: true,
		messages: [],
		errors: [],
		removedTasks: []
	};
	const taskIdsToRemove = taskIds
		.split(',')
		.map((id) => id.trim())
		.filter(Boolean); // Remove empty strings if any

	if (taskIdsToRemove.length === 0) {
		results.success = false;
		results.errors.push('No valid task IDs provided.');
		return results;
	}

	try {
		// Read the tasks file ONCE before the loop, preserving the full tagged structure
		const rawData = readJSON(tasksPath, projectRoot, tag); // Read raw data
		if (!rawData) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use the full tagged data if available, otherwise use the data as is
		const fullTaggedData = rawData._rawTaggedData || rawData;

		if (!fullTaggedData[tag] || !fullTaggedData[tag].tasks) {
			throw new Error(`Tag '${tag}' not found or has no tasks.`);
		}

		const tasks = fullTaggedData[tag].tasks; // Work with tasks from the correct tag

		const tasksToDeleteFiles = []; // Collect IDs of main tasks whose files should be deleted

		for (const taskId of taskIdsToRemove) {
			// Check if the task ID exists *before* attempting removal
			if (!taskExists(tasks, taskId)) {
				const errorMsg = `Task with ID ${taskId} in tag '${tag}' not found or already removed.`;
				results.errors.push(errorMsg);
				results.success = false; // Mark overall success as false if any error occurs
				continue; // Skip to the next ID
			}

			try {
				// Handle subtask removal (e.g., '5.2')
				if (typeof taskId === 'string' && taskId.includes('.')) {
					const [parentTaskId, subtaskId] = taskId
						.split('.')
						.map((id) => parseInt(id, 10));

					// Find the parent task
					const parentTask = tasks.find((t) => t.id === parentTaskId);
					if (!parentTask || !parentTask.subtasks) {
						throw new Error(
							`Parent task ${parentTaskId} or its subtasks not found for subtask ${taskId}`
						);
					}

					// Find the subtask to remove
					const subtaskIndex = parentTask.subtasks.findIndex(
						(st) => st.id === subtaskId
					);
					if (subtaskIndex === -1) {
						throw new Error(
							`Subtask ${subtaskId} not found in parent task ${parentTaskId}`
						);
					}

					// Store the subtask info before removal
					const removedSubtask = {
						...parentTask.subtasks[subtaskIndex],
						parentTaskId: parentTaskId
					};
					results.removedTasks.push(removedSubtask);

					// Remove the subtask from the parent
					parentTask.subtasks.splice(subtaskIndex, 1);

					results.messages.push(
						`Successfully removed subtask ${taskId} from tag '${tag}'`
					);
				}
				// Handle main task removal
				else {
					const taskIdNum = parseInt(taskId, 10);
					const taskIndex = tasks.findIndex((t) => t.id === taskIdNum);
					if (taskIndex === -1) {
						throw new Error(`Task with ID ${taskId} not found in tag '${tag}'`);
					}

					// Store the task info before removal
					const removedTask = tasks[taskIndex];
					results.removedTasks.push(removedTask);
					tasksToDeleteFiles.push(taskIdNum); // Add to list for file deletion

					// Remove the task from the main array
					tasks.splice(taskIndex, 1);

					results.messages.push(
						`Successfully removed task ${taskId} from tag '${tag}'`
					);
				}
			} catch (innerError) {
				// Catch errors specific to processing *this* ID
				const errorMsg = `Error processing ID ${taskId}: ${innerError.message}`;
				results.errors.push(errorMsg);
				results.success = false;
				log('warn', errorMsg); // Log as warning and continue with next ID
			}
		} // End of loop through taskIdsToRemove

		// --- Post-Loop Operations ---

		// Only proceed with cleanup and saving if at least one task was potentially removed
		if (results.removedTasks.length > 0) {
			const allRemovedIds = new Set(
				taskIdsToRemove.map((id) =>
					typeof id === 'string' && id.includes('.') ? id : parseInt(id, 10)
				)
			);

			// Update the tasks in the current tag of the full data structure
			fullTaggedData[tag].tasks = tasks;

			// Remove dependencies from all tags
			for (const tagName in fullTaggedData) {
				if (
					Object.prototype.hasOwnProperty.call(fullTaggedData, tagName) &&
					fullTaggedData[tagName] &&
					fullTaggedData[tagName].tasks
				) {
					const currentTagTasks = fullTaggedData[tagName].tasks;
					currentTagTasks.forEach((task) => {
						if (task.dependencies) {
							task.dependencies = task.dependencies.filter(
								(depId) => !allRemovedIds.has(depId)
							);
						}
						if (task.subtasks) {
							task.subtasks.forEach((subtask) => {
								if (subtask.dependencies) {
									subtask.dependencies = subtask.dependencies.filter(
										(depId) =>
											!allRemovedIds.has(`${task.id}.${depId}`) &&
											!allRemovedIds.has(depId)
									);
								}
							});
						}
					});
				}
			}

			// Save the updated raw data structure
			writeJSON(tasksPath, fullTaggedData, projectRoot, tag);

			// Delete task files AFTER saving tasks.json
			for (const taskIdNum of tasksToDeleteFiles) {
				const taskFileName = path.join(
					path.dirname(tasksPath),
					`task_${taskIdNum.toString().padStart(3, '0')}.txt`
				);
				if (fs.existsSync(taskFileName)) {
					try {
						fs.unlinkSync(taskFileName);
						results.messages.push(`Deleted task file: ${taskFileName}`);
					} catch (unlinkError) {
						const unlinkMsg = `Failed to delete task file ${taskFileName}: ${unlinkError.message}`;
						results.errors.push(unlinkMsg);
						results.success = false;
						log('warn', unlinkMsg);
					}
				}
			}

			// Generate updated task files ONCE, with context
			// try {
			// 	await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
			// 		projectRoot,
			// 		tag
			// 	});
			// 	results.messages.push('Task files regenerated successfully.');
			// } catch (genError) {
			// 	const genErrMsg = `Failed to regenerate task files: ${genError.message}`;
			// 	results.errors.push(genErrMsg);
			// 	results.success = false;
			// 	log('warn', genErrMsg);
			// }
		} else if (results.errors.length === 0) {
			results.messages.push('No tasks found matching the provided IDs.');
		}

		// Consolidate messages for final output
		const finalMessage = results.messages.join('\n');
		const finalError = results.errors.join('\n');

		return {
			success: results.success,
			message: finalMessage || 'No tasks were removed.',
			error: finalError || null,
			removedTasks: results.removedTasks
		};
	} catch (error) {
		// Catch errors from reading file or other initial setup
		log('error', `Error removing tasks: ${error.message}`);
		return {
			success: false,
			message: '',
			error: `Operation failed: ${error.message}`,
			removedTasks: []
		};
	}
}

export default removeTask;

```

--------------------------------------------------------------------------------
/apps/docs/getting-started/contribute.mdx:
--------------------------------------------------------------------------------

```markdown
# Contributing to Task Master

Thank you for your interest in contributing to Task Master! We're excited to work with you and appreciate your help in making this project better. 🚀

## 🤝 Our Collaborative Approach

We're a **PR-friendly team** that values collaboration:

- ✅ **We review PRs quickly** - Usually within hours, not days
- ✅ **We're super reactive** - Expect fast feedback and engagement
- ✅ **We sometimes take over PRs** - If your contribution is valuable but needs cleanup, we might jump in to help finish it
- ✅ **We're open to all contributions** - From bug fixes to major features

**We don't mind AI-generated code**, but we do expect you to:

- ✅ **Review and understand** what the AI generated
- ✅ **Test the code thoroughly** before submitting
- ✅ **Ensure it's well-written** and follows our patterns
- ❌ **Don't submit "AI slop"** - untested, unreviewed AI output

> **Why this matters**: We spend significant time reviewing PRs. Help us help you by submitting quality contributions that save everyone time!

## 🚀 Quick Start for Contributors

### 1. Fork and Clone

```bash
git clone https://github.com/YOUR_USERNAME/claude-task-master.git
cd claude-task-master
npm install
```

### 2. Create a Feature Branch

**Important**: Always target the `next` branch, not `main`:

```bash
git checkout next
git pull origin next
git checkout -b feature/your-feature-name
```

### 3. Make Your Changes

Follow our development guidelines below.

### 4. Test Everything Yourself

**Before submitting your PR**, ensure:

```bash
# Run all tests
npm test

# Check formatting
npm run format-check

# Fix formatting if needed
npm run format
```

### 5. Create a Changeset

**Required for most changes**:

```bash
npm run changeset
```

See the [Changeset Guidelines](#changeset-guidelines) below for details.

### 6. Submit Your PR

- Target the `next` branch
- Write a clear description
- Reference any related issues

## 📋 Development Guidelines

### Branch Strategy

- **`main`**: Production-ready code
- **`next`**: Development branch - **target this for PRs**
- **Feature branches**: `feature/description` or `fix/description`

### Code Quality Standards

1. **Write tests** for new functionality
2. **Follow existing patterns** in the codebase
3. **Add JSDoc comments** for functions
4. **Keep functions focused** and single-purpose

### Testing Requirements

Your PR **must pass all CI checks**:

- ✅ **Unit tests**: `npm test`
- ✅ **Format check**: `npm run format-check`

**Test your changes locally first** - this saves review time and shows you care about quality.

## 📦 Changeset Guidelines

We use [Changesets](https://github.com/changesets/changesets) to manage versioning and generate changelogs.

### When to Create a Changeset

**Always create a changeset for**:

- ✅ New features
- ✅ Bug fixes
- ✅ Breaking changes
- ✅ Performance improvements
- ✅ User-facing documentation updates
- ✅ Dependency updates that affect functionality

**Skip changesets for**:

- ❌ Internal documentation only
- ❌ Test-only changes
- ❌ Code formatting/linting
- ❌ Development tooling that doesn't affect users

### How to Create a Changeset

1. **After making your changes**:

   ```bash
   npm run changeset
   ```

2. **Choose the bump type**:

   - **Major**: Breaking changes
   - **Minor**: New features
   - **Patch**: Bug fixes, docs, performance improvements

3. **Write a clear summary**:

   ```
   Add support for custom AI models in MCP configuration
   ```

4. **Commit the changeset file** with your changes:
   ```bash
   git add .changeset/*.md
   git commit -m "feat: add custom AI model support"
   ```

### Changeset vs Git Commit Messages

- **Changeset summary**: User-facing, goes in CHANGELOG.md
- **Git commit**: Developer-facing, explains the technical change

Example:

```bash
# Changeset summary (user-facing)
"Add support for custom Ollama models"

# Git commit message (developer-facing)
"feat(models): implement custom Ollama model validation

- Add model validation for custom Ollama endpoints
- Update configuration schema to support custom models
- Add tests for new validation logic"
```

## 🔧 Development Setup

### Prerequisites

- Node.js 18+
- npm or yarn

### Environment Setup

1. **Copy environment template**:

   ```bash
   cp .env.example .env
   ```

2. **Add your API keys** (for testing AI features):
   ```bash
   ANTHROPIC_API_KEY=your_key_here
   OPENAI_API_KEY=your_key_here
   # Add others as needed
   ```

### Running Tests

```bash
# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run with coverage
npm run test:coverage

# Run E2E tests
npm run test:e2e
```

### Code Formatting

We use Prettier for consistent formatting:

```bash
# Check formatting
npm run format-check

# Fix formatting
npm run format
```

## 📝 PR Guidelines

### Before Submitting

- [ ] **Target the `next` branch**
- [ ] **Test everything locally**
- [ ] **Run the full test suite**
- [ ] **Check code formatting**
- [ ] **Create a changeset** (if needed)
- [ ] **Re-read your changes** - ensure they're clean and well-thought-out

### PR Description Template

```markdown
## Description

Brief description of what this PR does.

## Type of Change

- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Testing

- [ ] I have tested this locally
- [ ] All existing tests pass
- [ ] I have added tests for new functionality

## Changeset

- [ ] I have created a changeset (or this change doesn't need one)

## Additional Notes

Any additional context or notes for reviewers.
```

### What We Look For

✅ **Good PRs**:

- Clear, focused changes
- Comprehensive testing
- Good commit messages
- Proper changeset (when needed)
- Self-reviewed code

❌ **Avoid**:

- Massive PRs that change everything
- Untested code
- Formatting issues
- Missing changesets for user-facing changes
- AI-generated code that wasn't reviewed

## 🏗️ Project Structure

```
claude-task-master/
├── bin/                    # CLI executables
├── mcp-server/            # MCP server implementation
├── scripts/               # Core task management logic
├── src/                   # Shared utilities and providers and well refactored code (we are slowly moving everything here)
├── tests/                 # Test files
├── docs/                  # Documentation
└── .cursor/               # Cursor IDE rules and configuration
└── assets/							   # Assets like rules and configuration for all IDEs
```

### Key Areas for Contribution

- **CLI Commands**: `scripts/modules/commands.js`
- **MCP Tools**: `mcp-server/src/tools/`
- **Core Logic**: `scripts/modules/task-manager/`
- **AI Providers**: `src/ai-providers/`
- **Tests**: `tests/`

## 🐛 Reporting Issues

### Bug Reports

Include:

- Task Master version
- Node.js version
- Operating system
- Steps to reproduce
- Expected vs actual behavior
- Error messages/logs

### Feature Requests

Include:

- Clear description of the feature
- Use case/motivation
- Proposed implementation (if you have ideas)
- Willingness to contribute

## 💬 Getting Help

- **Discord**: [Join our community](https://discord.gg/taskmasterai)
- **Issues**: [GitHub Issues](https://github.com/eyaltoledano/claude-task-master/issues)
- **Discussions**: [GitHub Discussions](https://github.com/eyaltoledano/claude-task-master/discussions)

## 📄 License

By contributing, you agree that your contributions will be licensed under the same license as the project (MIT with Commons Clause).

---

**Thank you for contributing to Task Master!** 🎉

Your contributions help make AI-driven development more accessible and efficient for everyone.
```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/parse-prd/parse-prd.js:
--------------------------------------------------------------------------------

```javascript
import chalk from 'chalk';
import {
	StreamingError,
	STREAMING_ERROR_CODES
} from '../../../../src/utils/stream-parser.js';
import { TimeoutManager } from '../../../../src/utils/timeout-manager.js';
import { getDebugFlag, getDefaultPriority } from '../../config-manager.js';

// Import configuration classes
import { PrdParseConfig, LoggingConfig } from './parse-prd-config.js';

// Import helper functions
import {
	readPrdContent,
	loadExistingTasks,
	validateFileOperations,
	processTasks,
	saveTasksToFile,
	buildPrompts,
	displayCliSummary,
	displayNonStreamingCliOutput
} from './parse-prd-helpers.js';

// Import handlers
import { handleStreamingService } from './parse-prd-streaming.js';
import { handleNonStreamingService } from './parse-prd-non-streaming.js';

// ============================================================================
// MAIN PARSING FUNCTIONS (Simplified after refactoring)
// ============================================================================

/**
 * Shared parsing logic for both streaming and non-streaming
 * @param {PrdParseConfig} config - Configuration object
 * @param {Function} serviceHandler - Handler function for AI service
 * @param {boolean} isStreaming - Whether this is streaming mode
 * @returns {Promise<Object>} Result object with success status and telemetry
 */
async function parsePRDCore(config, serviceHandler, isStreaming) {
	const logger = new LoggingConfig(config.mcpLog, config.reportProgress);

	logger.report(
		`Parsing PRD file: ${config.prdPath}, Force: ${config.force}, Append: ${config.append}, Research: ${config.research}`,
		'debug'
	);

	try {
		// Load existing tasks
		const { existingTasks, nextId } = loadExistingTasks(
			config.tasksPath,
			config.targetTag
		);

		// Validate operations
		validateFileOperations({
			existingTasks,
			targetTag: config.targetTag,
			append: config.append,
			force: config.force,
			isMCP: config.isMCP,
			logger
		});

		// Read PRD content and build prompts
		const prdContent = readPrdContent(config.prdPath);
		const prompts = await buildPrompts(config, prdContent, nextId);

		// Call the appropriate service handler
		const serviceResult = await serviceHandler(
			config,
			prompts,
			config.numTasks
		);

		// Process tasks
		const defaultPriority = getDefaultPriority(config.projectRoot) || 'medium';
		const processedNewTasks = processTasks(
			serviceResult.parsedTasks,
			nextId,
			existingTasks,
			defaultPriority
		);

		// Combine with existing if appending
		const finalTasks = config.append
			? [...existingTasks, ...processedNewTasks]
			: processedNewTasks;

		// Save to file
		saveTasksToFile(config.tasksPath, finalTasks, config.targetTag, logger);

		// Handle completion reporting
		await handleCompletionReporting(
			config,
			serviceResult,
			processedNewTasks,
			finalTasks,
			nextId,
			isStreaming
		);

		return {
			success: true,
			tasksPath: config.tasksPath,
			telemetryData: serviceResult.aiServiceResponse?.telemetryData,
			tagInfo: serviceResult.aiServiceResponse?.tagInfo
		};
	} catch (error) {
		logger.report(`Error parsing PRD: ${error.message}`, 'error');

		if (!config.isMCP) {
			console.error(chalk.red(`Error: ${error.message}`));
			if (getDebugFlag(config.projectRoot)) {
				console.error(error);
			}
		}
		throw error;
	}
}

/**
 * Handle completion reporting for both CLI and MCP
 * @param {PrdParseConfig} config - Configuration object
 * @param {Object} serviceResult - Result from service handler
 * @param {Array} processedNewTasks - New tasks that were processed
 * @param {Array} finalTasks - All tasks after processing
 * @param {number} nextId - Next available task ID
 * @param {boolean} isStreaming - Whether this was streaming mode
 */
async function handleCompletionReporting(
	config,
	serviceResult,
	processedNewTasks,
	finalTasks,
	nextId,
	isStreaming
) {
	const { aiServiceResponse, estimatedInputTokens, estimatedOutputTokens } =
		serviceResult;

	// MCP progress reporting
	if (config.reportProgress) {
		const hasValidTelemetry =
			aiServiceResponse?.telemetryData &&
			(aiServiceResponse.telemetryData.inputTokens > 0 ||
				aiServiceResponse.telemetryData.outputTokens > 0);

		let completionMessage;
		if (hasValidTelemetry) {
			const cost = aiServiceResponse.telemetryData.totalCost || 0;
			const currency = aiServiceResponse.telemetryData.currency || 'USD';
			completionMessage = `✅ Task Generation Completed | Tokens (I/O): ${aiServiceResponse.telemetryData.inputTokens}/${aiServiceResponse.telemetryData.outputTokens} | Cost: ${currency === 'USD' ? '$' : currency}${cost.toFixed(4)}`;
		} else {
			const outputTokens = isStreaming ? estimatedOutputTokens : 'unknown';
			completionMessage = `✅ Task Generation Completed | ~Tokens (I/O): ${estimatedInputTokens}/${outputTokens} | Cost: ~$0.00`;
		}

		await config.reportProgress({
			progress: config.numTasks,
			total: config.numTasks,
			message: completionMessage
		});
	}

	// CLI output
	if (config.outputFormat === 'text' && !config.isMCP) {
		if (isStreaming && serviceResult.summary) {
			await displayCliSummary({
				processedTasks: processedNewTasks,
				nextId,
				summary: serviceResult.summary,
				prdPath: config.prdPath,
				tasksPath: config.tasksPath,
				usedFallback: serviceResult.usedFallback,
				aiServiceResponse
			});
		} else if (!isStreaming) {
			displayNonStreamingCliOutput({
				processedTasks: processedNewTasks,
				research: config.research,
				finalTasks,
				tasksPath: config.tasksPath,
				aiServiceResponse
			});
		}
	}
}

/**
 * Parse PRD with streaming progress reporting
 */
async function parsePRDWithStreaming(
	prdPath,
	tasksPath,
	numTasks,
	options = {}
) {
	const config = new PrdParseConfig(prdPath, tasksPath, numTasks, options);
	return parsePRDCore(config, handleStreamingService, true);
}

/**
 * Parse PRD without streaming (fallback)
 */
async function parsePRDWithoutStreaming(
	prdPath,
	tasksPath,
	numTasks,
	options = {}
) {
	const config = new PrdParseConfig(prdPath, tasksPath, numTasks, options);
	return parsePRDCore(config, handleNonStreamingService, false);
}

/**
 * Main entry point - decides between streaming and non-streaming
 */
async function parsePRD(prdPath, tasksPath, numTasks, options = {}) {
	const config = new PrdParseConfig(prdPath, tasksPath, numTasks, options);

	if (config.useStreaming) {
		try {
			return await parsePRDWithStreaming(prdPath, tasksPath, numTasks, options);
		} catch (streamingError) {
			// Check if this is a streaming-specific error (including timeout)
			const isStreamingError =
				streamingError instanceof StreamingError ||
				streamingError.code === STREAMING_ERROR_CODES.NOT_ASYNC_ITERABLE ||
				streamingError.code ===
					STREAMING_ERROR_CODES.STREAM_PROCESSING_FAILED ||
				streamingError.code === STREAMING_ERROR_CODES.STREAM_NOT_ITERABLE ||
				TimeoutManager.isTimeoutError(streamingError);

			if (isStreamingError) {
				const logger = new LoggingConfig(config.mcpLog, config.reportProgress);

				// Show fallback message
				if (config.outputFormat === 'text' && !config.isMCP) {
					console.log(
						chalk.yellow(
							`⚠️  Streaming operation ${streamingError.message.includes('timed out') ? 'timed out' : 'failed'}. Falling back to non-streaming mode...`
						)
					);
				} else {
					logger.report(
						`Streaming failed (${streamingError.message}), falling back to non-streaming mode...`,
						'warn'
					);
				}

				// Fallback to non-streaming
				return await parsePRDWithoutStreaming(
					prdPath,
					tasksPath,
					numTasks,
					options
				);
			} else {
				throw streamingError;
			}
		}
	} else {
		return await parsePRDWithoutStreaming(
			prdPath,
			tasksPath,
			numTasks,
			options
		);
	}
}

export default parsePRD;

```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/clear-subtasks.test.js:
--------------------------------------------------------------------------------

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

// Mock the dependencies before importing the module under test
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
	readJSON: jest.fn(),
	writeJSON: jest.fn(),
	log: jest.fn(),
	CONFIG: {
		model: 'mock-claude-model',
		maxTokens: 4000,
		temperature: 0.7,
		debug: false
	},
	findTaskById: jest.fn(),
	isSilentMode: jest.fn(() => false),
	truncate: jest.fn((text) => text),
	ensureTagMetadata: jest.fn()
}));

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

jest.unstable_mockModule(
	'../../../../../scripts/modules/task-manager/generate-task-files.js',
	() => ({
		default: jest.fn().mockResolvedValue()
	})
);

// Mock external UI libraries
jest.unstable_mockModule('chalk', () => ({
	default: {
		white: {
			bold: jest.fn((text) => text)
		},
		cyan: Object.assign(
			jest.fn((text) => text),
			{
				bold: jest.fn((text) => text)
			}
		),
		green: jest.fn((text) => text),
		yellow: jest.fn((text) => text),
		bold: jest.fn((text) => text)
	}
}));

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

jest.unstable_mockModule('cli-table3', () => ({
	default: jest.fn().mockImplementation(() => ({
		push: jest.fn(),
		toString: jest.fn(() => 'mocked table')
	}))
}));

// Mock process.exit to prevent Jest worker crashes
const mockExit = jest.spyOn(process, 'exit').mockImplementation((code) => {
	throw new Error(`process.exit called with "${code}"`);
});

// Import the mocked modules
const { readJSON, writeJSON, log, findTaskById, ensureTagMetadata } =
	await import('../../../../../scripts/modules/utils.js');
const generateTaskFiles = (
	await import(
		'../../../../../scripts/modules/task-manager/generate-task-files.js'
	)
).default;

// Import the module under test
const { default: clearSubtasks } = await import(
	'../../../../../scripts/modules/task-manager/clear-subtasks.js'
);

describe('clearSubtasks', () => {
	const sampleTasks = {
		master: {
			tasks: [
				{ id: 1, title: 'Task 1', subtasks: [] },
				{ id: 2, title: 'Task 2', subtasks: [] },
				{
					id: 3,
					title: 'Task 3',
					subtasks: [{ id: 1, title: 'Subtask 3.1' }]
				},
				{
					id: 4,
					title: 'Task 4',
					subtasks: [{ id: 1, title: 'Subtask 4.1' }]
				}
			]
		}
	};

	beforeEach(() => {
		jest.clearAllMocks();
		mockExit.mockClear();
		readJSON.mockImplementation((tasksPath, projectRoot, tag) => {
			// Ensure tag contract is honoured
			expect(tag).toBeDefined();
			expect(tag).toBe('master');
			// Create a deep copy to avoid mutation issues between tests
			const sampleTasksCopy = JSON.parse(JSON.stringify(sampleTasks));
			// Return the data for the 'master' tag, which is what the tests use
			return {
				...sampleTasksCopy.master,
				tag: tag || 'master',
				_rawTaggedData: sampleTasksCopy
			};
		});
		writeJSON.mockResolvedValue();
		generateTaskFiles.mockResolvedValue();
		log.mockImplementation(() => {});
	});

	test('should clear subtasks from a specific task', () => {
		// Arrange
		const taskId = '3';
		const tasksPath = 'tasks/tasks.json';
		const context = { tag: 'master' };

		// Act
		clearSubtasks(tasksPath, taskId, context);

		// Assert
		expect(readJSON).toHaveBeenCalledWith(tasksPath, undefined, 'master');
		expect(writeJSON).toHaveBeenCalledWith(
			tasksPath,
			expect.objectContaining({
				_rawTaggedData: expect.objectContaining({
					master: expect.objectContaining({
						tasks: expect.arrayContaining([
							expect.objectContaining({
								id: 3,
								subtasks: [] // Should be empty
							})
						])
					})
				})
			}),
			undefined,
			'master'
		);
	});

	test('should clear subtasks from multiple tasks when given comma-separated IDs', () => {
		// Arrange
		const taskIds = '3,4';
		const tasksPath = 'tasks/tasks.json';
		const context = { tag: 'master' };

		// Act
		clearSubtasks(tasksPath, taskIds, context);

		// Assert
		expect(readJSON).toHaveBeenCalledWith(tasksPath, undefined, 'master');
		expect(writeJSON).toHaveBeenCalledWith(
			tasksPath,
			expect.objectContaining({
				_rawTaggedData: expect.objectContaining({
					master: expect.objectContaining({
						tasks: expect.arrayContaining([
							expect.objectContaining({ id: 3, subtasks: [] }),
							expect.objectContaining({ id: 4, subtasks: [] })
						])
					})
				})
			}),
			undefined,
			'master'
		);
	});

	test('should handle tasks with no subtasks', () => {
		// Arrange
		const taskId = '1'; // Task 1 already has no subtasks
		const tasksPath = 'tasks/tasks.json';
		const context = { tag: 'master' };

		// Act
		clearSubtasks(tasksPath, taskId, context);

		// Assert
		expect(readJSON).toHaveBeenCalledWith(tasksPath, undefined, 'master');
		// Should not write the file if no changes were made
		expect(writeJSON).not.toHaveBeenCalled();
		expect(generateTaskFiles).not.toHaveBeenCalled();
	});

	test('should handle non-existent task IDs gracefully', () => {
		// Arrange
		const taskId = '99'; // Non-existent task
		const tasksPath = 'tasks/tasks.json';
		const context = { tag: 'master' };

		// Act
		clearSubtasks(tasksPath, taskId, context);

		// Assert
		expect(readJSON).toHaveBeenCalledWith(tasksPath, undefined, 'master');
		expect(log).toHaveBeenCalledWith('error', 'Task 99 not found');
		// Should not write the file if no changes were made
		expect(writeJSON).not.toHaveBeenCalled();
		expect(generateTaskFiles).not.toHaveBeenCalled();
	});

	test('should handle multiple task IDs including both valid and non-existent IDs', () => {
		// Arrange
		const taskIds = '3,99'; // Mix of valid and invalid IDs
		const tasksPath = 'tasks/tasks.json';
		const context = { tag: 'master' };

		// Act
		clearSubtasks(tasksPath, taskIds, context);

		// Assert
		expect(readJSON).toHaveBeenCalledWith(tasksPath, undefined, 'master');
		expect(log).toHaveBeenCalledWith('error', 'Task 99 not found');
		// Since task 3 has subtasks that should be cleared, writeJSON should be called
		expect(writeJSON).toHaveBeenCalledWith(
			tasksPath,
			expect.objectContaining({
				tasks: expect.arrayContaining([
					expect.objectContaining({ id: 3, subtasks: [] })
				]),
				tag: 'master',
				_rawTaggedData: expect.objectContaining({
					master: expect.objectContaining({
						tasks: expect.arrayContaining([
							expect.objectContaining({ id: 3, subtasks: [] })
						])
					})
				})
			}),
			undefined,
			'master'
		);
	});

	test('should handle file read errors', () => {
		// Arrange
		readJSON.mockImplementation(() => {
			throw new Error('File read failed');
		});

		// Act & Assert
		expect(() => {
			clearSubtasks('tasks/tasks.json', '3', { tag: 'master' });
		}).toThrow('File read failed');
	});

	test('should handle invalid tasks data', () => {
		// Arrange
		readJSON.mockReturnValue(null);

		// Act & Assert
		expect(() => {
			clearSubtasks('tasks/tasks.json', '3', { tag: 'master' });
		}).toThrow('process.exit called');

		expect(log).toHaveBeenCalledWith('error', 'No valid tasks found.');
	});

	test('should handle file write errors', () => {
		// Arrange
		// Ensure task 3 has subtasks to clear so writeJSON gets called
		readJSON.mockReturnValue({
			...sampleTasks.master,
			tag: 'master',
			_rawTaggedData: sampleTasks,
			tasks: [
				...sampleTasks.master.tasks.slice(0, 2),
				{
					...sampleTasks.master.tasks[2],
					subtasks: [{ id: 1, title: 'Subtask to clear' }]
				},
				...sampleTasks.master.tasks.slice(3)
			]
		});

		writeJSON.mockImplementation(() => {
			throw new Error('File write failed');
		});

		// Act & Assert
		expect(() => {
			clearSubtasks('tasks/tasks.json', '3', { tag: 'master' });
		}).toThrow('File write failed');
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/utils/run-id-generator.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, expect, it } from 'vitest';
import {
	compareRunIds,
	generateRunId,
	isValidRunId,
	parseRunId
} from './run-id-generator.js';

describe('Run ID Generator', () => {
	describe('generateRunId', () => {
		it('should generate a valid ISO 8601 timestamp-based ID', () => {
			const runId = generateRunId();

			// Should be in ISO 8601 format with milliseconds
			expect(runId).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
		});

		it('should generate unique IDs when called multiple times', () => {
			const id1 = generateRunId();
			const id2 = generateRunId();
			const id3 = generateRunId();

			expect(id1).not.toBe(id2);
			expect(id2).not.toBe(id3);
			expect(id1).not.toBe(id3);
		});

		it('should generate chronologically ordered IDs', () => {
			const id1 = generateRunId();
			// Small delay to ensure different timestamp
			const id2 = generateRunId();

			expect(id2 > id1).toBe(true);
		});

		it('should use current time by default', () => {
			const before = new Date().toISOString();
			const runId = generateRunId();
			const after = new Date().toISOString();

			expect(runId >= before).toBe(true);
			expect(runId <= after).toBe(true);
		});

		it('should accept custom Date object', () => {
			const customDate = new Date('2024-01-15T10:30:45.123Z');
			const runId = generateRunId(customDate);

			expect(runId).toBe('2024-01-15T10:30:45.123Z');
		});

		it('should handle date at year boundary', () => {
			const newYear = new Date('2025-01-01T00:00:00.000Z');
			const runId = generateRunId(newYear);

			expect(runId).toBe('2025-01-01T00:00:00.000Z');
		});

		it('should handle millisecond precision correctly', () => {
			const dateWithMs = new Date('2024-03-15T14:22:33.999Z');
			const runId = generateRunId(dateWithMs);

			expect(runId).toBe('2024-03-15T14:22:33.999Z');
		});
	});

	describe('isValidRunId', () => {
		it('should return true for valid ISO 8601 timestamp', () => {
			expect(isValidRunId('2024-01-15T10:30:45.123Z')).toBe(true);
		});

		it('should return true for generated run IDs', () => {
			const runId = generateRunId();
			expect(isValidRunId(runId)).toBe(true);
		});

		it('should return false for invalid format', () => {
			expect(isValidRunId('not-a-timestamp')).toBe(false);
			expect(isValidRunId('2024-01-15')).toBe(false);
			expect(isValidRunId('2024-01-15T10:30:45')).toBe(false); // missing Z
			expect(isValidRunId('2024-01-15 10:30:45.123Z')).toBe(false); // space instead of T
		});

		it('should return false for empty string', () => {
			expect(isValidRunId('')).toBe(false);
		});

		it('should return false for null or undefined', () => {
			expect(isValidRunId(null)).toBe(false);
			expect(isValidRunId(undefined)).toBe(false);
		});

		it('should return false for invalid dates', () => {
			expect(isValidRunId('2024-13-01T10:30:45.123Z')).toBe(false); // invalid month
			expect(isValidRunId('2024-01-32T10:30:45.123Z')).toBe(false); // invalid day
			expect(isValidRunId('2024-01-15T25:30:45.123Z')).toBe(false); // invalid hour
		});

		it('should return true for edge case valid dates', () => {
			expect(isValidRunId('2024-02-29T23:59:59.999Z')).toBe(true); // leap year
			expect(isValidRunId('2025-01-01T00:00:00.000Z')).toBe(true); // year boundary
		});

		it('should return false for missing milliseconds', () => {
			expect(isValidRunId('2024-01-15T10:30:45Z')).toBe(false);
		});

		it('should return false for non-UTC timezone', () => {
			expect(isValidRunId('2024-01-15T10:30:45.123+01:00')).toBe(false);
		});
	});

	describe('parseRunId', () => {
		it('should parse valid run ID to Date object', () => {
			const runId = '2024-01-15T10:30:45.123Z';
			const date = parseRunId(runId);

			expect(date).toBeInstanceOf(Date);
			expect(date?.toISOString()).toBe(runId);
		});

		it('should parse generated run ID', () => {
			const originalDate = new Date('2024-03-20T15:45:30.500Z');
			const runId = generateRunId(originalDate);
			const parsedDate = parseRunId(runId);

			expect(parsedDate?.getTime()).toBe(originalDate.getTime());
		});

		it('should return null for invalid run ID', () => {
			expect(parseRunId('invalid')).toBe(null);
			expect(parseRunId('')).toBe(null);
			expect(parseRunId(null)).toBe(null);
			expect(parseRunId(undefined)).toBe(null);
		});

		it('should handle edge case dates correctly', () => {
			const leapYear = '2024-02-29T12:00:00.000Z';
			const parsed = parseRunId(leapYear);

			expect(parsed?.toISOString()).toBe(leapYear);
		});
	});

	describe('compareRunIds', () => {
		it('should return negative when first ID is earlier', () => {
			const earlier = '2024-01-15T10:00:00.000Z';
			const later = '2024-01-15T11:00:00.000Z';

			expect(compareRunIds(earlier, later)).toBeLessThan(0);
		});

		it('should return positive when first ID is later', () => {
			const earlier = '2024-01-15T10:00:00.000Z';
			const later = '2024-01-15T11:00:00.000Z';

			expect(compareRunIds(later, earlier)).toBeGreaterThan(0);
		});

		it('should return zero when IDs are equal', () => {
			const runId = '2024-01-15T10:00:00.000Z';

			expect(compareRunIds(runId, runId)).toBe(0);
		});

		it('should handle millisecond differences', () => {
			const id1 = '2024-01-15T10:00:00.100Z';
			const id2 = '2024-01-15T10:00:00.200Z';

			expect(compareRunIds(id1, id2)).toBeLessThan(0);
			expect(compareRunIds(id2, id1)).toBeGreaterThan(0);
		});

		it('should handle cross-day comparisons', () => {
			const yesterday = '2024-01-14T23:59:59.999Z';
			const today = '2024-01-15T00:00:00.000Z';

			expect(compareRunIds(yesterday, today)).toBeLessThan(0);
		});

		it('should handle cross-year comparisons', () => {
			const lastYear = '2023-12-31T23:59:59.999Z';
			const thisYear = '2024-01-01T00:00:00.000Z';

			expect(compareRunIds(lastYear, thisYear)).toBeLessThan(0);
		});

		it('should throw error for invalid run IDs', () => {
			const valid = '2024-01-15T10:00:00.000Z';

			expect(() => compareRunIds('invalid', valid)).toThrow();
			expect(() => compareRunIds(valid, 'invalid')).toThrow();
			expect(() => compareRunIds('invalid', 'invalid')).toThrow();
		});
	});

	describe('Collision detection', () => {
		it('should generate different IDs in rapid succession', () => {
			const ids = new Set();
			const count = 100;

			for (let i = 0; i < count; i++) {
				ids.add(generateRunId());
			}

			// All IDs should be unique
			expect(ids.size).toBe(count);
		});

		it('should handle high-frequency generation', () => {
			const ids = [];
			const iterations = 1000;

			for (let i = 0; i < iterations; i++) {
				ids.push(generateRunId());
			}

			// Check uniqueness
			const uniqueIds = new Set(ids);
			expect(uniqueIds.size).toBe(iterations);

			// Check chronological order
			for (let i = 1; i < ids.length; i++) {
				expect(compareRunIds(ids[i - 1], ids[i])).toBeLessThanOrEqual(0);
			}
		});
	});

	describe('Chronological ordering', () => {
		it('should allow sorting run IDs chronologically', () => {
			const ids = [
				'2024-01-15T14:00:00.000Z',
				'2024-01-15T10:00:00.000Z',
				'2024-01-15T12:00:00.000Z',
				'2024-01-14T23:00:00.000Z',
				'2024-01-16T08:00:00.000Z'
			];

			const sorted = [...ids].sort(compareRunIds);

			expect(sorted).toEqual([
				'2024-01-14T23:00:00.000Z',
				'2024-01-15T10:00:00.000Z',
				'2024-01-15T12:00:00.000Z',
				'2024-01-15T14:00:00.000Z',
				'2024-01-16T08:00:00.000Z'
			]);
		});

		it('should handle reverse chronological sorting', () => {
			const ids = [
				'2024-01-15T10:00:00.000Z',
				'2024-01-15T14:00:00.000Z',
				'2024-01-15T12:00:00.000Z'
			];

			const sorted = [...ids].sort((a, b) => compareRunIds(b, a));

			expect(sorted).toEqual([
				'2024-01-15T14:00:00.000Z',
				'2024-01-15T12:00:00.000Z',
				'2024-01-15T10:00:00.000Z'
			]);
		});
	});
});

```

--------------------------------------------------------------------------------
/.taskmaster/docs/research/2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md:
--------------------------------------------------------------------------------

```markdown
---
title: Research Session
query: "how can i improve the scope-up and scope-down command"
date: 6/14/2025
time: 4:35:03 PM
timestamp: 2025-06-14T20:35:03.231Z
exchanges: 2
---

# Research Session

## Initial Query

**Question:** how can i improve the scope-up and scope-down command

**Response:**

## Improving the `scope-up` and `scope-down` Commands

**Clarify Command Purpose and User Experience**

- Ensure the commands have clear, intuitive behavior. For example, `scope-up` should consistently broaden the context or task view, while `scope-down` should reliably narrow it.
- Provide immediate, user-friendly feedback after each command, indicating what changed (e.g., "Now viewing parent task and all subtasks" or "Focused on subtask: X").
- Document the commands with examples in your `docs/command-reference.md` and `docs/examples.md` to help users understand their effects and use cases.

**Enhance Context Awareness**

- Make the commands context-sensitive: if the user is already at the top or bottom of the scope hierarchy, return a helpful message instead of failing silently.
- Allow optional arguments to specify the exact level or target (e.g., `scope-up 2` to move up two levels, or `scope-down <subtask-id>` to jump directly to a subtask).

**Robust Error Handling and Validation**

- Implement checks to prevent invalid operations, such as attempting to scope up from the root or scope down when no subtasks exist.
- Provide clear error messages and suggestions for next steps, improving user trust and reducing confusion[1][5].

**Integrate with Task and Tag Systems**

- Ensure `scope-up` and `scope-down` interact smoothly with your tagging and task hierarchy features. For example, scoping up from a subtask should show the parent task and its context, while scoping down should allow selection from available subtasks or related tags.
- Consider supporting batch operations, such as scoping down into all subtasks and displaying them in a summarized view.

**Improve Performance and Synchronization**

- Optimize the underlying logic to minimize delays, especially in large task trees. Use asynchronous operations where possible to keep the UI responsive.
- If the commands trigger data loading or context switching, ensure synchronization so that subsequent commands operate on the correct, updated context[2][4].

**Testing and Stakeholder Feedback**

- Add or expand unit and integration tests for these commands (see `tests/unit/commands.test.js` and `integration/cli/commands.test.js`) to cover edge cases and ensure reliability.
- Gather feedback from users and stakeholders to identify pain points and desired enhancements, then iterate on the command design and implementation[1][5].

**Example Improvements Table**

| Area                | Current Issue                | Suggested Improvement                        |
|---------------------|-----------------------------|----------------------------------------------|
| User Feedback       | Minimal or unclear output    | Add explicit, contextual messages            |
| Error Handling      | Silent failures              | Informative errors and recovery suggestions  |
| Flexibility         | Only single-level movement   | Support multi-level and targeted navigation  |
| Integration         | Limited context awareness    | Sync with tags, parent/child relationships   |
| Performance         | Slow on large hierarchies    | Optimize data access and UI updates          |

By focusing on these areas, you can make `scope-up` and `scope-down` more robust, user-friendly, and aligned with best practices in scope management and command design[1][5].

---

## Follow-up 1

**Question:** how might i do this relative to the tag system?

**Response:**

## Integrating Scope-Up and Scope-Down with the Tag System

**Conceptual Mapping**

- In your project, *scope-up* and *scope-down* commands currently operate on the task hierarchy, broadening or narrowing the context by moving between parent and child tasks.
- The tag system provides a parallel, orthogonal way to group and filter tasks—tags can represent categories, priorities, or any custom grouping, independent of strict parent/child relationships.

**How to Make Scope Commands Tag-Aware**

- **Scoping by Tag**: Allow users to "scope-down" into a tag, viewing only tasks associated with that tag. Conversely, "scope-up" could move from a specific tag context back to the broader task list or to a parent tag grouping if you support tag hierarchies.
- **Tag-Scoped Views**: When a tag is active as the current scope, all navigation and operations (listing, editing, etc.) are limited to tasks with that tag. This is similar to how some systems let you set the "Tag Scope" to filter navigation and search to only tagged items[1][5].
- **Combining Hierarchy and Tags**: Enable compound scoping, such as "all subtasks of Task X that also have Tag Y." This can be implemented by intersecting the results of hierarchical and tag-based queries.

**Implementation Strategies**

- **Scope Stack or Context Object**: Maintain a stack or context object that tracks both the current task scope and any active tag filters. Each scope change (up/down or tag in/out) updates this context.
- **Commands**:
  - `scope-down --tag <tag>`: Narrows the view to tasks with the specified tag.
  - `scope-up --tag`: Removes the tag filter, broadening the view.
  - `scope-down <subtask-id> --tag <tag>`: Narrows to a specific subtask *and* applies a tag filter.
- **Tag Hierarchies (Optional)**: If your tag system supports parent/child tags or tag groups, allow "scope-up" to move from a specific tag to its parent tag or group, and "scope-down" to move into a child tag[4][5].

**Example User Flows**

- *Scoping Down by Tag*:  
  User runs `scope-down --tag urgent` → Only tasks tagged "urgent" are shown.
- *Combining Task and Tag*:  
  User runs `scope-down <task-id> --tag frontend` → Only subtasks of `<task-id>` that are tagged "frontend" are shown.
- *Scoping Up from Tag*:  
  User runs `scope-up --tag` → Removes the tag filter, returning to the previous broader context.

**Technical Considerations**

- **Efficient Filtering**: Use indexed queries or in-memory filtering to efficiently combine tag and hierarchy filters, especially for large task sets.
- **Feedback and UI**: Always display the current scope context (e.g., "Viewing tasks tagged 'urgent' under 'Feature X'") so users understand what they're seeing.
- **Extensibility**: Design the scope system so new types of scopes (e.g., by status, assignee) can be added in the future, not just tags and hierarchy.

**Relevant Precedents**

- Tag-based file systems and management tools often implement "scopes" as dynamic groups defined by tag criteria, allowing users to build up complex views by combining tags and other filters[5].
- Some systems allow scoping into tag management itself, where you can create, edit, or view tags and their associated objects, which could be mirrored in your CLI or UI[4].

**Summary Table**

| Command Example                  | Resulting Scope                                  |
|----------------------------------|--------------------------------------------------|
| `scope-down --tag bug`           | All tasks tagged "bug"                           |
| `scope-down <task-id> --tag api` | Subtasks of `<task-id>` tagged "api"             |
| `scope-up --tag`                 | Removes tag filter, returns to previous scope    |
| `scope-down --tag frontend`      | All tasks tagged "frontend"                      |

By integrating tag-based scoping, you make your scope commands more flexible and powerful, supporting both hierarchical and ad-hoc organizational strategies[1][5].


---

*Generated by Task Master Research Command*  
*Timestamp: 2025-06-14T20:35:03.231Z*

```

--------------------------------------------------------------------------------
/apps/extension/docs/extension-development-guide.md:
--------------------------------------------------------------------------------

```markdown
# VS Code Extension Development Guide

## 📁 File Structure Overview

This VS Code extension uses a **3-file packaging system** to avoid dependency conflicts during publishing:

```
apps/extension/
├── package.json           # Development configuration
├── package.publish.json   # Clean publishing configuration  
├── package.mjs           # Build script for packaging
├── .vscodeignore         # Files to exclude from extension package
└── vsix-build/           # Generated clean package directory
```

## 📋 File Purposes

### `package.json` (Development)
- **Purpose**: Development environment with all build tools
- **Contains**: 
  - All `devDependencies` needed for building
  - Development scripts (`build`, `watch`, `lint`, etc.)
  - Development package name: `"taskr"`
- **Used for**: Local development, building, testing

### `package.publish.json` (Publishing)
- **Purpose**: Clean distribution version for VS Code Marketplace
- **Contains**:
  - **No devDependencies** (avoids dependency conflicts)
  - Publishing metadata (`keywords`, `repository`, `categories`)
  - Marketplace package name: `"taskr-kanban"`
  - VS Code extension configuration
- **Used for**: Final extension packaging

### `package.mjs` (Build Script)
- **Purpose**: Creates clean package for distribution
- **Process**:
  1. Builds the extension (`build:js` + `build:css`)
  2. Creates clean `vsix-build/` directory
  3. Copies only essential files (no source code)
  4. Renames `package.publish.json` → `package.json`
  5. Ready for `vsce package`

## 🚀 Development Workflow

### Local Development
```bash
# Install dependencies
npm install

# Start development with hot reload
npm run watch

# Run just JavaScript build
npm run build:js

# Run just CSS build  
npm run build:css

# Full production build
npm run build

# Type checking
npm run typecheck

# Linting
npm run lint
```

### Testing in VS Code
1. Press `F5` in VS Code to launch Extension Development Host
2. Test your extension functionality in the new window
3. Use `Developer: Reload Window` to reload after changes

## 📦 Production Packaging

### Step 1: Build Clean Package
```bash
npm run package
```
This creates `vsix-build/` with clean distribution files.

### Step 2: Create VSIX
```bash
cd vsix-build
npx vsce package --no-dependencies
```
Creates: `taskr-kanban-1.0.1.vsix`

### Alternative: One Command
```bash
npm run package && cd vsix-build && npx vsce package --no-dependencies
```

## 🔄 Keeping Files in Sync

### Critical Fields to Sync Between Files

When updating extension metadata, ensure these fields match between `package.json` and `package.publish.json`:

#### Version & Identity
```json
{
  "version": "1.0.1",                    // ⚠️ MUST MATCH
  "publisher": "Hamster",        // ⚠️ MUST MATCH  
  "displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH
  "description": "A visual Kanban board...",  // ⚠️ MUST MATCH
}
```

#### VS Code Configuration
```json
{
  "engines": { "vscode": "^1.101.0" },   // ⚠️ MUST MATCH
  "categories": [...],                    // ⚠️ MUST MATCH
  "activationEvents": [...],              // ⚠️ MUST MATCH
  "main": "./dist/extension.js",          // ⚠️ MUST MATCH
  "contributes": { ... }                  // ⚠️ MUST MATCH EXACTLY
}
```

### Key Differences (Should NOT Match)
```json
// package.json (dev)
{
  "name": "taskr",                       // ✅ Short dev name
  "devDependencies": { ... },            // ✅ Only in dev file
  "scripts": { ... }                     // ✅ Build scripts
}

// package.publish.json (publishing)
{
  "name": "taskr-kanban",               // ✅ Marketplace name
  "keywords": [...],                     // ✅ Only in publish file
  "repository": "https://github.com/...", // ✅ Only in publish file
  // NO devDependencies                  // ✅ Clean for publishing
  // NO build scripts                    // ✅ Not needed in package
}
```

## 🤖 Automated Release Process

### Changesets Workflow
This extension uses [Changesets](https://github.com/changesets/changesets) for automated version management and publishing.

#### Adding Changes
When making changes to the extension:

1. **Make your code changes**
2. **Create a changeset**:
   ```bash
   # From project root
   npx changeset add
   ```
3. **Select the extension package**: Choose `taskr-kanban` when prompted
4. **Select version bump type**:
   - `patch`: Bug fixes, minor updates
   - `minor`: New features, backwards compatible
   - `major`: Breaking changes
5. **Write a summary**: Describe what changed for users

#### Automated Publishing
The automation workflow runs on pushes to `main`:

1. **Version Workflow** (`.github/workflows/version.yml`):
   - Detects when changesets exist
   - Creates a "Version Packages" PR with updated versions and CHANGELOG
   - When the PR is merged, automatically publishes the extension

2. **Release Process** (`scripts/release.sh`):
   - Builds the extension using the 3-file packaging system
   - Creates VSIX package
   - Publishes to VS Code Marketplace (if `VSCE_PAT` is set)
   - Publishes to Open VSX Registry (if `OVSX_PAT` is set)
   - Creates git tags for the extension version

#### Required Secrets
For automated publishing, these secrets must be set in the repository:

- `VSCE_PAT`: Personal Access Token for VS Code Marketplace
- `OVSX_PAT`: Personal Access Token for Open VSX Registry
- `GITHUB_TOKEN`: Automatically provided by GitHub Actions

#### Manual Release
If needed, you can manually trigger a release:

```bash
# From project root
./scripts/release.sh
```

### Extension Tagging
The extension uses a separate tagging strategy from the main package:

- **Extension tags**: `[email protected]`
- **Main package tags**: `[email protected]`

This allows independent versioning and prevents conflicts in the monorepo.

## 🔍 Troubleshooting

### Dependency Conflicts
**Problem**: `vsce package` fails with missing dependencies
**Solution**: Use the 3-file system - never run `vsce package` from root

### Build Failures
**Problem**: Extension not working after build
**Check**:
1. All files copied to `vsix-build/dist/`
2. `package.publish.json` has correct `main` field
3. VS Code engine version compatibility

### Sync Issues
**Problem**: Extension works locally but fails when packaged
**Check**: Ensure critical fields are synced between package files

### Changeset Issues
**Problem**: Version workflow not triggering
**Check**: 
1. Changeset files exist in `.changeset/`
2. Package name in changeset matches `package.publish.json`
3. Changes are pushed to `main` branch

**Problem**: Publishing fails
**Check**:
1. Required secrets are set in repository settings
2. `package.publish.json` has correct repository URL
3. Build process completes successfully

## 📝 Version Release Checklist

### Manual Releases
1. **Create changeset**: `npx changeset add`
2. **Update critical fields** in both `package.json` and `package.publish.json`
3. **Test locally** with `F5` in VS Code
4. **Commit and push** to trigger automated workflow

### Automated Releases (Recommended)
1. **Create changeset**: `npx changeset add`
2. **Push to feature branch** and create PR
3. **Merge PR** - this triggers version PR creation
4. **Review and merge version PR** - this triggers automated publishing

## 🎯 Why This System?

- **Avoids dependency conflicts**: VS Code doesn't see dev dependencies
- **Clean distribution**: Only essential files in final package
- **Faster packaging**: No dependency resolution during `vsce package`
- **Maintainable**: Clear separation of dev vs. production configs
- **Reliable**: Consistent, conflict-free packaging process
- **Automated**: Changesets handle versioning and publishing automatically
- **Traceable**: Clear changelog and git tags for every release

---

**Remember**: Always use `npx changeset add` for changes, then push to trigger automated releases! 🚀 

```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/logger/logger.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Core logger implementation
 */

import chalk from 'chalk';

export enum LogLevel {
	SILENT = 0,
	ERROR = 1,
	WARN = 2,
	INFO = 3,
	DEBUG = 4
}

/**
 * Log object interface (e.g., MCP context.log)
 */
export interface LogObject {
	info: (message: string) => void;
	warn: (message: string) => void;
	error: (message: string) => void;
	debug: (message: string) => void;
}

/**
 * Log callback can be either a function or a log object
 */
export type LogCallback =
	| ((level: string, message: string) => void)
	| LogObject;

export interface LoggerConfig {
	level?: LogLevel;
	silent?: boolean;
	prefix?: string;
	timestamp?: boolean;
	colors?: boolean;
	// MCP mode silences all output (unless logCallback is provided)
	mcpMode?: boolean;
	// Callback function or object for logging (useful for MCP integration)
	logCallback?: LogCallback;
}

export class Logger {
	private config: LoggerConfig & {
		level: LogLevel;
		silent: boolean;
		prefix: string;
		timestamp: boolean;
		colors: boolean;
		mcpMode: boolean;
	};
	private static readonly DEFAULT_CONFIG = {
		level: LogLevel.SILENT,
		silent: false,
		prefix: '',
		timestamp: false,
		colors: true,
		mcpMode: false,
		logCallback: undefined as LogCallback | undefined
	};

	constructor(config: LoggerConfig = {}) {
		// Check environment variables
		const envConfig: LoggerConfig = {};

		// Check for MCP mode
		if (
			process.env.MCP_MODE === 'true' ||
			process.env.TASK_MASTER_MCP === 'true'
		) {
			envConfig.mcpMode = true;
		}

		// Check for silent mode
		if (
			process.env.TASK_MASTER_SILENT === 'true' ||
			process.env.TM_SILENT === 'true'
		) {
			envConfig.silent = true;
		}

		// Check for log level
		if (process.env.TASK_MASTER_LOG_LEVEL || process.env.TM_LOG_LEVEL) {
			const levelStr = (
				process.env.TASK_MASTER_LOG_LEVEL ||
				process.env.TM_LOG_LEVEL ||
				''
			).toUpperCase();
			if (levelStr in LogLevel) {
				envConfig.level = LogLevel[levelStr as keyof typeof LogLevel];
			}
		}

		// Check for no colors
		if (
			process.env.NO_COLOR === 'true' ||
			process.env.TASK_MASTER_NO_COLOR === 'true'
		) {
			envConfig.colors = false;
		}

		// Merge configs: defaults < constructor < environment
		this.config = {
			...Logger.DEFAULT_CONFIG,
			...config,
			...envConfig
		};

		// MCP mode overrides to silent ONLY if no callback is provided
		if (this.config.mcpMode && !this.config.logCallback) {
			this.config.silent = true;
		}
	}

	/**
	 * Check if logging is enabled for a given level
	 */
	private shouldLog(level: LogLevel): boolean {
		// If a callback is provided, route logs through it while still respecting the configured level
		if (this.config.logCallback) {
			return level <= this.config.level;
		}

		// Otherwise, respect silent/mcpMode flags
		if (this.config.silent || this.config.mcpMode) {
			return false;
		}
		return level <= this.config.level;
	}

	/**
	 * Format a log message
	 */
	private formatMessage(
		level: LogLevel,
		message: string,
		...args: any[]
	): string {
		let formatted = '';

		// Add timestamp if enabled
		if (this.config.timestamp) {
			const timestamp = new Date().toISOString();
			formatted += this.config.colors
				? chalk.gray(`[${timestamp}] `)
				: `[${timestamp}] `;
		}

		// Add prefix if configured
		if (this.config.prefix) {
			formatted += this.config.colors
				? chalk.cyan(`[${this.config.prefix}] `)
				: `[${this.config.prefix}] `;
		}

		// Skip level indicator for cleaner output
		// We can still color the message based on level
		if (this.config.colors) {
			switch (level) {
				case LogLevel.ERROR:
					message = chalk.red(message);
					break;
				case LogLevel.WARN:
					message = chalk.yellow(message);
					break;
				case LogLevel.INFO:
					// Info stays default color
					break;
				case LogLevel.DEBUG:
					message = chalk.gray(message);
					break;
			}
		}

		// Add the message
		formatted += message;

		// Add any additional arguments
		if (args.length > 0) {
			formatted +=
				' ' +
				args
					.map((arg) =>
						typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
					)
					.join(' ');
		}

		return formatted;
	}

	/**
	 * Check if callback is a log object (has info/warn/error/debug methods)
	 */
	private isLogObject(callback: LogCallback): callback is LogObject {
		return (
			typeof callback === 'object' &&
			callback !== null &&
			'info' in callback &&
			'warn' in callback &&
			'error' in callback &&
			'debug' in callback
		);
	}

	/**
	 * Output a log message either to console or callback
	 */
	private output(
		level: LogLevel,
		levelName: string,
		message: string,
		...args: any[]
	): void {
		const formatted = this.formatMessage(level, message, ...args);

		// Use callback if available
		if (this.config.logCallback) {
			// If callback is a log object, call the appropriate method
			if (this.isLogObject(this.config.logCallback)) {
				const method = levelName.toLowerCase() as keyof LogObject;
				if (method in this.config.logCallback) {
					this.config.logCallback[method](formatted);
				}
			} else {
				// Otherwise it's a function callback
				this.config.logCallback(levelName.toLowerCase(), formatted);
			}
			return;
		}

		// Otherwise use console
		switch (level) {
			case LogLevel.ERROR:
				console.error(formatted);
				break;
			case LogLevel.WARN:
				console.warn(formatted);
				break;
			default:
				console.log(formatted);
				break;
		}
	}

	/**
	 * Log an error message
	 */
	error(message: string, ...args: any[]): void {
		if (!this.shouldLog(LogLevel.ERROR)) return;
		this.output(LogLevel.ERROR, 'ERROR', message, ...args);
	}

	/**
	 * Log a warning message
	 */
	warn(message: string, ...args: any[]): void {
		if (!this.shouldLog(LogLevel.WARN)) return;
		this.output(LogLevel.WARN, 'WARN', message, ...args);
	}

	/**
	 * Log an info message
	 */
	info(message: string, ...args: any[]): void {
		if (!this.shouldLog(LogLevel.INFO)) return;
		this.output(LogLevel.INFO, 'INFO', message, ...args);
	}

	/**
	 * Log a debug message
	 */
	debug(message: string, ...args: any[]): void {
		if (!this.shouldLog(LogLevel.DEBUG)) return;
		this.output(LogLevel.DEBUG, 'DEBUG', message, ...args);
	}

	/**
	 * Log a message without any formatting (raw output)
	 * Useful for CLI output that should appear as-is
	 */
	log(message: string, ...args: any[]): void {
		// If callback is provided, use it for raw logs too
		if (this.config.logCallback) {
			const fullMessage =
				args.length > 0 ? [message, ...args].join(' ') : message;

			// If callback is a log object, use info method for raw logs
			if (this.isLogObject(this.config.logCallback)) {
				this.config.logCallback.info(fullMessage);
			} else {
				// Otherwise it's a function callback
				this.config.logCallback('log', fullMessage);
			}
			return;
		}

		// Otherwise, respect silent/mcpMode
		if (this.config.silent || this.config.mcpMode) return;

		if (args.length > 0) {
			console.log(message, ...args);
		} else {
			console.log(message);
		}
	}

	/**
	 * Update logger configuration
	 */
	setConfig(config: Partial<LoggerConfig>): void {
		this.config = {
			...this.config,
			...config
		};

		// MCP mode overrides to silent ONLY if no callback is provided
		if (this.config.mcpMode && !this.config.logCallback) {
			this.config.silent = true;
		}
	}

	/**
	 * Get current configuration
	 */
	getConfig(): Readonly<
		LoggerConfig & {
			level: LogLevel;
			silent: boolean;
			prefix: string;
			timestamp: boolean;
			colors: boolean;
			mcpMode: boolean;
		}
	> {
		return { ...this.config };
	}

	/**
	 * Create a child logger with a prefix
	 */
	child(prefix: string, config?: Partial<LoggerConfig>): Logger {
		const childPrefix = this.config.prefix
			? `${this.config.prefix}:${prefix}`
			: prefix;

		return new Logger({
			...this.config,
			...config,
			prefix: childPrefix
		});
	}
}

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/workflow/managers/workflow-state-manager.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview WorkflowStateManager - Manages persistence of TDD workflow state
 *
 * Stores workflow state in global user directory (~/.taskmaster/{project-id}/sessions/)
 * to avoid git conflicts and support multiple worktrees.
 * Each project gets its own directory for organizing workflow-related data.
 */

import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { Writer } from 'steno';
import { getLogger } from '../../../common/logger/index.js';
import type { WorkflowState } from '../types.js';

export interface WorkflowStateBackup {
	timestamp: string;
	state: WorkflowState;
}

/**
 * Manages workflow state persistence with backup support
 * Stores state in global user directory to avoid git noise
 */
export class WorkflowStateManager {
	private readonly projectRoot: string;
	private readonly statePath: string;
	private readonly backupDir: string;
	private readonly sessionDir: string;
	private maxBackups: number;
	private readonly logger = getLogger('WorkflowStateManager');
	private writer: Writer | null = null;
	private writerInitPromise: Promise<void> | null = null;

	constructor(projectRoot: string, maxBackups = 5) {
		this.projectRoot = path.resolve(projectRoot);
		this.maxBackups = maxBackups;

		// Create project-specific directory in global .taskmaster
		// Structure: ~/.taskmaster/{project-id}/sessions/
		const projectId = this.getProjectIdentifier(this.projectRoot);
		const homeDir = os.homedir();
		const projectDir = path.join(homeDir, '.taskmaster', projectId);
		this.sessionDir = path.join(projectDir, 'sessions');

		this.statePath = path.join(this.sessionDir, 'workflow-state.json');
		this.backupDir = path.join(this.sessionDir, 'backups');
	}

	/**
	 * Generate a unique identifier for the project using full sanitized path
	 * Uses Claude Code's pattern: leading dash + full path with case preserved
	 * Example: /Volumes/Workspace/... -> -Volumes-Workspace-...
	 */
	private getProjectIdentifier(projectRoot: string): string {
		// Resolve to absolute path
		const absolutePath = path.resolve(projectRoot);

		// Sanitize path like Claude Code does:
		// - Add leading dash
		// - Replace path separators and non-alphanumeric chars with dashes
		// - Preserve case for readability
		// - Collapse multiple dashes
		const sanitized =
			'-' +
			absolutePath
				.replace(/^\//, '') // Remove leading slash before adding dash
				.replace(/[^a-zA-Z0-9]+/g, '-') // Replace sequences of non-alphanumeric with single dash
				.replace(/-+/g, '-') // Collapse multiple dashes
				.replace(/-+$/, ''); // Remove trailing dashes

		return sanitized;
	}

	/**
	 * Ensure the steno Writer is initialized
	 * This ensures the session directory exists before creating the writer
	 */
	private async ensureWriter(): Promise<void> {
		if (this.writer) {
			return;
		}

		// If another call is already initializing, wait for it
		if (this.writerInitPromise) {
			await this.writerInitPromise;
			return;
		}

		this.writerInitPromise = (async () => {
			// Ensure session directory exists before creating writer
			await fs.mkdir(this.sessionDir, { recursive: true });
			this.writer = new Writer(this.statePath);
		})();

		await this.writerInitPromise;
		this.writerInitPromise = null;
	}

	/**
	 * Check if workflow state exists
	 */
	async exists(): Promise<boolean> {
		try {
			await fs.access(this.statePath);
			return true;
		} catch {
			return false;
		}
	}

	/**
	 * Load workflow state from disk
	 */
	async load(): Promise<WorkflowState> {
		try {
			const content = await fs.readFile(this.statePath, 'utf-8');
			return JSON.parse(content) as WorkflowState;
		} catch (error: any) {
			if (error.code === 'ENOENT') {
				throw new Error(`Workflow state file not found at ${this.statePath}`);
			}
			throw new Error(`Failed to load workflow state: ${error.message}`);
		}
	}

	/**
	 * Save workflow state to disk
	 * Uses steno for atomic writes and automatic queueing of concurrent saves
	 */
	async save(state: WorkflowState): Promise<void> {
		try {
			// Ensure writer is initialized (creates directory if needed)
			await this.ensureWriter();

			// Serialize and validate JSON
			const jsonContent = JSON.stringify(state, null, 2);

			// Validate that the JSON is well-formed by parsing it back
			try {
				JSON.parse(jsonContent);
			} catch (parseError) {
				this.logger.error('Generated invalid JSON:', jsonContent);
				throw new Error('Failed to generate valid JSON from workflow state');
			}

			// Write using steno (handles queuing and atomic writes automatically)
			await this.writer!.write(jsonContent + '\n');

			this.logger.debug(`Saved workflow state (${jsonContent.length} bytes)`);
		} catch (error: any) {
			throw new Error(`Failed to save workflow state: ${error.message}`);
		}
	}

	/**
	 * Create a backup of current state
	 */
	async createBackup(): Promise<void> {
		try {
			const exists = await this.exists();
			if (!exists) {
				return;
			}

			const state = await this.load();
			await fs.mkdir(this.backupDir, { recursive: true });

			const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
			const backupPath = path.join(
				this.backupDir,
				`workflow-state-${timestamp}.json`
			);

			const backup: WorkflowStateBackup = {
				timestamp: new Date().toISOString(),
				state
			};

			await fs.writeFile(backupPath, JSON.stringify(backup, null, 2), 'utf-8');

			// Clean up old backups
			await this.pruneBackups();
		} catch (error: any) {
			throw new Error(`Failed to create backup: ${error.message}`);
		}
	}

	/**
	 * Delete workflow state file
	 */
	async delete(): Promise<void> {
		try {
			await fs.unlink(this.statePath);
		} catch (error: any) {
			if (error.code !== 'ENOENT') {
				throw new Error(`Failed to delete workflow state: ${error.message}`);
			}
		}
	}

	/**
	 * List available backups
	 */
	async listBackups(): Promise<string[]> {
		try {
			const files = await fs.readdir(this.backupDir);
			return files
				.filter((f) => f.startsWith('workflow-state-') && f.endsWith('.json'))
				.sort()
				.reverse();
		} catch (error: any) {
			if (error.code === 'ENOENT') {
				return [];
			}
			throw new Error(`Failed to list backups: ${error.message}`);
		}
	}

	/**
	 * Restore from a backup
	 */
	async restoreBackup(backupFileName: string): Promise<void> {
		try {
			const backupPath = path.join(this.backupDir, backupFileName);
			const content = await fs.readFile(backupPath, 'utf-8');
			const backup: WorkflowStateBackup = JSON.parse(content);

			await this.save(backup.state);
		} catch (error: any) {
			throw new Error(`Failed to restore backup: ${error.message}`);
		}
	}

	/**
	 * Prune old backups to maintain max backup count
	 */
	private async pruneBackups(): Promise<void> {
		try {
			const backups = await this.listBackups();

			if (backups.length > this.maxBackups) {
				const toDelete = backups.slice(this.maxBackups);

				for (const backup of toDelete) {
					await fs.unlink(path.join(this.backupDir, backup));
				}
			}
		} catch (error: any) {
			// Non-critical error, log but don't throw
			this.logger.warn(`Failed to prune backups: ${error.message}`);
		}
	}

	/**
	 * Get the path to the state file (for debugging/testing)
	 */
	getStatePath(): string {
		return this.statePath;
	}

	/**
	 * Get the path to the backup directory (for debugging/testing)
	 */
	getBackupDir(): string {
		return this.backupDir;
	}

	/**
	 * Get the session directory path (for debugging/testing)
	 */
	getSessionDir(): string {
		return this.sessionDir;
	}

	/**
	 * Get the project root this manager is for
	 */
	getProjectRoot(): string {
		return this.projectRoot;
	}

	/**
	 * Get the path to the activity log file
	 * Activity log is stored next to workflow-state.json for correlation
	 */
	getActivityLogPath(): string {
		return path.join(this.sessionDir, 'activity.jsonl');
	}
}

```

--------------------------------------------------------------------------------
/tests/unit/mcp/tools/move-task-cross-tag.test.js:
--------------------------------------------------------------------------------

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

// Mock the utils functions
const mockFindTasksPath = jest
	.fn()
	.mockReturnValue('/test/path/.taskmaster/tasks/tasks.json');
jest.mock('../../../../mcp-server/src/core/utils/path-utils.js', () => ({
	findTasksPath: mockFindTasksPath
}));

const mockEnableSilentMode = jest.fn();
const mockDisableSilentMode = jest.fn();
const mockReadJSON = jest.fn();
const mockWriteJSON = jest.fn();
jest.mock('../../../../scripts/modules/utils.js', () => ({
	enableSilentMode: mockEnableSilentMode,
	disableSilentMode: mockDisableSilentMode,
	readJSON: mockReadJSON,
	writeJSON: mockWriteJSON
}));

// Import the direct function after setting up mocks
import { moveTaskCrossTagDirect } from '../../../../mcp-server/src/core/direct-functions/move-task-cross-tag.js';

describe('MCP Cross-Tag Move Direct Function', () => {
	const mockLog = {
		info: jest.fn(),
		error: jest.fn(),
		warn: jest.fn()
	};

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

	describe('Mock Verification', () => {
		it('should verify that mocks are working', () => {
			// Test that findTasksPath mock is working
			expect(mockFindTasksPath()).toBe(
				'/test/path/.taskmaster/tasks/tasks.json'
			);

			// Test that readJSON mock is working
			mockReadJSON.mockReturnValue('test');
			expect(mockReadJSON()).toBe('test');
		});
	});

	describe('Parameter Validation', () => {
		it('should return error when source IDs are missing', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_SOURCE_IDS');
			expect(result.error.message).toBe('Source IDs are required');
		});

		it('should return error when source tag is missing', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1,2',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_SOURCE_TAG');
			expect(result.error.message).toBe(
				'Source tag is required for cross-tag moves'
			);
		});

		it('should return error when target tag is missing', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1,2',
					sourceTag: 'backlog',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_TARGET_TAG');
			expect(result.error.message).toBe(
				'Target tag is required for cross-tag moves'
			);
		});

		it('should return error when source and target tags are the same', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1,2',
					sourceTag: 'backlog',
					targetTag: 'backlog',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('SAME_SOURCE_TARGET_TAG');
			expect(result.error.message).toBe(
				'Source and target tags are the same ("backlog")'
			);
			expect(result.error.suggestions).toHaveLength(3);
		});
	});

	describe('Error Code Mapping', () => {
		it('should map tag not found errors correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'invalid',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');
			expect(result.error.message).toBe(
				'Source tag "invalid" not found or invalid'
			);
			expect(result.error.suggestions).toHaveLength(3);
		});

		it('should map missing project root errors correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress'
					// Missing projectRoot
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_PROJECT_ROOT');
			expect(result.error.message).toBe(
				'Project root is required if tasksJsonPath is not provided'
			);
		});
	});

	describe('Move Options Handling', () => {
		it('should handle move options correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					withDependencies: true,
					ignoreDependencies: false,
					projectRoot: '/test'
				},
				mockLog
			);

			// The function should fail due to missing tag, but options should be processed
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');
		});
	});

	describe('Function Call Flow', () => {
		it('should call findTasksPath when projectRoot is provided', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			// The function should fail due to tag validation before reaching path resolution
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');

			// Since the function fails early, findTasksPath is not called
			expect(mockFindTasksPath).toHaveBeenCalledTimes(0);
		});

		it('should enable and disable silent mode during execution', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			// The function should fail due to tag validation before reaching silent mode calls
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');

			// Since the function fails early, silent mode is not called
			expect(mockEnableSilentMode).toHaveBeenCalledTimes(0);
			expect(mockDisableSilentMode).toHaveBeenCalledTimes(0);
		});

		it('should parse source IDs correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1, 2, 3', // With spaces
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			// Should fail due to tag validation, but ID parsing should work
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');
		});

		it('should handle move options correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					withDependencies: true,
					ignoreDependencies: false,
					projectRoot: '/test'
				},
				mockLog
			);

			// Should fail due to tag validation, but option processing should work
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');
		});
	});

	describe('Error Handling', () => {
		it('should handle missing project root correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress'
					// Missing projectRoot
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_PROJECT_ROOT');
			expect(result.error.message).toBe(
				'Project root is required if tasksJsonPath is not provided'
			);
		});

		it('should handle same source and target tags', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'backlog',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('SAME_SOURCE_TARGET_TAG');
			expect(result.error.message).toBe(
				'Source and target tags are the same ("backlog")'
			);
			expect(result.error.suggestions).toHaveLength(3);
		});
	});
});

```

--------------------------------------------------------------------------------
/apps/cli/src/utils/brief-selection.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Shared brief selection utilities
 * Reusable functions for selecting briefs interactively or via URL/ID
 */

import search from '@inquirer/search';
import type { AuthManager } from '@tm/core';
import { formatRelativeTime } from '@tm/core';
import chalk from 'chalk';
import ora, { type Ora } from 'ora';
import { getBriefStatusWithColor } from '../ui/formatters/status-formatters.js';
import * as ui from './ui.js';

export interface BriefSelectionResult {
	success: boolean;
	briefId?: string;
	briefName?: string;
	orgId?: string;
	orgName?: string;
	message?: string;
}

/**
 * Select a brief interactively using search
 */
export async function selectBriefInteractive(
	authManager: AuthManager,
	orgId: string
): Promise<BriefSelectionResult> {
	const spinner = ora('Fetching briefs...').start();

	try {
		// Fetch briefs from API
		const briefs = await authManager.getBriefs(orgId);
		spinner.stop();

		if (briefs.length === 0) {
			ui.displayWarning('No briefs available in this organization');
			return {
				success: false,
				message: 'No briefs available'
			};
		}

		// Prompt for selection with search
		const selectedBrief = await search<(typeof briefs)[0] | null>({
			message: 'Search for a brief:',
			source: async (input) => {
				const searchTerm = input?.toLowerCase() || '';

				// Static option for no brief
				const noBriefOption = {
					name: '(No brief - organization level)',
					value: null as any,
					description: 'Clear brief selection'
				};

				// Filter briefs based on search term
				const filteredBriefs = briefs.filter((brief) => {
					if (!searchTerm) return true;

					const title = brief.document?.title || '';
					const shortId = brief.id.slice(0, 8);
					const lastChars = brief.id.slice(-8);

					// Search by title, full UUID, first 8 chars, or last 8 chars
					return (
						title.toLowerCase().includes(searchTerm) ||
						brief.id.toLowerCase().includes(searchTerm) ||
						shortId.toLowerCase().includes(searchTerm) ||
						lastChars.toLowerCase().includes(searchTerm)
					);
				});

				// Group briefs by status
				const briefsByStatus = filteredBriefs.reduce(
					(acc, brief) => {
						const status = brief.status || 'unknown';
						if (!acc[status]) {
							acc[status] = [];
						}
						acc[status].push(brief);
						return acc;
					},
					{} as Record<string, typeof briefs>
				);

				// Define status order (most active first)
				const statusOrder = [
					'delivering',
					'aligned',
					'refining',
					'draft',
					'delivered',
					'done',
					'archived'
				];

				// Build grouped options
				const groupedOptions: any[] = [];

				for (const status of statusOrder) {
					const statusBriefs = briefsByStatus[status];
					if (!statusBriefs || statusBriefs.length === 0) continue;

					// Add status header as separator
					const statusHeader = getBriefStatusWithColor(status);
					groupedOptions.push({
						type: 'separator',
						separator: `\n${statusHeader}`
					});

					// Add briefs under this status
					statusBriefs.forEach((brief) => {
						const title =
							brief.document?.title || `Brief ${brief.id.slice(-8)}`;
						const shortId = brief.id.slice(-8);
						const description = brief.document?.description || '';
						const taskCountDisplay =
							brief.taskCount !== undefined && brief.taskCount > 0
								? chalk.gray(
										` (${brief.taskCount} ${brief.taskCount === 1 ? 'task' : 'tasks'})`
									)
								: '';

						const updatedAtDisplay = brief.updatedAt
							? chalk.gray(` • ${formatRelativeTime(brief.updatedAt)}`)
							: '';

						groupedOptions.push({
							name: `  ${title}${taskCountDisplay} ${chalk.gray(`(${shortId})`)}${updatedAtDisplay}`,
							value: brief,
							description: description
								? chalk.gray(`  ${description.slice(0, 80)}`)
								: undefined
						});
					});
				}

				// Handle any briefs with statuses not in our order
				const unorderedStatuses = Object.keys(briefsByStatus).filter(
					(s) => !statusOrder.includes(s)
				);
				for (const status of unorderedStatuses) {
					const statusBriefs = briefsByStatus[status];
					if (!statusBriefs || statusBriefs.length === 0) continue;

					const statusHeader = getBriefStatusWithColor(status);
					groupedOptions.push({
						type: 'separator',
						separator: `\n${statusHeader}`
					});

					statusBriefs.forEach((brief) => {
						const title =
							brief.document?.title || `Brief ${brief.id.slice(-8)}`;
						const shortId = brief.id.slice(-8);
						const description = brief.document?.description || '';
						const taskCountDisplay =
							brief.taskCount !== undefined && brief.taskCount > 0
								? chalk.gray(
										` (${brief.taskCount} ${brief.taskCount === 1 ? 'task' : 'tasks'})`
									)
								: '';

						const updatedAtDisplay = brief.updatedAt
							? chalk.gray(` • ${formatRelativeTime(brief.updatedAt)}`)
							: '';

						groupedOptions.push({
							name: `  ${title}${taskCountDisplay} ${chalk.gray(`(${shortId})`)}${updatedAtDisplay}`,
							value: brief,
							description: description
								? chalk.gray(`  ${description.slice(0, 80)}`)
								: undefined
						});
					});
				}

				return [noBriefOption, ...groupedOptions];
			}
		});

		if (selectedBrief) {
			// Update context with brief
			const briefName =
				selectedBrief.document?.title ||
				`Brief ${selectedBrief.id.slice(0, 8)}`;
			await authManager.updateContext({
				briefId: selectedBrief.id,
				briefName: briefName,
				briefStatus: selectedBrief.status,
				briefUpdatedAt: selectedBrief.updatedAt
			});

			ui.displaySuccess(`Selected brief: ${briefName}`);

			return {
				success: true,
				briefId: selectedBrief.id,
				briefName,
				message: `Selected brief: ${briefName}`
			};
		} else {
			// Clear brief selection
			await authManager.updateContext({
				briefId: undefined,
				briefName: undefined,
				briefStatus: undefined,
				briefUpdatedAt: undefined
			});

			ui.displaySuccess('Cleared brief selection (organization level)');

			return {
				success: true,
				message: 'Cleared brief selection'
			};
		}
	} catch (error) {
		spinner.fail('Failed to fetch briefs');
		throw error;
	}
}

/**
 * Select a brief from any input format (URL, ID, name) using tm-core
 * Presentation layer - handles display and context updates only
 *
 * All business logic (URL parsing, ID matching, name resolution) is in tm-core
 */
export async function selectBriefFromInput(
	authManager: AuthManager,
	input: string,
	tmCore: any
): Promise<BriefSelectionResult> {
	let spinner: Ora | undefined;
	try {
		spinner = ora('Resolving brief...');
		spinner.start();

		// Let tm-core handle ALL business logic:
		// - URL parsing
		// - ID extraction
		// - UUID matching (full or last 8 chars)
		// - Name matching
		const brief = await tmCore.tasks.resolveBrief(input);

		// Fetch org to get a friendly name and slug (optional)
		let orgName: string | undefined;
		let orgSlug: string | undefined;
		try {
			const org = await authManager.getOrganization(brief.accountId);
			orgName = org?.name;
			orgSlug = org?.slug;
		} catch {
			// Non-fatal if org lookup fails
		}

		// Update context: set org and brief
		const briefName = brief.document?.title || `Brief ${brief.id.slice(0, 8)}`;
		await authManager.updateContext({
			orgId: brief.accountId,
			orgName,
			orgSlug,
			briefId: brief.id,
			briefName,
			briefStatus: brief.status,
			briefUpdatedAt: brief.updatedAt
		});

		spinner.succeed('Context set from brief');
		console.log(
			chalk.gray(
				`  Organization: ${orgName || brief.accountId}\n  Brief: ${briefName}`
			)
		);

		return {
			success: true,
			briefId: brief.id,
			briefName,
			orgId: brief.accountId,
			orgName,
			message: 'Context set from brief'
		};
	} catch (error) {
		try {
			if (spinner?.isSpinning) spinner.stop();
		} catch {}
		throw error;
	}
}

```

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

```javascript
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import { createLogWrapper } from '../../../mcp-server/src/tools/utils.js';
import { findProjectRoot } from '../utils.js';
import {
	LEGACY_CONFIG_FILE,
	TASKMASTER_CONFIG_FILE
} from '../../../src/constants/paths.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Create a simple log wrapper for CLI use
const log = createLogWrapper({
	info: (msg) => console.log(chalk.blue('ℹ'), msg),
	warn: (msg) => console.log(chalk.yellow('⚠'), msg),
	error: (msg) => console.error(chalk.red('✗'), msg),
	success: (msg) => console.log(chalk.green('✓'), msg)
});

/**
 * Main migration function
 * @param {Object} options - Migration options
 */
export async function migrateProject(options = {}) {
	const projectRoot = findProjectRoot() || process.cwd();

	log.info(`Starting migration in: ${projectRoot}`);

	// Check if .taskmaster directory already exists
	const taskmasterDir = path.join(projectRoot, '.taskmaster');
	if (fs.existsSync(taskmasterDir) && !options.force) {
		log.warn(
			'.taskmaster directory already exists. Use --force to overwrite or skip migration.'
		);
		return;
	}

	// Analyze what needs to be migrated
	const migrationPlan = analyzeMigrationNeeds(projectRoot);

	if (migrationPlan.length === 0) {
		log.info(
			'No files to migrate. Project may already be using the new structure.'
		);
		return;
	}

	// Show migration plan
	log.info('Migration plan:');
	for (const item of migrationPlan) {
		const action = options.dryRun ? 'Would move' : 'Will move';
		log.info(`  ${action}: ${item.from} → ${item.to}`);
	}

	if (options.dryRun) {
		log.info(
			'Dry run complete. Use --dry-run=false to perform actual migration.'
		);
		return;
	}

	// Confirm migration
	if (!options.yes) {
		const readline = await import('readline');
		const rl = readline.createInterface({
			input: process.stdin,
			output: process.stdout
		});

		const answer = await new Promise((resolve) => {
			rl.question('Proceed with migration? (y/N): ', resolve);
		});
		rl.close();

		if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
			log.info('Migration cancelled.');
			return;
		}
	}

	// Perform migration
	try {
		await performMigration(projectRoot, migrationPlan, options);
		log.success('Migration completed successfully!');
		log.info('You can now use the new .taskmaster directory structure.');
		if (!options.cleanup) {
			log.info(
				'Old files were preserved. Use --cleanup to remove them after verification.'
			);
		}
	} catch (error) {
		log.error(`Migration failed: ${error.message}`);
		throw error;
	}
}

/**
 * Analyze what files need to be migrated
 * @param {string} projectRoot - Project root directory
 * @returns {Array} Migration plan items
 */
function analyzeMigrationNeeds(projectRoot) {
	const migrationPlan = [];

	// Check for tasks directory
	const tasksDir = path.join(projectRoot, 'tasks');
	if (fs.existsSync(tasksDir)) {
		const tasksFiles = fs.readdirSync(tasksDir);
		for (const file of tasksFiles) {
			migrationPlan.push({
				from: path.join('tasks', file),
				to: path.join('.taskmaster', 'tasks', file),
				type: 'task'
			});
		}
	}

	// Check for scripts directory files
	const scriptsDir = path.join(projectRoot, 'scripts');
	if (fs.existsSync(scriptsDir)) {
		const scriptsFiles = fs.readdirSync(scriptsDir);
		for (const file of scriptsFiles) {
			const filePath = path.join(scriptsDir, file);
			if (fs.statSync(filePath).isFile()) {
				// Categorize files more intelligently
				let destination;
				const lowerFile = file.toLowerCase();

				if (
					lowerFile.includes('example') ||
					lowerFile.includes('template') ||
					lowerFile.includes('boilerplate') ||
					lowerFile.includes('sample')
				) {
					// Template/example files go to templates (including example_prd.txt)
					destination = path.join('.taskmaster', 'templates', file);
				} else if (
					lowerFile.includes('complexity') &&
					lowerFile.includes('report') &&
					lowerFile.endsWith('.json')
				) {
					// Only actual complexity reports go to reports
					destination = path.join('.taskmaster', 'reports', file);
				} else if (
					lowerFile.includes('prd') ||
					lowerFile.endsWith('.md') ||
					lowerFile.endsWith('.txt')
				) {
					// Documentation files go to docs (but not examples or reports)
					destination = path.join('.taskmaster', 'docs', file);
				} else {
					// Other files stay in scripts or get skipped - don't force everything into templates
					log.warn(
						`Skipping migration of '${file}' - uncertain categorization. You may need to move this manually.`
					);
					continue;
				}

				migrationPlan.push({
					from: path.join('scripts', file),
					to: destination,
					type: 'script'
				});
			}
		}
	}

	// Check for .taskmasterconfig
	const oldConfig = path.join(projectRoot, LEGACY_CONFIG_FILE);
	if (fs.existsSync(oldConfig)) {
		migrationPlan.push({
			from: LEGACY_CONFIG_FILE,
			to: TASKMASTER_CONFIG_FILE,
			type: 'config'
		});
	}

	return migrationPlan;
}

/**
 * Perform the actual migration
 * @param {string} projectRoot - Project root directory
 * @param {Array} migrationPlan - List of files to migrate
 * @param {Object} options - Migration options
 */
async function performMigration(projectRoot, migrationPlan, options) {
	// Create .taskmaster directory
	const taskmasterDir = path.join(projectRoot, '.taskmaster');
	if (!fs.existsSync(taskmasterDir)) {
		fs.mkdirSync(taskmasterDir, { recursive: true });
	}

	// Group migration items by destination directory to create only needed subdirs
	const neededDirs = new Set();
	for (const item of migrationPlan) {
		const destDir = path.dirname(item.to);
		neededDirs.add(destDir);
	}

	// Create only the directories we actually need
	for (const dir of neededDirs) {
		const fullDirPath = path.join(projectRoot, dir);
		if (!fs.existsSync(fullDirPath)) {
			fs.mkdirSync(fullDirPath, { recursive: true });
			log.info(`Created directory: ${dir}`);
		}
	}

	// Create backup if requested
	if (options.backup) {
		const backupDir = path.join(projectRoot, '.taskmaster-migration-backup');
		log.info(`Creating backup in: ${backupDir}`);
		if (fs.existsSync(backupDir)) {
			fs.rmSync(backupDir, { recursive: true, force: true });
		}
		fs.mkdirSync(backupDir, { recursive: true });
	}

	// Migrate files
	for (const item of migrationPlan) {
		const fromPath = path.join(projectRoot, item.from);
		const toPath = path.join(projectRoot, item.to);

		if (!fs.existsSync(fromPath)) {
			log.warn(`Source file not found: ${item.from}`);
			continue;
		}

		// Create backup if requested
		if (options.backup) {
			const backupPath = path.join(
				projectRoot,
				'.taskmaster-migration-backup',
				item.from
			);
			const backupDir = path.dirname(backupPath);
			if (!fs.existsSync(backupDir)) {
				fs.mkdirSync(backupDir, { recursive: true });
			}
			fs.copyFileSync(fromPath, backupPath);
		}

		// Ensure destination directory exists
		const toDir = path.dirname(toPath);
		if (!fs.existsSync(toDir)) {
			fs.mkdirSync(toDir, { recursive: true });
		}

		// Copy file
		fs.copyFileSync(fromPath, toPath);
		log.info(`Migrated: ${item.from} → ${item.to}`);

		// Remove original if cleanup is requested
		if (options.cleanup) {
			fs.unlinkSync(fromPath);
		}
	}

	// Clean up empty directories if cleanup is requested
	if (options.cleanup) {
		const dirsToCheck = ['tasks', 'scripts'];
		for (const dir of dirsToCheck) {
			const dirPath = path.join(projectRoot, dir);
			if (fs.existsSync(dirPath)) {
				try {
					const files = fs.readdirSync(dirPath);
					if (files.length === 0) {
						fs.rmdirSync(dirPath);
						log.info(`Removed empty directory: ${dir}`);
					}
				} catch (error) {
					// Directory not empty or other error, skip
				}
			}
		}
	}
}

export default { migrateProject };

```

--------------------------------------------------------------------------------
/src/utils/create-mcp-config.js:
--------------------------------------------------------------------------------

```javascript
import fs from 'fs';
import path from 'path';
import { log } from '../../scripts/modules/utils.js';

// Return JSON with existing mcp.json formatting style
function formatJSONWithTabs(obj) {
	let json = JSON.stringify(obj, null, '\t');

	json = json.replace(
		/(\[\n\t+)([^[\]]+?)(\n\t+\])/g,
		(match, openBracket, content, closeBracket) => {
			// Only convert to single line if content doesn't contain nested objects/arrays
			if (!content.includes('{') && !content.includes('[')) {
				const singleLineContent = content
					.replace(/\n\t+/g, ' ')
					.replace(/\s+/g, ' ')
					.trim();
				return `[${singleLineContent}]`;
			}
			return match;
		}
	);

	return json;
}

// Structure matches project conventions (see scripts/init.js)
export function setupMCPConfiguration(projectRoot, mcpConfigPath) {
	// Handle null mcpConfigPath (e.g., for Claude/Codex profiles)
	if (!mcpConfigPath) {
		log(
			'debug',
			'[MCP Config] No mcpConfigPath provided, skipping MCP configuration setup'
		);
		return;
	}

	// Build the full path to the MCP config file
	const mcpPath = path.join(projectRoot, mcpConfigPath);
	const configDir = path.dirname(mcpPath);

	log('info', `Setting up MCP configuration at ${mcpPath}...`);

	// New MCP config to be added - references the installed package
	const newMCPServer = {
		'task-master-ai': {
			command: 'npx',
			args: ['-y', 'task-master-ai'],
			env: {
				ANTHROPIC_API_KEY: 'YOUR_ANTHROPIC_API_KEY_HERE',
				PERPLEXITY_API_KEY: 'YOUR_PERPLEXITY_API_KEY_HERE',
				OPENAI_API_KEY: 'YOUR_OPENAI_KEY_HERE',
				GOOGLE_API_KEY: 'YOUR_GOOGLE_KEY_HERE',
				XAI_API_KEY: 'YOUR_XAI_KEY_HERE',
				OPENROUTER_API_KEY: 'YOUR_OPENROUTER_KEY_HERE',
				MISTRAL_API_KEY: 'YOUR_MISTRAL_KEY_HERE',
				AZURE_OPENAI_API_KEY: 'YOUR_AZURE_KEY_HERE',
				OLLAMA_API_KEY: 'YOUR_OLLAMA_API_KEY_HERE'
			}
		}
	};

	// Create config directory if it doesn't exist
	if (!fs.existsSync(configDir)) {
		fs.mkdirSync(configDir, { recursive: true });
	}

	if (fs.existsSync(mcpPath)) {
		log(
			'info',
			'MCP configuration file already exists, checking for existing task-master-ai...'
		);
		try {
			// Read existing config
			const mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
			// Initialize mcpServers if it doesn't exist
			if (!mcpConfig.mcpServers) {
				mcpConfig.mcpServers = {};
			}
			// Check if any existing server configuration already has task-master-ai in its args
			const hasMCPString = Object.values(mcpConfig.mcpServers).some(
				(server) =>
					server.args &&
					Array.isArray(server.args) &&
					server.args.some(
						(arg) => typeof arg === 'string' && arg.includes('task-master-ai')
					)
			);
			if (hasMCPString) {
				log(
					'info',
					'Found existing task-master-ai MCP configuration in mcp.json, leaving untouched'
				);
				return; // Exit early, don't modify the existing configuration
			}
			// Add the task-master-ai server if it doesn't exist
			if (!mcpConfig.mcpServers['task-master-ai']) {
				mcpConfig.mcpServers['task-master-ai'] = newMCPServer['task-master-ai'];
				log(
					'info',
					'Added task-master-ai server to existing MCP configuration'
				);
			} else {
				log('info', 'task-master-ai server already configured in mcp.json');
			}
			// Write the updated configuration
			fs.writeFileSync(mcpPath, formatJSONWithTabs(mcpConfig) + '\n');
			log('success', 'Updated MCP configuration file');
		} catch (error) {
			log('error', `Failed to update MCP configuration: ${error.message}`);
			// Create a backup before potentially modifying
			const backupPath = `${mcpPath}.backup-${Date.now()}`;
			if (fs.existsSync(mcpPath)) {
				fs.copyFileSync(mcpPath, backupPath);
				log('info', `Created backup of existing mcp.json at ${backupPath}`);
			}
			// Create new configuration
			const newMCPConfig = {
				mcpServers: newMCPServer
			};
			fs.writeFileSync(mcpPath, formatJSONWithTabs(newMCPConfig) + '\n');
			log(
				'warn',
				'Created new MCP configuration file (backup of original file was created if it existed)'
			);
		}
	} else {
		// If mcp.json doesn't exist, create it
		const newMCPConfig = {
			mcpServers: newMCPServer
		};
		fs.writeFileSync(mcpPath, formatJSONWithTabs(newMCPConfig) + '\n');
		log('success', `Created MCP configuration file at ${mcpPath}`);
	}

	// Add note to console about MCP integration
	log('info', 'MCP server will use the installed task-master-ai package');
}

/**
 * Remove Task Master MCP server configuration from an existing mcp.json file
 * Only removes Task Master entries, preserving other MCP servers
 * @param {string} projectRoot - Target project directory
 * @param {string} mcpConfigPath - Relative path to MCP config file (e.g., '.cursor/mcp.json')
 * @returns {Object} Result object with success status and details
 */
export function removeTaskMasterMCPConfiguration(projectRoot, mcpConfigPath) {
	// Handle null mcpConfigPath (e.g., for Claude/Codex profiles)
	if (!mcpConfigPath) {
		return {
			success: true,
			removed: false,
			deleted: false,
			error: null,
			hasOtherServers: false
		};
	}

	const mcpPath = path.join(projectRoot, mcpConfigPath);

	let result = {
		success: false,
		removed: false,
		deleted: false,
		error: null,
		hasOtherServers: false
	};

	if (!fs.existsSync(mcpPath)) {
		result.success = true;
		result.removed = false;
		log('debug', `[MCP Config] MCP config file does not exist: ${mcpPath}`);
		return result;
	}

	try {
		// Read existing config
		const mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));

		if (!mcpConfig.mcpServers) {
			result.success = true;
			result.removed = false;
			log('debug', `[MCP Config] No mcpServers section found in: ${mcpPath}`);
			return result;
		}

		// Check if Task Master is configured
		const hasTaskMaster =
			mcpConfig.mcpServers['task-master-ai'] ||
			Object.values(mcpConfig.mcpServers).some(
				(server) =>
					server.args &&
					Array.isArray(server.args) &&
					server.args.some(
						(arg) => typeof arg === 'string' && arg.includes('task-master-ai')
					)
			);

		if (!hasTaskMaster) {
			result.success = true;
			result.removed = false;
			log(
				'debug',
				`[MCP Config] Task Master not found in MCP config: ${mcpPath}`
			);
			return result;
		}

		// Remove task-master-ai server
		delete mcpConfig.mcpServers['task-master-ai'];

		// Also remove any servers that have task-master-ai in their args
		Object.keys(mcpConfig.mcpServers).forEach((serverName) => {
			const server = mcpConfig.mcpServers[serverName];
			if (
				server.args &&
				Array.isArray(server.args) &&
				server.args.some(
					(arg) => typeof arg === 'string' && arg.includes('task-master-ai')
				)
			) {
				delete mcpConfig.mcpServers[serverName];
				log(
					'debug',
					`[MCP Config] Removed server '${serverName}' containing task-master-ai`
				);
			}
		});

		// Check if there are other MCP servers remaining
		const remainingServers = Object.keys(mcpConfig.mcpServers);
		result.hasOtherServers = remainingServers.length > 0;

		if (result.hasOtherServers) {
			// Write back the modified config with remaining servers
			fs.writeFileSync(mcpPath, formatJSONWithTabs(mcpConfig) + '\n');
			result.success = true;
			result.removed = true;
			result.deleted = false;
			log(
				'info',
				`[MCP Config] Removed Task Master from MCP config, preserving other servers: ${remainingServers.join(', ')}`
			);
		} else {
			// No other servers, delete the entire file
			fs.rmSync(mcpPath, { force: true });
			result.success = true;
			result.removed = true;
			result.deleted = true;
			log(
				'info',
				`[MCP Config] Removed MCP config file (no other servers remaining): ${mcpPath}`
			);
		}
	} catch (error) {
		result.error = error.message;
		log(
			'error',
			`[MCP Config] Failed to remove Task Master from MCP config: ${error.message}`
		);
	}

	return result;
}

// Export the formatting function for use by other modules
export { formatJSONWithTabs };

```
Page 17/50FirstPrevNextLast