#
tokens: 49004/50000 14/975 files (page 21/50)
lines: off (toggle) GitHub
raw markdown copy
This is page 21 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

--------------------------------------------------------------------------------
/tests/unit/profiles/amp-integration.test.js:
--------------------------------------------------------------------------------

```javascript
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { getRulesProfile } from '../../../src/utils/rule-transformer.js';

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

describe('Amp Profile Integration', () => {
	let tempDir;
	let ampProfile;

	beforeEach(() => {
		// Create temporary directory for testing
		tempDir = fs.mkdtempSync(path.join(__dirname, 'temp-amp-unit-'));

		// Get the Amp profile
		ampProfile = getRulesProfile('amp');
	});

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

	describe('Profile Structure', () => {
		test('should have expected profile structure', () => {
			expect(ampProfile).toBeDefined();
			expect(ampProfile.profileName).toBe('amp');
			expect(ampProfile.displayName).toBe('Amp');
			expect(ampProfile.profileDir).toBe('.vscode');
			expect(ampProfile.rulesDir).toBe('.');
			expect(ampProfile.mcpConfig).toBe(true);
			expect(ampProfile.mcpConfigName).toBe('settings.json');
			expect(ampProfile.mcpConfigPath).toBe('.vscode/settings.json');
			expect(ampProfile.includeDefaultRules).toBe(false);
		});

		test('should have correct file mapping', () => {
			expect(ampProfile.fileMap).toEqual({
				'AGENTS.md': '.taskmaster/AGENT.md'
			});
		});

		test('should not create unnecessary directories', () => {
			// Unlike profiles that copy entire directories, Amp should only create what's needed
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });
			fs.writeFileSync(
				path.join(assetsDir, 'AGENTS.md'),
				'Task Master instructions'
			);

			// Call onAddRulesProfile
			ampProfile.onAddRulesProfile(tempDir, assetsDir);

			// Should only have created .taskmaster directory and AGENT.md
			expect(fs.existsSync(path.join(tempDir, '.taskmaster'))).toBe(true);
			expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(true);

			// Should not have created any other directories (like .claude)
			expect(fs.existsSync(path.join(tempDir, '.amp'))).toBe(false);
			expect(fs.existsSync(path.join(tempDir, '.claude'))).toBe(false);
		});
	});

	describe('AGENT.md Import Logic', () => {
		test('should handle missing source file gracefully', () => {
			// Call onAddRulesProfile without creating source file
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });

			// Should not throw error
			expect(() => {
				ampProfile.onAddRulesProfile(tempDir, assetsDir);
			}).not.toThrow();

			// Should not create any files
			expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(false);
			expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
				false
			);
		});

		test('should preserve existing content when adding import', () => {
			// Create existing AGENT.md with specific content
			const existingContent =
				'# My Custom Amp Setup\n\nThis is my custom configuration.\n\n## Custom Section\n\nSome custom rules here.';
			fs.writeFileSync(path.join(tempDir, 'AGENT.md'), existingContent);

			// Create mock source
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });
			fs.writeFileSync(
				path.join(assetsDir, 'AGENTS.md'),
				'Task Master instructions'
			);

			// Call onAddRulesProfile
			ampProfile.onAddRulesProfile(tempDir, assetsDir);

			// Check that existing content is preserved
			const updatedContent = fs.readFileSync(
				path.join(tempDir, 'AGENT.md'),
				'utf8'
			);
			expect(updatedContent).toContain('# My Custom Amp Setup');
			expect(updatedContent).toContain('This is my custom configuration.');
			expect(updatedContent).toContain('## Custom Section');
			expect(updatedContent).toContain('Some custom rules here.');
			expect(updatedContent).toContain('@./.taskmaster/AGENT.md');
		});
	});

	describe('MCP Configuration Handling', () => {
		test('should handle missing .vscode directory gracefully', () => {
			// Call onAddRulesProfile without .vscode directory
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });

			// Should not throw error
			expect(() => {
				ampProfile.onAddRulesProfile(tempDir, assetsDir);
			}).not.toThrow();
		});

		test('should handle malformed JSON gracefully', () => {
			// Create .vscode directory with malformed JSON
			const vscodeDirPath = path.join(tempDir, '.vscode');
			fs.mkdirSync(vscodeDirPath, { recursive: true });
			fs.writeFileSync(
				path.join(vscodeDirPath, 'settings.json'),
				'{ malformed json'
			);

			// Should not throw error
			expect(() => {
				ampProfile.onAddRulesProfile(tempDir, path.join(tempDir, 'assets'));
			}).not.toThrow();
		});

		test('should preserve other VS Code settings when renaming', () => {
			// Create .vscode/settings.json with various settings
			const vscodeDirPath = path.join(tempDir, '.vscode');
			fs.mkdirSync(vscodeDirPath, { recursive: true });

			const initialConfig = {
				'editor.fontSize': 14,
				'editor.tabSize': 2,
				mcpServers: {
					'task-master-ai': {
						command: 'npx',
						args: ['-y', 'task-master-ai']
					}
				},
				'workbench.colorTheme': 'Dark+'
			};

			fs.writeFileSync(
				path.join(vscodeDirPath, 'settings.json'),
				JSON.stringify(initialConfig, null, '\t')
			);

			// Call onPostConvertRulesProfile (which handles MCP transformation)
			ampProfile.onPostConvertRulesProfile(
				tempDir,
				path.join(tempDir, 'assets')
			);

			// Check that other settings are preserved
			const settingsFile = path.join(vscodeDirPath, 'settings.json');
			const content = fs.readFileSync(settingsFile, 'utf8');
			const config = JSON.parse(content);

			expect(config['editor.fontSize']).toBe(14);
			expect(config['editor.tabSize']).toBe(2);
			expect(config['workbench.colorTheme']).toBe('Dark+');
			expect(config['amp.mcpServers']).toBeDefined();
			expect(config.mcpServers).toBeUndefined();
		});
	});

	describe('Removal Logic', () => {
		test('should handle missing files gracefully during removal', () => {
			// Should not throw error when removing non-existent files
			expect(() => {
				ampProfile.onRemoveRulesProfile(tempDir);
			}).not.toThrow();
		});

		test('should handle malformed JSON gracefully during removal', () => {
			// Create .vscode directory with malformed JSON
			const vscodeDirPath = path.join(tempDir, '.vscode');
			fs.mkdirSync(vscodeDirPath, { recursive: true });
			fs.writeFileSync(
				path.join(vscodeDirPath, 'settings.json'),
				'{ malformed json'
			);

			// Should not throw error
			expect(() => {
				ampProfile.onRemoveRulesProfile(tempDir);
			}).not.toThrow();
		});

		test('should preserve .vscode directory if it contains other files', () => {
			// Create .vscode directory with amp.mcpServers and other files
			const vscodeDirPath = path.join(tempDir, '.vscode');
			fs.mkdirSync(vscodeDirPath, { recursive: true });

			const initialConfig = {
				'amp.mcpServers': {
					'task-master-ai': {
						command: 'npx',
						args: ['-y', 'task-master-ai']
					}
				}
			};

			fs.writeFileSync(
				path.join(vscodeDirPath, 'settings.json'),
				JSON.stringify(initialConfig, null, '\t')
			);

			// Create another file in .vscode
			fs.writeFileSync(path.join(vscodeDirPath, 'launch.json'), '{}');

			// Call onRemoveRulesProfile
			ampProfile.onRemoveRulesProfile(tempDir);

			// Check that .vscode directory is preserved
			expect(fs.existsSync(vscodeDirPath)).toBe(true);
			expect(fs.existsSync(path.join(vscodeDirPath, 'launch.json'))).toBe(true);
		});
	});

	describe('Lifecycle Function Integration', () => {
		test('should have all required lifecycle functions', () => {
			expect(typeof ampProfile.onAddRulesProfile).toBe('function');
			expect(typeof ampProfile.onRemoveRulesProfile).toBe('function');
			expect(typeof ampProfile.onPostConvertRulesProfile).toBe('function');
		});

		test('onPostConvertRulesProfile should behave like onAddRulesProfile', () => {
			// Create mock source
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });
			fs.writeFileSync(
				path.join(assetsDir, 'AGENTS.md'),
				'Task Master instructions'
			);

			// Call onPostConvertRulesProfile
			ampProfile.onPostConvertRulesProfile(tempDir, assetsDir);

			// Should have same result as onAddRulesProfile
			expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
				true
			);
			expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(true);

			const agentContent = fs.readFileSync(
				path.join(tempDir, 'AGENT.md'),
				'utf8'
			);
			expect(agentContent).toContain('@./.taskmaster/AGENT.md');
		});
	});

	describe('Error Handling', () => {
		test('should handle file system errors gracefully', () => {
			// Mock fs.writeFileSync to throw an error
			const originalWriteFileSync = fs.writeFileSync;
			fs.writeFileSync = jest.fn().mockImplementation(() => {
				throw new Error('Permission denied');
			});

			// Create mock source
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });
			originalWriteFileSync.call(
				fs,
				path.join(assetsDir, 'AGENTS.md'),
				'Task Master instructions'
			);

			// Should not throw error
			expect(() => {
				ampProfile.onAddRulesProfile(tempDir, assetsDir);
			}).not.toThrow();

			// Restore original function
			fs.writeFileSync = originalWriteFileSync;
		});
	});
});

```

--------------------------------------------------------------------------------
/docs/mcp-provider.md:
--------------------------------------------------------------------------------

```markdown
# MCP Provider Implementation

## Overview

The MCP Provider creates a modern AI SDK-compliant custom provider that integrates with the existing Task Master MCP server infrastructure. This provider enables AI operations through MCP session sampling while following modern AI SDK patterns and **includes full support for structured object generation (generateObject)** for schema-driven features like PRD parsing and task creation.

## Architecture

### Components

1. **MCPProvider** (`mcp-server/src/providers/mcp-provider.js`)
   - Main provider class following Claude Code pattern
   - Session-based provider (no API key required)
   - Registers with provider registry on MCP server connect

2. **AI SDK Implementation** (`mcp-server/src/custom-sdk/`)
   - `index.js` - Provider factory function
   - `language-model.js` - LanguageModelV1 implementation with **doGenerateObject support**
   - `message-converter.js` - Format conversion utilities
   - `json-extractor.js` - **NEW**: Robust JSON extraction from AI responses
   - `schema-converter.js` - **NEW**: Schema-to-instructions conversion utility
   - `errors.js` - Error handling and mapping

3. **Integration Points**
   - MCP Server registration (`mcp-server/src/index.js`)
   - AI Services integration (`scripts/modules/ai-services-unified.js`)
   - Model configuration (`scripts/modules/supported-models.json`)

### Session Flow

```
MCP Client Connect → MCP Server → registerRemoteProvider()
                                        ↓
                           MCPRemoteProvider (existing)
                           MCPProvider 
                                        ↓
                               Provider Registry
                                        ↓
                               AI Services Layer
                                        ↓
                        Text Generation + Object Generation
```

## Implementation Details

### Provider Registration

The MCP server registers **both** providers when a client connects:

```javascript
// mcp-server/src/index.js
registerRemoteProvider(session) {
  if (session?.clientCapabilities?.sampling) {
    // Register existing provider
    // Register unified MCP provider
    const mcpProvider = new MCPProvider();
    mcpProvider.setSession(session);
    
    const providerRegistry = ProviderRegistry.getInstance();
    providerRegistry.registerProvider('mcp', mcpProvider);
  }
}
```

### AI Services Integration

The AI services layer includes the new provider:

```javascript
// scripts/modules/ai-services-unified.js
const PROVIDERS = {
  // ... other providers
  'mcp': () => {
    const providerRegistry = ProviderRegistry.getInstance();
    return providerRegistry.getProvider('mcp');
  }
};
```

### Message Conversion

The provider converts between AI SDK and MCP formats:

```javascript
// AI SDK prompt → MCP sampling format
const { messages, systemPrompt } = convertToMCPFormat(options.prompt);

// MCP response → AI SDK format
const result = convertFromMCPFormat(response);
```

## Structured Object Generation (generateObject)

### Overview

The MCP Provider includes full support for structured object generation, enabling schema-driven features like PRD parsing, task creation, and any operations requiring validated JSON outputs.

### Architecture

The generateObject implementation includes:

1. **Schema-to-Instructions Conversion** (`schema-converter.js`)
   - Converts Zod schemas to natural language instructions
   - Generates example outputs to guide AI responses
   - Handles complex nested schemas and validation requirements

2. **JSON Extraction Pipeline** (`json-extractor.js`)
   - Multiple extraction strategies for robust JSON parsing
   - Handles code blocks, malformed JSON, and various response formats
   - Fallback mechanisms for maximum reliability

3. **Validation System**
   - Complete schema validation using Zod
   - Detailed error reporting for failed validations
   - Type-safe object generation

### Implementation Details

#### doGenerateObject Method

The `MCPLanguageModel` class implements the AI SDK's `doGenerateObject` method:

```javascript
async doGenerateObject({ schema, objectName, prompt, ...options }) {
  // Convert schema to instructions
  const instructions = convertSchemaToInstructions(schema, objectName);
  
  // Enhance prompt with structured output requirements
  const enhancedPrompt = enhancePromptForObjectGeneration(prompt, instructions);
  
  // Generate response via MCP sampling
  const response = await this.doGenerate({ prompt: enhancedPrompt, ...options });
  
  // Extract and validate JSON
  const extractedJson = extractJsonFromResponse(response.text);
  const validatedObject = schema.parse(extractedJson);
  
  return {
    object: validatedObject,
    usage: response.usage,
    finishReason: response.finishReason
  };
}
```

#### AI SDK Compatibility

The provider includes required properties for AI SDK object generation:

```javascript
class MCPLanguageModel {
  get defaultObjectGenerationMode() {
    return 'tool';
  }
  
  get supportsStructuredOutputs() {
    return true;
  }
  
  // ... doGenerateObject implementation
}
```

### Usage Examples

#### PRD Parsing

```javascript
import { z } from 'zod';

const taskSchema = z.object({
  title: z.string(),
  description: z.string(),
  priority: z.enum(['high', 'medium', 'low']),
  dependencies: z.array(z.number()).optional()
});

const result = await generateObject({
  model: mcpModel,
  schema: taskSchema,
  prompt: 'Parse this PRD section into a task: [PRD content]'
});

console.log(result.object); // Validated task object
```

#### Task Creation

```javascript
const taskCreationSchema = z.object({
  task: z.object({
    title: z.string(),
    description: z.string(),
    details: z.string(),
    testStrategy: z.string(),
    priority: z.enum(['high', 'medium', 'low']),
    dependencies: z.array(z.number()).optional()
  })
});

const result = await generateObject({
  model: mcpModel,
  schema: taskCreationSchema,
  prompt: 'Create a comprehensive task for implementing user authentication'
});
```

### Error Handling

The implementation provides comprehensive error handling:

- **Schema Validation Errors**: Detailed Zod validation messages
- **JSON Extraction Failures**: Fallback strategies and clear error reporting
- **MCP Communication Errors**: Proper error mapping and recovery
- **Timeout Handling**: Configurable timeouts for long-running operations

### Testing

The generateObject functionality is fully tested:

```bash
# Test object generation
npm test -- --grep "generateObject"

# Test with actual MCP session
node test-object-generation.js
```

### Supported Features

✅ **Schema Conversion**: Zod schemas → Natural language instructions  
✅ **JSON Extraction**: Multiple strategies for robust parsing  
✅ **Validation**: Complete schema validation with error reporting  
✅ **Error Recovery**: Fallback mechanisms for failed extractions  
✅ **Type Safety**: Full TypeScript support with inferred types  
✅ **AI SDK Compliance**: Complete LanguageModelV1 interface implementation  

## Usage

### Configuration

Add to supported models configuration:

```json
{
  "mcp": [
    {
      "id": "claude-3-5-sonnet-20241022",
      "swe_score": 0.623,
      "cost_per_1m_tokens": { "input": 0, "output": 0 },
      "allowed_roles": ["main", "fallback", "research"],
      "max_tokens": 200000
    }
  ]
}
```

### CLI Usage

```bash
# Set provider for main role
tm models set-main --provider mcp --model claude-3-5-sonnet-20241022

