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

--------------------------------------------------------------------------------
/src/utils/stream-parser.js:
--------------------------------------------------------------------------------

```javascript
import { JSONParser } from '@streamparser/json';

/**
 * Custom error class for streaming-related failures
 * Provides error codes for robust error handling without string matching
 */
export class StreamingError extends Error {
	constructor(message, code) {
		super(message);
		this.name = 'StreamingError';
		this.code = code;

		// Maintain proper stack trace (V8 engines)
		if (Error.captureStackTrace) {
			Error.captureStackTrace(this, StreamingError);
		}
	}
}

/**
 * Standard streaming error codes
 */
export const STREAMING_ERROR_CODES = {
	NOT_ASYNC_ITERABLE: 'STREAMING_NOT_SUPPORTED',
	STREAM_PROCESSING_FAILED: 'STREAM_PROCESSING_FAILED',
	STREAM_NOT_ITERABLE: 'STREAM_NOT_ITERABLE',
	BUFFER_SIZE_EXCEEDED: 'BUFFER_SIZE_EXCEEDED'
};

/**
 * Default maximum buffer size (1MB)
 */
export const DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024; // 1MB in bytes

/**
 * Configuration options for the streaming JSON parser
 */
class StreamParserConfig {
	constructor(config = {}) {
		this.jsonPaths = config.jsonPaths;
		this.onProgress = config.onProgress;
		this.onError = config.onError;
		this.estimateTokens =
			config.estimateTokens || ((text) => Math.ceil(text.length / 4));
		this.expectedTotal = config.expectedTotal || 0;
		this.fallbackItemExtractor = config.fallbackItemExtractor;
		this.itemValidator =
			config.itemValidator || StreamParserConfig.defaultItemValidator;
		this.maxBufferSize = config.maxBufferSize || DEFAULT_MAX_BUFFER_SIZE;

		this.validate();
	}

	validate() {
		if (!this.jsonPaths || !Array.isArray(this.jsonPaths)) {
			throw new Error('jsonPaths is required and must be an array');
		}
		if (this.jsonPaths.length === 0) {
			throw new Error('jsonPaths array cannot be empty');
		}
		if (this.maxBufferSize <= 0) {
			throw new Error('maxBufferSize must be positive');
		}
		if (this.expectedTotal < 0) {
			throw new Error('expectedTotal cannot be negative');
		}
		if (this.estimateTokens && typeof this.estimateTokens !== 'function') {
			throw new Error('estimateTokens must be a function');
		}
		if (this.onProgress && typeof this.onProgress !== 'function') {
			throw new Error('onProgress must be a function');
		}
		if (this.onError && typeof this.onError !== 'function') {
			throw new Error('onError must be a function');
		}
		if (
			this.fallbackItemExtractor &&
			typeof this.fallbackItemExtractor !== 'function'
		) {
			throw new Error('fallbackItemExtractor must be a function');
		}
		if (this.itemValidator && typeof this.itemValidator !== 'function') {
			throw new Error('itemValidator must be a function');
		}
	}

	static defaultItemValidator(item) {
		return (
			item && item.title && typeof item.title === 'string' && item.title.trim()
		);
	}
}

/**
 * Manages progress tracking and metadata
 */
class ProgressTracker {
	constructor(config) {
		this.onProgress = config.onProgress;
		this.onError = config.onError;
		this.estimateTokens = config.estimateTokens;
		this.expectedTotal = config.expectedTotal;
		this.parsedItems = [];
		this.accumulatedText = '';
	}

	addItem(item) {
		this.parsedItems.push(item);
		this.reportProgress(item);
	}

	addText(chunk) {
		this.accumulatedText += chunk;
	}

	getMetadata() {
		return {
			currentCount: this.parsedItems.length,
			expectedTotal: this.expectedTotal,
			accumulatedText: this.accumulatedText,
			estimatedTokens: this.estimateTokens(this.accumulatedText)
		};
	}

	reportProgress(item) {
		if (!this.onProgress) return;

		try {
			this.onProgress(item, this.getMetadata());
		} catch (progressError) {
			this.handleProgressError(progressError);
		}
	}

	handleProgressError(error) {
		if (this.onError) {
			this.onError(new Error(`Progress callback failed: ${error.message}`));
		}
	}
}

/**
 * Handles stream processing with different stream types
 */
class StreamProcessor {
	constructor(onChunk) {
		this.onChunk = onChunk;
	}

	async process(textStream) {
		const streamHandler = this.detectStreamType(textStream);
		await streamHandler(textStream);
	}

	detectStreamType(textStream) {
		// Check for textStream property
		if (this.hasAsyncIterator(textStream?.textStream)) {
			return (stream) => this.processTextStream(stream.textStream);
		}

		// Check for fullStream property
		if (this.hasAsyncIterator(textStream?.fullStream)) {
			return (stream) => this.processFullStream(stream.fullStream);
		}

		// Check if stream itself is iterable
		if (this.hasAsyncIterator(textStream)) {
			return (stream) => this.processDirectStream(stream);
		}

		throw new StreamingError(
			'Stream object is not iterable - no textStream, fullStream, or direct async iterator found',
			STREAMING_ERROR_CODES.STREAM_NOT_ITERABLE
		);
	}

	hasAsyncIterator(obj) {
		return obj && typeof obj[Symbol.asyncIterator] === 'function';
	}

	async processTextStream(stream) {
		for await (const chunk of stream) {
			this.onChunk(chunk);
		}
	}

	async processFullStream(stream) {
		for await (const chunk of stream) {
			if (chunk.type === 'text-delta' && chunk.textDelta) {
				this.onChunk(chunk.textDelta);
			}
		}
	}

	async processDirectStream(stream) {
		for await (const chunk of stream) {
			this.onChunk(chunk);
		}
	}
}

/**
 * Manages JSON parsing with the streaming parser
 */
class JSONStreamParser {
	constructor(config, progressTracker) {
		this.config = config;
		this.progressTracker = progressTracker;
		this.parser = new JSONParser({ paths: config.jsonPaths });
		this.setupHandlers();
	}

	setupHandlers() {
		this.parser.onValue = (value, key, parent, stack) => {
			this.handleParsedValue(value);
		};

		this.parser.onError = (error) => {
			this.handleParseError(error);
		};
	}

	handleParsedValue(value) {
		// Extract the actual item object from the parser's nested structure
		const item = value.value || value;

		if (this.config.itemValidator(item)) {
			this.progressTracker.addItem(item);
		}
	}

	handleParseError(error) {
		if (this.config.onError) {
			this.config.onError(new Error(`JSON parsing error: ${error.message}`));
		}
		// Don't throw here - we'll handle this in the fallback logic
	}

	write(chunk) {
		this.parser.write(chunk);
	}

	end() {
		this.parser.end();
	}
}

/**
 * Handles fallback parsing when streaming fails
 */
class FallbackParser {
	constructor(config, progressTracker) {
		this.config = config;
		this.progressTracker = progressTracker;
	}

	async attemptParsing() {
		if (!this.shouldAttemptFallback()) {
			return [];
		}

		try {
			return await this.parseFallbackItems();
		} catch (parseError) {
			this.handleFallbackError(parseError);
			return [];
		}
	}

	shouldAttemptFallback() {
		return (
			this.config.expectedTotal > 0 &&
			this.progressTracker.parsedItems.length < this.config.expectedTotal &&
			this.progressTracker.accumulatedText &&
			this.config.fallbackItemExtractor
		);
	}

	async parseFallbackItems() {
		const jsonText = this._cleanJsonText(this.progressTracker.accumulatedText);
		const fullResponse = JSON.parse(jsonText);
		const fallbackItems = this.config.fallbackItemExtractor(fullResponse);

		if (!Array.isArray(fallbackItems)) {
			return [];
		}

		return this._processNewItems(fallbackItems);
	}

	_cleanJsonText(text) {
		// Remove markdown code block wrappers and trim whitespace
		return text
			.replace(/^```(?:json)?\s*\n?/i, '')
			.replace(/\n?```\s*$/i, '')
			.trim();
	}

	_processNewItems(fallbackItems) {
		// Only add items we haven't already parsed
		const itemsToAdd = fallbackItems.slice(
			this.progressTracker.parsedItems.length
		);
		const newItems = [];

		for (const item of itemsToAdd) {
			if (this.config.itemValidator(item)) {
				newItems.push(item);
				this.progressTracker.addItem(item);
			}
		}

		return newItems;
	}

	handleFallbackError(error) {
		if (this.progressTracker.parsedItems.length === 0) {
			throw new Error(`Failed to parse AI response as JSON: ${error.message}`);
		}
		// If we have some items from streaming, continue with those
	}
}

/**
 * Buffer size validator
 */
class BufferSizeValidator {
	constructor(maxSize) {
		this.maxSize = maxSize;
		this.currentSize = 0;
	}

	validateChunk(existingText, newChunk) {
		const newSize = Buffer.byteLength(existingText + newChunk, 'utf8');

		if (newSize > this.maxSize) {
			throw new StreamingError(
				`Buffer size exceeded: ${newSize} bytes > ${this.maxSize} bytes maximum`,
				STREAMING_ERROR_CODES.BUFFER_SIZE_EXCEEDED
			);
		}

		this.currentSize = newSize;
	}
}

/**
 * Main orchestrator for stream parsing
 */
class StreamParserOrchestrator {
	constructor(config) {
		this.config = new StreamParserConfig(config);
		this.progressTracker = new ProgressTracker(this.config);
		this.bufferValidator = new BufferSizeValidator(this.config.maxBufferSize);
		this.jsonParser = new JSONStreamParser(this.config, this.progressTracker);
		this.fallbackParser = new FallbackParser(this.config, this.progressTracker);
	}

	async parse(textStream) {
		if (!textStream) {
			throw new Error('No text stream provided');
		}

		await this.processStream(textStream);
		await this.waitForParsingCompletion();

		const usedFallback = await this.attemptFallbackIfNeeded();

		return this.buildResult(usedFallback);
	}

	async processStream(textStream) {
		const processor = new StreamProcessor((chunk) => {
			this.bufferValidator.validateChunk(
				this.progressTracker.accumulatedText,
				chunk
			);
			this.progressTracker.addText(chunk);
			this.jsonParser.write(chunk);
		});

		try {
			await processor.process(textStream);
		} catch (streamError) {
			this.handleStreamError(streamError);
		}

		this.jsonParser.end();
	}

	handleStreamError(error) {
		// Re-throw StreamingError as-is, wrap other errors
		if (error instanceof StreamingError) {
			throw error;
		}
		throw new StreamingError(
			`Failed to process AI text stream: ${error.message}`,
			STREAMING_ERROR_CODES.STREAM_PROCESSING_FAILED
		);
	}

	async waitForParsingCompletion() {
		// Wait for final parsing to complete (JSON parser may still be processing)
		await new Promise((resolve) => setTimeout(resolve, 100));
	}

	async attemptFallbackIfNeeded() {
		const fallbackItems = await this.fallbackParser.attemptParsing();
		return fallbackItems.length > 0;
	}

	buildResult(usedFallback) {
		const metadata = this.progressTracker.getMetadata();

		return {
			items: this.progressTracker.parsedItems,
			accumulatedText: metadata.accumulatedText,
			estimatedTokens: metadata.estimatedTokens,
			usedFallback
		};
	}
}

/**
 * Parse a streaming JSON response with progress tracking
 *
 * Example with custom buffer size:
 * ```js
 * const result = await parseStream(stream, {
 *   jsonPaths: ['$.tasks.*'],
 *   maxBufferSize: 2 * 1024 * 1024 // 2MB
 * });
 * ```
 *
 * @param {Object} textStream - The AI service text stream object
 * @param {Object} config - Configuration options
 * @returns {Promise<Object>} Parsed result with metadata
 */
export async function parseStream(textStream, config = {}) {
	const orchestrator = new StreamParserOrchestrator(config);
	return orchestrator.parse(textStream);
}

/**
 * Process different types of text streams
 * @param {Object} textStream - The stream object from AI service
 * @param {Function} onChunk - Callback for each text chunk
 */
export async function processTextStream(textStream, onChunk) {
	const processor = new StreamProcessor(onChunk);
	await processor.process(textStream);
}

/**
 * Attempt fallback JSON parsing when streaming parsing is incomplete
 * @param {string} accumulatedText - Complete accumulated text
 * @param {Array} existingItems - Items already parsed from streaming
 * @param {number} expectedTotal - Expected total number of items
 * @param {Object} config - Configuration for progress reporting
 * @returns {Promise<Array>} Additional items found via fallback parsing
 */
export async function attemptFallbackParsing(
	accumulatedText,
	existingItems,
	expectedTotal,
	config
) {
	// Create a temporary progress tracker for backward compatibility
	const progressTracker = new ProgressTracker({
		onProgress: config.onProgress,
		onError: config.onError,
		estimateTokens: config.estimateTokens,
		expectedTotal
	});

	progressTracker.parsedItems = existingItems;
	progressTracker.accumulatedText = accumulatedText;

	const fallbackParser = new FallbackParser(
		{
			...config,
			expectedTotal,
			itemValidator:
				config.itemValidator || StreamParserConfig.defaultItemValidator
		},
		progressTracker
	);

	return fallbackParser.attemptParsing();
}

```

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

```typescript
/**
 * Authentication manager for Task Master CLI
 */

import fs from 'fs';
import os from 'os';
import path from 'path';
import {
	ERROR_CODES,
	TaskMasterError
} from '../../../common/errors/task-master-error.js';
import { getLogger } from '../../../common/logger/index.js';
import type { Brief } from '../../briefs/types.js';
import { SupabaseAuthClient } from '../../integration/clients/supabase-client.js';
import { ContextStore } from '../services/context-store.js';
import { OAuthService } from '../services/oauth-service.js';
import {
	type Organization,
	OrganizationService,
	type RemoteTask
} from '../services/organization.service.js';
import {
	AuthConfig,
	AuthCredentials,
	AuthenticationError,
	OAuthFlowOptions,
	UserContext,
	UserContextWithBrief
} from '../types.js';

/**
 * Authentication manager class
 */
export class AuthManager {
	private static instance: AuthManager | null = null;
	private static readonly staticLogger = getLogger('AuthManager');
	private contextStore: ContextStore;
	private oauthService: OAuthService;
	public supabaseClient: SupabaseAuthClient;
	private organizationService?: OrganizationService;
	private readonly logger = getLogger('AuthManager');
	private readonly LEGACY_AUTH_FILE = path.join(
		os.homedir(),
		'.taskmaster',
		'auth.json'
	);

	private constructor(config?: Partial<AuthConfig>) {
		this.contextStore = ContextStore.getInstance();
		this.supabaseClient = new SupabaseAuthClient();
		// Pass the supabase client to OAuthService so they share the same instance
		this.oauthService = new OAuthService(
			this.contextStore,
			this.supabaseClient,
			config
		);

		// Initialize Supabase client with session restoration
		// Fire-and-forget with catch handler to prevent unhandled rejections
		this.initializeSupabaseSession().catch(() => {
			// Errors are already logged in initializeSupabaseSession
		});

		// Migrate legacy auth.json if it exists
		// Fire-and-forget with catch handler
		this.migrateLegacyAuth().catch(() => {
			// Errors are already logged in migrateLegacyAuth
		});
	}

	/**
	 * Initialize Supabase session from stored credentials
	 */
	private async initializeSupabaseSession(): Promise<void> {
		try {
			await this.supabaseClient.initialize();
		} catch (error) {
			// Log but don't throw - session might not exist yet
			this.logger.debug('No existing session to restore');
		}
	}

	/**
	 * Migrate legacy auth.json to Supabase session
	 * Called once during AuthManager initialization
	 */
	private async migrateLegacyAuth(): Promise<void> {
		if (!fs.existsSync(this.LEGACY_AUTH_FILE)) {
			return;
		}

		try {
			// If we have a valid Supabase session, delete legacy file
			const hasSession = await this.hasValidSession();
			if (hasSession) {
				fs.unlinkSync(this.LEGACY_AUTH_FILE);
				this.logger.info('Migrated to Supabase auth, removed legacy auth.json');
				return;
			}

			// Otherwise, user needs to re-authenticate
			this.logger.warn('Legacy auth.json found but no valid Supabase session.');
			this.logger.warn('Please run: task-master auth login');
		} catch (error) {
			this.logger.debug('Error during legacy auth migration:', error);
		}
	}

	/**
	 * Get singleton instance
	 */
	static getInstance(config?: Partial<AuthConfig>): AuthManager {
		if (!AuthManager.instance) {
			AuthManager.instance = new AuthManager(config);
		} else if (config) {
			// Warn if config is provided after initialization
			AuthManager.staticLogger.warn(
				'getInstance called with config after initialization; config is ignored.'
			);
		}
		return AuthManager.instance;
	}

	/**
	 * Reset the singleton instance (useful for testing)
	 */
	static resetInstance(): void {
		AuthManager.instance = null;
		ContextStore.resetInstance();
	}

	/**
	 * Get access token from current Supabase session
	 * @returns Access token or null if not authenticated
	 */
	async getAccessToken(): Promise<string | null> {
		const session = await this.supabaseClient.getSession();
		return session?.access_token || null;
	}

	/**
	 * Get authentication credentials from Supabase session
	 * Modern replacement for legacy getCredentials()
	 * @returns AuthCredentials object or null if not authenticated
	 */
	async getAuthCredentials(): Promise<AuthCredentials | null> {
		const session = await this.supabaseClient.getSession();
		if (!session) return null;

		const user = session.user;
		const context = this.contextStore.getUserContext();

		return {
			token: session.access_token,
			refreshToken: session.refresh_token,
			userId: user.id,
			email: user.email,
			expiresAt: session.expires_at
				? new Date(session.expires_at * 1000).toISOString()
				: undefined,
			tokenType: 'standard',
			savedAt: new Date().toISOString(),
			selectedContext: context || undefined
		};
	}

	/**
	 * Start OAuth 2.0 Authorization Code Flow with browser handling
	 */
	async authenticateWithOAuth(
		options: OAuthFlowOptions = {}
	): Promise<AuthCredentials> {
		return this.oauthService.authenticate(options);
	}

	/**
	 * Authenticate using a one-time token
	 * This is useful for CLI authentication in SSH/remote environments
	 * where browser-based auth is not practical
	 */
	async authenticateWithCode(token: string): Promise<AuthCredentials> {
		try {
			this.logger.info('Authenticating with one-time token...');

			// Verify the token and get session from Supabase
			const session = await this.supabaseClient.verifyOneTimeCode(token);

			if (!session || !session.access_token) {
				throw new AuthenticationError(
					'Failed to obtain access token from token',
					'NO_TOKEN'
				);
			}

			// Get user information
			const user = await this.supabaseClient.getUser();

			if (!user) {
				throw new AuthenticationError(
					'Failed to get user information',
					'INVALID_RESPONSE'
				);
			}

			// Store user context
			this.contextStore.saveContext({
				userId: user.id,
				email: user.email
			});

			// Build credentials response
			const context = this.contextStore.getUserContext();
			const credentials: AuthCredentials = {
				token: session.access_token,
				refreshToken: session.refresh_token,
				userId: user.id,
				email: user.email,
				expiresAt: session.expires_at
					? new Date(session.expires_at * 1000).toISOString()
					: undefined,
				tokenType: 'standard',
				savedAt: new Date().toISOString(),
				selectedContext: context || undefined
			};

			this.logger.info('Successfully authenticated with token');
			return credentials;
		} catch (error) {
			if (error instanceof AuthenticationError) {
				throw error;
			}
			throw new AuthenticationError(
				`Token authentication failed: ${(error as Error).message}`,
				'CODE_AUTH_FAILED'
			);
		}
	}

	/**
	 * Get the authorization URL (for browser opening)
	 */
	getAuthorizationUrl(): string | null {
		return this.oauthService.getAuthorizationUrl();
	}

	/**
	 * Refresh authentication token using Supabase session
	 * Note: Supabase handles token refresh automatically via the session storage adapter.
	 * This method is mainly for explicit refresh requests.
	 */
	async refreshToken(): Promise<AuthCredentials> {
		try {
			// Use Supabase's built-in session refresh
			const session = await this.supabaseClient.refreshSession();

			if (!session) {
				throw new AuthenticationError(
					'Failed to refresh session',
					'REFRESH_FAILED'
				);
			}

			// Sync user info to context store
			this.contextStore.saveContext({
				userId: session.user.id,
				email: session.user.email
			});

			// Build credentials response
			const context = this.contextStore.getContext();
			const credentials: AuthCredentials = {
				token: session.access_token,
				refreshToken: session.refresh_token,
				userId: session.user.id,
				email: session.user.email,
				expiresAt: session.expires_at
					? new Date(session.expires_at * 1000).toISOString()
					: undefined,
				savedAt: new Date().toISOString(),
				selectedContext: context?.selectedContext
			};

			return credentials;
		} catch (error) {
			if (error instanceof AuthenticationError) {
				throw error;
			}
			throw new AuthenticationError(
				`Token refresh failed: ${(error as Error).message}`,
				'REFRESH_FAILED'
			);
		}
	}

	/**
	 * Logout and clear credentials
	 */
	async logout(): Promise<void> {
		try {
			// First try to sign out from Supabase to revoke tokens
			await this.supabaseClient.signOut();
		} catch (error) {
			// Log but don't throw - we still want to clear local credentials
			this.logger.warn('Failed to sign out from Supabase:', error);
		}

		// Clear app context
		this.contextStore.clearContext();
		// Session is cleared by supabaseClient.signOut()

		// Clear legacy auth.json if it exists
		try {
			if (fs.existsSync(this.LEGACY_AUTH_FILE)) {
				fs.unlinkSync(this.LEGACY_AUTH_FILE);
				this.logger.debug('Cleared legacy auth.json');
			}
		} catch (error) {
			// Ignore errors clearing legacy file
			this.logger.debug('No legacy credentials to clear');
		}
	}

	/**
	 * Check if valid Supabase session exists
	 * @returns true if a valid session exists
	 */
	async hasValidSession(): Promise<boolean> {
		try {
			const session = await this.supabaseClient.getSession();
			return session !== null;
		} catch {
			return false;
		}
	}

	/**
	 * Get the current Supabase session
	 */
	async getSession() {
		return this.supabaseClient.getSession();
	}

	/**
	 * Get stored user context (userId, email)
	 */
	getStoredContext() {
		return this.contextStore.getContext();
	}

	/**
	 * Get the current user context (org/brief selection)
	 */
	getContext(): UserContext | null {
		return this.contextStore.getUserContext();
	}

	/**
	 * Update the user context (org/brief selection)
	 */
	async updateContext(context: Partial<UserContext>): Promise<void> {
		if (!(await this.hasValidSession())) {
			throw new AuthenticationError('Not authenticated', 'NOT_AUTHENTICATED');
		}

		this.contextStore.updateUserContext(context);
	}

	/**
	 * Clear the user context
	 */
	async clearContext(): Promise<void> {
		if (!(await this.hasValidSession())) {
			throw new AuthenticationError('Not authenticated', 'NOT_AUTHENTICATED');
		}

		this.contextStore.clearUserContext();
	}

	/**
	 * Get the organization service instance
	 * Uses the Supabase client with the current session
	 */
	private async getOrganizationService(): Promise<OrganizationService> {
		if (!this.organizationService) {
			// Check if we have a valid Supabase session
			const session = await this.supabaseClient.getSession();

			if (!session) {
				throw new AuthenticationError('Not authenticated', 'NOT_AUTHENTICATED');
			}

			// Use the SupabaseAuthClient which now has the session
			const supabaseClient = this.supabaseClient.getClient();
			this.organizationService = new OrganizationService(supabaseClient as any);
		}
		return this.organizationService;
	}

	/**
	 * Get all organizations for the authenticated user
	 */
	async getOrganizations(): Promise<Organization[]> {
		const service = await this.getOrganizationService();
		return service.getOrganizations();
	}

	/**
	 * Get all briefs for a specific organization
	 */
	async getBriefs(orgId: string): Promise<Brief[]> {
		const service = await this.getOrganizationService();
		return service.getBriefs(orgId);
	}

	/**
	 * Get a specific organization by ID
	 */
	async getOrganization(orgId: string): Promise<Organization | null> {
		const service = await this.getOrganizationService();
		return service.getOrganization(orgId);
	}

	/**
	 * Get a specific brief by ID
	 */
	async getBrief(briefId: string): Promise<Brief | null> {
		const service = await this.getOrganizationService();
		return service.getBrief(briefId);
	}

	/**
	 * Get all tasks for a specific brief
	 */
	async getTasks(briefId: string): Promise<RemoteTask[]> {
		const service = await this.getOrganizationService();
		return service.getTasks(briefId);
	}

	/**
	 * Ensure a brief is selected in the current context
	 * Throws a TaskMasterError if no brief is selected
	 * @param operation - The operation name for error context
	 * @returns The current user context with a guaranteed briefId
	 */
	ensureBriefSelected(operation: string): UserContextWithBrief {
		const context = this.getContext();

		if (!context?.briefId) {
			throw new TaskMasterError(
				'No brief selected',
				ERROR_CODES.NO_BRIEF_SELECTED,
				{
					operation,
					userMessage:
						'No brief selected. Please select a brief first using: tm context brief <brief-id> or tm context brief <brief-url>'
				}
			);
		}

		return context as UserContextWithBrief;
	}
}

```

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

```typescript
/**
 * @fileoverview Tags Command - Manage task organization with tags
 * Provides tag/brief management with file and API storage support
 */

import { Command } from 'commander';
import type { TmCore } from '@tm/core';
import { createTmCore, getProjectPaths } from '@tm/core';
import { displayError } from '../utils/index.js';

/**
 * TODO: TECH DEBT - Architectural Refactor Needed
 *
 * Current State:
 * - This command imports legacy JS functions from scripts/modules/task-manager/tag-management.js
 * - These functions contain business logic that violates architecture guidelines (see CLAUDE.md)
 *
 * Target State:
 * - Move all business logic to TagService in @tm/core
 * - CLI should only handle presentation (argument parsing, output formatting)
 * - Remove dependency on legacy scripts/ directory
 *
 * Complexity:
 * - Legacy functions handle both API and file storage via bridge pattern
 * - Need to migrate API integration logic to @tm/core first
 * - Affects MCP layer as well (should share same @tm/core APIs)
 *
 * Priority: Medium (improves testability, maintainability, and code reuse)
 */
import {
	createTag as legacyCreateTag,
	deleteTag as legacyDeleteTag,
	tags as legacyListTags,
	useTag as legacyUseTag,
	renameTag as legacyRenameTag,
	copyTag as legacyCopyTag
} from '../../../../scripts/modules/task-manager/tag-management.js';

/**
 * Result type from tags command
 */
export interface TagsResult {
	success: boolean;
	action: 'list' | 'add' | 'use' | 'remove' | 'rename' | 'copy';
	tags?: any[];
	currentTag?: string | null;
	message?: string;
}

/**
 * Legacy function return types
 */
interface LegacyListTagsResult {
	tags: any[];
	currentTag: string | null;
	totalTags: number;
}

interface LegacyUseTagResult {
	currentTag: string;
}

interface LegacyCreateTagOptions {
	description?: string;
	copyFromTag?: string;
	fromBranch?: boolean;
}

/**
 * TagsCommand - Manage tags/briefs for task organization
 */
export class TagsCommand extends Command {
	private tmCore?: TmCore;
	private lastResult?: TagsResult;
	private throwOnError: boolean = false;

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

		// Configure the command
		this.description('Manage tags for task organization');

		// Add subcommands
		this.addListCommand();
		this.addAddCommand();
		this.addUseCommand();
		this.addRemoveCommand();
		this.addRenameCommand();
		this.addCopyCommand();

		// Default action: list tags
		this.action(async () => {
			await this.executeList();
		});
	}

	/**
	 * Add list subcommand
	 */
	private addListCommand(): void {
		this.command('list')
			.description('List all tags with statistics (default action)')
			.option('--show-metadata', 'Show additional tag metadata')
			.addHelpText(
				'after',
				`
Examples:
  $ tm tags          # List all tags (default)
  $ tm tags list     # List all tags (explicit)
  $ tm tags list --show-metadata  # List with metadata
`
			)
			.action(async (options) => {
				await this.executeList(options);
			});
	}

	/**
	 * Add add subcommand
	 */
	private addAddCommand(): void {
		this.command('add')
			.description('Create a new tag')
			.argument('<name>', 'Name of the tag to create')
			.option('--description <desc>', 'Tag description')
			.option('--copy-from <tag>', 'Copy tasks from another tag')
			.option('--from-branch', 'Create tag from current git branch name')
			.addHelpText(
				'after',
				`
Examples:
  $ tm tags add feature-auth         # Create new tag
  $ tm tags add sprint-2 --copy-from sprint-1  # Create with tasks copied
  $ tm tags add --from-branch        # Create from current git branch

Note: When using API storage, this will redirect you to the web UI to create a brief.
`
			)
			.action(async (name, options) => {
				await this.executeAdd(name, options);
			});
	}

	/**
	 * Add use subcommand
	 */
	private addUseCommand(): void {
		this.command('use')
			.description('Switch to a different tag')
			.argument('<name>', 'Name or ID of the tag to switch to')
			.addHelpText(
				'after',
				`
Examples:
  $ tm tags use feature-auth    # Switch by name
  $ tm tags use abc123          # Switch by ID (last 8 chars)

Note: For API storage, this switches the active brief in your context.
`
			)
			.action(async (name) => {
				await this.executeUse(name);
			});
	}

	/**
	 * Add remove subcommand
	 */
	private addRemoveCommand(): void {
		this.command('remove')
			.description('Remove a tag')
			.argument('<name>', 'Name or ID of the tag to remove')
			.option('-y, --yes', 'Skip confirmation prompt')
			.addHelpText(
				'after',
				`
Examples:
  $ tm tags remove old-feature      # Remove tag with confirmation
  $ tm tags remove old-feature -y   # Remove without confirmation

Warning: This will delete all tasks in the tag!
`
			)
			.action(async (name, options) => {
				await this.executeRemove(name, options);
			});
	}

	/**
	 * Add rename subcommand
	 */
	private addRenameCommand(): void {
		this.command('rename')
			.description('Rename a tag')
			.argument('<oldName>', 'Current tag name')
			.argument('<newName>', 'New tag name')
			.addHelpText(
				'after',
				`
Examples:
  $ tm tags rename old-name new-name
`
			)
			.action(async (oldName, newName) => {
				await this.executeRename(oldName, newName);
			});
	}

	/**
	 * Add copy subcommand
	 */
	private addCopyCommand(): void {
		this.command('copy')
			.description('Copy a tag with all its tasks')
			.argument('<source>', 'Source tag name')
			.argument('<target>', 'Target tag name')
			.option('--description <desc>', 'Description for the new tag')
			.addHelpText(
				'after',
				`
Examples:
  $ tm tags copy sprint-1 sprint-2
  $ tm tags copy sprint-1 sprint-2 --description "Next sprint tasks"
`
			)
			.action(async (source, target, options) => {
				await this.executeCopy(source, target, options);
			});
	}

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

	/**
	 * Execute list tags
	 */
	private async executeList(options?: {
		showMetadata?: boolean;
	}): Promise<void> {
		try {
			// Initialize tmCore first (needed by bridge functions)
			await this.initTmCore();

			const { projectRoot, tasksPath } = getProjectPaths();

			// Use legacy function which handles both API and file storage
			const listResult = (await legacyListTags(
				tasksPath,
				{
					showTaskCounts: true,
					showMetadata: options?.showMetadata || false
				},
				{ projectRoot },
				'text'
			)) as LegacyListTagsResult;

			this.setLastResult({
				success: true,
				action: 'list',
				tags: listResult.tags,
				currentTag: listResult.currentTag,
				message: `Found ${listResult.totalTags} tag(s)`
			});
		} catch (error: any) {
			displayError(error);
			this.setLastResult({
				success: false,
				action: 'list',
				message: error.message
			});
			this.handleError(
				error instanceof Error
					? error
					: new Error(error.message || String(error))
			);
		}
	}

	/**
	 * Execute add tag
	 */
	private async executeAdd(
		name: string,
		options?: {
			description?: string;
			copyFrom?: string;
			fromBranch?: boolean;
		}
	): Promise<void> {
		try {
			// Initialize tmCore first (needed by bridge functions)
			await this.initTmCore();

			const { projectRoot, tasksPath } = getProjectPaths();

			// Use legacy function which handles both API and file storage
			await legacyCreateTag(
				tasksPath,
				name,
				{
					description: options?.description,
					copyFromTag: options?.copyFrom,
					fromBranch: options?.fromBranch
				} as LegacyCreateTagOptions,
				{ projectRoot },
				'text'
			);

			this.setLastResult({
				success: true,
				action: 'add',
				message: `Created tag: ${name}`
			});
		} catch (error: any) {
			displayError(error);
			this.setLastResult({
				success: false,
				action: 'add',
				message: error.message
			});
			this.handleError(
				error instanceof Error
					? error
					: new Error(error.message || String(error))
			);
		}
	}

	/**
	 * Execute use/switch tag
	 */
	private async executeUse(name: string): Promise<void> {
		try {
			// Initialize tmCore first (needed by bridge functions)
			await this.initTmCore();

			const { projectRoot, tasksPath } = getProjectPaths();

			// Use legacy function which handles both API and file storage
			const useResult = (await legacyUseTag(
				tasksPath,
				name,
				{},
				{ projectRoot },
				'text'
			)) as LegacyUseTagResult;

			this.setLastResult({
				success: true,
				action: 'use',
				currentTag: useResult.currentTag,
				message: `Switched to tag: ${name}`
			});
		} catch (error: any) {
			displayError(error);
			this.setLastResult({
				success: false,
				action: 'use',
				message: error.message
			});
			this.handleError(
				error instanceof Error
					? error
					: new Error(error.message || String(error))
			);
		}
	}

	/**
	 * Execute remove tag
	 */
	private async executeRemove(
		name: string,
		options?: { yes?: boolean }
	): Promise<void> {
		try {
			// Initialize tmCore first (needed by bridge functions)
			await this.initTmCore();

			const { projectRoot, tasksPath } = getProjectPaths();

			// Use legacy function which handles both API and file storage
			await legacyDeleteTag(
				tasksPath,
				name,
				{ yes: options?.yes || false },
				{ projectRoot },
				'text'
			);

			this.setLastResult({
				success: true,
				action: 'remove',
				message: `Removed tag: ${name}`
			});
		} catch (error: any) {
			displayError(error);
			this.setLastResult({
				success: false,
				action: 'remove',
				message: error.message
			});
			this.handleError(
				error instanceof Error
					? error
					: new Error(error.message || String(error))
			);
		}
	}

	/**
	 * Execute rename tag
	 */
	private async executeRename(oldName: string, newName: string): Promise<void> {
		try {
			// Initialize tmCore first (needed by bridge functions)
			await this.initTmCore();

			const { projectRoot, tasksPath } = getProjectPaths();

			// Use legacy function which handles both API and file storage
			await legacyRenameTag(
				tasksPath,
				oldName,
				newName,
				{},
				{ projectRoot },
				'text'
			);

			this.setLastResult({
				success: true,
				action: 'rename',
				message: `Renamed tag from "${oldName}" to "${newName}"`
			});
		} catch (error: any) {
			displayError(error);
			this.setLastResult({
				success: false,
				action: 'rename',
				message: error.message
			});
			this.handleError(
				error instanceof Error
					? error
					: new Error(error.message || String(error))
			);
		}
	}

	/**
	 * Execute copy tag
	 */
	private async executeCopy(
		source: string,
		target: string,
		options?: { description?: string }
	): Promise<void> {
		try {
			// Initialize tmCore first (needed by bridge functions)
			await this.initTmCore();

			const { projectRoot, tasksPath } = getProjectPaths();

			// Use legacy function which handles both API and file storage
			await legacyCopyTag(
				tasksPath,
				source,
				target,
				{ description: options?.description },
				{ projectRoot },
				'text'
			);

			this.setLastResult({
				success: true,
				action: 'copy',
				message: `Copied tag from "${source}" to "${target}"`
			});
		} catch (error: any) {
			displayError(error);
			this.setLastResult({
				success: false,
				action: 'copy',
				message: error.message
			});
			this.handleError(
				error instanceof Error
					? error
					: new Error(error.message || String(error))
			);
		}
	}

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

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

	/**
	 * Enable throwing errors instead of process.exit for programmatic usage
	 * @param shouldThrow If true, throws errors; if false, calls process.exit (default)
	 */
	public setThrowOnError(shouldThrow: boolean): this {
		this.throwOnError = shouldThrow;
		return this;
	}

	/**
	 * Handle error by either exiting or throwing based on throwOnError flag
	 */
	private handleError(error: Error): never {
		if (this.throwOnError) {
			throw error;
		}
		process.exit(1);
	}

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

```

--------------------------------------------------------------------------------
/apps/docs/capabilities/index.mdx:
--------------------------------------------------------------------------------

```markdown
---
title: Technical Capabilities
sidebarTitle: "Technical Capabilities"
---

# Capabilities (Technical)

Discover the technical capabilities of Task Master, including supported models, integrations, and more. 

# CLI Interface Synopsis

This document outlines the command-line interface (CLI) for the Task Master application, as defined in `bin/task-master.js` and the `scripts/modules/commands.js` file (which I will assume exists based on the context). This guide is intended for those writing user-facing documentation to understand how users interact with the application from the command line.

## Entry Point

The main entry point for the CLI is the `task-master` command, which is an executable script that spawns the main application logic in `scripts/dev.js`.

## Global Options

The following options are available for all commands:

-   `-h, --help`: Display help information.
-   `--version`: Display the application's version.

## Commands

The CLI is organized into a series of commands, each with its own set of options. The following is a summary of the available commands, categorized by their functionality.

### 1. Task and Subtask Management

-   **`add`**: Creates a new task using an AI-powered prompt.
    -   `--prompt <prompt>`: The prompt to use for generating the task.
    -   `--dependencies <dependencies>`: A comma-separated list of task IDs that this task depends on.
    -   `--priority <priority>`: The priority of the task (e.g., `high`, `medium`, `low`).
-   **`add-subtask`**: Adds a subtask to a parent task.
    -   `--parent-id <parentId>`: The ID of the parent task.
    -   `--task-id <taskId>`: The ID of an existing task to convert to a subtask.
    -   `--title <title>`: The title of the new subtask.
-   **`remove`**: Removes one or more tasks or subtasks.
    -   `--ids <ids>`: A comma-separated list of task or subtask IDs to remove.
-   **`remove-subtask`**: Removes a subtask from its parent.
    -   `--id <subtaskId>`: The ID of the subtask to remove (in the format `parentId.subtaskId`).
    -   `--convert-to-task`: Converts the subtask to a standalone task.
-   **`update`**: Updates multiple tasks starting from a specific ID.
    -   `--from <fromId>`: The ID of the task to start updating from.
    -   `--prompt <prompt>`: The new context to apply to the tasks.
-   **`update-task`**: Updates a single task.
    -   `--id <taskId>`: The ID of the task to update.
    -   `--prompt <prompt>`: The new context to apply to the task.
-   **`update-subtask`**: Appends information to a subtask.
    -   `--id <subtaskId>`: The ID of the subtask to update (in the format `parentId.subtaskId`).
    -   `--prompt <prompt>`: The information to append to the subtask.
-   **`move`**: Moves a task or subtask.
    -   `--from <sourceId>`: The ID of the task or subtask to move.
    -   `--to <destinationId>`: The destination ID.
-   **`clear-subtasks`**: Clears all subtasks from one or more tasks.
    -   `--ids <ids>`: A comma-separated list of task IDs.

### 2. Task Information and Status

-   **`list`**: Lists all tasks.
    -   `--status <status>`: Filters tasks by status.
    -   `--with-subtasks`: Includes subtasks in the list.
-   **`show`**: Shows the details of a specific task.
    -   `--id <taskId>`: The ID of the task to show.
-   **`next`**: Shows the next task to work on.
-   **`set-status`**: Sets the status of a task or subtask.
    -   `--id <id>`: The ID of the task or subtask.
    -   `--status <status>`: The new status.

### 3. Task Analysis and Expansion

-   **`parse-prd`**: Parses a PRD to generate tasks.
    -   `--file <file>`: The path to the PRD file.
    -   `--num-tasks <numTasks>`: The number of tasks to generate.
-   **`expand`**: Expands a task into subtasks.
    -   `--id <taskId>`: The ID of the task to expand.
    -   `--num-subtasks <numSubtasks>`: The number of subtasks to generate.
-   **`expand-all`**: Expands all eligible tasks.
    -   `--num-subtasks <numSubtasks>`: The number of subtasks to generate for each task.
-   **`analyze-complexity`**: Analyzes task complexity.
    -   `--file <file>`: The path to the tasks file.
-   **`complexity-report`**: Displays the complexity analysis report.

### 4. Project and Configuration

-   **`init`**: Initializes a new project.
-   **`generate`**: Generates individual task files.
-   **`migrate`**: Migrates a project to the new directory structure.
-   **`research`**: Performs AI-powered research.
    -   `--query <query>`: The research query.

This synopsis provides a comprehensive overview of the CLI commands and their options, which should be helpful for creating user-facing documentation.


# Core Implementation Synopsis

This document provides a high-level overview of the core implementation of the Task Master application, focusing on the functionalities exposed through `scripts/modules/task-manager.js`. This serves as a guide for understanding the application's capabilities when writing user-facing documentation.

## Core Concepts

The application revolves around the management of tasks and subtasks, which are stored in a `tasks.json` file. The core logic provides functionalities to create, read, update, and delete tasks and subtasks, as well as manage their dependencies and statuses.

### Task Structure

A task is a JSON object with the following key properties:

-   `id`: A unique number identifying the task.
-   `title`: A string representing the task's title.
-   `description`: A string providing a brief description of the task.
-   `details`: A string containing detailed information about the task.
-   `testStrategy`: A string describing how to test the task.
-   `status`: A string representing the task's current status (e.g., `pending`, `in-progress`, `done`).
-   `dependencies`: An array of task IDs that this task depends on.
-   `priority`: A string representing the task's priority (e.g., `high`, `medium`, `low`).
-   `subtasks`: An array of subtask objects.

A subtask has a similar structure to a task but is nested within a parent task.

## Feature Categories

The core functionalities can be categorized as follows:

### 1. Task and Subtask Management

These functions are the bread and butter of the application, allowing for the creation, modification, and deletion of tasks and subtasks.

-   **`addTask(prompt, dependencies, priority)`**: Creates a new task using an AI-powered prompt to generate the title, description, details, and test strategy. It can also be used to create a task manually by providing the task data directly.
-   **`addSubtask(parentId, existingTaskId, newSubtaskData)`**: Adds a subtask to a parent task. It can either convert an existing task into a subtask or create a new subtask from scratch.
-   **`removeTask(taskIds)`**: Removes one or more tasks or subtasks.
-   **`removeSubtask(subtaskId, convertToTask)`**: Removes a subtask from its parent. It can optionally convert the subtask into a standalone task.
-   **`updateTaskById(taskId, prompt)`**: Updates a task's information based on a prompt.
-   **`updateSubtaskById(subtaskId, prompt)`**: Appends additional information to a subtask's details.
-   **`updateTasks(fromId, prompt)`**: Updates multiple tasks starting from a specific ID based on a new context.
-   **`moveTask(sourceId, destinationId)`**: Moves a task or subtask to a new position.
-   **`clearSubtasks(taskIds)`**: Clears all subtasks from one or more tasks.

### 2. Task Information and Status

These functions are used to retrieve information about tasks and manage their status.

-   **`listTasks(statusFilter, withSubtasks)`**: Lists all tasks, with options to filter by status and include subtasks.
-   **`findTaskById(taskId)`**: Finds a task by its ID.
-   **`taskExists(taskId)`**: Checks if a task with a given ID exists.
-   **`setTaskStatus(taskIdInput, newStatus)`**: Sets the status of a task or subtask.
-al
-   **`updateSingleTaskStatus(taskIdInput, newStatus)`**: A helper function to update the status of a single task or subtask.
-   **`findNextTask()`**: Determines the next task to work on based on dependencies and status.

### 3. Task Analysis and Expansion

These functions leverage AI to analyze and break down tasks.

-   **`parsePRD(prdPath, numTasks)`**: Parses a Product Requirements Document (PRD) to generate an initial set of tasks.
-   **`expandTask(taskId, numSubtasks)`**: Expands a task into a specified number of subtasks using AI.
-   **`expandAllTasks(numSubtasks)`**: Expands all eligible pending or in-progress tasks.
-   **`analyzeTaskComplexity(options)`**: Analyzes the complexity of tasks and generates recommendations for expansion.
-   **`readComplexityReport()`**: Reads the complexity analysis report.

### 4. Dependency Management

These functions are crucial for managing the relationships between tasks.

-   **`isTaskDependentOn(task, targetTaskId)`**: Checks if a task has a direct or indirect dependency on another task.

### 5. Project and Configuration

These functions are for managing the project and its configuration.

-   **`generateTaskFiles()`**: Generates individual task files from `tasks.json`.
-   **`migrateProject()`**: Migrates the project to the new `.taskmaster` directory structure.
-   **`performResearch(query, options)`**: Performs AI-powered research with project context.

This overview should provide a solid foundation for creating user-facing documentation. For more detailed information on each function, refer to the source code in `scripts/modules/task-manager/`.


# MCP Interface Synopsis

This document provides an overview of the MCP (Machine-to-Machine Communication Protocol) interface for the Task Master application. The MCP interface is defined in the `mcp-server/` directory and exposes the application's core functionalities as a set of tools that can be called remotely.

## Core Concepts

The MCP interface is built on top of the `fastmcp` library and registers a set of tools that correspond to the core functionalities of the Task Master application. These tools are defined in the `mcp-server/src/tools/` directory and are registered with the MCP server in `mcp-server/src/tools/index.js`.

Each tool is defined with a name, a description, and a set of parameters that are validated using the `zod` library. The `execute` function of each tool calls the corresponding core logic function from `scripts/modules/task-manager.js`.

## Tool Categories

The MCP tools can be categorized in the same way as the core functionalities:

### 1. Task and Subtask Management

-   **`add_task`**: Creates a new task.
-   **`add_subtask`**: Adds a subtask to a parent task.
-   **`remove_task`**: Removes one or more tasks or subtasks.
-   **`remove_subtask`**: Removes a subtask from its parent.
-   **`update_task`**: Updates a single task.
-   **`update_subtask`**: Appends information to a subtask.
-   **`update`**: Updates multiple tasks.
-   **`move_task`**: Moves a task or subtask.
-   **`clear_subtasks`**: Clears all subtasks from one or more tasks.

### 2. Task Information and Status

-   **`get_tasks`**: Lists all tasks.
-   **`get_task`**: Shows the details of a specific task.
-   **`next_task`**: Shows the next task to work on.
-   **`set_task_status`**: Sets the status of a task or subtask.

### 3. Task Analysis and Expansion

-   **`parse_prd`**: Parses a PRD to generate tasks.
-   **`expand_task`**: Expands a task into subtasks.
-   **`expand_all`**: Expands all eligible tasks.
-   **`analyze_project_complexity`**: Analyzes task complexity.
-   **`complexity_report`**: Displays the complexity analysis report.

### 4. Dependency Management

-   **`add_dependency`**: Adds a dependency to a task.
-   **`remove_dependency`**: Removes a dependency from a task.
-   **`validate_dependencies`**: Validates the dependencies of all tasks.
-   **`fix_dependencies`**: Fixes any invalid dependencies.

### 5. Project and Configuration

-   **`initialize_project`**: Initializes a new project.
-   **`generate`**: Generates individual task files.
-   **`models`**: Manages AI model configurations.
-   **`research`**: Performs AI-powered research.

### 6. Tag Management

-   **`add_tag`**: Creates a new tag.
-   **`delete_tag`**: Deletes a tag.
-   **`list_tags`**: Lists all tags.
-   **`use_tag`**: Switches to a different tag.
-   **`rename_tag`**: Renames a tag.
-   **`copy_tag`**: Copies a tag.

This synopsis provides a clear overview of the MCP interface and its available tools, which will be valuable for anyone writing documentation for developers who need to interact with the Task Master application programmatically.
```

--------------------------------------------------------------------------------
/apps/extension/src/utils/mcpClient.ts:
--------------------------------------------------------------------------------

```typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import * as vscode from 'vscode';
import * as path from 'path';
import { logger } from './logger';

export interface MCPConfig {
	command: string;
	args: string[];
	cwd?: string;
	env?: Record<string, string>;
	timeout?: number;
}

export interface MCPServerStatus {
	isRunning: boolean;
	pid?: number;
	error?: string;
}

export class MCPClientManager {
	private client: Client | null = null;
	private transport: StdioClientTransport | null = null;
	private config: MCPConfig;
	private status: MCPServerStatus = { isRunning: false };
	private connectionPromise: Promise<void> | null = null;

	constructor(config: MCPConfig) {
		logger.log(
			'🔍 DEBUGGING: MCPClientManager constructor called with config:',
			config
		);
		this.config = config;
	}

	/**
	 * Get the current server status
	 */
	getStatus(): MCPServerStatus {
		return { ...this.status };
	}

	/**
	 * Start the MCP server process and establish client connection
	 */
	async connect(): Promise<void> {
		if (this.connectionPromise) {
			return this.connectionPromise;
		}

		this.connectionPromise = this._doConnect();
		return this.connectionPromise;
	}

	private async _doConnect(): Promise<void> {
		try {
			// Clean up any existing connections
			await this.disconnect();

			// Create the transport - it will handle spawning the server process internally
			logger.log(
				`Starting MCP server: ${this.config.command} ${this.config.args?.join(' ') || ''}`
			);
			logger.log('🔍 DEBUGGING: Transport config cwd:', this.config.cwd);
			logger.log('🔍 DEBUGGING: Process cwd before spawn:', process.cwd());

			// Test if the target directory and .taskmaster exist
			const fs = require('fs');
			const path = require('path');
			try {
				const targetDir = this.config.cwd;
				const taskmasterDir = path.join(targetDir, '.taskmaster');
				const tasksFile = path.join(taskmasterDir, 'tasks', 'tasks.json');

				logger.log(
					'🔍 DEBUGGING: Checking target directory:',
					targetDir,
					'exists:',
					fs.existsSync(targetDir)
				);
				logger.log(
					'🔍 DEBUGGING: Checking .taskmaster dir:',
					taskmasterDir,
					'exists:',
					fs.existsSync(taskmasterDir)
				);
				logger.log(
					'🔍 DEBUGGING: Checking tasks.json:',
					tasksFile,
					'exists:',
					fs.existsSync(tasksFile)
				);

				if (fs.existsSync(tasksFile)) {
					const stats = fs.statSync(tasksFile);
					logger.log('🔍 DEBUGGING: tasks.json size:', stats.size, 'bytes');
				}
			} catch (error) {
				logger.log('🔍 DEBUGGING: Error checking filesystem:', error);
			}

			this.transport = new StdioClientTransport({
				command: this.config.command,
				args: this.config.args || [],
				cwd: this.config.cwd,
				env: {
					...(Object.fromEntries(
						Object.entries(process.env).filter(([, v]) => v !== undefined)
					) as Record<string, string>),
					...this.config.env
				}
			});

			logger.log('🔍 DEBUGGING: Transport created, checking process...');

			// Set up transport event handlers
			this.transport.onerror = (error: Error) => {
				logger.error('❌ MCP transport error:', error);
				logger.error('Transport error details:', {
					message: error.message,
					stack: error.stack,
					code: (error as any).code,
					errno: (error as any).errno,
					syscall: (error as any).syscall
				});
				this.status = { isRunning: false, error: error.message };
				vscode.window.showErrorMessage(
					`TaskMaster MCP transport error: ${error.message}`
				);
			};

			this.transport.onclose = () => {
				logger.log('🔌 MCP transport closed');
				this.status = { isRunning: false };
				this.client = null;
				this.transport = null;
			};

			// Add message handler like the working debug script
			this.transport.onmessage = (message: any) => {
				logger.log('📤 MCP server message:', message);
			};

			// Create the client
			this.client = new Client(
				{
					name: 'task-master-vscode-extension',
					version: '1.0.0'
				},
				{
					capabilities: {
						tools: {}
					}
				}
			);

			// Connect the client to the transport (this automatically starts the transport)
			logger.log('🔄 Attempting MCP client connection...');
			logger.log('MCP config:', {
				command: this.config.command,
				args: this.config.args,
				cwd: this.config.cwd
			});
			logger.log('Current working directory:', process.cwd());
			logger.log(
				'VS Code workspace folders:',
				vscode.workspace.workspaceFolders?.map((f) => f.uri.fsPath)
			);

			// Check if process was created before connecting
			if (this.transport && (this.transport as any).process) {
				const proc = (this.transport as any).process;
				logger.log('📝 MCP server process PID:', proc.pid);
				logger.log('📝 Process working directory will be:', this.config.cwd);

				proc.on('exit', (code: number, signal: string) => {
					logger.log(
						`🔚 MCP server process exited with code ${code}, signal ${signal}`
					);
					if (code !== 0) {
						logger.log('❌ Non-zero exit code indicates server failure');
					}
				});

				proc.on('error', (error: Error) => {
					logger.log('❌ MCP server process error:', error);
				});

				// Listen to stderr to see server-side errors
				if (proc.stderr) {
					proc.stderr.on('data', (data: Buffer) => {
						logger.log('📥 MCP server stderr:', data.toString());
					});
				}

				// Listen to stdout for server messages
				if (proc.stdout) {
					proc.stdout.on('data', (data: Buffer) => {
						logger.log('📤 MCP server stdout:', data.toString());
					});
				}
			} else {
				logger.log('⚠️ No process found in transport before connection');
			}

			await this.client.connect(this.transport);

			// Update status
			this.status = {
				isRunning: true,
				pid: this.transport.pid || undefined
			};

			logger.log('MCP client connected successfully');

			// Log Task Master version information after successful connection
			try {
				const versionResult = await this.callTool('get_tasks', {});
				if (versionResult?.content?.[0]?.text) {
					const response = JSON.parse(versionResult.content[0].text);
					if (response?.version) {
						logger.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
						logger.log('✅ Task Master MCP Server Connected');
						logger.log(`   Version: ${response.version.version || 'unknown'}`);
						logger.log(
							`   Package: ${response.version.name || 'task-master-ai'}`
						);
						if (response.tag) {
							logger.log(
								`   Current Tag: ${response.tag.currentTag || 'master'}`
							);
						}
						logger.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
					}
				}
			} catch (versionError) {
				logger.log('Note: Could not retrieve Task Master version information');
			}
		} catch (error) {
			logger.error('Failed to connect to MCP server:', error);
			this.status = {
				isRunning: false,
				error: error instanceof Error ? error.message : 'Unknown error'
			};

			// Clean up on error
			await this.disconnect();

			throw error;
		} finally {
			this.connectionPromise = null;
		}
	}

	/**
	 * Disconnect from the MCP server and clean up resources
	 */
	async disconnect(): Promise<void> {
		logger.log('Disconnecting from MCP server');

		if (this.client) {
			try {
				await this.client.close();
			} catch (error) {
				logger.error('Error closing MCP client:', error);
			}
			this.client = null;
		}

		if (this.transport) {
			try {
				await this.transport.close();
			} catch (error) {
				logger.error('Error closing MCP transport:', error);
			}
			this.transport = null;
		}

		this.status = { isRunning: false };
	}

	/**
	 * Get the MCP client instance (if connected)
	 */
	getClient(): Client | null {
		return this.client;
	}

	/**
	 * Call an MCP tool
	 */
	async callTool(
		toolName: string,
		arguments_: Record<string, unknown>
	): Promise<any> {
		if (!this.client) {
			throw new Error('MCP client is not connected');
		}

		try {
			// Use the configured timeout or default to 5 minutes
			const timeout = this.config.timeout || 300000; // 5 minutes default

			logger.log(`Calling MCP tool "${toolName}" with timeout: ${timeout}ms`);

			const result = await this.client.callTool(
				{
					name: toolName,
					arguments: arguments_
				},
				undefined,
				{
					timeout: timeout
				}
			);

			return result;
		} catch (error) {
			logger.error(`Error calling MCP tool "${toolName}":`, error);
			throw error;
		}
	}

	/**
	 * Test the connection by calling a simple MCP tool
	 */
	async testConnection(): Promise<boolean> {
		try {
			// Try to list available tools as a connection test
			if (!this.client) {
				return false;
			}

			// listTools is a simple metadata request, no need for extended timeout
			const result = await this.client.listTools();
			logger.log(
				'Available MCP tools:',
				result.tools?.map((t) => t.name) || []
			);

			// Try to get version information by calling a simple tool
			// The get_tasks tool is lightweight and returns version info
			try {
				const versionResult = await this.callTool('get_tasks', {});
				if (versionResult?.content?.[0]?.text) {
					// Parse the response to extract version info
					const response = JSON.parse(versionResult.content[0].text);
					if (response?.version) {
						logger.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
						logger.log('📦 Task Master MCP Server Connected');
						logger.log(`   Version: ${response.version.version || 'unknown'}`);
						logger.log(
							`   Package: ${response.version.name || 'task-master-ai'}`
						);
						if (response.tag) {
							logger.log(
								`   Current Tag: ${response.tag.currentTag || 'master'}`
							);
						}
						logger.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
					}
				}
			} catch (versionError) {
				// Don't fail the connection test if we can't get version info
				logger.log('Could not retrieve Task Master version information');
			}

			return true;
		} catch (error) {
			logger.error('Connection test failed:', error);
			return false;
		}
	}

	/**
	 * Get stderr stream from the transport (if available)
	 */
	getStderr(): NodeJS.ReadableStream | null {
		const stderr = this.transport?.stderr;
		return stderr ? (stderr as unknown as NodeJS.ReadableStream) : null;
	}

	/**
	 * Get the process ID of the spawned server
	 */
	getPid(): number | null {
		return this.transport?.pid || null;
	}
}

/**
 * Create MCP configuration from VS Code settings
 */
export function createMCPConfigFromSettings(): MCPConfig {
	logger.log(
		'🔍 DEBUGGING: createMCPConfigFromSettings called at',
		new Date().toISOString()
	);
	const config = vscode.workspace.getConfiguration('taskmaster');

	let command = config.get<string>('mcp.command', 'node');
	let args = config.get<string[]>('mcp.args', []);

	// If using default settings, use the bundled MCP server
	if (command === 'node' && args.length === 0) {
		try {
			// Try to resolve the bundled MCP server
			const taskMasterPath = require.resolve('task-master-ai');
			const mcpServerPath = path.resolve(
				path.dirname(taskMasterPath),
				'./dist/mcp-server.js'
			);

			// Verify the server file exists
			const fs = require('fs');
			if (!fs.existsSync(mcpServerPath)) {
				throw new Error('MCP server file not found at: ' + mcpServerPath);
			}

			args = [mcpServerPath];
			logger.log(`📦 Using bundled MCP server at: ${mcpServerPath}`);
		} catch (error) {
			logger.error('❌ Could not find bundled task-master-ai server:', error);
			// Fallback to npx
			command = 'npx';
			args = ['-y', 'task-master-ai'];
		}
	}

	// Use proper VS Code workspace detection
	const defaultCwd =
		vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd();
	const cwd = config.get<string>('mcp.cwd', defaultCwd);
	const env = config.get<Record<string, string>>('mcp.env');
	const timeout = config.get<number>('mcp.requestTimeoutMs', 300000);

	logger.log('✅ Using workspace directory:', defaultCwd);

	// If using default 'npx', try to find the full path on macOS/Linux
	if (command === 'npx') {
		const fs = require('fs');
		const npxPaths = [
			'/opt/homebrew/bin/npx', // Homebrew on Apple Silicon
			'/usr/local/bin/npx', // Homebrew on Intel
			'/usr/bin/npx', // System npm
			'npx' // Final fallback to PATH
		];

		for (const path of npxPaths) {
			try {
				if (path === 'npx' || fs.existsSync(path)) {
					command = path;
					logger.log(`✅ Using npx at: ${path}`);
					break;
				}
			} catch (error) {
				// Continue to next path
			}
		}
	}

	return {
		command,
		args,
		cwd: cwd || defaultCwd,
		env,
		timeout
	};
}

```

--------------------------------------------------------------------------------
/apps/extension/src/utils/configManager.ts:
--------------------------------------------------------------------------------

```typescript
import * as vscode from 'vscode';
import { logger } from './logger';
import type { MCPConfig } from './mcpClient';

export interface TaskMasterConfig {
	mcp: MCPServerConfig;
	ui: UIConfig;
	performance: PerformanceConfig;
	debug: DebugConfig;
}

export interface MCPServerConfig {
	command: string;
	args: string[];
	cwd?: string;
	env?: Record<string, string>;
	timeout: number;
	maxReconnectAttempts: number;
	reconnectBackoffMs: number;
	maxBackoffMs: number;
	healthCheckIntervalMs: number;
}

export interface UIConfig {
	autoRefresh: boolean;
	refreshIntervalMs: number;
	theme: 'auto' | 'light' | 'dark';
	showCompletedTasks: boolean;
	taskDisplayLimit: number;
	showPriority: boolean;
	showTaskIds: boolean;
}

export interface PerformanceConfig {
	maxConcurrentRequests: number;
	requestTimeoutMs: number;
	cacheTasksMs: number;
	lazyLoadThreshold: number;
}

export interface DebugConfig {
	enableLogging: boolean;
	logLevel: 'error' | 'warn' | 'info' | 'debug';
	enableConnectionMetrics: boolean;
	saveEventLogs: boolean;
	maxEventLogSize: number;
}

export interface ConfigValidationResult {
	isValid: boolean;
	errors: string[];
	warnings: string[];
}

export class ConfigManager {
	private static instance: ConfigManager | null = null;
	private config: TaskMasterConfig;
	private configListeners: ((config: TaskMasterConfig) => void)[] = [];

	private constructor() {
		this.config = this.loadConfig();
		this.setupConfigWatcher();
	}

	/**
	 * Get singleton instance
	 */
	static getInstance(): ConfigManager {
		if (!ConfigManager.instance) {
			ConfigManager.instance = new ConfigManager();
		}
		return ConfigManager.instance;
	}

	/**
	 * Get current configuration
	 */
	getConfig(): TaskMasterConfig {
		return { ...this.config };
	}

	/**
	 * Get MCP configuration for the client
	 */
	getMCPConfig(): MCPConfig {
		const mcpConfig = this.config.mcp;
		return {
			command: mcpConfig.command,
			args: mcpConfig.args,
			cwd: mcpConfig.cwd,
			env: mcpConfig.env
		};
	}

	/**
	 * Update configuration (programmatically)
	 */
	async updateConfig(updates: Partial<TaskMasterConfig>): Promise<void> {
		const newConfig = this.mergeConfig(this.config, updates);
		const validation = this.validateConfig(newConfig);

		if (!validation.isValid) {
			throw new Error(
				`Configuration validation failed: ${validation.errors.join(', ')}`
			);
		}

		// Update VS Code settings
		const vsConfig = vscode.workspace.getConfiguration('taskmaster');

		if (updates.mcp) {
			if (updates.mcp.command !== undefined) {
				await vsConfig.update(
					'mcp.command',
					updates.mcp.command,
					vscode.ConfigurationTarget.Workspace
				);
			}
			if (updates.mcp.args !== undefined) {
				await vsConfig.update(
					'mcp.args',
					updates.mcp.args,
					vscode.ConfigurationTarget.Workspace
				);
			}
			if (updates.mcp.cwd !== undefined) {
				await vsConfig.update(
					'mcp.cwd',
					updates.mcp.cwd,
					vscode.ConfigurationTarget.Workspace
				);
			}
			if (updates.mcp.timeout !== undefined) {
				await vsConfig.update(
					'mcp.timeout',
					updates.mcp.timeout,
					vscode.ConfigurationTarget.Workspace
				);
			}
		}

		if (updates.ui) {
			if (updates.ui.autoRefresh !== undefined) {
				await vsConfig.update(
					'ui.autoRefresh',
					updates.ui.autoRefresh,
					vscode.ConfigurationTarget.Workspace
				);
			}
			if (updates.ui.theme !== undefined) {
				await vsConfig.update(
					'ui.theme',
					updates.ui.theme,
					vscode.ConfigurationTarget.Workspace
				);
			}
		}

		if (updates.debug) {
			if (updates.debug.enableLogging !== undefined) {
				await vsConfig.update(
					'debug.enableLogging',
					updates.debug.enableLogging,
					vscode.ConfigurationTarget.Workspace
				);
			}
			if (updates.debug.logLevel !== undefined) {
				await vsConfig.update(
					'debug.logLevel',
					updates.debug.logLevel,
					vscode.ConfigurationTarget.Workspace
				);
			}
		}

		this.config = newConfig;
		this.notifyConfigChange();
	}

	/**
	 * Validate configuration
	 */
	validateConfig(config: TaskMasterConfig): ConfigValidationResult {
		const errors: string[] = [];
		const warnings: string[] = [];

		// Validate MCP configuration
		if (!config.mcp.command || config.mcp.command.trim() === '') {
			errors.push('MCP command cannot be empty');
		}

		if (config.mcp.timeout < 1000) {
			warnings.push(
				'MCP timeout is very low (< 1s), this may cause connection issues'
			);
		} else if (config.mcp.timeout > 60000) {
			warnings.push(
				'MCP timeout is very high (> 60s), this may cause slow responses'
			);
		}

		if (config.mcp.maxReconnectAttempts < 1) {
			errors.push('Max reconnect attempts must be at least 1');
		} else if (config.mcp.maxReconnectAttempts > 10) {
			warnings.push(
				'Max reconnect attempts is very high, this may cause long delays'
			);
		}

		// Validate UI configuration
		if (config.ui.refreshIntervalMs < 1000) {
			warnings.push(
				'UI refresh interval is very low (< 1s), this may impact performance'
			);
		}

		if (config.ui.taskDisplayLimit < 1) {
			errors.push('Task display limit must be at least 1');
		} else if (config.ui.taskDisplayLimit > 1000) {
			warnings.push(
				'Task display limit is very high, this may impact performance'
			);
		}

		// Validate performance configuration
		if (config.performance.maxConcurrentRequests < 1) {
			errors.push('Max concurrent requests must be at least 1');
		} else if (config.performance.maxConcurrentRequests > 20) {
			warnings.push(
				'Max concurrent requests is very high, this may overwhelm the server'
			);
		}

		if (config.performance.requestTimeoutMs < 1000) {
			warnings.push(
				'Request timeout is very low (< 1s), this may cause premature timeouts'
			);
		}

		// Validate debug configuration
		if (config.debug.maxEventLogSize < 10) {
			errors.push('Max event log size must be at least 10');
		} else if (config.debug.maxEventLogSize > 10000) {
			warnings.push(
				'Max event log size is very high, this may consume significant memory'
			);
		}

		return {
			isValid: errors.length === 0,
			errors,
			warnings
		};
	}

	/**
	 * Reset configuration to defaults
	 */
	async resetToDefaults(): Promise<void> {
		const defaultConfig = this.getDefaultConfig();
		await this.updateConfig(defaultConfig);
	}

	/**
	 * Export configuration to JSON
	 */
	exportConfig(): string {
		return JSON.stringify(this.config, null, 2);
	}

	/**
	 * Import configuration from JSON
	 */
	async importConfig(jsonConfig: string): Promise<void> {
		try {
			const importedConfig = JSON.parse(jsonConfig) as TaskMasterConfig;
			const validation = this.validateConfig(importedConfig);

			if (!validation.isValid) {
				throw new Error(
					`Invalid configuration: ${validation.errors.join(', ')}`
				);
			}

			if (validation.warnings.length > 0) {
				const proceed = await vscode.window.showWarningMessage(
					`Configuration has warnings: ${validation.warnings.join(', ')}. Import anyway?`,
					'Yes',
					'No'
				);

				if (proceed !== 'Yes') {
					return;
				}
			}

			await this.updateConfig(importedConfig);
			vscode.window.showInformationMessage(
				'Configuration imported successfully'
			);
		} catch (error) {
			const errorMessage =
				error instanceof Error ? error.message : 'Unknown error';
			vscode.window.showErrorMessage(
				`Failed to import configuration: ${errorMessage}`
			);
			throw error;
		}
	}

	/**
	 * Add configuration change listener
	 */
	onConfigChange(listener: (config: TaskMasterConfig) => void): void {
		this.configListeners.push(listener);
	}

	/**
	 * Remove configuration change listener
	 */
	removeConfigListener(listener: (config: TaskMasterConfig) => void): void {
		const index = this.configListeners.indexOf(listener);
		if (index !== -1) {
			this.configListeners.splice(index, 1);
		}
	}

	/**
	 * Load configuration from VS Code settings
	 */
	private loadConfig(): TaskMasterConfig {
		const vsConfig = vscode.workspace.getConfiguration('taskmaster');
		const defaultConfig = this.getDefaultConfig();

		return {
			mcp: {
				command: vsConfig.get('mcp.command', defaultConfig.mcp.command),
				args: vsConfig.get('mcp.args', defaultConfig.mcp.args),
				cwd: vsConfig.get('mcp.cwd', defaultConfig.mcp.cwd),
				env: vsConfig.get('mcp.env', defaultConfig.mcp.env),
				timeout: vsConfig.get('mcp.timeout', defaultConfig.mcp.timeout),
				maxReconnectAttempts: vsConfig.get(
					'mcp.maxReconnectAttempts',
					defaultConfig.mcp.maxReconnectAttempts
				),
				reconnectBackoffMs: vsConfig.get(
					'mcp.reconnectBackoffMs',
					defaultConfig.mcp.reconnectBackoffMs
				),
				maxBackoffMs: vsConfig.get(
					'mcp.maxBackoffMs',
					defaultConfig.mcp.maxBackoffMs
				),
				healthCheckIntervalMs: vsConfig.get(
					'mcp.healthCheckIntervalMs',
					defaultConfig.mcp.healthCheckIntervalMs
				)
			},
			ui: {
				autoRefresh: vsConfig.get(
					'ui.autoRefresh',
					defaultConfig.ui.autoRefresh
				),
				refreshIntervalMs: vsConfig.get(
					'ui.refreshIntervalMs',
					defaultConfig.ui.refreshIntervalMs
				),
				theme: vsConfig.get('ui.theme', defaultConfig.ui.theme),
				showCompletedTasks: vsConfig.get(
					'ui.showCompletedTasks',
					defaultConfig.ui.showCompletedTasks
				),
				taskDisplayLimit: vsConfig.get(
					'ui.taskDisplayLimit',
					defaultConfig.ui.taskDisplayLimit
				),
				showPriority: vsConfig.get(
					'ui.showPriority',
					defaultConfig.ui.showPriority
				),
				showTaskIds: vsConfig.get(
					'ui.showTaskIds',
					defaultConfig.ui.showTaskIds
				)
			},
			performance: {
				maxConcurrentRequests: vsConfig.get(
					'performance.maxConcurrentRequests',
					defaultConfig.performance.maxConcurrentRequests
				),
				requestTimeoutMs: vsConfig.get(
					'performance.requestTimeoutMs',
					defaultConfig.performance.requestTimeoutMs
				),
				cacheTasksMs: vsConfig.get(
					'performance.cacheTasksMs',
					defaultConfig.performance.cacheTasksMs
				),
				lazyLoadThreshold: vsConfig.get(
					'performance.lazyLoadThreshold',
					defaultConfig.performance.lazyLoadThreshold
				)
			},
			debug: {
				enableLogging: vsConfig.get(
					'debug.enableLogging',
					defaultConfig.debug.enableLogging
				),
				logLevel: vsConfig.get('debug.logLevel', defaultConfig.debug.logLevel),
				enableConnectionMetrics: vsConfig.get(
					'debug.enableConnectionMetrics',
					defaultConfig.debug.enableConnectionMetrics
				),
				saveEventLogs: vsConfig.get(
					'debug.saveEventLogs',
					defaultConfig.debug.saveEventLogs
				),
				maxEventLogSize: vsConfig.get(
					'debug.maxEventLogSize',
					defaultConfig.debug.maxEventLogSize
				)
			}
		};
	}

	/**
	 * Get default configuration
	 */
	private getDefaultConfig(): TaskMasterConfig {
		return {
			mcp: {
				command: 'npx',
				args: ['task-master-ai'],
				cwd: vscode.workspace.rootPath || '',
				env: undefined,
				timeout: 30000,
				maxReconnectAttempts: 5,
				reconnectBackoffMs: 1000,
				maxBackoffMs: 30000,
				healthCheckIntervalMs: 15000
			},
			ui: {
				autoRefresh: true,
				refreshIntervalMs: 10000,
				theme: 'auto',
				showCompletedTasks: true,
				taskDisplayLimit: 100,
				showPriority: true,
				showTaskIds: true
			},
			performance: {
				maxConcurrentRequests: 5,
				requestTimeoutMs: 30000,
				cacheTasksMs: 5000,
				lazyLoadThreshold: 50
			},
			debug: {
				enableLogging: true,
				logLevel: 'info',
				enableConnectionMetrics: true,
				saveEventLogs: false,
				maxEventLogSize: 1000
			}
		};
	}

	/**
	 * Setup configuration watcher
	 */
	private setupConfigWatcher(): void {
		vscode.workspace.onDidChangeConfiguration((event) => {
			if (event.affectsConfiguration('taskmaster')) {
				logger.log('Task Master configuration changed, reloading...');
				this.config = this.loadConfig();
				this.notifyConfigChange();
			}
		});
	}

	/**
	 * Merge configurations
	 */
	private mergeConfig(
		baseConfig: TaskMasterConfig,
		updates: Partial<TaskMasterConfig>
	): TaskMasterConfig {
		return {
			mcp: { ...baseConfig.mcp, ...updates.mcp },
			ui: { ...baseConfig.ui, ...updates.ui },
			performance: { ...baseConfig.performance, ...updates.performance },
			debug: { ...baseConfig.debug, ...updates.debug }
		};
	}

	/**
	 * Notify configuration change listeners
	 */
	private notifyConfigChange(): void {
		this.configListeners.forEach((listener) => {
			try {
				listener(this.config);
			} catch (error) {
				logger.error('Error in configuration change listener:', error);
			}
		});
	}
}

/**
 * Utility function to get configuration manager instance
 */
export function getConfigManager(): ConfigManager {
	return ConfigManager.getInstance();
}

```

--------------------------------------------------------------------------------
/.taskmaster/docs/research/2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md:
--------------------------------------------------------------------------------

```markdown
---
title: Research Session
query: "do we need to add new commands or can we just weaponize the gh cli tool"
date: 2025-08-01
time: 2:27:52 p.m.
timestamp: 2025-08-01T11:27:52.249Z
exchanges: 3
---

# Research Session

## Initial Query

**Question:** do we need to add new commands or can we just weaponize the gh cli tool

**Response:**

For the implementation of the `task-master git` command suite to automate git workflows (Task 97), it is possible to leverage the existing **GitHub CLI (`gh`) tool** rather than creating entirely new git commands from scratch. The GitHub CLI provides a rich set of commands that cover many GitHub-related operations such as branch management, pull request handling, workflow triggering, and release management, which can be scripted and integrated into your CLI tool[1][2][3][4].

### Key Points Supporting Weaponizing GitHub CLI:

- **Comprehensive GitHub Operations**: The `gh` CLI supports creating branches, managing pull requests, releases, tags, and even triggering and monitoring GitHub Actions workflows. This aligns well with the requirements of automated branch management, commit generation, and PR description generation in Task 97[1][3].

- **Scripting and Automation Friendly**: GitHub CLI commands can be scripted and integrated into custom workflows or CLI tools. You can create aliases or wrap `gh` commands inside your own CLI commands to provide a seamless user experience while relying on the battle-tested GitHub CLI for underlying operations[1][3].

- **Authentication and Permissions**: `gh` uses environment variables like `GH_TOKEN` or `GITHUB_TOKEN` for authentication, which can be securely managed in CI/CD pipelines or local environments. This simplifies integration without needing to implement custom authentication logic[3][4].

- **Extensibility**: If some specific git workflow features are not directly supported by `gh`, you can extend it with custom scripts or combine it with native git commands. This hybrid approach reduces the need to reinvent core git operations while allowing customization[1].

### Considerations for Your Project:

- Since Task 97 requires **automated branch management** (branch creation, validation, switching, cleanup), **intelligent commit generation**, and **PR description generation**, you can implement these by invoking `gh` commands programmatically within your `task-master git` commands.

- For example, branch creation and switching can be done with native git commands wrapped in your CLI, while PR creation and management can be handled via `gh pr create`, `gh pr merge`, etc.

- You may still need to implement some **custom logic** for commit type detection, branch name validation, and PR description generation based on your task metadata, but the underlying git and GitHub operations can be delegated to `gh`.

- This approach aligns with your existing CLI foundation (Task 2) and leverages a well-supported tool, reducing development effort and increasing reliability.

### Summary

You **do not necessarily need to add entirely new git commands** for Task 97. Instead, you can **weaponize the GitHub CLI (`gh`) tool** by wrapping and scripting its commands within your `task-master git` command suite. This will allow you to automate git workflows efficiently, maintain consistency, and leverage GitHub’s API capabilities without reinventing the wheel[1][2][3][4].

If there are gaps in `gh` functionality for your specific workflow needs, you can supplement with native git commands or custom scripts, but the core git and GitHub operations should be handled by `gh` for best results.

---

## Follow-up 1

**Question:** how does this relate to git worktrees? i hear that they get super complicated when trying to implement them.

**Response:**

Git worktrees allow you to have multiple working directories attached to the same Git repository, each checked out to different branches simultaneously. This enables parallel development workflows without the overhead of cloning the repository multiple times or constantly switching branches in a single working directory[2][3].

However, **implementing or managing git worktrees can get complicated** for several reasons:

- **Complexity for users and tooling:** Managing multiple active worktrees requires careful tracking of which directory corresponds to which branch. This can be confusing and error-prone, especially for novices or when many worktrees are in use simultaneously. Mistakes like committing to the wrong branch or working in the wrong directory are common risks[1].

- **Maintenance overhead:** Worktrees accumulate over time and need to be pruned or cleaned up regularly (`git worktree prune`) to avoid clutter and potential confusion. This adds an extra maintenance step to the workflow[1].

- **Syncing and update challenges:** Each worktree is a separate checkout and can become outdated if not regularly updated from the main repository. This can lead to merge conflicts or redundant work if branches diverge significantly[1].

- **Tooling and integration limitations:** Not all Git tools, scripts, or GUIs fully support worktrees or are optimized for them. This can cause inconsistencies or require additional custom tooling to manage worktrees effectively[1].

- **Performance considerations:** Managing multiple worktrees simultaneously can have performance implications, especially in very large repositories, due to the overhead of maintaining multiple checkouts and metadata[1].

In the context of your project, which involves automating git workflows and possibly wrapping or extending git/GitHub CLI commands, these complexities imply:

- If you consider supporting git worktrees in your `task-master git` command suite, you will need to handle the complexity of managing multiple directories and branches carefully.

- You might need to implement additional logic or tooling to track and manage worktrees, avoid user confusion, and ensure synchronization between them.

- Since your project already plans to leverage the GitHub CLI (`gh`) and native git commands, you could integrate worktree commands (`git worktree add`, `git worktree remove`, `git worktree list`) but should be prepared for the added complexity in workflow management and user documentation.

- Custom wrappers or managers (like the one described in [3]) can help reduce friction by automating worktree creation, switching, and cleanup, which might be a useful approach to consider.

Overall, **git worktrees provide powerful parallel branch management but introduce complexity in implementation and user experience** that must be carefully managed through tooling, automation, and clear documentation[1][3][4].

---

**Summary Table of Git Worktree Challenges and Considerations**

| Aspect                  | Description                                                                                   | Implication for Project                                  |
|-------------------------|-----------------------------------------------------------------------------------------------|----------------------------------------------------------|
| User Complexity         | Confusing multiple directories and branches                                                  | Need clear UI/UX and safeguards                          |
| Maintenance Overhead    | Requires pruning and cleanup of stale worktrees                                              | Automate cleanup or provide commands                     |
| Syncing Issues         | Worktrees can become outdated or diverge                                                     | Implement update checks and sync workflows               |
| Tooling Support        | Limited support in some Git tools and GUIs                                                  | May require custom tooling or wrappers                   |
| Performance            | Overhead in large repos with many worktrees                                                 | Monitor and optimize performance                          |
| Workflow Automation    | Manual worktree management is tedious                                                       | Build custom managers or wrappers to simplify usage     |

---

Given your project’s focus on automating git workflows and leveraging existing CLI tools, **implementing git worktree support is feasible but will require careful design to handle these complexities effectively**. You may want to start with basic worktree commands and incrementally add automation and safeguards as needed.

---

## Follow-up 2

**Question:** since we already have a way to have separated task lists per branch, couldn't we do the same thing for worktrees? each taskmaster list (tag) would have its own worktree based on the name of the tag?

**Response:**

Yes, you can apply a similar approach used for separated task lists per branch to git worktrees by associating each taskmaster list (tag) with its own git worktree named after the tag. This means each tag would correspond to a dedicated worktree directory checked out to a branch or commit related to that tag, enabling isolated parallel work environments for each task list.

### How This Maps to Git Worktrees

- **Git worktrees** allow multiple working directories linked to the same repository, each checked out to a different branch or commit[1][2]. This lets you work on multiple branches simultaneously without switching contexts in a single directory.

- When you create a worktree with `git worktree add <path> [<branch>]`, Git creates a new linked working tree at `<path>`, optionally checking out a specified branch or creating a new one[1][2]. Naming the worktree directory after the tag (taskmaster list) is straightforward and semantically clear.

- This setup mirrors your existing branch-based task list separation but extends it to physical directories, which can improve workflow isolation and reduce context switching.

### Benefits for Your Project

- **Isolated environments per task list/tag:** Each tag’s worktree can have its own checked-out branch, allowing independent development, testing, or review without interference.

- **Parallel workflows:** Developers can switch between worktrees (directories) instead of switching branches in one directory, reducing errors and improving productivity.

- **Automation potential:** Your `task-master git` commands can automate creating, switching, and cleaning up these worktrees based on tags, similar to how you manage branches and task lists.

### Challenges and Considerations

- **Complexity in management:** Git worktrees add complexity in tracking multiple directories and ensuring users operate in the correct one[1]. Your CLI should provide clear commands and feedback to avoid confusion.

- **Maintenance overhead:** Worktrees need pruning and cleanup (`git worktree prune` or `git worktree remove`) to avoid clutter[1]. Automating cleanup in your tool would be beneficial.

- **Syncing and updates:** Each worktree is a separate checkout and can become outdated. Your automation should include commands to update or rebase worktrees as needed.

- **Tooling support:** Not all Git tools or scripts fully support worktrees, so your CLI wrappers should handle edge cases and provide a consistent experience[1][3].

### Practical Implementation Ideas

- When a new tag (taskmaster list) is created, run `git worktree add` to create a new worktree directory named after the tag.

- Store metadata linking tags to worktree paths in your taskmaster configuration to track and manage them.

- Provide commands like `task-master worktree list`, `task-master worktree add <tag>`, and `task-master worktree remove <tag>` to manage these worktrees.

- Integrate with GitHub CLI (`gh`) and native git commands to automate branch creation, PR handling, and syncing within each worktree.

- Consider inspiration from existing tools like [wt](https://github.com/taecontrol/wt) that automate worktree environment setup and teardown[3].

### Summary

Using git worktrees per taskmaster tag is a feasible and potentially powerful extension of your current branch-based task list separation. It offers isolated, parallel working directories aligned with your tags, improving workflow clarity and reducing branch-switching overhead. However, it requires careful tooling and automation to manage complexity, maintenance, and synchronization effectively. Given your project’s focus on automating git workflows and leveraging CLI tools, implementing this feature incrementally with robust user feedback and cleanup commands would be a practical approach.


---

*Generated by Task Master Research Command*  
*Timestamp: 2025-08-01T11:27:52.249Z*

```

--------------------------------------------------------------------------------
/tests/e2e/run_fallback_verification.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# --- Fallback Model Verification Script ---
# Purpose: Tests models marked as 'fallback' in supported-models.json
#          to see if they work with generateObjectService (via update-subtask).
# Usage:   1. Run from within a prepared E2E test run directory:
#             ./path/to/script.sh .
#          2. Run from project root (or anywhere) to use the latest run dir:
#             ./tests/e2e/run_fallback_verification.sh
#          3. Run from project root (or anywhere) targeting a specific run dir:
#             ./tests/e2e/run_fallback_verification.sh /path/to/tests/e2e/_runs/run_YYYYMMDD_HHMMSS
# Output: Prints a summary report to standard output. Errors to standard error.

# Treat unset variables as an error when substituting.
set -u
# Prevent errors in pipelines from being masked.
set -o pipefail

# --- Embedded Helper Functions ---
# Copied from e2e_helpers.sh to make this script standalone
# OR source it if preferred and path is reliable

# <<< Determine SCRIPT_DIR and PROJECT_ROOT_DIR early >>>
SCRIPT_DIR_FV="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
PROJECT_ROOT_DIR_FV="$( cd "$SCRIPT_DIR_FV/../.." &> /dev/null && pwd )" # Assumes script is in tests/e2e/

# --- Try to Source e2e_helpers.sh ---
E2E_HELPERS_PATH_FV="${PROJECT_ROOT_DIR_FV}/tests/e2e/e2e_helpers.sh"
if [ -f "$E2E_HELPERS_PATH_FV" ]; then
    # shellcheck source=tests/e2e/e2e_helpers.sh
    source "$E2E_HELPERS_PATH_FV"
    echo "[INFO FV] Sourced e2e_helpers.sh successfully."
else
    echo "[ERROR FV] e2e_helpers.sh not found at $E2E_HELPERS_PATH_FV. Cost extraction will fail."
    # Define a placeholder if not found, so the script doesn't break immediately,
    # but cost extraction will effectively be a no-op.
    extract_and_sum_cost() { echo "$2"; } # Returns current total, effectively adding 0
fi


_format_duration() {
  local total_seconds=$1
  local minutes=$((total_seconds / 60))
  local seconds=$((total_seconds % 60))
  printf "%dm%02ds" "$minutes" "$seconds"
}

_get_elapsed_time_for_log() {
  local current_time
  current_time=$(date +%s)
  local elapsed_seconds
  elapsed_seconds=$((current_time - overall_start_time)) # Needs overall_start_time
  _format_duration "$elapsed_seconds"
}

log_info() {
  echo "[INFO FV] [$(_get_elapsed_time_for_log)] $(date +"%Y-%m-%d %H:%M:%S") $1"
}

log_success() {
  echo "[SUCCESS FV] [$(_get_elapsed_time_for_log)] $(date +"%Y-%m-%d %H:%M:%S") $1"
}

log_error() {
  echo "[ERROR FV] [$(_get_elapsed_time_for_log)] $(date +"%Y-%m-%d %H:%M:%S") $1" >&2
}

log_step() {
  test_step_count=$((test_step_count + 1)) # Needs test_step_count
  echo ""
  echo "============================================="
  echo "  FV STEP ${test_step_count}: [$(_get_elapsed_time_for_log)] $(date +"%Y-%m-%d %H:%M:%S") $1"
  echo "============================================="
}

# --- Signal Handling ---
child_pid=0
PROGRESS_LOG_FILE="fallback_verification_progress.log" # Stays in run dir

cleanup() {
    echo ""
    log_error "Interrupt received. Cleaning up any running child process..."
    if [ "$child_pid" -ne 0 ]; then
        log_info "Killing child process (PID: $child_pid) and its group..."
        kill -TERM -- "-$child_pid" 2>/dev/null || kill -KILL -- "-$child_pid" 2>/dev/null
        child_pid=0
    fi
    log_info "Progress saved in: $PROGRESS_LOG_FILE"
    # Print current total cost on interrupt
    if [[ -n "${total_fallback_cost+x}" && "$total_fallback_cost" != "0.0" ]]; then # Check if var is set and not initial
        log_info "Current Total Fallback AI Cost at interruption: $total_fallback_cost USD"
    fi
    exit 130
}

trap cleanup INT TERM

# --- Configuration ---
# SCRIPT_DIR and PROJECT_ROOT_DIR already defined above
SUPPORTED_MODELS_FILE="$PROJECT_ROOT_DIR_FV/scripts/modules/supported-models.json"
BASE_RUNS_DIR="$PROJECT_ROOT_DIR_FV/tests/e2e/_runs"

# --- Determine Target Run Directory ---
TARGET_RUN_DIR=""
if [ "$#" -ge 1 ] && [ -n "$1" ]; then
    TARGET_RUN_DIR="$1"
    if [[ "$TARGET_RUN_DIR" != /* ]]; then
        TARGET_RUN_DIR="$(pwd)/$TARGET_RUN_DIR"
    fi
    echo "[INFO FV] Using provided target run directory: $TARGET_RUN_DIR"
else
    echo "[INFO FV] No run directory provided, finding latest in $BASE_RUNS_DIR..."
    TARGET_RUN_DIR=$(ls -td "$BASE_RUNS_DIR"/run_* 2>/dev/null | head -n 1)
    if [ -z "$TARGET_RUN_DIR" ]; then
        echo "[ERROR FV] No run directories found matching 'run_*' in $BASE_RUNS_DIR. Cannot proceed." >&2
        exit 1
    fi
     echo "[INFO FV] Found latest run directory: $TARGET_RUN_DIR"
fi

if [ ! -d "$TARGET_RUN_DIR" ]; then
    echo "[ERROR FV] Target run directory not found or is not a directory: $TARGET_RUN_DIR" >&2
    exit 1
fi

echo "[INFO FV] Changing working directory to: $TARGET_RUN_DIR"
if ! cd "$TARGET_RUN_DIR"; then
     echo "[ERROR FV] Failed to cd into target directory: $TARGET_RUN_DIR" >&2
     exit 1
fi
echo "[INFO FV] Now operating inside: $(pwd)"

overall_start_time=$(date +%s) # Initialize for logging helpers
test_step_count=0               # Initialize for logging helpers
total_fallback_cost="0.0"       # Initialize total cost for this script

log_info "Starting fallback verification script execution in $(pwd)"
log_info "Progress will be logged to: $(pwd)/$PROGRESS_LOG_FILE"

log_step "Checking for dependencies (jq, bc) in verification script"
if ! command -v jq &> /dev/null; then log_error "Dependency 'jq' not installed."; exit 1; fi
if ! command -v bc &> /dev/null; then log_error "Dependency 'bc' not installed (for cost calculation)."; exit 1; fi
log_success "Dependencies 'jq' and 'bc' found."


log_step "Starting/Resuming Fallback Model (generateObjectService) Verification"
touch "$PROGRESS_LOG_FILE"

if [ ! -f "$SUPPORTED_MODELS_FILE" ]; then
    log_error "supported-models.json not found at: $SUPPORTED_MODELS_FILE."
    exit 1
fi
log_info "Using supported models file: $SUPPORTED_MODELS_FILE"

if [ ! -f "tasks/tasks.json" ]; then
    log_error "tasks/tasks.json not found in current directory ($(pwd)). Was this run directory properly initialized?"
    exit 1
fi
if ! jq -e '.tasks[] | select(.id == 1) | .subtasks[] | select(.id == 1)' tasks/tasks.json > /dev/null 2>&1; then
    log_error "Subtask 1.1 not found in tasks.json within $(pwd). Cannot perform update-subtask tests."
    exit 1
fi
log_info "Subtask 1.1 found in $(pwd)/tasks/tasks.json, proceeding with verification."

jq -c 'to_entries[] | .key as $provider | .value[] | select(.allowed_roles[]? == "fallback") | {provider: $provider, id: .id}' "$SUPPORTED_MODELS_FILE" | while IFS= read -r model_info; do
    provider=$(echo "$model_info" | jq -r '.provider')
    model_id=$(echo "$model_info" | jq -r '.id')
    flag=""

    if grep -Fq "${provider},${model_id}," "$PROGRESS_LOG_FILE"; then
        log_info "--- Skipping: $provider / $model_id (already tested, result in $PROGRESS_LOG_FILE) ---"
        # Still need to sum up its cost if it was successful before
        previous_test_output=$(grep -F "${provider},${model_id}," "$PROGRESS_LOG_FILE" | head -n 1)
        # Assuming the output file for successful test exists and contains cost
        prev_output_file="update_subtask_raw_output_${provider}_${model_id//\//_}.log"
        if [[ "$previous_test_output" == *",SUCCESS"* && -f "$prev_output_file" ]]; then
            # shellcheck disable=SC2154 # overall_start_time is set
            log_info "Summing cost from previous successful test of $provider / $model_id from $prev_output_file"
            # shellcheck disable=SC2154 # total_fallback_cost is set
            total_fallback_cost=$(extract_and_sum_cost "$(cat "$prev_output_file")" "$total_fallback_cost")
            log_info "Cumulative fallback AI cost after previous $provider / $model_id: $total_fallback_cost USD"
        fi
        continue
    fi

    log_info "--- Verifying: $provider / $model_id ---"

    if [ "$provider" == "openrouter" ]; then flag="--openrouter"; fi
    if [ "$provider" == "ollama" ]; then flag="--ollama"; fi

    if ! command -v task-master &> /dev/null; then
        log_error "task-master command not found."
        echo "[INSTRUCTION FV] Please run 'npm link task-master-ai' in the project root first."
        exit 1
    fi
    log_info "Setting main model to $model_id ${flag:+using flag $flag}..."
    set_model_cmd="task-master models --set-main \"$model_id\" $flag"
    if ! eval "$set_model_cmd" > /dev/null 2>&1; then
        log_error "Failed to set main model for $provider / $model_id. Skipping test."
        echo "$provider,$model_id,SET_MODEL_FAILED" >> "$PROGRESS_LOG_FILE"
        continue
    fi
    log_info "Set main model ok."

    log_info "Running update-subtask --id=1.1 --prompt='Test generateObjectService' (timeout 120s)"
    update_subtask_output_file="update_subtask_raw_output_${provider}_${model_id//\//_}.log"
    
    # Capture output to a variable AND a file
    update_subtask_command_output=""
    timeout 120s task-master update-subtask --id=1.1 --prompt="Simple test prompt to verify generateObjectService call." 2>&1 | tee "$update_subtask_output_file" &
    # Store the command output in a variable simultaneously
    # update_subtask_command_output=$(timeout 120s task-master update-subtask --id=1.1 --prompt="Simple test prompt to verify generateObjectService call." 2>&1)
    # The above direct capture won't work well with tee and backgrounding. Instead, read the file after command completion.
    child_pid=$!
    wait "$child_pid"
    update_subtask_exit_code=$?
    child_pid=0

    # Read output from file for cost extraction
    if [ -f "$update_subtask_output_file" ]; then
        update_subtask_command_output=$(cat "$update_subtask_output_file")
    else
        update_subtask_command_output="" # Ensure it's defined
    fi

    result_status=""
    if [ $update_subtask_exit_code -eq 0 ] && echo "$update_subtask_command_output" | grep -q "Successfully updated subtask #1.1"; then
        log_success "update-subtask succeeded for $provider / $model_id (Verified Output)."
        result_status="SUCCESS"
        # Extract and sum cost if successful
        # shellcheck disable=SC2154 # total_fallback_cost is set
        total_fallback_cost=$(extract_and_sum_cost "$update_subtask_command_output" "$total_fallback_cost")
        log_info "Cumulative fallback AI cost after $provider / $model_id: $total_fallback_cost USD"
    elif [ $update_subtask_exit_code -eq 124 ]; then
        log_error "update-subtask TIMED OUT for $provider / $model_id. Check $update_subtask_output_file."
        result_status="FAILED_TIMEOUT"
    elif [ $update_subtask_exit_code -eq 130 ] || [ $update_subtask_exit_code -eq 143 ]; then
         log_error "update-subtask INTERRUPTED for $provider / $model_id."
         result_status="INTERRUPTED"
    else
        log_error "update-subtask FAILED for $provider / $model_id (Exit Code: $update_subtask_exit_code). Check $update_subtask_output_file."
        result_status="FAILED"
    fi

    echo "$provider,$model_id,$result_status" >> "$PROGRESS_LOG_FILE"

done

echo ""
echo "--- Fallback Model Verification Report (via $0) ---"
echo "Executed inside run directory: $(pwd)"
echo "Progress log: $(pwd)/$PROGRESS_LOG_FILE"
echo ""
echo "Test Command: task-master update-subtask --id=1.1 --prompt=\"...\" (tests generateObjectService)"
echo "Models were tested by setting them as the 'main' model temporarily."
echo "Results based on exit code and output verification:"
echo ""
echo "Models CONFIRMED to support generateObjectService (Keep 'fallback' role):"
awk -F',' '$3 == "SUCCESS" { print "- " $1 " / " $2 }' "$PROGRESS_LOG_FILE" | sort
echo ""
echo "Models FAILED generateObjectService test (Suggest REMOVING 'fallback' role):"
awk -F',' '$3 == "FAILED" { print "- " $1 " / " $2 }' "$PROGRESS_LOG_FILE" | sort
echo ""
echo "Models TIMED OUT during test (Suggest REMOVING 'fallback' role):"
awk -F',' '$3 == "FAILED_TIMEOUT" { print "- " $1 " / " $2 }' "$PROGRESS_LOG_FILE" | sort
echo ""
echo "Models where setting the model failed (Inconclusive):"
awk -F',' '$3 == "SET_MODEL_FAILED" { print "- " $1 " / " $2 }' "$PROGRESS_LOG_FILE" | sort
echo ""
echo "Models INTERRUPTED during test (Inconclusive - Rerun):"
awk -F',' '$3 == "INTERRUPTED" { print "- " $1 " / " $2 }' "$PROGRESS_LOG_FILE" | sort
echo ""
# Print the total cost for this script's operations
formatted_total_fallback_cost=$(printf "%.6f" "$total_fallback_cost")
echo "Total Fallback AI Cost (this script run): $formatted_total_fallback_cost USD" # This line will be parsed
echo "-------------------------------------------------------"
echo ""

log_info "Finished Fallback Model (generateObjectService) Verification Script"

trap - INT TERM
exit 0

```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/interfaces/storage.interface.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Storage interface definitions for the tm-core package
 * This file defines the contract for all storage implementations
 */

import type { ExpandTaskResult } from '../../modules/integration/services/task-expansion.service.js';
import type { Task, TaskMetadata, TaskStatus } from '../types/index.js';

/**
 * Options for loading tasks from storage
 */
export interface LoadTasksOptions {
	/** Filter tasks by status */
	status?: TaskStatus;
	/** Exclude subtasks from loaded tasks (default: false) */
	excludeSubtasks?: boolean;
}

/**
 * Result type for updateTaskStatus operations
 */
export interface UpdateStatusResult {
	success: boolean;
	oldStatus: TaskStatus;
	newStatus: TaskStatus;
	taskId: string;
}

/**
 * Interface for storage operations on tasks
 * All storage implementations must implement this interface
 */
export interface IStorage {
	/**
	 * Load all tasks from storage, optionally filtered by tag and other criteria
	 * @param tag - Optional tag to filter tasks by
	 * @param options - Optional filtering options (status, excludeSubtasks)
	 * @returns Promise that resolves to an array of tasks
	 */
	loadTasks(tag?: string, options?: LoadTasksOptions): Promise<Task[]>;

	/**
	 * Load a single task by ID
	 * @param taskId - ID of the task to load
	 * @param tag - Optional tag context for the task
	 * @returns Promise that resolves to the task or null if not found
	 */
	loadTask(taskId: string, tag?: string): Promise<Task | null>;

	/**
	 * Save tasks to storage, replacing existing tasks
	 * @param tasks - Array of tasks to save
	 * @param tag - Optional tag context for the tasks
	 * @returns Promise that resolves when save is complete
	 */
	saveTasks(tasks: Task[], tag?: string): Promise<void>;

	/**
	 * Append new tasks to existing storage without replacing
	 * @param tasks - Array of tasks to append
	 * @param tag - Optional tag context for the tasks
	 * @returns Promise that resolves when append is complete
	 */
	appendTasks(tasks: Task[], tag?: string): Promise<void>;

	/**
	 * Update a specific task by ID (direct structural update)
	 * @param taskId - ID of the task to update
	 * @param updates - Partial task object with fields to update
	 * @param tag - Optional tag context for the task
	 * @returns Promise that resolves when update is complete
	 */
	updateTask(
		taskId: string,
		updates: Partial<Task>,
		tag?: string
	): Promise<void>;

	/**
	 * Update a task using AI-powered prompt (natural language update)
	 * @param taskId - ID of the task to update
	 * @param prompt - Natural language prompt describing the update
	 * @param tag - Optional tag context for the task
	 * @param options - Optional update options
	 * @param options.useResearch - Whether to use research capabilities (for file storage)
	 * @param options.mode - Update mode: 'append' adds info, 'update' makes targeted changes, 'rewrite' restructures (for API storage)
	 * @returns Promise that resolves when update is complete
	 */
	updateTaskWithPrompt(
		taskId: string,
		prompt: string,
		tag?: string,
		options?: { useResearch?: boolean; mode?: 'append' | 'update' | 'rewrite' }
	): Promise<void>;

	/**
	 * Expand task into subtasks using AI-powered generation
	 * @param taskId - ID of the task to expand
	 * @param tag - Optional tag context for the task
	 * @param options - Optional expansion options
	 * @param options.numSubtasks - Number of subtasks to generate
	 * @param options.useResearch - Whether to use research capabilities
	 * @param options.additionalContext - Additional context for generation
	 * @param options.force - Force regeneration even if subtasks exist
	 * @returns ExpandTaskResult for API storage, void for file storage
	 */
	expandTaskWithPrompt(
		taskId: string,
		tag?: string,
		options?: {
			numSubtasks?: number;
			useResearch?: boolean;
			additionalContext?: string;
			force?: boolean;
		}
	): Promise<ExpandTaskResult | void>;

	/**
	 * Update task or subtask status by ID
	 * @param taskId - ID of the task or subtask (e.g., "1" or "1.2")
	 * @param newStatus - New status to set
	 * @param tag - Optional tag context for the task
	 * @returns Promise that resolves to update result with old and new status
	 */
	updateTaskStatus(
		taskId: string,
		newStatus: TaskStatus,
		tag?: string
	): Promise<UpdateStatusResult>;

	/**
	 * Delete a task by ID
	 * @param taskId - ID of the task to delete
	 * @param tag - Optional tag context for the task
	 * @returns Promise that resolves when deletion is complete
	 */
	deleteTask(taskId: string, tag?: string): Promise<void>;

	/**
	 * Check if tasks exist in storage for the given tag
	 * @param tag - Optional tag to check existence for
	 * @returns Promise that resolves to boolean indicating existence
	 */
	exists(tag?: string): Promise<boolean>;

	/**
	 * Load metadata about the task collection
	 * @param tag - Optional tag to get metadata for
	 * @returns Promise that resolves to task metadata
	 */
	loadMetadata(tag?: string): Promise<TaskMetadata | null>;

	/**
	 * Save metadata about the task collection
	 * @param metadata - Metadata object to save
	 * @param tag - Optional tag context for the metadata
	 * @returns Promise that resolves when save is complete
	 */
	saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;

	/**
	 * Get all available tags in storage
	 * @returns Promise that resolves to array of available tags
	 */
	getAllTags(): Promise<string[]>;

	/**
	 * Create a new tag
	 * @param tagName - Name of the tag to create
	 * @param options - Creation options
	 * @param options.copyFrom - Tag to copy tasks from (optional)
	 * @param options.description - Tag description (optional)
	 * @returns Promise that resolves when creation is complete
	 */
	createTag(
		tagName: string,
		options?: { copyFrom?: string; description?: string }
	): Promise<void>;

	/**
	 * Delete all tasks and metadata for a specific tag
	 * @param tag - Tag to delete
	 * @returns Promise that resolves when deletion is complete
	 */
	deleteTag(tag: string): Promise<void>;

	/**
	 * Rename a tag (move all tasks from old tag to new tag)
	 * @param oldTag - Current tag name
	 * @param newTag - New tag name
	 * @returns Promise that resolves when rename is complete
	 */
	renameTag(oldTag: string, newTag: string): Promise<void>;

	/**
	 * Copy all tasks from one tag to another
	 * @param sourceTag - Source tag to copy from
	 * @param targetTag - Target tag to copy to
	 * @returns Promise that resolves when copy is complete
	 */
	copyTag(sourceTag: string, targetTag: string): Promise<void>;

	/**
	 * Initialize storage (create necessary directories, files, etc.)
	 * @returns Promise that resolves when initialization is complete
	 */
	initialize(): Promise<void>;

	/**
	 * Clean up and close storage connections
	 * @returns Promise that resolves when cleanup is complete
	 */
	close(): Promise<void>;

	/**
	 * Get storage statistics (file sizes, task counts, etc.)
	 * @returns Promise that resolves to storage statistics
	 */
	getStats(): Promise<StorageStats>;

	/**
	 * Get the storage type identifier
	 * @returns The type of storage implementation ('file' or 'api')
	 */
	getStorageType(): 'file' | 'api';

	/**
	 * Get the current brief name (only applicable for API storage)
	 * @returns The brief name if using API storage with a selected brief, null otherwise
	 */
	getCurrentBriefName(): string | null;

	/**
	 * Get all tags with detailed statistics including task counts
	 * @returns Promise that resolves to tags with statistics
	 */
	getTagsWithStats(): Promise<TagsWithStatsResult>;
}

/**
 * Tag information with detailed statistics
 */
export interface TagInfo {
	/** Tag/Brief name */
	name: string;
	/** Whether this is the current/active tag */
	isCurrent: boolean;
	/** Total number of tasks in this tag */
	taskCount: number;
	/** Number of completed tasks */
	completedTasks: number;
	/** Breakdown of tasks by status */
	statusBreakdown: Record<string, number>;
	/** Subtask counts if available */
	subtaskCounts?: {
		totalSubtasks: number;
		subtasksByStatus: Record<string, number>;
	};
	/** Tag creation date */
	created?: string;

	/** Tag last modified date */
	updatedAt?: string;

	/** Tag description */
	description?: string;
	/** Brief/Tag status (for API storage briefs) */
	status?: string;
	/** Brief ID/UUID (for API storage) */
	briefId?: string;
}

/**
 * Result returned by getTagsWithStats
 */
export interface TagsWithStatsResult {
	/** List of tags with statistics */
	tags: TagInfo[];
	/** Current active tag name */
	currentTag: string | null;
	/** Total number of tags */
	totalTags: number;
}

/**
 * Storage statistics interface
 */
export interface StorageStats {
	/** Total number of tasks across all tags */
	totalTasks: number;
	/** Total number of tags */
	totalTags: number;
	/** Storage size in bytes */
	storageSize: number;
	/** Last modified timestamp */
	lastModified: string;
	/** Available tags with task counts */
	tagStats: Array<{
		tag: string;
		taskCount: number;
		lastModified: string;
	}>;
}

/**
 * Configuration options for storage implementations
 */
export interface StorageConfig {
	/** Base path for storage */
	basePath: string;
	/** Enable backup creation */
	enableBackup?: boolean;
	/** Maximum number of backups to keep */
	maxBackups?: number;
	/** Enable compression for storage */
	enableCompression?: boolean;
	/** File encoding (default: utf8) */
	encoding?: BufferEncoding;
	/** Enable atomic writes */
	atomicWrites?: boolean;
}

/**
 * Base abstract class for storage implementations
 * Provides common functionality and enforces the interface
 */
export abstract class BaseStorage implements IStorage {
	protected config: StorageConfig;

	constructor(config: StorageConfig) {
		this.config = config;
	}

	// Abstract methods that must be implemented by concrete classes
	abstract loadTasks(tag?: string, options?: LoadTasksOptions): Promise<Task[]>;
	abstract loadTask(taskId: string, tag?: string): Promise<Task | null>;
	abstract saveTasks(tasks: Task[], tag?: string): Promise<void>;
	abstract appendTasks(tasks: Task[], tag?: string): Promise<void>;
	abstract updateTask(
		taskId: string,
		updates: Partial<Task>,
		tag?: string
	): Promise<void>;
	abstract updateTaskWithPrompt(
		taskId: string,
		prompt: string,
		tag?: string,
		options?: { useResearch?: boolean; mode?: 'append' | 'update' | 'rewrite' }
	): Promise<void>;
	abstract expandTaskWithPrompt(
		taskId: string,
		tag?: string,
		options?: {
			numSubtasks?: number;
			useResearch?: boolean;
			additionalContext?: string;
			force?: boolean;
		}
	): Promise<ExpandTaskResult | void>;
	abstract updateTaskStatus(
		taskId: string,
		newStatus: TaskStatus,
		tag?: string
	): Promise<UpdateStatusResult>;
	abstract deleteTask(taskId: string, tag?: string): Promise<void>;
	abstract exists(tag?: string): Promise<boolean>;
	abstract loadMetadata(tag?: string): Promise<TaskMetadata | null>;
	abstract saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;
	abstract getAllTags(): Promise<string[]>;
	abstract createTag(
		tagName: string,
		options?: { copyFrom?: string; description?: string }
	): Promise<void>;
	abstract deleteTag(tag: string): Promise<void>;
	abstract renameTag(oldTag: string, newTag: string): Promise<void>;
	abstract copyTag(sourceTag: string, targetTag: string): Promise<void>;
	abstract initialize(): Promise<void>;
	abstract close(): Promise<void>;
	abstract getStats(): Promise<StorageStats>;
	abstract getStorageType(): 'file' | 'api';
	abstract getCurrentBriefName(): string | null;
	abstract getTagsWithStats(): Promise<TagsWithStatsResult>;
	/**
	 * Utility method to generate backup filename
	 * @param originalPath - Original file path
	 * @returns Backup file path with timestamp
	 */
	protected generateBackupPath(originalPath: string): string {
		const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
		const parts = originalPath.split('.');
		const extension = parts.pop();
		const baseName = parts.join('.');
		return `${baseName}.backup.${timestamp}.${extension}`;
	}

	/**
	 * Utility method to validate task data before storage operations
	 * @param task - Task to validate
	 * @throws Error if task is invalid
	 */
	protected validateTask(task: Task): void {
		if (!task.id) {
			throw new Error('Task ID is required');
		}
		if (!task.title) {
			throw new Error('Task title is required');
		}
		if (!task.description) {
			throw new Error('Task description is required');
		}
		if (!task.status) {
			throw new Error('Task status is required');
		}
	}

	/**
	 * Utility method to sanitize tag names for file system safety
	 * @param tag - Tag name to sanitize
	 * @returns Sanitized tag name
	 */
	protected sanitizeTag(tag: string): string {
		return tag.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
	}
}

```

--------------------------------------------------------------------------------
/tests/unit/parse-prd.test.js:
--------------------------------------------------------------------------------

```javascript
// In tests/unit/parse-prd.test.js
// Testing parse-prd.js file extension compatibility with real files

import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import os from 'os';

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

// Mock the AI services to avoid real API calls
jest.unstable_mockModule(
	'../../scripts/modules/ai-services-unified.js',
	() => ({
		streamTextService: jest.fn(),
		generateObjectService: jest.fn(),
		streamObjectService: jest.fn().mockImplementation(async () => {
			return {
				get partialObjectStream() {
					return (async function* () {
						yield { tasks: [] };
						yield { tasks: [{ id: 1, title: 'Test Task', priority: 'high' }] };
					})();
				},
				object: Promise.resolve({
					tasks: [{ id: 1, title: 'Test Task', priority: 'high' }]
				})
			};
		})
	})
);

// Mock all config-manager exports comprehensively
jest.unstable_mockModule('../../scripts/modules/config-manager.js', () => ({
	getDebugFlag: jest.fn(() => false),
	getDefaultPriority: jest.fn(() => 'medium'),
	getMainModelId: jest.fn(() => 'test-model'),
	getResearchModelId: jest.fn(() => 'test-research-model'),
	getParametersForRole: jest.fn(() => ({ maxTokens: 1000, temperature: 0.7 })),
	getMainProvider: jest.fn(() => 'anthropic'),
	getResearchProvider: jest.fn(() => 'perplexity'),
	getFallbackProvider: jest.fn(() => 'anthropic'),
	getResponseLanguage: jest.fn(() => 'English'),
	getDefaultNumTasks: jest.fn(() => 10),
	getDefaultSubtasks: jest.fn(() => 5),
	getLogLevel: jest.fn(() => 'info'),
	getConfig: jest.fn(() => ({})),
	getAllProviders: jest.fn(() => ['anthropic', 'perplexity']),
	MODEL_MAP: {},
	VALID_PROVIDERS: ['anthropic', 'perplexity'],
	validateProvider: jest.fn(() => true),
	validateProviderModelCombination: jest.fn(() => true),
	isApiKeySet: jest.fn(() => true),
	hasCodebaseAnalysis: jest.fn(() => false)
}));

// Mock utils comprehensively to prevent CLI behavior
jest.unstable_mockModule('../../scripts/modules/utils.js', () => ({
	log: jest.fn(),
	writeJSON: jest.fn(),
	enableSilentMode: jest.fn(),
	disableSilentMode: jest.fn(),
	isSilentMode: jest.fn(() => false),
	getCurrentTag: jest.fn(() => 'master'),
	ensureTagMetadata: jest.fn(),
	readJSON: jest.fn(() => ({ master: { tasks: [] } })),
	findProjectRoot: jest.fn(() => '/tmp/test'),
	resolveEnvVariable: jest.fn(() => 'mock-key'),
	findTaskById: jest.fn(() => null),
	findTaskByPattern: jest.fn(() => []),
	validateTaskId: jest.fn(() => true),
	createTask: jest.fn(() => ({ id: 1, title: 'Mock Task' })),
	sortByDependencies: jest.fn((tasks) => tasks),
	isEmpty: jest.fn(() => false),
	truncate: jest.fn((text) => text),
	slugify: jest.fn((text) => text.toLowerCase()),
	getTagFromPath: jest.fn(() => 'master'),
	isValidTag: jest.fn(() => true),
	migrateToTaggedFormat: jest.fn(() => ({ master: { tasks: [] } })),
	performCompleteTagMigration: jest.fn(),
	resolveCurrentTag: jest.fn(() => 'master'),
	getDefaultTag: jest.fn(() => 'master'),
	performMigrationIfNeeded: jest.fn()
}));

// Mock prompt manager
jest.unstable_mockModule('../../scripts/modules/prompt-manager.js', () => ({
	getPromptManager: jest.fn(() => ({
		loadPrompt: jest.fn(() => ({
			systemPrompt: 'Test system prompt',
			userPrompt: 'Test user prompt'
		}))
	}))
}));

// Mock progress/UI components to prevent real CLI UI
jest.unstable_mockModule('../../src/progress/parse-prd-tracker.js', () => ({
	createParsePrdTracker: jest.fn(() => ({
		start: jest.fn(),
		stop: jest.fn(),
		cleanup: jest.fn(),
		addTaskLine: jest.fn(),
		updateTokens: jest.fn(),
		complete: jest.fn(),
		getSummary: jest.fn().mockReturnValue({
			taskPriorities: { high: 0, medium: 0, low: 0 },
			elapsedTime: 0,
			actionVerb: 'generated'
		})
	}))
}));

jest.unstable_mockModule('../../src/ui/parse-prd.js', () => ({
	displayParsePrdStart: jest.fn(),
	displayParsePrdSummary: jest.fn()
}));

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

// Mock task generation to prevent file operations
jest.unstable_mockModule(
	'../../scripts/modules/task-manager/generate-task-files.js',
	() => ({
		default: jest.fn()
	})
);

// Mock stream parser
jest.unstable_mockModule('../../src/utils/stream-parser.js', () => {
	// Define mock StreamingError class
	class StreamingError extends Error {
		constructor(message, code) {
			super(message);
			this.name = 'StreamingError';
			this.code = code;
		}
	}

	// Define mock error codes
	const STREAMING_ERROR_CODES = {
		NOT_ASYNC_ITERABLE: 'STREAMING_NOT_SUPPORTED',
		STREAM_PROCESSING_FAILED: 'STREAM_PROCESSING_FAILED',
		STREAM_NOT_ITERABLE: 'STREAM_NOT_ITERABLE'
	};

	return {
		parseStream: jest.fn(),
		StreamingError,
		STREAMING_ERROR_CODES
	};
});

// Mock other potential UI elements
jest.unstable_mockModule('ora', () => ({
	default: jest.fn(() => ({
		start: jest.fn(),
		stop: jest.fn(),
		succeed: jest.fn(),
		fail: jest.fn()
	}))
}));

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

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

// Mock constants
jest.unstable_mockModule('../../src/constants/task-priority.js', () => ({
	DEFAULT_TASK_PRIORITY: 'medium',
	TASK_PRIORITY_OPTIONS: ['low', 'medium', 'high']
}));

// Mock UI indicators
jest.unstable_mockModule('../../src/ui/indicators.js', () => ({
	getPriorityIndicators: jest.fn(() => ({
		high: '🔴',
		medium: '🟡',
		low: '🟢'
	}))
}));

// Import modules after mocking
const { generateObjectService } = await import(
	'../../scripts/modules/ai-services-unified.js'
);
const parsePRD = (
	await import('../../scripts/modules/task-manager/parse-prd/parse-prd.js')
).default;

describe('parse-prd file extension compatibility', () => {
	let tempDir;
	let testFiles;

	const mockTasksResponse = {
		tasks: [
			{
				id: 1,
				title: 'Test Task 1',
				description: 'First test task',
				status: 'pending',
				dependencies: [],
				priority: 'high',
				details: 'Implementation details for task 1',
				testStrategy: 'Unit tests for task 1'
			},
			{
				id: 2,
				title: 'Test Task 2',
				description: 'Second test task',
				status: 'pending',
				dependencies: [1],
				priority: 'medium',
				details: 'Implementation details for task 2',
				testStrategy: 'Integration tests for task 2'
			}
		],
		metadata: {
			projectName: 'Test Project',
			totalTasks: 2,
			sourceFile: 'test-prd',
			generatedAt: new Date().toISOString()
		}
	};

	const samplePRDContent = `# Test Project PRD

## Overview
Build a simple task management application.

## Features
1. Create and manage tasks
2. Set task priorities
3. Track task dependencies

## Technical Requirements
- React frontend
- Node.js backend
- PostgreSQL database

## Success Criteria
- Users can create tasks successfully
- Task dependencies work correctly`;

	beforeAll(() => {
		// Create temporary directory for test files
		tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'parse-prd-test-'));

		// Create test files with different extensions
		testFiles = {
			txt: path.join(tempDir, 'test-prd.txt'),
			md: path.join(tempDir, 'test-prd.md'),
			rst: path.join(tempDir, 'test-prd.rst'),
			noExt: path.join(tempDir, 'test-prd')
		};

		// Write the same content to all test files
		Object.values(testFiles).forEach((filePath) => {
			fs.writeFileSync(filePath, samplePRDContent);
		});

		// Mock process.exit to prevent actual exit
		jest.spyOn(process, 'exit').mockImplementation(() => undefined);

		// Mock console methods to prevent output
		jest.spyOn(console, 'log').mockImplementation(() => {});
		jest.spyOn(console, 'error').mockImplementation(() => {});
	});

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

		// Restore mocks
		jest.restoreAllMocks();
	});

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

		// Mock successful AI response
		generateObjectService.mockResolvedValue({
			mainResult: { object: mockTasksResponse },
			telemetryData: {
				timestamp: new Date().toISOString(),
				userId: 'test-user',
				commandName: 'parse-prd',
				modelUsed: 'test-model',
				providerName: 'test-provider',
				inputTokens: 100,
				outputTokens: 200,
				totalTokens: 300,
				totalCost: 0.01,
				currency: 'USD'
			}
		});
	});

	test('should accept and parse .txt files', async () => {
		const outputPath = path.join(tempDir, 'tasks-txt.json');

		const result = await parsePRD(testFiles.txt, outputPath, 2, {
			force: true,
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			},
			projectRoot: tempDir
		});

		expect(result.success).toBe(true);
		expect(result.tasksPath).toBe(outputPath);
		expect(fs.existsSync(outputPath)).toBe(true);

		// Verify the content was parsed correctly
		const tasksData = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
		expect(tasksData.master.tasks).toHaveLength(2);
		expect(tasksData.master.tasks[0].title).toBe('Test Task 1');
	});

	test('should accept and parse .md files', async () => {
		const outputPath = path.join(tempDir, 'tasks-md.json');

		const result = await parsePRD(testFiles.md, outputPath, 2, {
			force: true,
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			},
			projectRoot: tempDir
		});

		expect(result.success).toBe(true);
		expect(result.tasksPath).toBe(outputPath);
		expect(fs.existsSync(outputPath)).toBe(true);

		// Verify the content was parsed correctly
		const tasksData = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
		expect(tasksData.master.tasks).toHaveLength(2);
	});

	test('should accept and parse files with other text extensions', async () => {
		const outputPath = path.join(tempDir, 'tasks-rst.json');

		const result = await parsePRD(testFiles.rst, outputPath, 2, {
			force: true,
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			},
			projectRoot: tempDir
		});

		expect(result.success).toBe(true);
		expect(result.tasksPath).toBe(outputPath);
		expect(fs.existsSync(outputPath)).toBe(true);
	});

	test('should accept and parse files with no extension', async () => {
		const outputPath = path.join(tempDir, 'tasks-noext.json');

		const result = await parsePRD(testFiles.noExt, outputPath, 2, {
			force: true,
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			},
			projectRoot: tempDir
		});

		expect(result.success).toBe(true);
		expect(result.tasksPath).toBe(outputPath);
		expect(fs.existsSync(outputPath)).toBe(true);
	});

	test('should produce identical results regardless of file extension', async () => {
		const outputs = {};

		// Parse each file type with a unique project root to avoid ID conflicts
		for (const [ext, filePath] of Object.entries(testFiles)) {
			// Create a unique subdirectory for each test to isolate them
			const testSubDir = path.join(tempDir, `test-${ext}`);
			fs.mkdirSync(testSubDir, { recursive: true });

			const outputPath = path.join(testSubDir, `tasks.json`);

			await parsePRD(filePath, outputPath, 2, {
				force: true,
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				},
				projectRoot: testSubDir
			});

			const tasksData = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
			outputs[ext] = tasksData;
		}

		// Compare all outputs - they should be identical (except metadata timestamps)
		const baseOutput = outputs.txt;
		Object.values(outputs).forEach((output) => {
			expect(output.master.tasks).toEqual(baseOutput.master.tasks);
			expect(output.master.metadata.projectName).toEqual(
				baseOutput.master.metadata.projectName
			);
			expect(output.master.metadata.totalTasks).toEqual(
				baseOutput.master.metadata.totalTasks
			);
		});
	});

	test('should handle non-existent files gracefully', async () => {
		const nonExistentFile = path.join(tempDir, 'does-not-exist.txt');
		const outputPath = path.join(tempDir, 'tasks-error.json');

		await expect(
			parsePRD(nonExistentFile, outputPath, 2, {
				force: true,
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				},
				projectRoot: tempDir
			})
		).rejects.toThrow();
	});
});