# Use in task operations
tm add-task "Create user authentication system"
```

### Programmatic Usage

```javascript
const provider = registry.getProvider('mcp');
if (provider && provider.hasValidSession()) {
  const client = provider.getClient({ temperature: 0.7 });
  const model = client({ modelId: 'claude-3-5-sonnet-20241022' });
  
  const result = await model.doGenerate({
    prompt: [
      { role: 'user', content: 'Hello!' }
    ]
  });
}
```

## Testing

### Component Tests

```bash
# Test individual components
node test-mcp-components.js
```

### Integration Testing

1. Start MCP server
2. Connect Claude client
3. Verify both providers are registered
4. Test AI operations through mcp provider

### Validation Checklist

- ✅ Provider creation and initialization
- ✅ Registry integration
- ✅ Session management
- ✅ Message conversion
- ✅ Error handling
- ✅ AI Services integration
- ✅ Model configuration

## Key Benefits

1. **AI SDK Compliance** - Full LanguageModelV1 implementation
2. **Session Integration** - Leverages existing MCP session infrastructure
3. **Registry Pattern** - Uses provider registry for discovery
4. **Backward Compatibility** - Coexists with existing MCPRemoteProvider
5. **Future Ready** - Supports AI SDK features and patterns

## Troubleshooting

### Provider Not Found

```
Error: Provider "mcp" not found in registry
```

**Solution**: Ensure MCP server is running and client is connected

### Session Errors

```
Error: MCP Provider requires active MCP session
```

**Solution**: Check MCP client connection and session capabilities

### Sampling Errors

```
Error: MCP session must have client sampling capabilities
```

**Solution**: Verify MCP client supports sampling operations

## Next Steps

1. **Performance Optimization** - Add caching and connection pooling
2. **Enhanced Streaming** - Implement native streaming if MCP supports it
3. **Tool Integration** - Add support for function calling through MCP tools
4. **Monitoring** - Add metrics and logging for provider usage
5. **Documentation** - Update user guides and API documentation

```

--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-vscode.test.js:
--------------------------------------------------------------------------------

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

// Mock fs module before importing anything that uses it
jest.mock('fs', () => ({
	readFileSync: jest.fn(),
	writeFileSync: jest.fn(),
	existsSync: jest.fn(),
	mkdirSync: jest.fn()
}));

// Import modules after mocking
import fs from 'fs';
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
import { vscodeProfile } from '../../../src/profiles/vscode.js';

describe('VS Code Rule Transformer', () => {
	// Set up spies on the mocked modules
	const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
	const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
	const mockExistsSync = jest.spyOn(fs, 'existsSync');
	const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
	const mockConsoleError = jest
		.spyOn(console, 'error')
		.mockImplementation(() => {});

	beforeEach(() => {
		jest.clearAllMocks();
		// Setup default mocks
		mockReadFileSync.mockReturnValue('');
		mockWriteFileSync.mockImplementation(() => {});
		mockExistsSync.mockReturnValue(true);
		mockMkdirSync.mockImplementation(() => {});
	});

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

	it('should correctly convert basic terms', () => {
		const testContent = `---
description: Test Cursor rule for basic terms
globs: **/*
alwaysApply: true
---

This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
Also has references to .mdc files and cursor rules.`;

		// Mock file read to return our test content
		mockReadFileSync.mockReturnValue(testContent);

		// Call the actual function
		const result = convertRuleToProfileRule(
			'source.mdc',
			'target.md',
			vscodeProfile
		);

		// Verify the function succeeded
		expect(result).toBe(true);

		// Verify file operations were called correctly
		expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8');
		expect(mockWriteFileSync).toHaveBeenCalledTimes(1);

		// Get the transformed content that was written
		const writeCall = mockWriteFileSync.mock.calls[0];
		const transformedContent = writeCall[1];

		// Verify transformations
		expect(transformedContent).toContain('VS Code');
		expect(transformedContent).toContain('code.visualstudio.com');
		expect(transformedContent).toContain('.md');
		expect(transformedContent).toContain('vscode rules'); // "cursor rules" -> "vscode rules"
		expect(transformedContent).toContain('applyTo: "**/*"'); // globs -> applyTo transformation
		expect(transformedContent).not.toContain('cursor.so');
		expect(transformedContent).not.toContain('Cursor rule');
		expect(transformedContent).not.toContain('globs:');
		expect(transformedContent).not.toContain('alwaysApply:');
	});

	it('should correctly convert tool references', () => {
		const testContent = `---
description: Test Cursor rule for tool references
globs: **/*
alwaysApply: true
---

- Use the search tool to find code
- The edit_file tool lets you modify files
- run_command executes terminal commands
- use_mcp connects to external services`;

		// Mock file read to return our test content
		mockReadFileSync.mockReturnValue(testContent);

		// Call the actual function
		const result = convertRuleToProfileRule(
			'source.mdc',
			'target.md',
			vscodeProfile
		);

		// Verify the function succeeded
		expect(result).toBe(true);

		// Get the transformed content that was written
		const writeCall = mockWriteFileSync.mock.calls[0];
		const transformedContent = writeCall[1];

		// Verify transformations (VS Code uses standard tool names, so no transformation)
		expect(transformedContent).toContain('search tool');
		expect(transformedContent).toContain('edit_file tool');
		expect(transformedContent).toContain('run_command');
		expect(transformedContent).toContain('use_mcp');
		expect(transformedContent).toContain('applyTo: "**/*"'); // globs -> applyTo transformation
	});

	it('should correctly update file references and directory paths', () => {
		const testContent = `---
description: Test Cursor rule for file references
globs: .cursor/rules/*.md
alwaysApply: true
---

This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and 
[taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).
Files are in the .cursor/rules directory and we should reference the rules directory.`;

		// Mock file read to return our test content
		mockReadFileSync.mockReturnValue(testContent);

		// Call the actual function
		const result = convertRuleToProfileRule(
			'source.mdc',
			'target.instructions.md',
			vscodeProfile
		);

		// Verify the function succeeded
		expect(result).toBe(true);

		// Get the transformed content that was written
		const writeCall = mockWriteFileSync.mock.calls[0];
		const transformedContent = writeCall[1];

		// Verify transformations specific to VS Code
		expect(transformedContent).toContain(
			'applyTo: ".github/instructions/*.md"'
		); // globs -> applyTo with path transformation
		expect(transformedContent).toContain(
			'(.github/instructions/dev_workflow.instructions.md)'
		); // File path transformation - no taskmaster subdirectory for VS Code
		expect(transformedContent).toContain(
			'(.github/instructions/taskmaster.instructions.md)'
		); // File path transformation - no taskmaster subdirectory for VS Code
		expect(transformedContent).toContain('instructions directory'); // "rules directory" -> "instructions directory"
		expect(transformedContent).not.toContain('(mdc:.cursor/rules/');
		expect(transformedContent).not.toContain('.cursor/rules');
		expect(transformedContent).not.toContain('globs:');
		expect(transformedContent).not.toContain('rules directory');
	});

	it('should transform globs to applyTo with various patterns', () => {
		const testContent = `---
description: Test VS Code applyTo transformation
globs: .cursor/rules/*.md
alwaysApply: true
---

Another section:
globs: **/*.ts
final: true

Last one:
globs: src/**/*
---`;

		// Mock file read to return our test content
		mockReadFileSync.mockReturnValue(testContent);

		// Call the actual function
		const result = convertRuleToProfileRule(
			'source.mdc',
			'target.md',
			vscodeProfile
		);

		// Verify the function succeeded
		expect(result).toBe(true);

		// Get the transformed content that was written
		const writeCall = mockWriteFileSync.mock.calls[0];
		const transformedContent = writeCall[1];

		// Verify all globs transformations
		expect(transformedContent).toContain(
			'applyTo: ".github/instructions/*.md"'
		); // Path transformation applied
		expect(transformedContent).toContain('applyTo: "**/*.ts"'); // Pattern with quotes
		expect(transformedContent).toContain('applyTo: "src/**/*"'); // Complex pattern with quotes
		expect(transformedContent).not.toContain('globs:'); // No globs should remain
	});

	it('should handle VS Code MCP configuration paths correctly', () => {
		const testContent = `---
description: Test MCP configuration paths
globs: **/*
alwaysApply: true
---

MCP configuration is at .cursor/mcp.json for Cursor.
The .cursor/rules directory contains rules.
Update your .cursor/mcp.json file accordingly.`;

		// Mock file read to return our test content
		mockReadFileSync.mockReturnValue(testContent);

		// Call the actual function
		const result = convertRuleToProfileRule(
			'source.mdc',
			'target.md',
			vscodeProfile
		);

		// Verify the function succeeded
		expect(result).toBe(true);

		// Get the transformed content that was written
		const writeCall = mockWriteFileSync.mock.calls[0];
		const transformedContent = writeCall[1];

		// Verify MCP paths are correctly transformed
		expect(transformedContent).toContain('.vscode/mcp.json'); // MCP config in .vscode
		expect(transformedContent).toContain('.github/instructions'); // Rules/instructions in .github/instructions
		expect(transformedContent).not.toContain('.cursor/mcp.json');
		expect(transformedContent).not.toContain('.cursor/rules');
	});

	it('should handle file read errors', () => {
		// Mock file read to throw an error
		mockReadFileSync.mockImplementation(() => {
			throw new Error('File not found');
		});

		// Call the actual function
		const result = convertRuleToProfileRule(
			'nonexistent.mdc',
			'target.md',
			vscodeProfile
		);

		// Verify the function failed gracefully
		expect(result).toBe(false);

		// Verify writeFileSync was not called
		expect(mockWriteFileSync).not.toHaveBeenCalled();

		// Verify error was logged
		expect(mockConsoleError).toHaveBeenCalledWith(
			'Error converting rule file: File not found'
		);
	});

	it('should handle file write errors', () => {
		const testContent = 'test content';
		mockReadFileSync.mockReturnValue(testContent);

		// Mock file write to throw an error
		mockWriteFileSync.mockImplementation(() => {
			throw new Error('Permission denied');
		});

		// Call the actual function
		const result = convertRuleToProfileRule(
			'source.mdc',
			'target.md',
			vscodeProfile
		);

		// Verify the function failed gracefully
		expect(result).toBe(false);

		// Verify error was logged
		expect(mockConsoleError).toHaveBeenCalledWith(
			'Error converting rule file: Permission denied'
		);
	});

	it('should create target directory if it does not exist', () => {
		const testContent = 'test content';
		mockReadFileSync.mockReturnValue(testContent);

		// Mock directory doesn't exist initially
		mockExistsSync.mockReturnValue(false);

		// Call the actual function
		convertRuleToProfileRule(
			'source.mdc',
			'.github/instructions/deep/path/target.md',
			vscodeProfile
		);

		// Verify directory creation was called
		expect(mockMkdirSync).toHaveBeenCalledWith(
			'.github/instructions/deep/path',
			{
				recursive: true
			}
		);
	});
});

```

--------------------------------------------------------------------------------
/apps/cli/src/commands/briefs.command.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Briefs Command - Friendly alias for tag management in API storage
 * Provides brief-specific commands that only work with API storage
 */

import {
	type LogLevel,
	type TagInfo,
	tryAddTagViaRemote,
	tryListTagsViaRemote
} from '@tm/bridge';
import type { TmCore } from '@tm/core';
import { AuthManager, createTmCore } from '@tm/core';
import { Command } from 'commander';
import { checkAuthentication } from '../utils/auth-helpers.js';
import {
	selectBriefFromInput,
	selectBriefInteractive
} from '../utils/brief-selection.js';
import * as ui from '../utils/ui.js';

/**
 * Result type from briefs command
 */
export interface BriefsResult {
	success: boolean;
	action: 'list' | 'select' | 'create';
	briefs?: TagInfo[];
	currentBrief?: string | null;
	message?: string;
}

/**
 * BriefsCommand - Manage briefs for API storage (friendly alias)
 * Only works when using API storage (tryhamster.com)
 */
export class BriefsCommand extends Command {
	private tmCore?: TmCore;
	private authManager: AuthManager;
	private lastResult?: BriefsResult;

	constructor(name?: string) {
		super(name || 'briefs');

		// Initialize auth manager
		this.authManager = AuthManager.getInstance();

		// Configure the command
		this.description('Manage briefs (API storage only)');
		this.alias('brief');

		// Add subcommands
		this.addListCommand();
		this.addSelectCommand();
		this.addCreateCommand();

		// Accept optional positional argument for brief URL/ID
		this.argument('[briefOrUrl]', 'Brief ID or Hamster brief URL');

		// Default action: if argument provided, select brief; else list briefs
		this.action(async (briefOrUrl?: string) => {
			if (briefOrUrl && briefOrUrl.trim().length > 0) {
				await this.executeSelectFromUrl(briefOrUrl.trim());
				return;
			}
			await this.executeList();
		});
	}

	/**
	 * Check if user is authenticated (required for briefs)
	 */
	private async checkAuth(): Promise<boolean> {
		return checkAuthentication(this.authManager, {
			message:
				'The "briefs" command requires you to be logged in to your Hamster account.',
			footer:
				'Working locally instead?\n' +
				'  → Use "task-master tags" for local tag management.',
			authCommand: 'task-master auth login'
		});
	}

	/**
	 * Add list subcommand
	 */
	private addListCommand(): void {
		this.command('list')
			.description('List all briefs (default action)')
			.option('--show-metadata', 'Show additional brief metadata')
			.addHelpText(
				'after',
				`
Examples:
  $ tm briefs            # List all briefs (default)
  $ tm briefs list       # List all briefs (explicit)
  $ tm briefs list --show-metadata  # List with metadata

Note: This command only works with API storage (tryhamster.com).
`
			)
			.action(async (options) => {
				await this.executeList(options);
			});
	}

	/**
	 * Add select subcommand
	 */
	private addSelectCommand(): void {
		this.command('select')
			.description('Select a brief to work with')
			.argument(
				'[briefOrUrl]',
				'Brief ID or Hamster URL (optional, interactive if omitted)'
			)
			.addHelpText(
				'after',
				`
Examples:
  $ tm brief select                                    # Interactive selection
  $ tm brief select abc12345                           # Select by ID
  $ tm brief select https://app.tryhamster.com/...     # Select by URL

Shortcuts:
  $ tm brief <brief-url>                               # Same as "select"
  $ tm brief                                           # List all briefs

Note: Works exactly like "tm context brief" - reuses the same interactive interface.
`
			)
			.action(async (briefOrUrl) => {
				await this.executeSelect(briefOrUrl);
			});
	}

	/**
	 * Add create subcommand
	 */
	private addCreateCommand(): void {
		this.command('create')
			.description('Create a new brief (redirects to web UI)')
			.argument('[name]', 'Brief name (optional)')
			.addHelpText(
				'after',
				`
Examples:
  $ tm briefs create              # Redirect to web UI to create brief
  $ tm briefs create my-new-brief # Redirect with suggested name