```

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

```typescript
/**
 * @fileoverview StartCommand using Commander's native class pattern
 * Extends Commander.Command for better integration with the framework
 * This is a thin presentation layer over @tm/core's TaskExecutionService
 */

import { spawn } from 'child_process';
import {
	type StartTaskResult as CoreStartTaskResult,
	type StorageType,
	type TmCore,
	createTmCore
} from '@tm/core';
import boxen from 'boxen';
import chalk from 'chalk';
import { Command } from 'commander';
import ora, { type Ora } from 'ora';
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
import { displayError } from '../utils/error-handler.js';
import { getProjectRoot } from '../utils/project-root.js';
import * as ui from '../utils/ui.js';

/**
 * CLI-specific options interface for the start command
 */
export interface StartCommandOptions {
	id?: string;
	format?: 'text' | 'json';
	project?: string;
	dryRun?: boolean;
	force?: boolean;
	noStatusUpdate?: boolean;
}

/**
 * CLI-specific result type from start command
 * Extends the core result with CLI-specific display information
 */
export interface StartCommandResult extends CoreStartTaskResult {
	storageType?: Exclude<StorageType, 'auto'>;
}

/**
 * StartCommand extending Commander's Command class
 * This is a thin presentation layer over @tm/core's TaskExecutionService
 */