Note: Briefs must be created through the Hamster Studio web interface.
`
			)
			.action(async (name) => {
				await this.executeCreate(name);
			});
	}

	/**
	 * Initialize TmCore if not already initialized
	 */
	private async initTmCore(): Promise<void> {
		if (!this.tmCore) {
			this.tmCore = await createTmCore({
				projectPath: process.cwd()
			});
		}
	}

	/**
	 * Execute list briefs
	 */
	private async executeList(options?: {
		showMetadata?: boolean;
	}): Promise<void> {
		try {
			// Check authentication
			if (!(await this.checkAuth())) {
				process.exit(1);
			}

			// Use the bridge to list briefs
			const remoteResult = await tryListTagsViaRemote({
				projectRoot: process.cwd(),
				showMetadata: options?.showMetadata || false,
				report: (level: LogLevel, ...args: unknown[]) => {
					const message = args[0] as string;
					if (level === 'error') ui.displayError(message);
					else if (level === 'warn') ui.displayWarning(message);
					else if (level === 'info') ui.displayInfo(message);
				}
			});

			if (!remoteResult) {
				throw new Error('Failed to fetch briefs from API');
			}

			this.setLastResult({
				success: remoteResult.success,
				action: 'list',
				briefs: remoteResult.tags,
				currentBrief: remoteResult.currentTag,
				message: remoteResult.message
			});
		} catch (error) {
			ui.displayError(`Failed to list briefs: ${(error as Error).message}`);
			this.setLastResult({
				success: false,
				action: 'list',
				message: (error as Error).message
			});
			process.exit(1);
		}
	}

	/**
	 * Execute select brief interactively or by name/ID
	 */
	private async executeSelect(nameOrId?: string): Promise<void> {
		try {
			// Check authentication
			const hasSession = await this.authManager.hasValidSession();
			if (!hasSession) {
				ui.displayError('Not authenticated. Run "tm auth login" first.');
				process.exit(1);
			}

			// If name/ID provided, treat it as URL/ID selection
			if (nameOrId && nameOrId.trim().length > 0) {
				await this.executeSelectFromUrl(nameOrId.trim());
				return;
			}

			// Check if org is selected for interactive selection
			const context = this.authManager.getContext();
			if (!context?.orgId) {
				ui.displayErrorBox(
					'No organization selected. Run "tm context org" first.'
				);
				process.exit(1);
			}

			// Use shared utility for interactive selection
			const result = await selectBriefInteractive(
				this.authManager,
				context.orgId
			);

			this.setLastResult({
				success: result.success,
				action: 'select',
				currentBrief: result.briefId,
				message: result.message
			});

			if (!result.success) {
				process.exit(1);
			}
		} catch (error) {
			ui.displayErrorBox(`Failed to select brief: ${(error as Error).message}`);
			this.setLastResult({
				success: false,
				action: 'select',
				message: (error as Error).message
			});
			process.exit(1);
		}
	}

	/**
	 * Execute select brief from any input (URL, ID, or name)
	 * All parsing logic is in tm-core
	 */
	private async executeSelectFromUrl(input: string): Promise<void> {
		try {
			// Check authentication
			const hasSession = await this.authManager.hasValidSession();
			if (!hasSession) {
				ui.displayError('Not authenticated. Run "tm auth login" first.');
				process.exit(1);
			}

			// Initialize tmCore to access business logic
			await this.initTmCore();

			// Use shared utility - tm-core handles ALL parsing
			const result = await selectBriefFromInput(
				this.authManager,
				input,
				this.tmCore
			);

			this.setLastResult({
				success: result.success,
				action: 'select',
				currentBrief: result.briefId,
				message: result.message
			});

			if (!result.success) {
				process.exit(1);
			}
		} catch (error) {
			ui.displayErrorBox(`Failed to select brief: ${(error as Error).message}`);
			this.setLastResult({
				success: false,
				action: 'select',
				message: (error as Error).message
			});
			process.exit(1);
		}
	}

	/**
	 * Execute create brief (redirect to web UI)
	 */
	private async executeCreate(name?: string): Promise<void> {
		try {
			// Check authentication
			if (!(await this.checkAuth())) {
				process.exit(1);
			}

			// Use the bridge to redirect to web UI
			const remoteResult = await tryAddTagViaRemote({
				tagName: name || 'new-brief',
				projectRoot: process.cwd(),
				report: (level: LogLevel, ...args: unknown[]) => {
					const message = args[0] as string;
					if (level === 'error') ui.displayError(message);
					else if (level === 'warn') ui.displayWarning(message);
					else if (level === 'info') ui.displayInfo(message);
				}
			});

			if (!remoteResult) {
				throw new Error('Failed to get brief creation URL');
			}

			this.setLastResult({
				success: remoteResult.success,
				action: 'create',
				message: remoteResult.message
			});

			if (!remoteResult.success) {
				process.exit(1);
			}
		} catch (error) {
			ui.displayErrorBox(`Failed to create brief: ${(error as Error).message}`);
			this.setLastResult({
				success: false,
				action: 'create',
				message: (error as Error).message
			});
			process.exit(1);
		}
	}

	/**
	 * Set the last result for programmatic access
	 */
	private setLastResult(result: BriefsResult): void {
		this.lastResult = result;
	}

	/**
	 * Get the last result (for programmatic usage)
	 */
	getLastResult(): BriefsResult | undefined {
		return this.lastResult;
	}

	/**
	 * Register this command on an existing program
	 */
	static register(program: Command, name?: string): BriefsCommand {
		const briefsCommand = new BriefsCommand(name);
		program.addCommand(briefsCommand);
		return briefsCommand;
	}
}

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/tasks/services/preflight-checker.service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Preflight Checker Service
 * Validates environment and prerequisites for autopilot execution
 */

import { readFileSync, existsSync, readdirSync } from 'node:fs';
import { join } from 'path';
import { execSync } from 'child_process';
import { getLogger } from '../../../common/logger/factory.js';
import {
	isGitRepository,
	isGhCliAvailable,
	getDefaultBranch
} from '../../../common/utils/git-utils.js';

const logger = getLogger('PreflightChecker');

/**
 * Result of a single preflight check
 */
export interface CheckResult {
	/** Whether the check passed */
	success: boolean;
	/** The value detected/validated */
	value?: any;
	/** Error or warning message */
	message?: string;
}

/**
 * Complete preflight validation results
 */
export interface PreflightResult {
	/** Overall success - all checks passed */
	success: boolean;
	/** Test command detection result */
	testCommand: CheckResult;
	/** Git working tree status */
	gitWorkingTree: CheckResult;
	/** Required tools availability */
	requiredTools: CheckResult;
	/** Default branch detection */
	defaultBranch: CheckResult;
	/** Summary message */
	summary: string;
}

/**
 * Tool validation result
 */
interface ToolCheck {
	name: string;
	available: boolean;
	version?: string;
	message?: string;
}

/**
 * PreflightChecker validates environment for autopilot execution
 */
export class PreflightChecker {
	private projectRoot: string;

	constructor(projectRoot: string) {
		if (!projectRoot) {
			throw new Error('projectRoot is required for PreflightChecker');
		}
		this.projectRoot = projectRoot;
	}

	/**
	 * Detect test command from package.json
	 */
	async detectTestCommand(): Promise<CheckResult> {
		try {
			const packageJsonPath = join(this.projectRoot, 'package.json');
			const packageJsonContent = readFileSync(packageJsonPath, 'utf-8');
			const packageJson = JSON.parse(packageJsonContent);

			if (!packageJson.scripts || !packageJson.scripts.test) {
				return {
					success: false,
					message:
						'No test script found in package.json. Please add a "test" script.'
				};
			}

			const testCommand = packageJson.scripts.test;

			return {
				success: true,
				value: testCommand,
				message: `Test command: ${testCommand}`
			};
		} catch (error: any) {
			if (error.code === 'ENOENT') {
				return {
					success: false,
					message: 'package.json not found in project root'
				};
			}

			return {
				success: false,
				message: `Failed to read package.json: ${error.message}`
			};
		}
	}

	/**
	 * Check git working tree status
	 */
	async checkGitWorkingTree(): Promise<CheckResult> {
		try {
			// Check if it's a git repository
			const isRepo = await isGitRepository(this.projectRoot);
			if (!isRepo) {
				return {
					success: false,
					message: 'Not a git repository. Initialize git first.'
				};
			}

			// Check for changes (staged/unstaged/untracked) without requiring HEAD
			const status = execSync('git status --porcelain', {
				cwd: this.projectRoot,
				encoding: 'utf-8',
				timeout: 5000
			});
			if (status.trim().length > 0) {
				return {
					success: false,
					value: 'dirty',
					message:
						'Working tree has uncommitted or untracked changes. Please commit or stash them.'
				};
			}
			return {
				success: true,
				value: 'clean',
				message: 'Working tree is clean'
			};
		} catch (error: any) {
			return {
				success: false,
				message: `Git check failed: ${error.message}`
			};
		}
	}

	/**
	 * Detect project types based on common configuration files
	 */
	private detectProjectTypes(): string[] {
		const types: string[] = [];

		if (existsSync(join(this.projectRoot, 'package.json'))) types.push('node');
		if (
			existsSync(join(this.projectRoot, 'requirements.txt')) ||
			existsSync(join(this.projectRoot, 'setup.py')) ||
			existsSync(join(this.projectRoot, 'pyproject.toml'))
		)
			types.push('python');
		if (
			existsSync(join(this.projectRoot, 'pom.xml')) ||
			existsSync(join(this.projectRoot, 'build.gradle'))
		)
			types.push('java');
		if (existsSync(join(this.projectRoot, 'go.mod'))) types.push('go');
		if (existsSync(join(this.projectRoot, 'Cargo.toml'))) types.push('rust');
		if (existsSync(join(this.projectRoot, 'composer.json'))) types.push('php');
		if (existsSync(join(this.projectRoot, 'Gemfile'))) types.push('ruby');
		const files = readdirSync(this.projectRoot);
		if (files.some((f) => f.endsWith('.csproj') || f.endsWith('.sln')))
			types.push('dotnet');

		return types;
	}

	/**
	 * Get required tools for a project type
	 */
	private getToolsForProjectType(
		type: string
	): Array<{ command: string; args: string[] }> {
		const toolMap: Record<
			string,
			Array<{ command: string; args: string[] }>
		> = {
			node: [
				{ command: 'node', args: ['--version'] },
				{ command: 'npm', args: ['--version'] }
			],
			python: [
				{ command: 'python3', args: ['--version'] },
				{ command: 'pip3', args: ['--version'] }
			],
			java: [{ command: 'java', args: ['--version'] }],
			go: [{ command: 'go', args: ['version'] }],
			rust: [{ command: 'cargo', args: ['--version'] }],
			php: [
				{ command: 'php', args: ['--version'] },
				{ command: 'composer', args: ['--version'] }
			],
			ruby: [
				{ command: 'ruby', args: ['--version'] },
				{ command: 'bundle', args: ['--version'] }
			],
			dotnet: [{ command: 'dotnet', args: ['--version'] }]
		};

		return toolMap[type] || [];
	}

	/**
	 * Validate required tools availability
	 */
	async validateRequiredTools(): Promise<CheckResult> {
		const tools: ToolCheck[] = [];

		// Always check git and gh CLI
		tools.push(this.checkTool('git', ['--version']));
		tools.push(await this.checkGhCli());

		// Detect project types and check their tools
		const projectTypes = this.detectProjectTypes();

		if (projectTypes.length === 0) {
			logger.warn('No recognized project type detected');
		} else {
			logger.info(`Detected project types: ${projectTypes.join(', ')}`);
		}

		for (const type of projectTypes) {
			const typeTools = this.getToolsForProjectType(type);
			for (const tool of typeTools) {
				tools.push(this.checkTool(tool.command, tool.args));
			}
		}

		// Determine overall success
		const allAvailable = tools.every((tool) => tool.available);
		const missingTools = tools
			.filter((tool) => !tool.available)
			.map((tool) => tool.name);

		if (!allAvailable) {
			return {
				success: false,
				value: tools,
				message: `Missing required tools: ${missingTools.join(', ')}`
			};
		}

		return {
			success: true,
			value: tools,
			message: 'All required tools are available'
		};
	}

	/**
	 * Check if a command-line tool is available
	 */
	private checkTool(command: string, versionArgs: string[]): ToolCheck {
		try {
			const version = execSync(`${command} ${versionArgs.join(' ')}`, {
				cwd: this.projectRoot,
				encoding: 'utf-8',
				stdio: 'pipe',
				timeout: 5000
			})
				.trim()
				.split('\n')[0];

			return {
				name: command,
				available: true,
				version,
				message: `${command} ${version}`
			};
		} catch (error) {
			return {
				name: command,
				available: false,
				message: `${command} not found`
			};
		}
	}

	/**
	 * Check GitHub CLI installation and authentication status
	 */
	private async checkGhCli(): Promise<ToolCheck> {
		try {
			const version = execSync('gh --version', {
				cwd: this.projectRoot,
				encoding: 'utf-8',
				stdio: 'pipe',
				timeout: 5000
			})
				.trim()
				.split('\n')[0];
			const authed = await isGhCliAvailable(this.projectRoot);
			return {
				name: 'gh',
				available: true,
				version,
				message: authed
					? 'GitHub CLI installed (authenticated)'
					: 'GitHub CLI installed (not authenticated)'
			};
		} catch {
			return { name: 'gh', available: false, message: 'GitHub CLI not found' };
		}
	}

	/**
	 * Detect default branch
	 */
	async detectDefaultBranch(): Promise<CheckResult> {
		try {
			const defaultBranch = await getDefaultBranch(this.projectRoot);

			if (!defaultBranch) {
				return {
					success: false,
					message:
						'Could not determine default branch. Make sure remote is configured.'
				};
			}

			return {
				success: true,
				value: defaultBranch,
				message: `Default branch: ${defaultBranch}`
			};
		} catch (error: any) {
			return {
				success: false,
				message: `Failed to detect default branch: ${error.message}`
			};
		}
	}

	/**
	 * Run all preflight checks
	 */
	async runAllChecks(): Promise<PreflightResult> {
		logger.info('Running preflight checks...');

		const testCommand = await this.detectTestCommand();
		const gitWorkingTree = await this.checkGitWorkingTree();
		const requiredTools = await this.validateRequiredTools();
		const defaultBranch = await this.detectDefaultBranch();

		const allSuccess =
			testCommand.success &&
			gitWorkingTree.success &&
			requiredTools.success &&
			defaultBranch.success;

		// Build summary
		const passed: string[] = [];
		const failed: string[] = [];

		if (testCommand.success) passed.push('Test command');
		else failed.push('Test command');

		if (gitWorkingTree.success) passed.push('Git working tree');
		else failed.push('Git working tree');

		if (requiredTools.success) passed.push('Required tools');
		else failed.push('Required tools');

		if (defaultBranch.success) passed.push('Default branch');
		else failed.push('Default branch');

		const total = passed.length + failed.length;
		const summary = allSuccess
			? `All preflight checks passed (${passed.length}/${total})`
			: `Preflight checks failed: ${failed.join(', ')} (${passed.length}/${total} passed)`;

		logger.info(summary);

		return {
			success: allSuccess,
			testCommand,
			gitWorkingTree,
			requiredTools,
			defaultBranch,
			summary
		};
	}
}

```

--------------------------------------------------------------------------------
/docs/examples/codex-cli-usage.md:
--------------------------------------------------------------------------------

```markdown
# Codex CLI Provider Usage Examples

This guide provides practical examples of using Task Master with the Codex CLI provider.

## Prerequisites

Before using these examples, ensure you have:

```bash
# 1. Codex CLI installed
npm install -g @openai/codex

# 2. Authenticated with ChatGPT
codex login

# 3. Codex CLI configured as your provider
task-master models --set-main gpt-5-codex --codex-cli
```

## Example 1: Basic Task Creation

Use Codex CLI to create tasks from a simple description:

```bash
# Add a task with AI-powered enhancement
task-master add-task --prompt="Implement user authentication with JWT" --research
```

**What happens**:
1. Task Master sends your prompt to GPT-5-Codex via the CLI
2. The AI analyzes your request and generates a detailed task
3. The task is added to your `.taskmaster/tasks/tasks.json`
4. OAuth credentials are automatically used (no API key needed)

## Example 2: Parsing a Product Requirements Document

Create a comprehensive task list from a PRD:

```bash
# Create your PRD
cat > my-feature.txt <<EOF
# User Profile Feature

## Requirements
1. Users can view their profile
2. Users can edit their information
3. Profile pictures can be uploaded
4. Email verification required

## Technical Constraints
- Use React for frontend
- Node.js/Express backend
- PostgreSQL database
EOF

# Parse with Codex CLI
task-master parse-prd my-feature.txt --num-tasks 12
```

**What happens**:
1. GPT-5-Codex reads and analyzes your PRD
2. Generates structured tasks with dependencies
3. Creates subtasks for complex items
4. Saves everything to `.taskmaster/tasks/`

## Example 3: Expanding Tasks with Research

Break down a complex task into detailed subtasks:

```bash
# First, show your current tasks
task-master list

# Expand a specific task (e.g., task 1.2)
task-master expand --id=1.2 --research --force
```

**What happens**:
1. Codex CLI uses GPT-5 for research-level analysis
2. Breaks down the task into logical subtasks
3. Adds implementation details and test strategies
4. Updates the task with dependency information

## Example 4: Analyzing Project Complexity

Get AI-powered insights into your project's task complexity:

```bash
# Analyze all tasks
task-master analyze-complexity --research

# View the complexity report
task-master complexity-report
```

**What happens**:
1. GPT-5 analyzes each task's scope and requirements
2. Assigns complexity scores and estimates subtask counts
3. Generates a detailed report
4. Saves to `.taskmaster/reports/task-complexity-report.json`

## Example 5: Using Custom Codex CLI Settings

Configure Codex CLI behavior for different commands:

```json
// In .taskmaster/config.json
{
  "models": {
    "main": {
      "provider": "codex-cli",
      "modelId": "gpt-5-codex",
      "maxTokens": 128000,
      "temperature": 0.2
    }
  },
  "codexCli": {
    "allowNpx": true,
    "approvalMode": "on-failure",
    "sandboxMode": "workspace-write",
    "commandSpecific": {
      "parse-prd": {
        "verbose": true,
        "approvalMode": "never"
      },
      "expand": {
        "sandboxMode": "read-only",
        "verbose": true
      }
    }
  }
}
```

```bash
# Now parse-prd runs with verbose output and no approvals
task-master parse-prd requirements.txt

# Expand runs with read-only mode
task-master expand --id=2.1
```

## Example 6: Workflow - Building a Feature End-to-End

Complete workflow from PRD to implementation tracking:

```bash
# Step 1: Initialize project
task-master init

# Step 2: Set up Codex CLI
task-master models --set-main gpt-5-codex --codex-cli
task-master models --set-fallback gpt-5 --codex-cli

# Step 3: Create PRD
cat > feature-prd.txt <<EOF
# Authentication System

Implement a complete authentication system with:
- User registration
- Email verification
- Password reset
- Two-factor authentication
- Session management
EOF

# Step 4: Parse PRD into tasks
task-master parse-prd feature-prd.txt --num-tasks 8

# Step 5: Analyze complexity
task-master analyze-complexity --research

# Step 6: Expand complex tasks
task-master expand --all --research

# Step 7: Start working
task-master next
# Shows: Task 1.1: User registration database schema

# Step 8: Mark completed as you work
task-master set-status --id=1.1 --status=done