export class StartCommand extends Command {
	private tmCore?: TmCore;
	private lastResult?: StartCommandResult;

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

		// Configure the command
		this.description(
			'Start working on a task by launching claude-code with context'
		)
			.argument('[id]', 'Task ID to start working on')
			.option('-i, --id <id>', 'Task ID to start working on')
			.option('-f, --format <format>', 'Output format (text, json)', 'text')
			.option(
				'-p, --project <path>',
				'Project root directory (auto-detected if not provided)'
			)
			.option(
				'--dry-run',
				'Show what would be executed without launching claude-code'
			)
			.option(
				'--force',
				'Force start even if another task is already in-progress'
			)
			.option(
				'--no-status-update',
				'Do not automatically update task status to in-progress'
			)
			.action(
				async (taskId: string | undefined, options: StartCommandOptions) => {
					await this.executeCommand(taskId, options);
				}
			);
	}

	/**
	 * Execute the start command
	 */
	private async executeCommand(
		taskId: string | undefined,
		options: StartCommandOptions
	): Promise<void> {
		let spinner: Ora | null = null;

		try {
			// Validate options
			if (!this.validateOptions(options)) {
				process.exit(1);
			}

			// Initialize tm-core with spinner
			spinner = ora('Initializing Task Master...').start();
			await this.initializeCore(getProjectRoot(options.project));
			spinner.succeed('Task Master initialized');

			// Get the task ID from argument or option, or find next available task
			const idArg = taskId || options.id || null;
			let targetTaskId = idArg;

			if (!targetTaskId) {
				spinner = ora('Finding next available task...').start();
				targetTaskId = await this.performGetNextTask();
				if (targetTaskId) {
					spinner.succeed(`Found next task: #${targetTaskId}`);
				} else {
					spinner.fail('No available tasks found');
				}
			}

			if (!targetTaskId) {
				ui.displayError('No task ID provided and no available tasks found');
				process.exit(1);
			}

			// Show pre-launch message (no spinner needed, it's just display)
			if (!options.dryRun) {
				await this.showPreLaunchMessage(targetTaskId);
			}

			// Use tm-core's startTask method with spinner
			spinner = ora('Preparing task execution...').start();
			const coreResult = await this.performStartTask(targetTaskId, options);

			if (coreResult.started) {
				spinner.succeed(
					options.dryRun
						? 'Dry run completed'
						: 'Task prepared - launching Claude...'
				);
			} else {
				spinner.fail('Task execution failed');
			}

			// Execute command if we have one and it's not a dry run
			if (!options.dryRun && coreResult.command) {
				// Stop any remaining spinners before launching Claude
				if (spinner && !spinner.isSpinning) {
					// Clear the line to make room for Claude
					console.log();
				}
				await this.executeChildProcess(coreResult.command);
			}

			// Convert core result to CLI result with storage type (resolved, not config value)
			const result: StartCommandResult = {
				...coreResult,
				storageType: this.tmCore?.tasks.getStorageType()
			};

			// Store result for programmatic access
			this.setLastResult(result);

			// Display results (only for dry run or if execution failed)
			if (options.dryRun || !coreResult.started) {
				this.displayResults(result, options);
			}
		} catch (error: any) {
			if (spinner) {
				spinner.fail('Operation failed');
			}
			displayError(error);
		}
	}

	/**
	 * Validate command options
	 */
	private validateOptions(options: StartCommandOptions): boolean {
		// Validate format
		if (options.format && !['text', 'json'].includes(options.format)) {
			console.error(chalk.red(`Invalid format: ${options.format}`));
			console.error(chalk.gray(`Valid formats: text, json`));
			return false;
		}

		return true;
	}

	/**
	 * Initialize TmCore
	 */
	private async initializeCore(projectRoot: string): Promise<void> {
		if (!this.tmCore) {
			this.tmCore = await createTmCore({ projectPath: projectRoot });
		}
	}

	/**
	 * Get the next available task using tm-core
	 */
	private async performGetNextTask(): Promise<string | null> {
		if (!this.tmCore) {
			throw new Error('TmCore not initialized');
		}
		return this.tmCore.tasks.getNextAvailable();
	}

	/**
	 * Show pre-launch message using tm-core data
	 */
	private async showPreLaunchMessage(targetTaskId: string): Promise<void> {
		if (!this.tmCore) return;

		const { task, isSubtask } = await this.tmCore.tasks.get(targetTaskId);
		if (task) {
			const workItemText = isSubtask
				? `Subtask #${targetTaskId} - ${task.title}`
				: `Task #${task.id} - ${task.title}`;

			console.log(
				chalk.green('🚀 Starting: ') + chalk.white.bold(workItemText)
			);
			console.log(chalk.gray('Launching Claude Code...'));
			console.log(); // Empty line
		}
	}

	/**
	 * Perform start task using tm-core business logic
	 */
	private async performStartTask(
		targetTaskId: string,
		options: StartCommandOptions
	): Promise<CoreStartTaskResult> {
		if (!this.tmCore) {
			throw new Error('TmCore not initialized');
		}

		// Show spinner for status update if enabled
		let statusSpinner: Ora | null = null;
		if (!options.noStatusUpdate && !options.dryRun) {
			statusSpinner = ora('Updating task status to in-progress...').start();
		}

		// Get execution command from tm-core (instead of executing directly)
		const result = await this.tmCore.tasks.start(targetTaskId, {
			dryRun: options.dryRun,
			force: options.force,
			updateStatus: !options.noStatusUpdate
		});

		if (statusSpinner) {
			if (result.started) {
				statusSpinner.succeed('Task status updated');
			} else {
				statusSpinner.warn('Task status update skipped');
			}
		}

		if (!result) {
			throw new Error('Failed to start task - core result is undefined');
		}

		// Don't execute here - let the main executeCommand method handle it
		return result;
	}

	/**
	 * Execute the child process directly in the main thread for better process control
	 */
	private async executeChildProcess(command: {
		executable: string;
		args: string[];
		cwd: string;
	}): Promise<void> {
		return new Promise((resolve, reject) => {
			// Don't show the full command with args as it can be very long
			console.log(chalk.green('🚀 Launching Claude Code...'));
			console.log(); // Add space before Claude takes over

			const childProcess = spawn(command.executable, command.args, {
				cwd: command.cwd,
				stdio: 'inherit', // Inherit stdio from parent process
				shell: false
			});

			childProcess.on('close', (code) => {
				if (code === 0) {
					resolve();
				} else {
					reject(new Error(`Process exited with code ${code}`));
				}
			});

			childProcess.on('error', (error) => {
				reject(new Error(`Failed to spawn process: ${error.message}`));
			});

			// Handle process termination signals gracefully
			const cleanup = () => {
				if (childProcess && !childProcess.killed) {
					childProcess.kill('SIGTERM');
				}
			};

			process.on('SIGINT', cleanup);
			process.on('SIGTERM', cleanup);
			process.on('exit', cleanup);
		});
	}

	/**
	 * Display results based on format
	 */
	private displayResults(
		result: StartCommandResult,
		options: StartCommandOptions
	): void {
		const format = options.format || 'text';

		switch (format) {
			case 'json':
				this.displayJson(result);
				break;

			case 'text':
			default:
				this.displayTextResult(result, options);
				break;
		}
	}

	/**
	 * Display in JSON format
	 */
	private displayJson(result: StartCommandResult): void {
		console.log(JSON.stringify(result, null, 2));
	}

	/**
	 * Display result in text format
	 */
	private displayTextResult(
		result: StartCommandResult,
		options: StartCommandOptions
	): void {
		if (!result.found || !result.task) {
			console.log(
				boxen(chalk.yellow(`Task not found!`), {
					padding: { top: 0, bottom: 0, left: 1, right: 1 },
					borderColor: 'yellow',
					borderStyle: 'round',
					margin: { top: 1 }
				})
			);
			return;
		}

		const task = result.task;

		if (options.dryRun) {
			// For dry run, show full details since Claude Code won't be launched
			let headerText = `Dry Run: Starting Task #${task.id} - ${task.title}`;

			// If working on a specific subtask, highlight it in the header
			if (result.subtask && result.subtaskId) {
				headerText = `Dry Run: Starting Subtask #${task.id}.${result.subtaskId} - ${result.subtask.title}`;
			}

			displayTaskDetails(task, {
				customHeader: headerText,
				headerColor: 'yellow',
				storageType: result.storageType
			});

			// Show claude-code prompt
			if (result.executionOutput) {
				console.log(); // Empty line for spacing
				console.log(
					boxen(
						chalk.white.bold('Claude-Code Prompt:') +
							'\n\n' +
							result.executionOutput,
						{
							padding: 1,
							borderStyle: 'round',
							borderColor: 'cyan',
							width: process.stdout.columns * 0.95 || 100
						}
					)
				);
			}

			console.log(); // Empty line for spacing
			console.log(
				boxen(
					chalk.yellow(
						'🔍 Dry run - claude-code would be launched with the above prompt'
					),
					{
						padding: { top: 0, bottom: 0, left: 1, right: 1 },
						borderColor: 'yellow',
						borderStyle: 'round'
					}
				)
			);
		} else {
			// For actual execution, show minimal info since Claude Code will clear the terminal
			if (result.started) {
				// Determine what was worked on - task or subtask
				let workItemText = `Task: #${task.id} - ${task.title}`;
				let statusTarget = task.id;

				if (result.subtask && result.subtaskId) {
					workItemText = `Subtask: #${task.id}.${result.subtaskId} - ${result.subtask.title}`;
					statusTarget = `${task.id}.${result.subtaskId}`;
				}

				// Post-execution message (shown after Claude Code exits)
				console.log(
					boxen(
						chalk.green.bold('🎉 Task Session Complete!') +
							'\n\n' +
							chalk.white(workItemText) +
							'\n\n' +
							chalk.cyan('Next steps:') +
							'\n' +
							`• Run ${chalk.yellow('tm show ' + task.id)} to review task details\n` +
							`• Run ${chalk.yellow('tm set-status --id=' + statusTarget + ' --status=done')} when complete\n` +
							`• Run ${chalk.yellow('tm next')} to find the next available task\n` +
							`• Run ${chalk.yellow('tm start')} to begin the next task`,
						{
							padding: 1,
							borderStyle: 'round',
							borderColor: 'green',
							width: process.stdout.columns * 0.95 || 100,
							margin: { top: 1 }
						}
					)
				);
			} else {
				// Error case
				console.log(
					boxen(
						chalk.red(
							'❌ Failed to launch claude-code' +
								(result.error ? `\nError: ${result.error}` : '')
						),
						{
							padding: { top: 0, bottom: 0, left: 1, right: 1 },
							borderColor: 'red',
							borderStyle: 'round'
						}
					)
				);
			}
		}

		console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
	}

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

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

	/**
	 * Clean up resources
	 */
	async cleanup(): Promise<void> {
		if (this.tmCore) {
			this.tmCore = undefined;
		}
	}

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

```
Page 25/50FirstPrevNextLast