# Step 9: Continue to next task
task-master next
```

## Example 7: Multi-Role Configuration

Use Codex CLI for main tasks, Perplexity for research:

```json
// In .taskmaster/config.json
{
  "models": {
    "main": {
      "provider": "codex-cli",
      "modelId": "gpt-5-codex",
      "maxTokens": 128000,
      "temperature": 0.2
    },
    "research": {
      "provider": "perplexity",
      "modelId": "sonar-pro",
      "maxTokens": 8700,
      "temperature": 0.1
    },
    "fallback": {
      "provider": "codex-cli",
      "modelId": "gpt-5",
      "maxTokens": 128000,
      "temperature": 0.2
    }
  }
}
```

```bash
# Main task operations use GPT-5-Codex
task-master add-task --prompt="Build REST API endpoint"

# Research operations use Perplexity
task-master analyze-complexity --research

# Fallback to GPT-5 if needed
task-master expand --id=3.2 --force
```

## Example 8: Troubleshooting Common Issues

### Issue: Codex CLI not found

```bash
# Check if Codex is installed
codex --version

# If not found, install globally
npm install -g @openai/codex

# Or enable npx fallback in config
cat >> .taskmaster/config.json <<EOF
{
  "codexCli": {
    "allowNpx": true
  }
}
EOF
```

### Issue: Not authenticated

```bash
# Check auth status
codex
# Use /about command to see auth info

# Re-authenticate if needed
codex login
```

### Issue: Want more verbose output

```bash
# Enable verbose mode in config
cat >> .taskmaster/config.json <<EOF
{
  "codexCli": {
    "verbose": true
  }
}
EOF

# Or for specific commands
task-master parse-prd my-prd.txt
# (verbose output shows detailed Codex CLI interactions)
```

## Example 9: CI/CD Integration

Use Codex CLI in automated workflows:

```yaml
# .github/workflows/task-analysis.yml
name: Analyze Task Complexity

on:
  push:
    paths:
      - '.taskmaster/**'

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Task Master
        run: npm install -g task-master-ai

      - name: Configure Codex CLI
        run: |
          npm install -g @openai/codex
          echo "${{ secrets.OPENAI_CODEX_API_KEY }}" > ~/.codex-auth
        env:
          OPENAI_CODEX_API_KEY: ${{ secrets.OPENAI_CODEX_API_KEY }}

      - name: Configure Task Master
        run: |
          cat > .taskmaster/config.json <<EOF
          {
            "models": {
              "main": {
                "provider": "codex-cli",
                "modelId": "gpt-5"
              }
            },
            "codexCli": {
              "allowNpx": true,
              "skipGitRepoCheck": true,
              "approvalMode": "never",
              "fullAuto": true
            }
          }
          EOF

      - name: Analyze Complexity
        run: task-master analyze-complexity --research

      - name: Upload Report
        uses: actions/upload-artifact@v3
        with:
          name: complexity-report
          path: .taskmaster/reports/task-complexity-report.json
```

## Best Practices

### 1. Use OAuth for Development

```bash
# For local development, use OAuth (no API key needed)
codex login
task-master models --set-main gpt-5-codex --codex-cli
```

### 2. Configure Approval Modes Appropriately

```json
{
  "codexCli": {
    "approvalMode": "on-failure",  // Safe default
    "sandboxMode": "workspace-write"  // Restricts to project directory
  }
}
```

### 3. Use Command-Specific Settings

```json
{
  "codexCli": {
    "commandSpecific": {
      "parse-prd": {
        "approvalMode": "never",  // PRD parsing is safe
        "verbose": true
      },
      "expand": {
        "approvalMode": "on-request",  // More cautious for task expansion
        "verbose": false
      }
    }
  }
}
```

### 4. Leverage Codebase Analysis

```json
{
  "global": {
    "enableCodebaseAnalysis": true  // Let Codex analyze your code
  }
}
```

### 5. Handle Errors Gracefully

```bash
# Always configure a fallback model
task-master models --set-fallback gpt-5 --codex-cli

# Or use a different provider as fallback
task-master models --set-fallback claude-3-5-sonnet
```

## Next Steps

- Read the [Codex CLI Provider Documentation](../providers/codex-cli.md)
- Explore [Configuration Options](../configuration.md#codex-cli-provider)
- Check out [Command Reference](../command-reference.md)
- Learn about [Task Structure](../task-structure.md)

## Common Patterns

### Pattern: Daily Development Workflow

```bash
# Morning: Review tasks
task-master list

# Get next task
task-master next

# Work on task...

# Update task with notes
task-master update-subtask --id=2.3 --prompt="Implemented authentication middleware"

# Mark complete
task-master set-status --id=2.3 --status=done

# Repeat
```

### Pattern: Feature Planning

```bash
# Write feature spec
vim new-feature.txt

# Generate tasks
task-master parse-prd new-feature.txt --num-tasks 10

# Analyze and expand
task-master analyze-complexity --research
task-master expand --all --research --force

# Review and adjust
task-master list
```

### Pattern: Sprint Planning

```bash
# Parse sprint requirements
task-master parse-prd sprint-requirements.txt

# Analyze complexity
task-master analyze-complexity --research

# View report
task-master complexity-report

# Adjust task estimates based on complexity scores
```

---

For more examples and advanced usage, see the [full documentation](https://docs.task-master.dev).

```

--------------------------------------------------------------------------------
/apps/cli/src/utils/auto-update.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Auto-update utilities for task-master-ai CLI
 */

import { spawn } from 'child_process';
import https from 'https';
import boxen from 'boxen';
import chalk from 'chalk';
import ora from 'ora';
import process from 'process';

export interface UpdateInfo {
	currentVersion: string;
	latestVersion: string;
	needsUpdate: boolean;
	highlights?: string[];
}

/**
 * Get current version from build-time injected environment variable
 */
function getCurrentVersion(): string {
	// Version is injected at build time via TM_PUBLIC_VERSION
	const version = process.env.TM_PUBLIC_VERSION;
	if (version && version !== 'unknown') {
		return version;
	}

	// Fallback for development or if injection failed
	console.warn('Could not read version from TM_PUBLIC_VERSION, using fallback');
	return '0.0.0';
}

/**
 * Compare semantic versions with proper pre-release handling
 * @param v1 - First version
 * @param v2 - Second version
 * @returns -1 if v1 < v2, 0 if v1 = v2, 1 if v1 > v2
 */
export function compareVersions(v1: string, v2: string): number {
	const toParts = (v: string) => {
		const [core, pre = ''] = v.split('-', 2);
		const nums = core.split('.').map((n) => Number.parseInt(n, 10) || 0);
		return { nums, pre };
	};

	const a = toParts(v1);
	const b = toParts(v2);
	const len = Math.max(a.nums.length, b.nums.length);

	// Compare numeric parts
	for (let i = 0; i < len; i++) {
		const d = (a.nums[i] || 0) - (b.nums[i] || 0);
		if (d !== 0) return d < 0 ? -1 : 1;
	}

	// Handle pre-release comparison
	if (a.pre && !b.pre) return -1; // prerelease < release
	if (!a.pre && b.pre) return 1; // release > prerelease
	if (a.pre === b.pre) return 0; // same or both empty
	return a.pre < b.pre ? -1 : 1; // basic prerelease tie-break
}

/**
 * Fetch CHANGELOG.md from GitHub and extract highlights for a specific version
 */
async function fetchChangelogHighlights(version: string): Promise<string[]> {
	return new Promise((resolve) => {
		const options = {
			hostname: 'raw.githubusercontent.com',
			path: '/eyaltoledano/claude-task-master/main/CHANGELOG.md',
			method: 'GET',
			headers: {
				'User-Agent': `task-master-ai/${version}`
			}
		};

		const req = https.request(options, (res) => {
			let data = '';

			res.on('data', (chunk) => {
				data += chunk;
			});

			res.on('end', () => {
				try {
					if (res.statusCode !== 200) {
						resolve([]);
						return;
					}

					const highlights = parseChangelogHighlights(data, version);
					resolve(highlights);
				} catch (error) {
					resolve([]);
				}
			});
		});

		req.on('error', () => {
			resolve([]);
		});

		req.setTimeout(3000, () => {
			req.destroy();
			resolve([]);
		});

		req.end();
	});
}

/**
 * Parse changelog markdown to extract Minor Changes for a specific version
 * @internal - Exported for testing purposes only
 */
export function parseChangelogHighlights(
	changelog: string,
	version: string
): string[] {
	try {
		// Validate version format (basic semver pattern) to prevent ReDoS
		if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(version)) {
			return [];
		}

		// Find the version section
		const versionRegex = new RegExp(
			`## ${version.replace(/\./g, '\\.')}\\s*\\n`,
			'i'
		);
		const versionMatch = changelog.match(versionRegex);

		if (!versionMatch) {
			return [];
		}

		// Extract content from this version to the next version heading
		const startIdx = versionMatch.index! + versionMatch[0].length;
		const nextVersionIdx = changelog.indexOf('\n## ', startIdx);
		const versionContent =
			nextVersionIdx > 0
				? changelog.slice(startIdx, nextVersionIdx)
				: changelog.slice(startIdx);

		// Find Minor Changes section
		const minorChangesMatch = versionContent.match(
			/### Minor Changes\s*\n([\s\S]*?)(?=\n###|\n##|$)/i
		);

		if (!minorChangesMatch) {
			return [];
		}

		const minorChangesContent = minorChangesMatch[1];
		const highlights: string[] = [];

		// Extract all bullet points (lines starting with -)
		// Format: - [#PR](...) Thanks [@author]! - Description
		const bulletRegex = /^-\s+\[#\d+\][^\n]*?!\s+-\s+(.+?)$/gm;
		let match;

		while ((match = bulletRegex.exec(minorChangesContent)) !== null) {
			const desc = match[1].trim();
			highlights.push(desc);
		}

		return highlights;
	} catch (error) {
		return [];
	}
}

/**
 * Check for newer version of task-master-ai
 */
export async function checkForUpdate(
	currentVersionOverride?: string
): Promise<UpdateInfo> {
	const currentVersion = currentVersionOverride || getCurrentVersion();

	return new Promise((resolve) => {
		const options = {
			hostname: 'registry.npmjs.org',
			path: '/task-master-ai',
			method: 'GET',
			headers: {
				Accept: 'application/vnd.npm.install-v1+json',
				'User-Agent': `task-master-ai/${currentVersion}`
			}
		};

		const req = https.request(options, (res) => {
			let data = '';

			res.on('data', (chunk) => {
				data += chunk;
			});

			res.on('end', async () => {
				try {
					if (res.statusCode !== 200)
						throw new Error(`npm registry status ${res.statusCode}`);
					const npmData = JSON.parse(data);
					const latestVersion = npmData['dist-tags']?.latest || currentVersion;

					const needsUpdate =
						compareVersions(currentVersion, latestVersion) < 0;

					// Fetch highlights if update is needed
					let highlights: string[] | undefined;
					if (needsUpdate) {
						highlights = await fetchChangelogHighlights(latestVersion);
					}

					resolve({
						currentVersion,
						latestVersion,
						needsUpdate,
						highlights
					});
				} catch (error) {
					resolve({
						currentVersion,
						latestVersion: currentVersion,
						needsUpdate: false
					});
				}
			});
		});

		req.on('error', () => {
			resolve({
				currentVersion,
				latestVersion: currentVersion,
				needsUpdate: false
			});
		});

		req.setTimeout(3000, () => {
			req.destroy();
			resolve({
				currentVersion,
				latestVersion: currentVersion,
				needsUpdate: false
			});
		});

		req.end();
	});
}

/**
 * Display upgrade notification message
 */
export function displayUpgradeNotification(
	currentVersion: string,
	latestVersion: string,
	highlights?: string[]
) {
	let content = `${chalk.blue.bold('Update Available!')} ${chalk.dim(currentVersion)} → ${chalk.green(latestVersion)}`;

	if (highlights && highlights.length > 0) {
		content += '\n\n' + chalk.bold("What's New:");
		for (const highlight of highlights) {
			content += '\n' + chalk.cyan('• ') + highlight;
		}
		content += '\n\n' + 'Auto-updating to the latest version...';
	} else {
		content +=
			'\n\n' +
			'Auto-updating to the latest version with new features and bug fixes...';
	}

	const message = boxen(content, {
		padding: 1,
		margin: { top: 1, bottom: 1 },
		borderColor: 'yellow',
		borderStyle: 'round'
	});

	console.log(message);
}

/**
 * Automatically update task-master-ai to the latest version
 */
export async function performAutoUpdate(
	latestVersion: string
): Promise<boolean> {
	if (
		process.env.TASKMASTER_SKIP_AUTO_UPDATE === '1' ||
		process.env.CI ||
		process.env.NODE_ENV === 'test'
	) {
		const reason =
			process.env.TASKMASTER_SKIP_AUTO_UPDATE === '1'
				? 'TASKMASTER_SKIP_AUTO_UPDATE=1'
				: process.env.CI
					? 'CI environment'
					: 'NODE_ENV=test';
		console.log(chalk.dim(`Skipping auto-update (${reason})`));
		return false;
	}
	const spinner = ora({
		text: chalk.blue(
			`Updating task-master-ai to version ${chalk.green(latestVersion)}`
		),
		spinner: 'dots',
		color: 'blue'
	}).start();

	return new Promise((resolve) => {
		const updateProcess = spawn(
			'npm',
			[
				'install',
				'-g',
				`task-master-ai@${latestVersion}`,
				'--no-fund',
				'--no-audit',
				'--loglevel=warn'
			],
			{
				stdio: ['ignore', 'pipe', 'pipe']
			}
		);

		let errorOutput = '';

		updateProcess.stdout.on('data', () => {
			// Update spinner text with progress
			spinner.text = chalk.blue(
				`Installing task-master-ai@${latestVersion}...`
			);
		});

		updateProcess.stderr.on('data', (data) => {
			errorOutput += data.toString();
		});

		updateProcess.on('close', (code) => {
			if (code === 0) {
				spinner.succeed(
					chalk.green(
						`Successfully updated to version ${chalk.bold(latestVersion)}`
					)
				);
				resolve(true);
			} else {
				spinner.fail(chalk.red('Auto-update failed'));
				console.log(
					chalk.cyan(
						`Please run manually: npm install -g task-master-ai@${latestVersion}`
					)
				);
				if (errorOutput) {
					console.log(chalk.dim(`Error: ${errorOutput.trim()}`));
				}
				resolve(false);
			}
		});

		updateProcess.on('error', (error) => {
			spinner.fail(chalk.red('Auto-update failed'));
			console.log(chalk.red('Error:'), error.message);
			console.log(
				chalk.cyan(
					`Please run manually: npm install -g task-master-ai@${latestVersion}`
				)
			);
			resolve(false);
		});
	});
}

/**
 * Restart the CLI with the newly installed version
 * @param argv - Original command-line arguments (process.argv)
 */
export function restartWithNewVersion(argv: string[]): void {
	const args = argv.slice(2); // Remove 'node' and script path

	console.log(chalk.dim('Restarting with updated version...\n'));

	// Spawn the updated task-master command
	const child = spawn('task-master', args, {
		stdio: 'inherit', // Inherit stdin/stdout/stderr so it looks seamless
		detached: false,
		shell: process.platform === 'win32' // Windows compatibility
	});

	child.on('exit', (code, signal) => {
		if (signal) {
			process.kill(process.pid, signal);
			return;
		}
		process.exit(code ?? 0);
	});

	child.on('error', (error) => {
		console.error(
			chalk.red('Failed to restart with new version:'),
			error.message
		);
		console.log(chalk.yellow('Please run your command again manually.'));
		process.exit(1);
	});
}

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/tasks/services/task-loader.service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Task Loader Service
 * Loads and validates tasks for autopilot execution
 */

import type { Task, Subtask } from '../../../common/types/index.js';
import type { TaskService } from './task-service.js';
import { getLogger } from '../../../common/logger/factory.js';
import { isTaskComplete } from '../../../common/constants/index.js';

const logger = getLogger('TaskLoader');

/**
 * Validation error types
 */
export type ValidationErrorType =
	| 'task_not_found'
	| 'task_completed'
	| 'no_subtasks'
	| 'circular_dependencies'
	| 'missing_dependencies'
	| 'invalid_structure';

/**
 * Validation result for task loading
 */
export interface TaskValidationResult {
	/** Whether validation passed */
	success: boolean;
	/** Loaded task (only present if validation succeeded) */
	task?: Task;
	/** Error type */
	errorType?: ValidationErrorType;
	/** Human-readable error message */
	errorMessage?: string;
	/** Actionable suggestion for fixing the error */
	suggestion?: string;
	/** Dependency analysis (only for dependency errors) */
	dependencyIssues?: DependencyIssue[];
}

/**
 * Dependency issue details
 */
export interface DependencyIssue {
	/** Subtask ID with the issue */
	subtaskId: string;
	/** Type of dependency issue */
	issueType: 'circular' | 'missing' | 'invalid';
	/** Description of the issue */
	message: string;
	/** The problematic dependency reference */
	dependencyRef?: string;
}

/**
 * TaskLoaderService loads and validates tasks for autopilot execution
 */
export class TaskLoaderService {
	private taskService: TaskService;

	constructor(taskService: TaskService) {
		if (!taskService) {
			throw new Error('taskService is required for TaskLoaderService');
		}
		this.taskService = taskService;
	}

	/**
	 * Load and validate a task for autopilot execution
	 */
	async loadAndValidateTask(taskId: string): Promise<TaskValidationResult> {
		logger.info(`Loading task ${taskId}...`);

		// Step 1: Load task
		const task = await this.loadTask(taskId);
		if (!task) {
			return {
				success: false,
				errorType: 'task_not_found',
				errorMessage: `Task with ID "${taskId}" not found`,
				suggestion:
					'Use "task-master list" to see available tasks or verify the task ID is correct.'
			};
		}

		// Step 2: Validate task status
		const statusValidation = this.validateTaskStatus(task);
		if (!statusValidation.success) {
			return statusValidation;
		}

		// Step 3: Check for subtasks
		const subtaskValidation = this.validateSubtasksExist(task);
		if (!subtaskValidation.success) {
			return subtaskValidation;
		}

		// Step 4: Validate subtask structure
		const structureValidation = this.validateSubtaskStructure(task);
		if (!structureValidation.success) {
			return structureValidation;
		}

		// Step 5: Analyze dependencies
		const dependencyValidation = this.validateDependencies(task);
		if (!dependencyValidation.success) {
			return dependencyValidation;
		}

		logger.info(`Task ${taskId} validated successfully`);

		return {
			success: true,
			task
		};
	}

	/**
	 * Load task using TaskService
	 */
	private async loadTask(taskId: string): Promise<Task | null> {
		try {
			return await this.taskService.getTask(taskId);
		} catch (error) {
			logger.error(`Failed to load task ${taskId}:`, error);
			return null;
		}
	}

	/**
	 * Validate task status is appropriate for autopilot
	 */
	private validateTaskStatus(task: Task): TaskValidationResult {
		if (isTaskComplete(task.status)) {
			return {
				success: false,
				errorType: 'task_completed',
				errorMessage: `Task "${task.title}" is already ${task.status}`,
				suggestion:
					'Autopilot can only execute tasks that are pending or in-progress. Use a different task.'
			};
		}

		return { success: true };
	}

	/**
	 * Validate task has subtasks
	 */
	private validateSubtasksExist(task: Task): TaskValidationResult {
		if (!task.subtasks || task.subtasks.length === 0) {
			return {
				success: false,
				errorType: 'no_subtasks',
				errorMessage: `Task "${task.title}" has no subtasks`,
				suggestion: this.buildExpansionSuggestion(task)
			};
		}

		return { success: true };
	}

	/**
	 * Build helpful suggestion for expanding tasks
	 */
	private buildExpansionSuggestion(task: Task): string {
		const suggestions: string[] = [
			`Autopilot requires tasks to be broken down into subtasks for execution.`
		];

		// Add expansion command suggestion
		suggestions.push(`\nExpand this task using:`);
		suggestions.push(`  task-master expand --id=${task.id}`);

		// If task has complexity analysis, mention it
		if (task.complexity || task.recommendedSubtasks) {
			suggestions.push(
				`\nThis task has complexity analysis available. Consider reviewing it first:`
			);
			suggestions.push(`  task-master show ${task.id}`);
		} else {
			suggestions.push(
				`\nOr analyze task complexity first to determine optimal subtask count:`
			);
			suggestions.push(`  task-master analyze-complexity --from=${task.id}`);
		}

		return suggestions.join('\n');
	}

	/**
	 * Validate subtask structure
	 */
	private validateSubtaskStructure(task: Task): TaskValidationResult {
		for (const subtask of task.subtasks) {
			// Check required fields
			if (!subtask.title || !subtask.description) {
				return {
					success: false,
					errorType: 'invalid_structure',
					errorMessage: `Subtask ${task.id}.${subtask.id} is missing required fields`,
					suggestion:
						'Subtasks must have title and description. Re-expand the task or manually fix the subtask structure.'
				};
			}

			// Validate dependencies are arrays
			if (subtask.dependencies && !Array.isArray(subtask.dependencies)) {
				return {
					success: false,
					errorType: 'invalid_structure',
					errorMessage: `Subtask ${task.id}.${subtask.id} has invalid dependencies format`,
					suggestion:
						'Dependencies must be an array. Fix the task structure manually.'
				};
			}
		}

		return { success: true };
	}

	/**
	 * Validate subtask dependencies
	 */
	private validateDependencies(task: Task): TaskValidationResult {
		const issues: DependencyIssue[] = [];
		const subtaskIds = new Set(task.subtasks.map((st) => String(st.id)));

		for (const subtask of task.subtasks) {
			const subtaskId = `${task.id}.${subtask.id}`;

			// Check for missing dependencies
			if (subtask.dependencies && subtask.dependencies.length > 0) {
				for (const depId of subtask.dependencies) {
					const depIdStr = String(depId);

					if (!subtaskIds.has(depIdStr)) {
						issues.push({
							subtaskId,
							issueType: 'missing',
							message: `References non-existent subtask ${depIdStr}`,
							dependencyRef: depIdStr
						});
					}
				}
			}

			// Check for circular dependencies
			const circularCheck = this.detectCircularDependency(
				subtask,
				task.subtasks,
				new Set()
			);

			if (circularCheck) {
				issues.push({
					subtaskId,
					issueType: 'circular',
					message: `Circular dependency detected: ${circularCheck.join(' -> ')}`
				});
			}
		}

		if (issues.length > 0) {
			const errorType =
				issues[0].issueType === 'circular'
					? 'circular_dependencies'
					: 'missing_dependencies';

			return {
				success: false,
				errorType,
				errorMessage: `Task "${task.title}" has dependency issues`,
				suggestion:
					'Fix dependency issues manually or re-expand the task:\n' +
					issues
						.map((issue) => `  - ${issue.subtaskId}: ${issue.message}`)
						.join('\n'),
				dependencyIssues: issues
			};
		}

		return { success: true };
	}

	/**
	 * Detect circular dependencies using depth-first search
	 */
	private detectCircularDependency(
		subtask: Subtask,
		allSubtasks: Subtask[],
		visited: Set<string>
	): string[] | null {
		const subtaskId = String(subtask.id);

		if (visited.has(subtaskId)) {
			return [subtaskId];
		}

		visited.add(subtaskId);

		if (subtask.dependencies && subtask.dependencies.length > 0) {
			for (const depId of subtask.dependencies) {
				const depIdStr = String(depId);
				const dependency = allSubtasks.find((st) => String(st.id) === depIdStr);

				if (dependency) {
					const circular = this.detectCircularDependency(
						dependency,
						allSubtasks,
						new Set(visited)
					);

					if (circular) {
						return [subtaskId, ...circular];
					}
				}
			}
		}

		return null;
	}

	/**
	 * Get ordered subtask execution sequence
	 * Returns subtasks in dependency order (tasks with no deps first)
	 */
	getExecutionOrder(task: Task): Subtask[] {
		const ordered: Subtask[] = [];
		const completed = new Set<string>();

		// Keep adding subtasks whose dependencies are all completed
		while (ordered.length < task.subtasks.length) {
			let added = false;

			for (const subtask of task.subtasks) {
				const subtaskId = String(subtask.id);

				if (completed.has(subtaskId)) {
					continue;
				}

				// Check if all dependencies are completed
				const allDepsCompleted =
					!subtask.dependencies ||
					subtask.dependencies.length === 0 ||
					subtask.dependencies.every((depId) => completed.has(String(depId)));

				if (allDepsCompleted) {
					ordered.push(subtask);
					completed.add(subtaskId);
					added = true;
					break;
				}
			}

			// Safety check to prevent infinite loop
			if (!added && ordered.length < task.subtasks.length) {
				logger.warn(
					`Could not determine complete execution order for task ${task.id}`
				);
				// Add remaining subtasks in original order
				for (const subtask of task.subtasks) {
					if (!completed.has(String(subtask.id))) {
						ordered.push(subtask);
					}
				}
				break;
			}
		}

		return ordered;
	}

	/**
	 * Clean up resources
	 */
	async cleanup(): Promise<void> {
		// TaskService doesn't require explicit cleanup
		// Resources are automatically released when instance is garbage collected
	}
}

```

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

```typescript
/**
 * @fileoverview Tests for MCP logging integration
 */

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type LogCallback, LogLevel, Logger } from './logger.js';

describe('Logger - MCP Integration', () => {
	// Store original environment
	let originalEnv: Record<string, string | undefined>;

	beforeEach(() => {
		// Save original environment
		originalEnv = {
			MCP_MODE: process.env.MCP_MODE,
			TASK_MASTER_MCP: process.env.TASK_MASTER_MCP,
			TASK_MASTER_SILENT: process.env.TASK_MASTER_SILENT,
			TM_SILENT: process.env.TM_SILENT,
			TASK_MASTER_LOG_LEVEL: process.env.TASK_MASTER_LOG_LEVEL,
			TM_LOG_LEVEL: process.env.TM_LOG_LEVEL,
			NO_COLOR: process.env.NO_COLOR,
			TASK_MASTER_NO_COLOR: process.env.TASK_MASTER_NO_COLOR
		};

		// Clear environment variables for clean tests
		delete process.env.MCP_MODE;
		delete process.env.TASK_MASTER_MCP;
		delete process.env.TASK_MASTER_SILENT;
		delete process.env.TM_SILENT;
		delete process.env.TASK_MASTER_LOG_LEVEL;
		delete process.env.TM_LOG_LEVEL;
		delete process.env.NO_COLOR;
		delete process.env.TASK_MASTER_NO_COLOR;
	});

	afterEach(() => {
		// Restore original environment
		for (const [key, value] of Object.entries(originalEnv)) {
			if (value === undefined) {
				delete process.env[key];
			} else {
				process.env[key] = value;
			}
		}
	});
	describe('Callback-based logging', () => {
		it('should call callback instead of console when logCallback is provided', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.INFO,
				logCallback: mockCallback
			});

			logger.info('Test message');

			expect(mockCallback).toHaveBeenCalledWith(
				'info',
				expect.stringContaining('Test message')
			);
		});

		it('should call callback for all log levels', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.DEBUG,
				logCallback: mockCallback
			});

			logger.error('Error message');
			logger.warn('Warning message');
			logger.info('Info message');
			logger.debug('Debug message');

			expect(mockCallback).toHaveBeenNthCalledWith(
				1,
				'error',
				expect.stringContaining('Error message')
			);
			expect(mockCallback).toHaveBeenNthCalledWith(
				2,
				'warn',
				expect.stringContaining('Warning message')
			);
			expect(mockCallback).toHaveBeenNthCalledWith(
				3,
				'info',
				expect.stringContaining('Info message')
			);
			expect(mockCallback).toHaveBeenNthCalledWith(
				4,
				'debug',
				expect.stringContaining('Debug message')
			);
		});

		it('should respect log level with callback', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.WARN,
				logCallback: mockCallback
			});

			logger.debug('Debug message');
			logger.info('Info message');
			logger.warn('Warning message');
			logger.error('Error message');

			// Only warn and error should be logged
			expect(mockCallback).toHaveBeenCalledTimes(2);
			expect(mockCallback).toHaveBeenNthCalledWith(
				1,
				'warn',
				expect.stringContaining('Warning message')
			);
			expect(mockCallback).toHaveBeenNthCalledWith(
				2,
				'error',
				expect.stringContaining('Error message')
			);
		});

		it('should handle raw log() calls with callback', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.INFO,
				logCallback: mockCallback
			});

			logger.log('Raw message', 'with args');

			expect(mockCallback).toHaveBeenCalledWith('log', 'Raw message with args');
		});
	});

	describe('MCP mode with callback', () => {
		it('should not silence logs when mcpMode=true and callback is provided', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.INFO,
				mcpMode: true,
				logCallback: mockCallback
			});

			logger.info('Test message');

			expect(mockCallback).toHaveBeenCalledWith(
				'info',
				expect.stringContaining('Test message')
			);
		});

		it('should silence logs when mcpMode=true and no callback', () => {
			const consoleSpy = vi.spyOn(console, 'log');
			const logger = new Logger({
				level: LogLevel.INFO,
				mcpMode: true
				// No callback
			});

			logger.info('Test message');

			expect(consoleSpy).not.toHaveBeenCalled();
			consoleSpy.mockRestore();
		});
	});

	describe('Child loggers', () => {
		it('should inherit callback from parent', () => {
			const mockCallback = vi.fn();
			const parent = new Logger({
				level: LogLevel.INFO,
				logCallback: mockCallback
			});

			const child = parent.child('child');
			child.info('Child message');

			expect(mockCallback).toHaveBeenCalledWith(
				'info',
				expect.stringContaining('[child]')
			);
			expect(mockCallback).toHaveBeenCalledWith(
				'info',
				expect.stringContaining('Child message')
			);
		});

		it('should allow child to override callback', () => {
			const parentCallback = vi.fn();
			const childCallback = vi.fn();

			const parent = new Logger({
				level: LogLevel.INFO,
				logCallback: parentCallback
			});

			const child = parent.child('child', {
				logCallback: childCallback
			});

			parent.info('Parent message');
			child.info('Child message');

			expect(parentCallback).toHaveBeenCalledTimes(1);
			expect(childCallback).toHaveBeenCalledTimes(1);
		});
	});

	describe('Configuration updates', () => {
		it('should allow updating logCallback via setConfig', () => {
			const callback1 = vi.fn();
			const callback2 = vi.fn();

			const logger = new Logger({
				level: LogLevel.INFO,
				logCallback: callback1
			});

			logger.info('Message 1');
			expect(callback1).toHaveBeenCalledTimes(1);

			logger.setConfig({ logCallback: callback2 });
			logger.info('Message 2');

			expect(callback1).toHaveBeenCalledTimes(1);
			expect(callback2).toHaveBeenCalledTimes(1);
		});

		it('should maintain mcpMode behavior when updating config', () => {
			const callback = vi.fn();
			const logger = new Logger({
				level: LogLevel.INFO,
				mcpMode: true
			});

			// Initially silent (no callback)
			logger.info('Message 1');
			expect(callback).not.toHaveBeenCalled();

			// Add callback - should start logging
			logger.setConfig({ logCallback: callback });
			logger.info('Message 2');
			expect(callback).toHaveBeenCalledTimes(1);
		});
	});

	describe('Formatting with callback', () => {
		it('should include prefix in callback messages', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.INFO,
				prefix: 'test-prefix',
				logCallback: mockCallback
			});

			logger.info('Test message');

			expect(mockCallback).toHaveBeenCalledWith(
				'info',
				expect.stringContaining('[test-prefix]')
			);
		});

		it('should include timestamp when enabled', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.INFO,
				timestamp: true,
				logCallback: mockCallback
			});

			logger.info('Test message');

			const [[, message]] = mockCallback.mock.calls;
			// Message should contain ISO timestamp pattern
			expect(message).toMatch(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
		});

		it('should format additional arguments', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.INFO,
				logCallback: mockCallback
			});

			const data = { key: 'value' };
			logger.info('Test message', data, 'string arg');

			expect(mockCallback).toHaveBeenCalledWith(
				'info',
				expect.stringContaining('Test message')
			);
			expect(mockCallback).toHaveBeenCalledWith(
				'info',
				expect.stringContaining('"key"')
			);
			expect(mockCallback).toHaveBeenCalledWith(
				'info',
				expect.stringContaining('string arg')
			);
		});
	});

	describe('Edge cases', () => {
		it('should handle null/undefined callback gracefully', () => {
			const logger = new Logger({
				level: LogLevel.INFO,
				logCallback: undefined
			});

			const consoleSpy = vi.spyOn(console, 'log');

			// Should fallback to console
			logger.info('Test message');

			expect(consoleSpy).toHaveBeenCalled();
			consoleSpy.mockRestore();
		});

		it('should not call callback when level is SILENT', () => {
			const mockCallback = vi.fn();
			const logger = new Logger({
				level: LogLevel.SILENT,
				logCallback: mockCallback
			});

			logger.error('Error');
			logger.warn('Warning');
			logger.info('Info');
			logger.debug('Debug');

			expect(mockCallback).not.toHaveBeenCalled();
		});

		it('should propagate callback errors', () => {
			const errorCallback: LogCallback = () => {
				throw new Error('Callback error');
			};

			const logger = new Logger({
				level: LogLevel.INFO,
				logCallback: errorCallback
			});

			// Should throw
			expect(() => {
				logger.info('Test message');
			}).toThrow('Callback error');
		});
	});

	describe('Environment variable detection', () => {
		it('should detect MCP mode from environment', () => {
			const originalEnv = process.env.MCP_MODE;
			process.env.MCP_MODE = 'true';

			const logger = new Logger({
				level: LogLevel.INFO
			});

			const config = logger.getConfig();
			expect(config.mcpMode).toBe(true);
			expect(config.silent).toBe(true); // Should be silent without callback

			// Cleanup
			if (originalEnv === undefined) {
				delete process.env.MCP_MODE;
			} else {
				process.env.MCP_MODE = originalEnv;
			}
		});

		it('should detect log level from environment', () => {
			const originalEnv = process.env.TASK_MASTER_LOG_LEVEL;
			process.env.TASK_MASTER_LOG_LEVEL = 'DEBUG';

			const logger = new Logger();
			const config = logger.getConfig();
			expect(config.level).toBe(LogLevel.DEBUG);

			// Cleanup
			if (originalEnv === undefined) {
				delete process.env.TASK_MASTER_LOG_LEVEL;
			} else {
				process.env.TASK_MASTER_LOG_LEVEL = originalEnv;
			}
		});
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/services/config-persistence.service.spec.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Unit tests for ConfigPersistence service
 */

import fs from 'node:fs/promises';
import type { PartialConfiguration } from '@tm/core/common/interfaces/configuration.interface.js';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ConfigPersistence } from './config-persistence.service.js';

vi.mock('node:fs', () => ({
	promises: {
		readFile: vi.fn(),
		writeFile: vi.fn(),
		mkdir: vi.fn(),
		unlink: vi.fn(),
		access: vi.fn(),
		readdir: vi.fn(),
		rename: vi.fn()
	}
}));

describe('ConfigPersistence', () => {
	let persistence: ConfigPersistence;
	const testProjectRoot = '/test/project';

	beforeEach(() => {
		persistence = new ConfigPersistence(testProjectRoot);
		vi.clearAllMocks();
	});

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

	describe('saveConfig', () => {
		const mockConfig: PartialConfiguration = {
			models: { main: 'test-model', fallback: 'test-fallback' },
			storage: {
				type: 'file' as const,
				enableBackup: true,
				maxBackups: 5,
				enableCompression: true,
				encoding: 'utf-8',
				atomicOperations: true
			}
		};

		it('should save configuration to file', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			await persistence.saveConfig(mockConfig);

			expect(fs.mkdir).toHaveBeenCalledWith('/test/project/.taskmaster', {
				recursive: true
			});

			expect(fs.writeFile).toHaveBeenCalledWith(
				'/test/project/.taskmaster/config.json',
				JSON.stringify(mockConfig, null, 2),
				'utf-8'
			);
		});

		it('should use atomic write when specified', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
			vi.mocked(fs.rename).mockResolvedValue(undefined);

			await persistence.saveConfig(mockConfig, { atomic: true });

			// Should write to temp file first
			expect(fs.writeFile).toHaveBeenCalledWith(
				'/test/project/.taskmaster/config.json.tmp',
				JSON.stringify(mockConfig, null, 2),
				'utf-8'
			);

			// Then rename to final location
			expect(fs.rename).toHaveBeenCalledWith(
				'/test/project/.taskmaster/config.json.tmp',
				'/test/project/.taskmaster/config.json'
			);
		});

		it('should create backup when requested', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
			vi.mocked(fs.access).mockResolvedValue(undefined); // Config exists
			vi.mocked(fs.readFile).mockResolvedValue('{"old": "config"}');
			vi.mocked(fs.readdir).mockResolvedValue([]);

			await persistence.saveConfig(mockConfig, { createBackup: true });

			// Should create backup directory
			expect(fs.mkdir).toHaveBeenCalledWith(
				'/test/project/.taskmaster/backups',
				{ recursive: true }
			);

			// Should read existing config for backup
			expect(fs.readFile).toHaveBeenCalledWith(
				'/test/project/.taskmaster/config.json',
				'utf-8'
			);

			// Should write backup file
			expect(fs.writeFile).toHaveBeenCalledWith(
				expect.stringContaining('/test/project/.taskmaster/backups/config-'),
				'{"old": "config"}',
				'utf-8'
			);
		});

		it('should not create backup if config does not exist', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
			vi.mocked(fs.access).mockRejectedValue(new Error('Not found'));

			await persistence.saveConfig(mockConfig, { createBackup: true });

			// Should not read or create backup
			expect(fs.readFile).not.toHaveBeenCalled();
			expect(fs.writeFile).toHaveBeenCalledTimes(1); // Only the main config
		});

		it('should throw TaskMasterError on save failure', async () => {
			vi.mocked(fs.mkdir).mockRejectedValue(new Error('Disk full'));

			await expect(persistence.saveConfig(mockConfig)).rejects.toThrow(
				'Failed to save configuration'
			);
		});
	});

	describe('configExists', () => {
		it('should return true when config exists', async () => {
			vi.mocked(fs.access).mockResolvedValue(undefined);

			const exists = await persistence.configExists();

			expect(fs.access).toHaveBeenCalledWith(
				'/test/project/.taskmaster/config.json'
			);
			expect(exists).toBe(true);
		});

		it('should return false when config does not exist', async () => {
			vi.mocked(fs.access).mockRejectedValue(new Error('Not found'));

			const exists = await persistence.configExists();

			expect(exists).toBe(false);
		});
	});

	describe('deleteConfig', () => {
		it('should delete configuration file', async () => {
			vi.mocked(fs.unlink).mockResolvedValue(undefined);

			await persistence.deleteConfig();

			expect(fs.unlink).toHaveBeenCalledWith(
				'/test/project/.taskmaster/config.json'
			);
		});

		it('should not throw when file does not exist', async () => {
			const error = new Error('File not found') as any;
			error.code = 'ENOENT';
			vi.mocked(fs.unlink).mockRejectedValue(error);

			await expect(persistence.deleteConfig()).resolves.not.toThrow();
		});

		it('should throw TaskMasterError for other errors', async () => {
			vi.mocked(fs.unlink).mockRejectedValue(new Error('Permission denied'));

			await expect(persistence.deleteConfig()).rejects.toThrow(
				'Failed to delete configuration'
			);
		});
	});

	describe('getBackups', () => {
		it('should return list of backup files sorted newest first', async () => {
			vi.mocked(fs.readdir).mockResolvedValue([
				'config-2024-01-01T10-00-00-000Z.json',
				'config-2024-01-02T10-00-00-000Z.json',
				'config-2024-01-03T10-00-00-000Z.json',
				'other-file.txt'
			] as any);

			const backups = await persistence.getBackups();

			expect(fs.readdir).toHaveBeenCalledWith(
				'/test/project/.taskmaster/backups'
			);

			expect(backups).toEqual([
				'config-2024-01-03T10-00-00-000Z.json',
				'config-2024-01-02T10-00-00-000Z.json',
				'config-2024-01-01T10-00-00-000Z.json'
			]);
		});

		it('should return empty array when backup directory does not exist', async () => {
			vi.mocked(fs.readdir).mockRejectedValue(new Error('Not found'));

			const backups = await persistence.getBackups();

			expect(backups).toEqual([]);
		});

		it('should filter out non-backup files', async () => {
			vi.mocked(fs.readdir).mockResolvedValue([
				'config-2024-01-01T10-00-00-000Z.json',
				'README.md',
				'.DS_Store',
				'config.json',
				'config-backup.json' // Wrong format
			] as any);

			const backups = await persistence.getBackups();

			expect(backups).toEqual(['config-2024-01-01T10-00-00-000Z.json']);
		});
	});

	describe('restoreFromBackup', () => {
		const backupFile = 'config-2024-01-01T10-00-00-000Z.json';
		const backupContent = '{"restored": "config"}';

		it('should restore configuration from backup', async () => {
			vi.mocked(fs.readFile).mockResolvedValue(backupContent);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			await persistence.restoreFromBackup(backupFile);

			expect(fs.readFile).toHaveBeenCalledWith(
				`/test/project/.taskmaster/backups/${backupFile}`,
				'utf-8'
			);

			expect(fs.writeFile).toHaveBeenCalledWith(
				'/test/project/.taskmaster/config.json',
				backupContent,
				'utf-8'
			);
		});

		it('should throw TaskMasterError when backup file not found', async () => {
			vi.mocked(fs.readFile).mockRejectedValue(new Error('File not found'));

			await expect(
				persistence.restoreFromBackup('nonexistent.json')
			).rejects.toThrow('Failed to restore from backup');
		});

		it('should throw TaskMasterError on write failure', async () => {
			vi.mocked(fs.readFile).mockResolvedValue(backupContent);
			vi.mocked(fs.writeFile).mockRejectedValue(new Error('Disk full'));

			await expect(persistence.restoreFromBackup(backupFile)).rejects.toThrow(
				'Failed to restore from backup'
			);
		});
	});

	describe('backup management', () => {
		it('should clean old backups when limit exceeded', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
			vi.mocked(fs.access).mockResolvedValue(undefined);
			vi.mocked(fs.readFile).mockResolvedValue('{"old": "config"}');
			vi.mocked(fs.unlink).mockResolvedValue(undefined);

			// Mock 7 existing backups
			vi.mocked(fs.readdir).mockResolvedValue([
				'config-2024-01-01T10-00-00-000Z.json',
				'config-2024-01-02T10-00-00-000Z.json',
				'config-2024-01-03T10-00-00-000Z.json',
				'config-2024-01-04T10-00-00-000Z.json',
				'config-2024-01-05T10-00-00-000Z.json',
				'config-2024-01-06T10-00-00-000Z.json',
				'config-2024-01-07T10-00-00-000Z.json'
			] as any);

			await persistence.saveConfig({}, { createBackup: true });

			// Should delete oldest backups (keeping 5)
			expect(fs.unlink).toHaveBeenCalledWith(
				'/test/project/.taskmaster/backups/config-2024-01-01T10-00-00-000Z.json'
			);
			expect(fs.unlink).toHaveBeenCalledWith(
				'/test/project/.taskmaster/backups/config-2024-01-02T10-00-00-000Z.json'
			);
		});

		it('should handle backup cleanup errors gracefully', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
			vi.mocked(fs.access).mockResolvedValue(undefined);
			vi.mocked(fs.readFile).mockResolvedValue('{"old": "config"}');
			vi.mocked(fs.readdir).mockResolvedValue(['config-old.json'] as any);
			vi.mocked(fs.unlink).mockRejectedValue(new Error('Permission denied'));

			// Mock console.warn to verify it's called
			const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

			// Should not throw even if cleanup fails
			await expect(
				persistence.saveConfig({}, { createBackup: true })
			).resolves.not.toThrow();

			expect(warnSpy).toHaveBeenCalledWith(
				'Failed to clean old backups:',
				expect.any(Error)
			);

			warnSpy.mockRestore();
		});
	});
});

```

--------------------------------------------------------------------------------
/apps/cli/src/ui/components/task-detail.component.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Task detail component for show command
 * Displays detailed task information in a structured format
 */

import type { StorageType, Subtask, Task } from '@tm/core';
import boxen from 'boxen';
import chalk from 'chalk';
import Table from 'cli-table3';
import { MarkedExtension, marked } from 'marked';
import { markedTerminal } from 'marked-terminal';
import {
	getComplexityWithColor,
	getPriorityWithColor,
	getStatusWithColor
} from '../../utils/ui.js';

// Configure marked to use terminal renderer with subtle colors
marked.use(
	markedTerminal({
		// More subtle colors that match the overall design
		code: (code: string) => {
			// Custom code block handler to preserve formatting
			return code
				.split('\n')
				.map((line) => '    ' + chalk.cyan(line))
				.join('\n');
		},
		blockquote: chalk.gray.italic,
		html: chalk.gray,
		heading: chalk.white.bold, // White bold for headings
		hr: chalk.gray,
		listitem: chalk.white, // White for list items
		paragraph: chalk.white, // White for paragraphs (default text color)
		strong: chalk.white.bold, // White bold for strong text
		em: chalk.white.italic, // White italic for emphasis
		codespan: chalk.cyan, // Cyan for inline code (no background)
		del: chalk.dim.strikethrough,
		link: chalk.blue,
		href: chalk.blue.underline,
		// Add more explicit code block handling
		showSectionPrefix: false,
		unescape: true,
		emoji: false,
		// Try to preserve whitespace in code blocks
		tab: 4,
		width: 120
	}) as MarkedExtension
);

// Also set marked options to preserve whitespace
marked.setOptions({
	breaks: true,
	gfm: true
});

/**
 * Display the task header with tag
 */
export function displayTaskHeader(
	taskId: string | number,
	title: string
): void {
	// Display task header box
	console.log(
		boxen(chalk.white.bold(`Task: #${taskId} - ${title}`), {
			padding: { top: 0, bottom: 0, left: 1, right: 1 },
			borderColor: 'blue',
			borderStyle: 'round'
		})
	);
}

/**
 * Display task properties in a table format
 */
export function displayTaskProperties(
	task: Task | Subtask,
	originalTaskId?: string
): void {
	const terminalWidth = process.stdout.columns * 0.95 || 100;
	// Create table for task properties - simple 2-column layout
	const table = new Table({
		head: [],
		style: {
			head: [],
			border: ['grey']
		},
		colWidths: [
			Math.floor(terminalWidth * 0.2),
			Math.floor(terminalWidth * 0.8)
		],
		wordWrap: true
	});

	const deps =
		task.dependencies && task.dependencies.length > 0
			? task.dependencies.map((d) => String(d)).join(', ')
			: 'None';

	// Use originalTaskId if provided (for subtasks like "104.1")
	const displayId = originalTaskId || String(task.id);

	// Build the left column (labels) and right column (values)
	const labels = [
		chalk.cyan('ID:'),
		chalk.cyan('Title:'),
		chalk.cyan('Status:'),
		chalk.cyan('Priority:'),
		chalk.cyan('Dependencies:'),
		chalk.cyan('Complexity:'),
		chalk.cyan('Description:')
	].join('\n');

	const values = [
		displayId,
		task.title,
		getStatusWithColor(task.status),
		getPriorityWithColor(task.priority),
		deps,
		typeof task.complexity === 'number'
			? getComplexityWithColor(task.complexity)
			: chalk.gray('N/A'),
		task.description || ''
	].join('\n');

	table.push([labels, values]);

	console.log(table.toString());
}

/**
 * Display implementation details in a box
 */
export function displayImplementationDetails(details: string): void {
	// Handle all escaped characters properly
	const cleanDetails = details
		.replace(/\\n/g, '\n') // Convert \n to actual newlines
		.replace(/\\t/g, '\t') // Convert \t to actual tabs
		.replace(/\\"/g, '"') // Convert \" to actual quotes
		.replace(/\\\\/g, '\\'); // Convert \\ to single backslash

	const terminalWidth = process.stdout.columns * 0.95 || 100;

	// Parse markdown to terminal-friendly format
	const markdownResult = marked(cleanDetails);
	const formattedDetails =
		typeof markdownResult === 'string' ? markdownResult.trim() : cleanDetails; // Fallback to original if Promise

	console.log(
		boxen(
			chalk.white.bold('Implementation Details:') + '\n\n' + formattedDetails,
			{
				padding: 1,
				borderStyle: 'round',
				borderColor: 'cyan', // Changed to cyan to match the original
				width: terminalWidth // Fixed width to match the original
			}
		)
	);
}

/**
 * Display test strategy in a box
 */
export function displayTestStrategy(testStrategy: string): void {
	// Handle all escaped characters properly (same as implementation details)
	const cleanStrategy = testStrategy
		.replace(/\\n/g, '\n') // Convert \n to actual newlines
		.replace(/\\t/g, '\t') // Convert \t to actual tabs
		.replace(/\\"/g, '"') // Convert \" to actual quotes
		.replace(/\\\\/g, '\\'); // Convert \\ to single backslash

	const terminalWidth = process.stdout.columns * 0.95 || 100;

	// Parse markdown to terminal-friendly format (same as implementation details)
	const markdownResult = marked(cleanStrategy);
	const formattedStrategy =
		typeof markdownResult === 'string' ? markdownResult.trim() : cleanStrategy; // Fallback to original if Promise

	console.log(
		boxen(chalk.white.bold('Test Strategy:') + '\n\n' + formattedStrategy, {
			padding: 1,
			borderStyle: 'round',
			borderColor: 'cyan', // Changed to cyan to match implementation details
			width: terminalWidth
		})
	);
}

/**
 * Display subtasks in a table format
 */
export function displaySubtasks(
	subtasks: Array<{
		id: string | number;
		title: string;
		status: any;
		description?: string;
		dependencies?: string[];
	}>,
	parentTaskId?: string | number,
	storageType?: Exclude<StorageType, 'auto'>
): void {
	const terminalWidth = process.stdout.columns * 0.95 || 100;
	// Display subtasks header
	console.log(
		boxen(chalk.magenta.bold('Subtasks'), {
			padding: { top: 0, bottom: 0, left: 1, right: 1 },
			borderColor: 'magenta',
			borderStyle: 'round',
			margin: { top: 1, bottom: 0 }
		})
	);

	// Create subtasks table
	const table = new Table({
		head: [
			chalk.magenta.bold('ID'),
			chalk.magenta.bold('Status'),
			chalk.magenta.bold('Title'),
			chalk.magenta.bold('Deps')
		],
		style: {
			head: [],
			border: ['grey']
		},
		colWidths: [
			Math.floor(terminalWidth * 0.1),
			Math.floor(terminalWidth * 0.15),
			Math.floor(terminalWidth * 0.6),
			Math.floor(terminalWidth * 0.15)
		],
		wordWrap: true
	});

	subtasks.forEach((subtask) => {
		// Format subtask ID based on storage type:
		// - File storage: Show parent prefix (e.g., 10.1, 10.2)
		// - API storage: Show subtask ID only (e.g., 1, 2)
		const subtaskId =
			storageType === 'file' && parentTaskId
				? `${parentTaskId}.${subtask.id}`
				: String(subtask.id);

		// Format dependencies
		const deps =
			subtask.dependencies && subtask.dependencies.length > 0
				? subtask.dependencies.join(', ')
				: 'None';

		table.push([
			subtaskId,
			getStatusWithColor(subtask.status),
			subtask.title,
			deps
		]);
	});

	console.log(table.toString());
}

/**
 * Display suggested actions
 */
export function displaySuggestedActions(taskId: string | number): void {
	console.log(
		boxen(
			chalk.white.bold('Suggested Actions:') +
				'\n\n' +
				`${chalk.cyan('1.')} Run ${chalk.yellow(`task-master set-status --id=${taskId} --status=in-progress`)} to start working\n` +
				`${chalk.cyan('2.')} Run ${chalk.yellow(`task-master expand --id=${taskId}`)} to break down into subtasks\n` +
				`${chalk.cyan('3.')} Run ${chalk.yellow(`task-master update-task --id=${taskId} --prompt="..."`)} to update details`,
			{
				padding: 1,
				margin: { top: 1 },
				borderStyle: 'round',
				borderColor: 'green',
				width: process.stdout.columns * 0.95 || 100
			}
		)
	);
}

/**
 * Display complete task details - used by both show and start commands
 */
export function displayTaskDetails(
	task: Task | Subtask,
	options?: {
		statusFilter?: string;
		showSuggestedActions?: boolean;
		customHeader?: string;
		headerColor?: string;
		originalTaskId?: string;
		storageType?: Exclude<StorageType, 'auto'>;
	}
): void {
	const {
		statusFilter,
		showSuggestedActions = false,
		customHeader,
		headerColor = 'blue',
		originalTaskId,
		storageType
	} = options || {};

	// Display header - either custom or default
	if (customHeader) {
		console.log(
			boxen(chalk.white.bold(customHeader), {
				padding: { top: 0, bottom: 0, left: 1, right: 1 },
				borderColor: headerColor,
				borderStyle: 'round',
				margin: { top: 1 }
			})
		);
	} else {
		// Use originalTaskId if provided (for subtasks like "104.1")
		const displayId = originalTaskId || task.id;
		displayTaskHeader(displayId, task.title);
	}

	// Display task properties in table format
	displayTaskProperties(task, originalTaskId);

	// Display implementation details if available
	if (task.details) {
		console.log(); // Empty line for spacing
		displayImplementationDetails(task.details);
	}

	// Display test strategy if available
	if ('testStrategy' in task && task.testStrategy) {
		console.log(); // Empty line for spacing
		displayTestStrategy(task.testStrategy as string);
	}

	// Display subtasks if available
	if (task.subtasks && task.subtasks.length > 0) {
		// Filter subtasks by status if provided
		const filteredSubtasks = statusFilter
			? task.subtasks.filter((sub) => sub.status === statusFilter)
			: task.subtasks;

		if (filteredSubtasks.length === 0 && statusFilter) {
			console.log(); // Empty line for spacing
			console.log(chalk.gray(`  No subtasks with status '${statusFilter}'`));
		} else if (filteredSubtasks.length > 0) {
			console.log(); // Empty line for spacing
			displaySubtasks(filteredSubtasks, task.id, storageType);
		}
	}

	// Display suggested actions if requested
	if (showSuggestedActions) {
		console.log(); // Empty line for spacing
		const actionTaskId = originalTaskId || task.id;
		displaySuggestedActions(actionTaskId);
	}
}

```

--------------------------------------------------------------------------------
/src/utils/profiles.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Profiles Utility
 * Consolidated utilities for profile detection, setup, and summary generation
 */
import fs from 'fs';
import path from 'path';
import inquirer from 'inquirer';
import chalk from 'chalk';
import boxen from 'boxen';
import { log } from '../../scripts/modules/utils.js';
import { getRulesProfile } from './rule-transformer.js';
import { RULE_PROFILES } from '../constants/profiles.js';

// =============================================================================
// PROFILE DETECTION
// =============================================================================

/**
 * Get the display name for a profile
 * @param {string} profileName - The profile name
 * @returns {string} - The display name
 */
export function getProfileDisplayName(profileName) {
	try {
		const profile = getRulesProfile(profileName);
		return profile.displayName || profileName;
	} catch (error) {
		return profileName;
	}
}

/**
 * Get installed profiles in the project directory
 * @param {string} projectRoot - Project directory path
 * @returns {string[]} - Array of installed profile names
 */
export function getInstalledProfiles(projectRoot) {
	const installedProfiles = [];

	for (const profileName of RULE_PROFILES) {
		try {
			const profile = getRulesProfile(profileName);
			const profileDir = path.join(projectRoot, profile.profileDir);

			// Check if profile directory exists (skip root directory check)
			if (profile.profileDir === '.' || fs.existsSync(profileDir)) {
				// Check if any files from the profile's fileMap exist
				const rulesDir = path.join(projectRoot, profile.rulesDir);
				if (fs.existsSync(rulesDir)) {
					const ruleFiles = Object.values(profile.fileMap);
					const hasRuleFiles = ruleFiles.some((ruleFile) =>
						fs.existsSync(path.join(rulesDir, ruleFile))
					);
					if (hasRuleFiles) {
						installedProfiles.push(profileName);
					}
				}
			}
		} catch (error) {
			// Skip profiles that can't be loaded
		}
	}

	return installedProfiles;
}

/**
 * Check if removing specified profiles would leave no profiles installed
 * @param {string} projectRoot - Project root directory
 * @param {string[]} profilesToRemove - Array of profile names to remove
 * @returns {boolean} - True if removal would leave no profiles
 */
export function wouldRemovalLeaveNoProfiles(projectRoot, profilesToRemove) {
	const installedProfiles = getInstalledProfiles(projectRoot);

	// If no profiles are currently installed, removal cannot leave no profiles
	if (installedProfiles.length === 0) {
		return false;
	}

	const remainingProfiles = installedProfiles.filter(
		(profile) => !profilesToRemove.includes(profile)
	);
	return remainingProfiles.length === 0;
}

// =============================================================================
// PROFILE SETUP
// =============================================================================

// Note: Profile choices are now generated dynamically within runInteractiveProfilesSetup()
// to ensure proper alphabetical sorting and pagination configuration

/**
 * Launches an interactive prompt for selecting which rule profiles to include in your project.
 *
 * This function dynamically lists all available profiles (from RULE_PROFILES) and presents them as checkboxes.
 * The user must select at least one profile (no defaults are pre-selected). The result is an array of selected profile names.
 *
 * Used by both project initialization (init) and the CLI 'task-master rules setup' command.
 *
 * @returns {Promise<string[]>} Array of selected profile names (e.g., ['cursor', 'windsurf'])
 */
export async function runInteractiveProfilesSetup() {
	// Generate the profile list dynamically with proper display names, alphabetized
	const profileDescriptions = RULE_PROFILES.map((profileName) => {
		const displayName = getProfileDisplayName(profileName);
		const profile = getRulesProfile(profileName);

		// Determine description based on profile capabilities
		let description;
		const hasRules = Object.keys(profile.fileMap).length > 0;
		const hasMcpConfig = profile.mcpConfig === true;

		if (!profile.includeDefaultRules) {
			// Integration guide profiles (claude, codex, gemini, opencode, zed, amp) - don't include standard coding rules
			if (profileName === 'claude') {
				description = 'Integration guide with Task Master slash commands';
			} else if (profileName === 'codex') {
				description = 'Comprehensive Task Master integration guide';
			} else if (hasMcpConfig) {
				description = 'Integration guide and MCP config';
			} else {
				description = 'Integration guide';
			}
		} else if (hasRules && hasMcpConfig) {
			// Full rule profiles with MCP config
			if (profileName === 'roo') {
				description = 'Rule profile, MCP config, and agent modes';
			} else {
				description = 'Rule profile and MCP config';
			}
		} else if (hasRules) {
			// Rule profiles without MCP config
			description = 'Rule profile';
		}

		return {
			profileName,
			displayName,
			description
		};
	}).sort((a, b) => a.displayName.localeCompare(b.displayName));

	const profileListText = profileDescriptions
		.map(
			({ displayName, description }) =>
				`${chalk.white('• ')}${chalk.yellow(displayName)}${chalk.white(` - ${description}`)}`
		)
		.join('\n');

	console.log(
		boxen(
			`${chalk.white.bold('Rule Profiles Setup')}\n\n${chalk.white(
				'Rule profiles help enforce best practices and conventions for Task Master.\n' +
					'Each profile provides coding guidelines tailored for specific AI coding environments.\n\n'
			)}${chalk.cyan('Available Profiles:')}\n${profileListText}`,
			{
				padding: 1,
				borderColor: 'blue',
				borderStyle: 'round',
				margin: { top: 1, bottom: 1 }
			}
		)
	);

	// Generate choices in the same order as the display text above
	const sortedChoices = profileDescriptions.map(
		({ profileName, displayName }) => ({
			name: displayName,
			value: profileName
		})
	);

	const ruleProfilesQuestion = {
		type: 'checkbox',
		name: 'ruleProfiles',
		message: 'Which rule profiles would you like to add to your project?',
		choices: sortedChoices,
		pageSize: sortedChoices.length, // Show all options without pagination
		loop: false, // Disable loop scrolling
		validate: (input) => input.length > 0 || 'You must select at least one.'
	};
	const { ruleProfiles } = await inquirer.prompt([ruleProfilesQuestion]);
	return ruleProfiles;
}

// =============================================================================
// PROFILE SUMMARY
// =============================================================================

/**
 * Generate appropriate summary message for a profile based on its type
 * @param {string} profileName - Name of the profile
 * @param {Object} addResult - Result object with success/failed counts
 * @returns {string} Formatted summary message
 */
export function generateProfileSummary(profileName, addResult) {
	const profileConfig = getRulesProfile(profileName);

	if (!profileConfig.includeDefaultRules) {
		// Integration guide profiles (claude, codex, gemini, amp)
		return `Summary for ${profileName}: Integration guide installed.`;
	} else {
		// Rule profiles with coding guidelines
		return `Summary for ${profileName}: ${addResult.success} files processed, ${addResult.failed} failed.`;
	}
}

/**
 * Generate appropriate summary message for profile removal
 * @param {string} profileName - Name of the profile
 * @param {Object} removeResult - Result object from removal operation
 * @returns {string} Formatted summary message
 */
export function generateProfileRemovalSummary(profileName, removeResult) {
	if (removeResult.skipped) {
		return `Summary for ${profileName}: Skipped (default or protected files)`;
	}

	if (removeResult.error && !removeResult.success) {
		return `Summary for ${profileName}: Failed to remove - ${removeResult.error}`;
	}

	const profileConfig = getRulesProfile(profileName);

	if (!profileConfig.includeDefaultRules) {
		// Integration guide profiles (claude, codex, gemini, amp)
		const baseMessage = `Summary for ${profileName}: Integration guide removed`;
		if (removeResult.notice) {
			return `${baseMessage} (${removeResult.notice})`;
		}
		return baseMessage;
	} else {
		// Rule profiles with coding guidelines
		const baseMessage = `Summary for ${profileName}: Rule profile removed`;
		if (removeResult.notice) {
			return `${baseMessage} (${removeResult.notice})`;
		}
		return baseMessage;
	}
}

/**
 * Categorize profiles and generate final summary statistics
 * @param {Array} addResults - Array of add result objects
 * @returns {Object} Object with categorized profiles and totals
 */
export function categorizeProfileResults(addResults) {
	const successfulProfiles = [];
	let totalSuccess = 0;
	let totalFailed = 0;

	addResults.forEach((r) => {
		totalSuccess += r.success;
		totalFailed += r.failed;

		// All profiles are considered successful if they completed without major errors
		if (r.success > 0 || r.failed === 0) {
			successfulProfiles.push(r.profileName);
		}
	});

	return {
		successfulProfiles,
		allSuccessfulProfiles: successfulProfiles,
		totalSuccess,
		totalFailed
	};
}

/**
 * Categorize removal results and generate final summary statistics
 * @param {Array} removalResults - Array of removal result objects
 * @returns {Object} Object with categorized removal results
 */
export function categorizeRemovalResults(removalResults) {
	const successfulRemovals = [];
	const skippedRemovals = [];
	const failedRemovals = [];
	const removalsWithNotices = [];

	removalResults.forEach((result) => {
		if (result.success) {
			successfulRemovals.push(result.profileName);
		} else if (result.skipped) {
			skippedRemovals.push(result.profileName);
		} else if (result.error) {
			failedRemovals.push(result);
		}

		if (result.notice) {
			removalsWithNotices.push(result);
		}
	});

	return {
		successfulRemovals,
		skippedRemovals,
		failedRemovals,
		removalsWithNotices
	};
}

```

--------------------------------------------------------------------------------
/tests/e2e/e2e_helpers.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# --- LLM Analysis Helper Function ---
# This function should be sourced by the main E2E script or test scripts.
# It requires curl and jq to be installed.
# It expects the project root path to be passed as the second argument.

# --- New Function: extract_and_sum_cost ---
# Takes a string containing command output.
# Extracts costs (lines with "Est. Cost: $X.YYYYYY" or similar from telemetry output)
# from the output, sums them, and adds them to the GLOBAL total_e2e_cost variable.
extract_and_sum_cost() {
  local command_output="$1"
  # Ensure total_e2e_cost is treated as a number, default to 0.0 if not set or invalid
  if ! [[ "$total_e2e_cost" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
    total_e2e_cost="0.0"
  fi

  local extracted_cost_sum="0.0"

  # Grep for lines containing "Est. Cost: $", then extract the numeric value.
  # Example line: │     Est. Cost: $0.093549                       │
  # Accumulate all costs found in the command_output
  while IFS= read -r line; do
    # Extract the numeric part after 'Est. Cost: $' and before any trailing spaces/chars
    cost_value=$(echo "$line" | grep -o -E 'Est\. Cost: \$([0-9]+\.[0-9]+)' | sed -E 's/Est\. Cost: \$//g')
    if [[ -n "$cost_value" && "$cost_value" =~ ^[0-9]+\.[0-9]+$ ]]; then
      # echo "[DEBUG] Found cost value: $cost_value in line: '$line'" # For debugging
      extracted_cost_sum=$(echo "$extracted_cost_sum + $cost_value" | bc)
    # else # For debugging
      # echo "[DEBUG] No valid cost value found or extracted in line: '$line' (extracted: '$cost_value')" # For debugging
    fi
  done < <(echo "$command_output" | grep -E 'Est\. Cost: \$')

  # echo "[DEBUG] Extracted sum from this command output: $extracted_cost_sum" # For debugging
  if (( $(echo "$extracted_cost_sum > 0" | bc -l) )); then
    total_e2e_cost=$(echo "$total_e2e_cost + $extracted_cost_sum" | bc)
    # echo "[DEBUG] Updated global total_e2e_cost: $total_e2e_cost" # For debugging
  fi
  # No echo here, the function modifies a global variable.
}
export -f extract_and_sum_cost # Export for use in other scripts if sourced

analyze_log_with_llm() {
  local log_file="$1"
  local project_root="$2" # Expect project root as the second argument

  if [ -z "$project_root" ]; then
      echo "[HELPER_ERROR] Project root argument is missing. Skipping LLM analysis." >&2
      return 1
  fi

  local env_file="${project_root}/.env" # Path to .env in project root
  local supported_models_file="${project_root}/scripts/modules/supported-models.json"

  local provider_summary_log="provider_add_task_summary.log" # File summarizing provider test outcomes
  local api_key=""
  local api_endpoint="https://api.anthropic.com/v1/messages"
  local api_key_name="ANTHROPIC_API_KEY"
  local llm_analysis_model_id="claude-3-7-sonnet-20250219" # Model used for this analysis
  local llm_analysis_provider="anthropic"

  echo "" # Add a newline before analysis starts

  if ! command -v jq &> /dev/null; then
    echo "[HELPER_ERROR] LLM Analysis requires 'jq'. Skipping analysis." >&2
    return 1
  fi
  if ! command -v curl &> /dev/null; then
    echo "[HELPER_ERROR] LLM Analysis requires 'curl'. Skipping analysis." >&2
    return 1
  fi
  if ! command -v bc &> /dev/null; then
    echo "[HELPER_ERROR] LLM Analysis requires 'bc' for cost calculation. Skipping analysis." >&2
    return 1
  fi

  if [ -f "$env_file" ]; then
    api_key=$(grep "^${api_key_name}=" "$env_file" | sed -e "s/^${api_key_name}=//" -e 's/^[[:space:]"]*//' -e 's/[[:space:]"]*$//')
  fi

  if [ -z "$api_key" ]; then
    echo "[HELPER_ERROR] ${api_key_name} not found or empty in project root .env file ($env_file). Skipping LLM analysis." >&2
    return 1
  fi

  if [ ! -f "$log_file" ]; then
    echo "[HELPER_ERROR] Log file not found: $log_file (PWD: $(pwd)). Check path passed to function. Skipping LLM analysis." >&2
    return 1
  fi

  local log_content
  log_content=$(cat "$log_file") || {
    echo "[HELPER_ERROR] Failed to read log file: $log_file. Skipping LLM analysis." >&2
    return 1
  }

  read -r -d '' prompt_template <<'EOF'
Analyze the following E2E test log for the task-master tool. The log contains output from various 'task-master' commands executed sequentially.

Your goal is to:
1. Verify if the key E2E steps completed successfully based on the log messages (e.g., init, parse PRD, list tasks, analyze complexity, expand task, set status, manage models, add/remove dependencies, add/update/remove tasks/subtasks, generate files).
2. **Specifically analyze the Multi-Provider Add-Task Test Sequence:**
   a. Identify which providers were tested for `add-task`. Look for log steps like "Testing Add-Task with Provider: ..." and the summary log 'provider_add_task_summary.log'.
   b. For each tested provider, determine if `add-task` succeeded or failed. Note the created task ID if successful.
   c. Review the corresponding `add_task_show_output_<provider>_id_<id>.log` file (if created) for each successful `add-task` execution.
   d. **Compare the quality and completeness** of the task generated by each successful provider based on their `show` output. Assign a score (e.g., 1-10, 10 being best) based on relevance to the prompt, detail level, and correctness.
   e. Note any providers where `add-task` failed or where the task ID could not be extracted.
3. Identify any general explicit "[ERROR]" messages or stack traces throughout the *entire* log.
4. Identify any potential warnings or unusual output that might indicate a problem even if not marked as an explicit error.
5. Provide an overall assessment of the test run's health based *only* on the log content.

Return your analysis **strictly** in the following JSON format. Do not include any text outside of the JSON structure:

{
  "overall_status": "Success|Failure|Warning",
  "verified_steps": [ "Initialization", "PRD Parsing", /* ...other general steps observed... */ ],
  "provider_add_task_comparison": {
     "prompt_used": "... (extract from log if possible or state 'standard auth prompt') ...",
     "provider_results": {
       "anthropic": { "status": "Success|Failure|ID_Extraction_Failed|Set_Model_Failed", "task_id": "...", "score": "X/10 | N/A", "notes": "..." },
       "openai": { "status": "Success|Failure|...", "task_id": "...", "score": "X/10 | N/A", "notes": "..." },
       /* ... include all tested providers ... */
     },
     "comparison_summary": "Brief overall comparison of generated tasks..."
   },
  "detected_issues": [ { "severity": "Error|Warning|Anomaly", "description": "...", "log_context": "[Optional, short snippet from log near the issue]" } ],
  "llm_summary_points": [ "Overall summary point 1", "Provider comparison highlight", "Any major issues noted" ]
}

Here is the main log content:

%s
EOF

  local full_prompt
  if ! printf -v full_prompt "$prompt_template" "$log_content"; then
    echo "[HELPER_ERROR] Failed to format prompt using printf." >&2
    return 1
  fi

  local payload
  payload=$(jq -n --arg prompt "$full_prompt" '{
    "model": "'"$llm_analysis_model_id"'",
    "max_tokens": 3072,
    "messages": [
      {"role": "user", "content": $prompt}
    ]
  }') || {
      echo "[HELPER_ERROR] Failed to create JSON payload using jq." >&2
      return 1
  }

  local response_raw response_http_code response_body
  response_raw=$(curl -s -w "\nHTTP_STATUS_CODE:%{http_code}" -X POST "$api_endpoint" \
       -H "Content-Type: application/json" \
       -H "x-api-key: $api_key" \
       -H "anthropic-version: 2023-06-01" \
       --data "$payload")

  response_http_code=$(echo "$response_raw" | grep '^HTTP_STATUS_CODE:' | sed 's/HTTP_STATUS_CODE://')
  response_body=$(echo "$response_raw" | sed '$d')

  if [ "$response_http_code" != "200" ]; then
      echo "[HELPER_ERROR] LLM API call failed with HTTP status $response_http_code." >&2
      echo "[HELPER_ERROR] Response Body: $response_body" >&2
      return 1
  fi

  if [ -z "$response_body" ]; then
      echo "[HELPER_ERROR] LLM API call returned empty response body." >&2
      return 1
  fi

  # Calculate cost of this LLM analysis call
  local input_tokens output_tokens input_cost_per_1m output_cost_per_1m calculated_llm_cost
  input_tokens=$(echo "$response_body" | jq -r '.usage.input_tokens // 0')
  output_tokens=$(echo "$response_body" | jq -r '.usage.output_tokens // 0')

  if [ -f "$supported_models_file" ]; then
      model_cost_info=$(jq -r --arg provider "$llm_analysis_provider" --arg model_id "$llm_analysis_model_id" '
          .[$provider][] | select(.id == $model_id) | .cost_per_1m_tokens
      ' "$supported_models_file")

      if [[ -n "$model_cost_info" && "$model_cost_info" != "null" ]]; then
          input_cost_per_1m=$(echo "$model_cost_info" | jq -r '.input // 0')
          output_cost_per_1m=$(echo "$model_cost_info" | jq -r '.output // 0')

          calculated_llm_cost=$(echo "($input_tokens / 1000000 * $input_cost_per_1m) + ($output_tokens / 1000000 * $output_cost_per_1m)" | bc -l)
          # Format to 6 decimal places
          formatted_llm_cost=$(printf "%.6f" "$calculated_llm_cost")
          echo "LLM Analysis AI Cost: $formatted_llm_cost USD" # This line will be parsed by run_e2e.sh
      else
          echo "[HELPER_WARNING] Cost data for model $llm_analysis_model_id not found in $supported_models_file. LLM analysis cost not calculated."
      fi
  else
      echo "[HELPER_WARNING] $supported_models_file not found. LLM analysis cost not calculated."
  fi
  # --- End cost calculation for this call ---

  if echo "$response_body" | node "${project_root}/tests/e2e/parse_llm_output.cjs" "$log_file"; then
      echo "[HELPER_SUCCESS] LLM analysis parsed and printed successfully by Node.js script."
      return 0
  else
      local node_exit_code=$?
      echo "[HELPER_ERROR] Node.js parsing script failed with exit code ${node_exit_code}."
      echo "[HELPER_ERROR] Raw API response body (first 500 chars): $(echo "$response_body" | head -c 500)"
      return 1
  fi
}

export -f analyze_log_with_llm 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/utils/git-utils.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Git utilities for Task Master
 * Git integration utilities using raw git commands and gh CLI
 */

import { exec, execSync } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

/**
 * GitHub repository information
 */
export interface GitHubRepoInfo {
	name: string;
	owner: { login: string };
	defaultBranchRef: { name: string };
}

/**
 * Check if the specified directory is inside a git repository
 */
export async function isGitRepository(projectRoot: string): Promise<boolean> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for isGitRepository');
	}

	try {
		await execAsync('git rev-parse --git-dir', { cwd: projectRoot });
		return true;
	} catch (error) {
		return false;
	}
}

/**
 * Synchronous check if directory is in a git repository
 */
export function isGitRepositorySync(projectRoot: string): boolean {
	if (!projectRoot) {
		return false;
	}

	try {
		execSync('git rev-parse --git-dir', {
			cwd: projectRoot,
			stdio: 'ignore'
		});
		return true;
	} catch (error) {
		return false;
	}
}

/**
 * Get the current git branch name
 */
export async function getCurrentBranch(
	projectRoot: string
): Promise<string | null> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for getCurrentBranch');
	}

	try {
		const { stdout } = await execAsync('git rev-parse --abbrev-ref HEAD', {
			cwd: projectRoot
		});
		return stdout.trim();
	} catch (error) {
		return null;
	}
}

/**
 * Synchronous get current git branch name
 */
export function getCurrentBranchSync(projectRoot: string): string | null {
	if (!projectRoot) {
		return null;
	}

	try {
		const stdout = execSync('git rev-parse --abbrev-ref HEAD', {
			cwd: projectRoot,
			encoding: 'utf8'
		});
		return stdout.trim();
	} catch (error) {
		return null;
	}
}

/**
 * Get list of all local git branches
 */
export async function getLocalBranches(projectRoot: string): Promise<string[]> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for getLocalBranches');
	}

	try {
		const { stdout } = await execAsync(
			'git branch --format="%(refname:short)"',
			{ cwd: projectRoot, maxBuffer: 10 * 1024 * 1024 }
		);
		return stdout
			.trim()
			.split('\n')
			.filter((branch) => branch.length > 0)
			.map((branch) => branch.trim());
	} catch (error) {
		return [];
	}
}

/**
 * Get list of all remote branches
 */
export async function getRemoteBranches(
	projectRoot: string
): Promise<string[]> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for getRemoteBranches');
	}

	try {
		const { stdout } = await execAsync(
			'git branch -r --format="%(refname:short)"',
			{ cwd: projectRoot, maxBuffer: 10 * 1024 * 1024 }
		);
		const names = stdout
			.trim()
			.split('\n')
			.filter((branch) => branch.length > 0 && !branch.includes('HEAD'))
			.map((branch) => branch.replace(/^[^/]+\//, '').trim());
		return Array.from(new Set(names));
	} catch (error) {
		return [];
	}
}

/**
 * Check if gh CLI is available and authenticated
 */
export async function isGhCliAvailable(projectRoot?: string): Promise<boolean> {
	try {
		const options = projectRoot ? { cwd: projectRoot } : {};
		await execAsync('gh auth status', options);
		return true;
	} catch (error) {
		return false;
	}
}

/**
 * Get GitHub repository information using gh CLI
 */
export async function getGitHubRepoInfo(
	projectRoot: string
): Promise<GitHubRepoInfo | null> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for getGitHubRepoInfo');
	}

	try {
		const { stdout } = await execAsync(
			'gh repo view --json name,owner,defaultBranchRef',
			{ cwd: projectRoot }
		);
		return JSON.parse(stdout) as GitHubRepoInfo;
	} catch (error) {
		return null;
	}
}

/**
 * Get git repository root directory
 */
export async function getGitRepositoryRoot(
	projectRoot: string
): Promise<string | null> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for getGitRepositoryRoot');
	}

	try {
		const { stdout } = await execAsync('git rev-parse --show-toplevel', {
			cwd: projectRoot
		});
		return stdout.trim();
	} catch (error) {
		return null;
	}
}

/**
 * Get the default branch name for the repository
 */
export async function getDefaultBranch(
	projectRoot: string
): Promise<string | null> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for getDefaultBranch');
	}

	try {
		// Try to get from GitHub first (if gh CLI is available)
		if (await isGhCliAvailable(projectRoot)) {
			const repoInfo = await getGitHubRepoInfo(projectRoot);
			if (repoInfo && repoInfo.defaultBranchRef) {
				return repoInfo.defaultBranchRef.name;
			}
		}

		// Fallback to git remote info (support non-origin remotes)
		const remotesRaw = await execAsync('git remote', { cwd: projectRoot });
		const remotes = remotesRaw.stdout.trim().split('\n').filter(Boolean);
		if (remotes.length > 0) {
			const primary = remotes.includes('origin') ? 'origin' : remotes[0];
			// Parse `git remote show` (preferred)
			try {
				const { stdout } = await execAsync(`git remote show ${primary}`, {
					cwd: projectRoot,
					maxBuffer: 10 * 1024 * 1024
				});
				const m = stdout.match(/HEAD branch:\s+([^\s]+)/);
				if (m) return m[1].trim();
			} catch {}
			// Fallback to symbolic-ref of remote HEAD
			try {
				const { stdout } = await execAsync(
					`git symbolic-ref refs/remotes/${primary}/HEAD`,
					{ cwd: projectRoot }
				);
				return stdout.replace(`refs/remotes/${primary}/`, '').trim();
			} catch {}
		}
		// If we couldn't determine, throw to trigger final fallbacks
		throw new Error('default-branch-not-found');
	} catch (error) {
		// Final fallback - common default branch names
		const commonDefaults = ['main', 'master'];
		const branches = await getLocalBranches(projectRoot);
		const remoteBranches = await getRemoteBranches(projectRoot);

		for (const defaultName of commonDefaults) {
			if (
				branches.includes(defaultName) ||
				remoteBranches.includes(defaultName)
			) {
				return defaultName;
			}
		}

		return null;
	}
}

/**
 * Check if we're currently on the default branch
 */
export async function isOnDefaultBranch(projectRoot: string): Promise<boolean> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for isOnDefaultBranch');
	}

	try {
		const [currentBranch, defaultBranch] = await Promise.all([
			getCurrentBranch(projectRoot),
			getDefaultBranch(projectRoot)
		]);
		return (
			currentBranch !== null &&
			defaultBranch !== null &&
			currentBranch === defaultBranch
		);
	} catch (error) {
		return false;
	}
}

/**
 * Check if the current working directory is inside a Git work-tree
 */
export function insideGitWorkTree(): boolean {
	try {
		execSync('git rev-parse --is-inside-work-tree', {
			stdio: 'ignore',
			cwd: process.cwd()
		});
		return true;
	} catch {
		return false;
	}
}

/**
 * Sanitize branch name to be a valid tag name
 */
export function sanitizeBranchNameForTag(branchName: string): string {
	if (!branchName || typeof branchName !== 'string') {
		return 'unknown-branch';
	}

	// Replace invalid characters with hyphens and clean up
	return branchName
		.replace(/[^a-zA-Z0-9_.-]/g, '-') // Replace invalid chars with hyphens (allow dots)
		.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
		.replace(/-+/g, '-') // Collapse multiple hyphens
		.toLowerCase() // Convert to lowercase
		.substring(0, 50); // Limit length
}

/**
 * Check if a branch name would create a valid tag name
 */
export function isValidBranchForTag(branchName: string): boolean {
	if (!branchName || typeof branchName !== 'string') {
		return false;
	}

	// Check if it's a reserved branch name that shouldn't become tags
	const reservedBranches = ['main', 'master', 'develop', 'dev', 'head'];
	if (reservedBranches.includes(branchName.toLowerCase())) {
		return false;
	}

	// Check if sanitized name would be meaningful
	const sanitized = sanitizeBranchNameForTag(branchName);
	return sanitized.length > 0 && sanitized !== 'unknown-branch';
}

/**
 * Git worktree information
 */
export interface GitWorktree {
	path: string;
	branch: string | null;
	head: string;
}

/**
 * Get list of all git worktrees
 */
export async function getWorktrees(
	projectRoot: string
): Promise<GitWorktree[]> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for getWorktrees');
	}

	try {
		const { stdout } = await execAsync('git worktree list --porcelain', {
			cwd: projectRoot
		});

		const worktrees: GitWorktree[] = [];
		const lines = stdout.trim().split('\n');
		let current: Partial<GitWorktree> = {};

		for (const line of lines) {
			if (line.startsWith('worktree ')) {
				// flush previous entry if present
				if (current.path) {
					worktrees.push({
						path: current.path,
						branch: current.branch || null,
						head: current.head || ''
					});
					current = {};
				}
				current.path = line.substring(9);
			} else if (line.startsWith('HEAD ')) {
				current.head = line.substring(5);
			} else if (line.startsWith('branch ')) {
				current.branch = line.substring(7).replace('refs/heads/', '');
			} else if (line === '' && current.path) {
				worktrees.push({
					path: current.path,
					branch: current.branch || null,
					head: current.head || ''
				});
				current = {};
			}
		}

		// Handle last entry if no trailing newline
		if (current.path) {
			worktrees.push({
				path: current.path,
				branch: current.branch || null,
				head: current.head || ''
			});
		}

		return worktrees;
	} catch (error) {
		return [];
	}
}

/**
 * Check if a branch is checked out in any worktree
 * Returns the worktree path if found, null otherwise
 */
export async function isBranchCheckedOut(
	projectRoot: string,
	branchName: string
): Promise<string | null> {
	if (!projectRoot) {
		throw new Error('projectRoot is required for isBranchCheckedOut');
	}
	if (!branchName) {
		throw new Error('branchName is required for isBranchCheckedOut');
	}

	const worktrees = await getWorktrees(projectRoot);
	const worktree = worktrees.find((wt) => wt.branch === branchName);
	return worktree ? worktree.path : null;
}

```
Page 21/50FirstPrevNextLast