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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/scripts/modules/task-manager/analyze-task-complexity.js:
--------------------------------------------------------------------------------

```javascript
import chalk from 'chalk';
import boxen from 'boxen';
import readline from 'readline';
import fs from 'fs';

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

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

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

import {
	getDebugFlag,
	getProjectName,
	hasCodebaseAnalysis
} from '../config-manager.js';
import { getPromptManager } from '../prompt-manager.js';
import { LEGACY_TASKS_FILE } from '../../../src/constants/paths.js';
import { resolveComplexityReportOutputPath } from '../../../src/utils/path-utils.js';
import { ContextGatherer } from '../utils/contextGatherer.js';
import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
import { flattenTasksWithSubtasks } from '../utils.js';

/**
 * Analyzes task complexity and generates expansion recommendations
 * @param {Object} options Command options
 * @param {string} options.file - Path to tasks file
 * @param {string} options.output - Path to report output file
 * @param {string|number} [options.threshold] - Complexity threshold
 * @param {boolean} [options.research] - Use research role
 * @param {string} [options.projectRoot] - Project root path (for MCP/env fallback).
 * @param {string} [options.tag] - Tag for the task
 * @param {string} [options.id] - Comma-separated list of task IDs to analyze specifically
 * @param {number} [options.from] - Starting task ID in a range to analyze
 * @param {number} [options.to] - Ending task ID in a range to analyze
 * @param {Object} [options._filteredTasksData] - Pre-filtered task data (internal use)
 * @param {number} [options._originalTaskCount] - Original task count (internal use)
 * @param {Object} context - Context object, potentially containing session and mcpLog
 * @param {Object} [context.session] - Session object from MCP server (optional)
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {function} [context.reportProgress] - Deprecated: Function to report progress (ignored)
 */
async function analyzeTaskComplexity(options, context = {}) {
	const { session, mcpLog } = context;
	const tasksPath = options.file || LEGACY_TASKS_FILE;
	const thresholdScore = parseFloat(options.threshold || '5');
	const useResearch = options.research || false;
	const projectRoot = options.projectRoot;
	const tag = options.tag;
	// New parameters for task ID filtering
	const specificIds = options.id
		? options.id
				.split(',')
				.map((id) => parseInt(id.trim(), 10))
				.filter((id) => !Number.isNaN(id))
		: null;
	const fromId = options.from !== undefined ? parseInt(options.from, 10) : null;
	const toId = options.to !== undefined ? parseInt(options.to, 10) : null;

	const outputFormat = mcpLog ? 'json' : 'text';

	const reportLog = (message, level = 'info') => {
		if (mcpLog) {
			mcpLog[level](message);
		} else if (!isSilentMode() && outputFormat === 'text') {
			log(level, message);
		}
	};

	// Resolve output path using tag-aware resolution
	const outputPath = resolveComplexityReportOutputPath(
		options.output,
		{ projectRoot, tag },
		reportLog
	);

	if (outputFormat === 'text') {
		console.log(
			chalk.blue(
				'Analyzing task complexity and generating expansion recommendations...'
			)
		);
	}

	try {
		reportLog(`Reading tasks from ${tasksPath}...`, 'info');
		let tasksData;
		let originalTaskCount = 0;
		let originalData = null;

		if (options._filteredTasksData) {
			tasksData = options._filteredTasksData;
			originalTaskCount = options._originalTaskCount || tasksData.tasks.length;
			if (!options._originalTaskCount) {
				try {
					originalData = readJSON(tasksPath, projectRoot, tag);
					if (originalData && originalData.tasks) {
						originalTaskCount = originalData.tasks.length;
					}
				} catch (e) {
					log('warn', `Could not read original tasks file: ${e.message}`);
				}
			}
		} else {
			originalData = readJSON(tasksPath, projectRoot, tag);
			if (
				!originalData ||
				!originalData.tasks ||
				!Array.isArray(originalData.tasks) ||
				originalData.tasks.length === 0
			) {
				throw new Error('No tasks found in the tasks file');
			}
			originalTaskCount = originalData.tasks.length;

			// Filter tasks based on active status
			const activeStatuses = ['pending', 'blocked', 'in-progress'];
			let filteredTasks = originalData.tasks.filter((task) =>
				activeStatuses.includes(task.status?.toLowerCase() || 'pending')
			);

			// Apply ID filtering if specified
			if (specificIds && specificIds.length > 0) {
				reportLog(
					`Filtering tasks by specific IDs: ${specificIds.join(', ')}`,
					'info'
				);
				filteredTasks = filteredTasks.filter((task) =>
					specificIds.includes(task.id)
				);

				if (outputFormat === 'text') {
					if (filteredTasks.length === 0 && specificIds.length > 0) {
						console.log(
							chalk.yellow(
								`Warning: No active tasks found with IDs: ${specificIds.join(', ')}`
							)
						);
					} else if (filteredTasks.length < specificIds.length) {
						const foundIds = filteredTasks.map((t) => t.id);
						const missingIds = specificIds.filter(
							(id) => !foundIds.includes(id)
						);
						console.log(
							chalk.yellow(
								`Warning: Some requested task IDs were not found or are not active: ${missingIds.join(', ')}`
							)
						);
					}
				}
			}
			// Apply range filtering if specified
			else if (fromId !== null || toId !== null) {
				const effectiveFromId = fromId !== null ? fromId : 1;
				const effectiveToId =
					toId !== null
						? toId
						: Math.max(...originalData.tasks.map((t) => t.id));

				reportLog(
					`Filtering tasks by ID range: ${effectiveFromId} to ${effectiveToId}`,
					'info'
				);
				filteredTasks = filteredTasks.filter(
					(task) => task.id >= effectiveFromId && task.id <= effectiveToId
				);

				if (outputFormat === 'text' && filteredTasks.length === 0) {
					console.log(
						chalk.yellow(
							`Warning: No active tasks found in range: ${effectiveFromId}-${effectiveToId}`
						)
					);
				}
			}

			tasksData = {
				...originalData,
				tasks: filteredTasks,
				_originalTaskCount: originalTaskCount
			};
		}

		// --- Context Gathering ---
		let gatheredContext = '';
		if (originalData && originalData.tasks.length > 0) {
			try {
				const contextGatherer = new ContextGatherer(projectRoot, tag);
				const allTasksFlat = flattenTasksWithSubtasks(originalData.tasks);
				const fuzzySearch = new FuzzyTaskSearch(
					allTasksFlat,
					'analyze-complexity'
				);
				// Create a query from the tasks being analyzed
				const searchQuery = tasksData.tasks
					.map((t) => `${t.title} ${t.description}`)
					.join(' ');
				const searchResults = fuzzySearch.findRelevantTasks(searchQuery, {
					maxResults: 10
				});
				const relevantTaskIds = fuzzySearch.getTaskIds(searchResults);

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

		const skippedCount = originalTaskCount - tasksData.tasks.length;
		reportLog(
			`Found ${originalTaskCount} total tasks in the task file.`,
			'info'
		);

		// Updated messaging to reflect filtering logic
		if (specificIds || fromId !== null || toId !== null) {
			const filterMsg = specificIds
				? `Analyzing ${tasksData.tasks.length} tasks with specific IDs: ${specificIds.join(', ')}`
				: `Analyzing ${tasksData.tasks.length} tasks in range: ${fromId || 1} to ${toId || 'end'}`;

			reportLog(filterMsg, 'info');
			if (outputFormat === 'text') {
				console.log(chalk.blue(filterMsg));
			}
		} else if (skippedCount > 0) {
			const skipMessage = `Skipping ${skippedCount} tasks marked as done/cancelled/deferred. Analyzing ${tasksData.tasks.length} active tasks.`;
			reportLog(skipMessage, 'info');
			if (outputFormat === 'text') {
				console.log(chalk.yellow(skipMessage));
			}
		}

		// Check for existing report before doing analysis
		let existingReport = null;
		const existingAnalysisMap = new Map(); // For quick lookups by task ID
		try {
			if (fs.existsSync(outputPath)) {
				existingReport = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
				reportLog(`Found existing complexity report at ${outputPath}`, 'info');

				if (
					existingReport &&
					existingReport.complexityAnalysis &&
					Array.isArray(existingReport.complexityAnalysis)
				) {
					// Create lookup map of existing analysis entries
					existingReport.complexityAnalysis.forEach((item) => {
						existingAnalysisMap.set(item.taskId, item);
					});
					reportLog(
						`Existing report contains ${existingReport.complexityAnalysis.length} task analyses`,
						'info'
					);
				}
			}
		} catch (readError) {
			reportLog(
				`Warning: Could not read existing report: ${readError.message}`,
				'warn'
			);
			existingReport = null;
			existingAnalysisMap.clear();
		}

		if (tasksData.tasks.length === 0) {
			// If using ID filtering but no matching tasks, return existing report or empty
			if (existingReport && (specificIds || fromId !== null || toId !== null)) {
				reportLog(
					'No matching tasks found for analysis. Keeping existing report.',
					'info'
				);
				if (outputFormat === 'text') {
					console.log(
						chalk.yellow(
							'No matching tasks found for analysis. Keeping existing report.'
						)
					);
				}
				return {
					report: existingReport,
					telemetryData: null
				};
			}

			// Otherwise create empty report
			const emptyReport = {
				meta: {
					generatedAt: new Date().toISOString(),
					tasksAnalyzed: 0,
					thresholdScore: thresholdScore,
					projectName: getProjectName(session),
					usedResearch: useResearch
				},
				complexityAnalysis: existingReport?.complexityAnalysis || []
			};
			reportLog(`Writing complexity report to ${outputPath}...`, 'info');
			fs.writeFileSync(
				outputPath,
				JSON.stringify(emptyReport, null, '\t'),
				'utf8'
			);
			reportLog(
				`Task complexity analysis complete. Report written to ${outputPath}`,
				'success'
			);
			if (outputFormat === 'text') {
				console.log(
					chalk.green(
						`Task complexity analysis complete. Report written to ${outputPath}`
					)
				);
				const highComplexity = 0;
				const mediumComplexity = 0;
				const lowComplexity = 0;
				const totalAnalyzed = 0;

				console.log('\nComplexity Analysis Summary:');
				console.log('----------------------------');
				console.log(`Tasks in input file: ${originalTaskCount}`);
				console.log(`Tasks successfully analyzed: ${totalAnalyzed}`);
				console.log(`High complexity tasks: ${highComplexity}`);
				console.log(`Medium complexity tasks: ${mediumComplexity}`);
				console.log(`Low complexity tasks: ${lowComplexity}`);
				console.log(
					`Sum verification: ${highComplexity + mediumComplexity + lowComplexity} (should equal ${totalAnalyzed})`
				);
				console.log(`Research-backed analysis: ${useResearch ? 'Yes' : 'No'}`);
				console.log(
					`\nSee ${outputPath} for the full report and expansion commands.`
				);

				console.log(
					boxen(
						chalk.white.bold('Suggested Next Steps:') +
							'\n\n' +
							`${chalk.cyan('1.')} Run ${chalk.yellow('task-master complexity-report')} to review detailed findings\n` +
							`${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=<id>')} to break down complex tasks\n` +
							`${chalk.cyan('3.')} Run ${chalk.yellow('task-master expand --all')} to expand all pending tasks based on complexity`,
						{
							padding: 1,
							borderColor: 'cyan',
							borderStyle: 'round',
							margin: { top: 1 }
						}
					)
				);
			}
			return {
				report: emptyReport,
				telemetryData: null
			};
		}

		// Continue with regular analysis path
		// Load prompts using PromptManager
		const promptManager = getPromptManager();

		// Check if Claude Code is being used as the provider

		const promptParams = {
			tasks: tasksData.tasks,
			gatheredContext: gatheredContext || '',
			useResearch: useResearch,
			hasCodebaseAnalysis: hasCodebaseAnalysis(
				useResearch,
				projectRoot,
				session
			),
			projectRoot: projectRoot || ''
		};

		const { systemPrompt, userPrompt: prompt } = await promptManager.loadPrompt(
			'analyze-complexity',
			promptParams,
			'default'
		);

		let loadingIndicator = null;
		if (outputFormat === 'text') {
			loadingIndicator = startLoadingIndicator(
				`${useResearch ? 'Researching' : 'Analyzing'} the complexity of your tasks with AI...\n`
			);
		}

		let aiServiceResponse = null;
		let complexityAnalysis = null;

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

			aiServiceResponse = await generateObjectService({
				prompt,
				systemPrompt,
				role,
				session,
				projectRoot,
				schema: COMMAND_SCHEMAS['analyze-complexity'],
				objectName: 'complexityAnalysis',
				commandName: 'analyze-complexity',
				outputType: mcpLog ? 'mcp' : 'cli'
			});

			if (loadingIndicator) {
				stopLoadingIndicator(loadingIndicator);
				loadingIndicator = null;
			}
			if (outputFormat === 'text') {
				readline.clearLine(process.stdout, 0);
				readline.cursorTo(process.stdout, 0);
				console.log(chalk.green('AI service call complete.'));
			}

			// With generateObject, we get structured data directly
			complexityAnalysis = aiServiceResponse.mainResult?.complexityAnalysis;
			reportLog(
				`Received ${complexityAnalysis.length} complexity analyses from AI.`,
				'info'
			);

			const taskIds = tasksData.tasks.map((t) => t.id);
			const analysisTaskIds = complexityAnalysis.map((a) => a.taskId);
			const missingTaskIds = taskIds.filter(
				(id) => !analysisTaskIds.includes(id)
			);

			if (missingTaskIds.length > 0) {
				reportLog(
					`Missing analysis for ${missingTaskIds.length} tasks: ${missingTaskIds.join(', ')}`,
					'warn'
				);
				if (outputFormat === 'text') {
					console.log(
						chalk.yellow(
							`Missing analysis for ${missingTaskIds.length} tasks: ${missingTaskIds.join(', ')}`
						)
					);
				}
				for (const missingId of missingTaskIds) {
					const missingTask = tasksData.tasks.find((t) => t.id === missingId);
					if (missingTask) {
						reportLog(`Adding default analysis for task ${missingId}`, 'info');
						complexityAnalysis.push({
							taskId: missingId,
							taskTitle: missingTask.title,
							complexityScore: 5,
							recommendedSubtasks: 3,
							expansionPrompt: `Break down this task with a focus on ${missingTask.title.toLowerCase()}.`,
							reasoning:
								'Automatically added due to missing analysis in AI response.'
						});
					}
				}
			}

			// Merge with existing report - only keep entries from the current tag
			let finalComplexityAnalysis = [];

			if (existingReport && Array.isArray(existingReport.complexityAnalysis)) {
				// Create a map of task IDs that we just analyzed
				const analyzedTaskIds = new Set(
					complexityAnalysis.map((item) => item.taskId)
				);

				// Keep existing entries that weren't in this analysis run AND belong to the current tag
				// We determine tag membership by checking if the task ID exists in the current tag's tasks
				const currentTagTaskIds = new Set(tasksData.tasks.map((t) => t.id));
				const existingEntriesNotAnalyzed =
					existingReport.complexityAnalysis.filter(
						(item) =>
							!analyzedTaskIds.has(item.taskId) &&
							currentTagTaskIds.has(item.taskId) // Only keep entries for tasks in current tag
					);

				// Combine with new analysis
				finalComplexityAnalysis = [
					...existingEntriesNotAnalyzed,
					...complexityAnalysis
				];

				reportLog(
					`Merged ${complexityAnalysis.length} new analyses with ${existingEntriesNotAnalyzed.length} existing entries from current tag`,
					'info'
				);
			} else {
				// No existing report or invalid format, just use the new analysis
				finalComplexityAnalysis = complexityAnalysis;
			}

			const report = {
				meta: {
					generatedAt: new Date().toISOString(),
					tasksAnalyzed: tasksData.tasks.length,
					totalTasks: originalTaskCount,
					analysisCount: finalComplexityAnalysis.length,
					thresholdScore: thresholdScore,
					projectName: getProjectName(session),
					usedResearch: useResearch
				},
				complexityAnalysis: finalComplexityAnalysis
			};
			reportLog(`Writing complexity report to ${outputPath}...`, 'info');
			fs.writeFileSync(outputPath, JSON.stringify(report, null, '\t'), 'utf8');

			reportLog(
				`Task complexity analysis complete. Report written to ${outputPath}`,
				'success'
			);

			if (outputFormat === 'text') {
				console.log(
					chalk.green(
						`Task complexity analysis complete. Report written to ${outputPath}`
					)
				);
				// Calculate statistics specifically for this analysis run
				const highComplexity = complexityAnalysis.filter(
					(t) => t.complexityScore >= 8
				).length;
				const mediumComplexity = complexityAnalysis.filter(
					(t) => t.complexityScore >= 5 && t.complexityScore < 8
				).length;
				const lowComplexity = complexityAnalysis.filter(
					(t) => t.complexityScore < 5
				).length;
				const totalAnalyzed = complexityAnalysis.length;

				console.log('\nCurrent Analysis Summary:');
				console.log('----------------------------');
				console.log(`Tasks analyzed in this run: ${totalAnalyzed}`);
				console.log(`High complexity tasks: ${highComplexity}`);
				console.log(`Medium complexity tasks: ${mediumComplexity}`);
				console.log(`Low complexity tasks: ${lowComplexity}`);

				if (existingReport) {
					console.log('\nUpdated Report Summary:');
					console.log('----------------------------');
					console.log(
						`Total analyses in report: ${finalComplexityAnalysis.length}`
					);
					console.log(
						`Analyses from previous runs: ${finalComplexityAnalysis.length - totalAnalyzed}`
					);
					console.log(`New/updated analyses: ${totalAnalyzed}`);
				}

				console.log(`Research-backed analysis: ${useResearch ? 'Yes' : 'No'}`);
				console.log(
					`\nSee ${outputPath} for the full report and expansion commands.`
				);

				console.log(
					boxen(
						chalk.white.bold('Suggested Next Steps:') +
							'\n\n' +
							`${chalk.cyan('1.')} Run ${chalk.yellow('task-master complexity-report')} to review detailed findings\n` +
							`${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=<id>')} to break down complex tasks\n` +
							`${chalk.cyan('3.')} Run ${chalk.yellow('task-master expand --all')} to expand all pending tasks based on complexity`,
						{
							padding: 1,
							borderColor: 'cyan',
							borderStyle: 'round',
							margin: { top: 1 }
						}
					)
				);

				if (getDebugFlag(session)) {
					console.debug(
						chalk.gray(
							`Final analysis object: ${JSON.stringify(report, null, 2)}`
						)
					);
				}

				if (aiServiceResponse.telemetryData) {
					displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli');
				}
			}

			return {
				report: report,
				telemetryData: aiServiceResponse?.telemetryData,
				tagInfo: aiServiceResponse?.tagInfo
			};
		} catch (aiError) {
			if (loadingIndicator) stopLoadingIndicator(loadingIndicator);
			reportLog(`Error during AI service call: ${aiError.message}`, 'error');
			if (outputFormat === 'text') {
				console.error(
					chalk.red(`Error during AI service call: ${aiError.message}`)
				);
				if (aiError.message.includes('API key')) {
					console.log(
						chalk.yellow(
							'\nPlease ensure your API keys are correctly configured in .env or ~/.taskmaster/.env'
						)
					);
					console.log(
						chalk.yellow("Run 'task-master models --setup' if needed.")
					);
				}
			}
			throw aiError;
		}
	} catch (error) {
		reportLog(`Error analyzing task complexity: ${error.message}`, 'error');
		if (outputFormat === 'text') {
			console.error(
				chalk.red(`Error analyzing task complexity: ${error.message}`)
			);
			if (getDebugFlag(session)) {
				console.error(error);
			}
			process.exit(1);
		} else {
			throw error;
		}
	}
}

export default analyzeTaskComplexity;

```

--------------------------------------------------------------------------------
/tests/unit/ai-providers/gemini-cli.test.js:
--------------------------------------------------------------------------------

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

// Mock the ai module
jest.unstable_mockModule('ai', () => ({
	generateObject: jest.fn(),
	generateText: jest.fn(),
	streamText: jest.fn()
}));

// Mock the gemini-cli SDK module
jest.unstable_mockModule('ai-sdk-provider-gemini-cli', () => ({
	createGeminiProvider: jest.fn((options) => {
		const provider = (modelId, settings) => ({
			// Mock language model
			id: modelId,
			settings,
			authOptions: options
		});
		provider.languageModel = jest.fn((id, settings) => ({ id, settings }));
		provider.chat = provider.languageModel;
		return provider;
	})
}));

// Mock the base provider
jest.unstable_mockModule('../../../src/ai-providers/base-provider.js', () => ({
	BaseAIProvider: class {
		constructor() {
			this.name = 'Base Provider';
		}
		handleError(context, error) {
			throw error;
		}
		validateParams(params) {
			// Basic validation
			if (!params.modelId) {
				throw new Error('Model ID is required');
			}
		}
		validateMessages(messages) {
			if (!messages || !Array.isArray(messages)) {
				throw new Error('Invalid messages array');
			}
		}
		async generateObject(params) {
			// Mock implementation that can be overridden
			throw new Error('Mock base generateObject error');
		}
	}
}));

// Mock the log module
jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
	log: jest.fn()
}));

// Import after mocking
const { GeminiCliProvider } = await import(
	'../../../src/ai-providers/gemini-cli.js'
);
const { createGeminiProvider } = await import('ai-sdk-provider-gemini-cli');
const { generateObject, generateText, streamText } = await import('ai');
const { log } = await import('../../../scripts/modules/utils.js');

describe('GeminiCliProvider', () => {
	let provider;
	let consoleLogSpy;

	beforeEach(() => {
		provider = new GeminiCliProvider();
		jest.clearAllMocks();
		consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
	});

	afterEach(() => {
		consoleLogSpy.mockRestore();
	});

	describe('constructor', () => {
		it('should set the provider name to Gemini CLI', () => {
			expect(provider.name).toBe('Gemini CLI');
		});
	});

	describe('validateAuth', () => {
		it('should not throw an error when API key is provided', () => {
			expect(() => provider.validateAuth({ apiKey: 'test-key' })).not.toThrow();
			expect(consoleLogSpy).not.toHaveBeenCalled();
		});

		it('should not require API key and should not log messages', () => {
			expect(() => provider.validateAuth({})).not.toThrow();
			expect(consoleLogSpy).not.toHaveBeenCalled();
		});

		it('should not require any parameters', () => {
			expect(() => provider.validateAuth()).not.toThrow();
			expect(consoleLogSpy).not.toHaveBeenCalled();
		});
	});

	describe('getClient', () => {
		it('should return a gemini client with API key auth when apiKey is provided', async () => {
			const client = await provider.getClient({ apiKey: 'test-api-key' });

			expect(client).toBeDefined();
			expect(typeof client).toBe('function');
			expect(createGeminiProvider).toHaveBeenCalledWith({
				authType: 'api-key',
				apiKey: 'test-api-key'
			});
		});

		it('should return a gemini client with OAuth auth when no apiKey is provided', async () => {
			const client = await provider.getClient({});

			expect(client).toBeDefined();
			expect(typeof client).toBe('function');
			expect(createGeminiProvider).toHaveBeenCalledWith({
				authType: 'oauth-personal'
			});
		});

		it('should include baseURL when provided', async () => {
			const client = await provider.getClient({
				apiKey: 'test-key',
				baseURL: 'https://custom-endpoint.com'
			});

			expect(client).toBeDefined();
			expect(createGeminiProvider).toHaveBeenCalledWith({
				authType: 'api-key',
				apiKey: 'test-key',
				baseURL: 'https://custom-endpoint.com'
			});
		});

		it('should have languageModel and chat methods', async () => {
			const client = await provider.getClient({ apiKey: 'test-key' });
			expect(client.languageModel).toBeDefined();
			expect(client.chat).toBeDefined();
			expect(client.chat).toBe(client.languageModel);
		});
	});

	describe('_extractSystemMessage', () => {
		it('should extract single system message', () => {
			const messages = [
				{ role: 'system', content: 'You are a helpful assistant' },
				{ role: 'user', content: 'Hello' }
			];
			const result = provider._extractSystemMessage(messages);
			expect(result.systemPrompt).toBe('You are a helpful assistant');
			expect(result.messages).toEqual([{ role: 'user', content: 'Hello' }]);
		});

		it('should combine multiple system messages', () => {
			const messages = [
				{ role: 'system', content: 'You are helpful' },
				{ role: 'system', content: 'Be concise' },
				{ role: 'user', content: 'Hello' }
			];
			const result = provider._extractSystemMessage(messages);
			expect(result.systemPrompt).toBe('You are helpful\n\nBe concise');
			expect(result.messages).toEqual([{ role: 'user', content: 'Hello' }]);
		});

		it('should handle messages without system prompts', () => {
			const messages = [
				{ role: 'user', content: 'Hello' },
				{ role: 'assistant', content: 'Hi there' }
			];
			const result = provider._extractSystemMessage(messages);
			expect(result.systemPrompt).toBeUndefined();
			expect(result.messages).toEqual(messages);
		});

		it('should handle empty or invalid input', () => {
			expect(provider._extractSystemMessage([])).toEqual({
				systemPrompt: undefined,
				messages: []
			});
			expect(provider._extractSystemMessage(null)).toEqual({
				systemPrompt: undefined,
				messages: []
			});
			expect(provider._extractSystemMessage(undefined)).toEqual({
				systemPrompt: undefined,
				messages: []
			});
		});

		it('should add JSON enforcement when enforceJsonOutput is true', () => {
			const messages = [
				{ role: 'system', content: 'You are a helpful assistant' },
				{ role: 'user', content: 'Hello' }
			];
			const result = provider._extractSystemMessage(messages, {
				enforceJsonOutput: true
			});
			expect(result.systemPrompt).toContain('You are a helpful assistant');
			expect(result.systemPrompt).toContain(
				'CRITICAL: You MUST respond with ONLY valid JSON'
			);
			expect(result.messages).toEqual([{ role: 'user', content: 'Hello' }]);
		});

		it('should add JSON enforcement with no existing system message', () => {
			const messages = [{ role: 'user', content: 'Return JSON format' }];
			const result = provider._extractSystemMessage(messages, {
				enforceJsonOutput: true
			});
			expect(result.systemPrompt).toBe(
				'CRITICAL: You MUST respond with ONLY valid JSON. Do not include any explanatory text, markdown formatting, code block markers, or conversational phrases like "Here is" or "Of course". Your entire response must be parseable JSON that starts with { or [ and ends with } or ]. No exceptions.'
			);
			expect(result.messages).toEqual([
				{ role: 'user', content: 'Return JSON format' }
			]);
		});
	});

	describe('_detectJsonRequest', () => {
		it('should detect JSON requests from user messages', () => {
			const messages = [
				{
					role: 'user',
					content: 'Please return JSON format with subtasks array'
				}
			];
			expect(provider._detectJsonRequest(messages)).toBe(true);
		});

		it('should detect various JSON indicators', () => {
			const testCases = [
				'respond only with valid JSON',
				'return JSON format',
				'output schema: {"test": true}',
				'format: [{"id": 1}]',
				'Please return subtasks in array format',
				'Return an object with properties'
			];

			testCases.forEach((content) => {
				const messages = [{ role: 'user', content }];
				expect(provider._detectJsonRequest(messages)).toBe(true);
			});
		});

		it('should not detect JSON requests for regular conversation', () => {
			const messages = [{ role: 'user', content: 'Hello, how are you today?' }];
			expect(provider._detectJsonRequest(messages)).toBe(false);
		});

		it('should handle multiple user messages', () => {
			const messages = [
				{ role: 'user', content: 'Hello' },
				{ role: 'assistant', content: 'Hi there' },
				{ role: 'user', content: 'Now please return JSON format' }
			];
			expect(provider._detectJsonRequest(messages)).toBe(true);
		});
	});

	describe('_getJsonEnforcementPrompt', () => {
		it('should return strict JSON enforcement prompt', () => {
			const prompt = provider._getJsonEnforcementPrompt();
			expect(prompt).toContain('CRITICAL');
			expect(prompt).toContain('ONLY valid JSON');
			expect(prompt).toContain('No exceptions');
		});
	});

	describe('_isValidJson', () => {
		it('should return true for valid JSON objects', () => {
			expect(provider._isValidJson('{"test": true}')).toBe(true);
			expect(provider._isValidJson('{"subtasks": [{"id": 1}]}')).toBe(true);
		});

		it('should return true for valid JSON arrays', () => {
			expect(provider._isValidJson('[1, 2, 3]')).toBe(true);
			expect(provider._isValidJson('[{"id": 1}, {"id": 2}]')).toBe(true);
		});

		it('should return false for invalid JSON', () => {
			expect(provider._isValidJson('Of course. Here is...')).toBe(false);
			expect(provider._isValidJson('{"invalid": json}')).toBe(false);
			expect(provider._isValidJson('not json at all')).toBe(false);
		});

		it('should handle edge cases', () => {
			expect(provider._isValidJson('')).toBe(false);
			expect(provider._isValidJson(null)).toBe(false);
			expect(provider._isValidJson(undefined)).toBe(false);
			expect(provider._isValidJson('   {"test": true}   ')).toBe(true); // with whitespace
		});
	});

	describe('extractJson', () => {
		it('should extract JSON from markdown code blocks', () => {
			const input = '```json\n{"subtasks": [{"id": 1}]}\n```';
			const result = provider.extractJson(input);
			const parsed = JSON.parse(result);
			expect(parsed).toEqual({ subtasks: [{ id: 1 }] });
		});

		it('should extract JSON with explanatory text', () => {
			const input = 'Here\'s the JSON response:\n{"subtasks": [{"id": 1}]}';
			const result = provider.extractJson(input);
			const parsed = JSON.parse(result);
			expect(parsed).toEqual({ subtasks: [{ id: 1 }] });
		});

		it('should handle variable declarations', () => {
			const input = 'const result = {"subtasks": [{"id": 1}]};';
			const result = provider.extractJson(input);
			const parsed = JSON.parse(result);
			expect(parsed).toEqual({ subtasks: [{ id: 1 }] });
		});

		it('should handle trailing commas with jsonc-parser', () => {
			const input = '{"subtasks": [{"id": 1,}],}';
			const result = provider.extractJson(input);
			const parsed = JSON.parse(result);
			expect(parsed).toEqual({ subtasks: [{ id: 1 }] });
		});

		it('should handle arrays', () => {
			const input = 'The result is: [1, 2, 3]';
			const result = provider.extractJson(input);
			const parsed = JSON.parse(result);
			expect(parsed).toEqual([1, 2, 3]);
		});

		it('should handle nested objects with proper bracket matching', () => {
			const input =
				'Response: {"outer": {"inner": {"value": "test"}}} extra text';
			const result = provider.extractJson(input);
			const parsed = JSON.parse(result);
			expect(parsed).toEqual({ outer: { inner: { value: 'test' } } });
		});

		it('should handle escaped quotes in strings', () => {
			const input = '{"message": "He said \\"hello\\" to me"}';
			const result = provider.extractJson(input);
			const parsed = JSON.parse(result);
			expect(parsed).toEqual({ message: 'He said "hello" to me' });
		});

		it('should return original text if no JSON found', () => {
			const input = 'No JSON here';
			expect(provider.extractJson(input)).toBe(input);
		});

		it('should handle null or non-string input', () => {
			expect(provider.extractJson(null)).toBe(null);
			expect(provider.extractJson(undefined)).toBe(undefined);
			expect(provider.extractJson(123)).toBe(123);
		});

		it('should handle partial JSON by finding valid boundaries', () => {
			const input = '{"valid": true, "partial": "incomplete';
			// Should return original text since no valid JSON can be extracted
			expect(provider.extractJson(input)).toBe(input);
		});

		it('should handle performance edge cases with large text', () => {
			// Test with large text that has JSON at the end
			const largePrefix = 'This is a very long explanation. '.repeat(1000);
			const json = '{"result": "success"}';
			const input = largePrefix + json;

			const result = provider.extractJson(input);
			const parsed = JSON.parse(result);
			expect(parsed).toEqual({ result: 'success' });
		});

		it('should handle early termination for very large invalid content', () => {
			// Test that it doesn't hang on very large content without JSON
			const largeText = 'No JSON here. '.repeat(2000);
			const result = provider.extractJson(largeText);
			expect(result).toBe(largeText);
		});
	});

	describe('generateObject', () => {
		const mockParams = {
			modelId: 'gemini-2.0-flash-exp',
			apiKey: 'test-key',
			messages: [{ role: 'user', content: 'Test message' }],
			schema: { type: 'object', properties: {} },
			objectName: 'testObject'
		};

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

		it('should handle JSON parsing errors by attempting manual extraction', async () => {
			// Mock the parent generateObject to throw a JSON parsing error
			jest
				.spyOn(
					Object.getPrototypeOf(Object.getPrototypeOf(provider)),
					'generateObject'
				)
				.mockRejectedValueOnce(new Error('Failed to parse JSON response'));

			// Mock generateObject from ai module to return text with JSON
			generateObject.mockResolvedValueOnce({
				rawResponse: {
					text: 'Here is the JSON:\n```json\n{"subtasks": [{"id": 1}]}\n```'
				},
				object: null,
				usage: { promptTokens: 10, completionTokens: 20, totalTokens: 30 }
			});

			const result = await provider.generateObject(mockParams);

			expect(log).toHaveBeenCalledWith(
				'debug',
				expect.stringContaining('attempting manual extraction')
			);
			expect(generateObject).toHaveBeenCalledWith({
				model: expect.objectContaining({
					id: 'gemini-2.0-flash-exp',
					authOptions: expect.objectContaining({
						authType: 'api-key',
						apiKey: 'test-key'
					})
				}),
				messages: mockParams.messages,
				schema: mockParams.schema,
				mode: 'json', // Should use json mode for Gemini
				system: expect.stringContaining(
					'CRITICAL: You MUST respond with ONLY valid JSON'
				),
				maxTokens: undefined,
				temperature: undefined
			});
			expect(result.object).toEqual({ subtasks: [{ id: 1 }] });
		});

		it('should throw error if manual extraction also fails', async () => {
			// Mock parent to throw JSON error
			jest
				.spyOn(
					Object.getPrototypeOf(Object.getPrototypeOf(provider)),
					'generateObject'
				)
				.mockRejectedValueOnce(new Error('Failed to parse JSON'));

			// Mock generateObject to return unparseable text
			generateObject.mockResolvedValueOnce({
				rawResponse: { text: 'Not valid JSON at all' },
				object: null
			});

			await expect(provider.generateObject(mockParams)).rejects.toThrow(
				'Gemini CLI failed to generate valid JSON object: Failed to parse JSON'
			);
		});

		it('should pass through non-JSON errors unchanged', async () => {
			const otherError = new Error('Network error');
			jest
				.spyOn(
					Object.getPrototypeOf(Object.getPrototypeOf(provider)),
					'generateObject'
				)
				.mockRejectedValueOnce(otherError);

			await expect(provider.generateObject(mockParams)).rejects.toThrow(
				'Network error'
			);
			expect(generateObject).not.toHaveBeenCalled();
		});

		it('should handle successful response from parent', async () => {
			const mockResult = {
				object: { test: 'data' },
				usage: { inputTokens: 5, outputTokens: 10, totalTokens: 15 }
			};
			jest
				.spyOn(
					Object.getPrototypeOf(Object.getPrototypeOf(provider)),
					'generateObject'
				)
				.mockResolvedValueOnce(mockResult);

			const result = await provider.generateObject(mockParams);
			expect(result).toEqual(mockResult);
			expect(generateObject).not.toHaveBeenCalled();
		});
	});

	describe('system message support', () => {
		const mockParams = {
			modelId: 'gemini-2.0-flash-exp',
			apiKey: 'test-key',
			messages: [
				{ role: 'system', content: 'You are a helpful assistant' },
				{ role: 'user', content: 'Hello' }
			],
			maxTokens: 100,
			temperature: 0.7
		};

		describe('generateText with system messages', () => {
			beforeEach(() => {
				jest.clearAllMocks();
			});

			it('should pass system prompt separately to AI SDK', async () => {
				const { generateText } = await import('ai');
				generateText.mockResolvedValueOnce({
					text: 'Hello! How can I help you?',
					usage: { promptTokens: 10, completionTokens: 8, totalTokens: 18 }
				});

				const result = await provider.generateText(mockParams);

				expect(generateText).toHaveBeenCalledWith({
					model: expect.objectContaining({
						id: 'gemini-2.0-flash-exp'
					}),
					system: 'You are a helpful assistant',
					messages: [{ role: 'user', content: 'Hello' }],
					maxOutputTokens: 100,
					temperature: 0.7
				});
				expect(result.text).toBe('Hello! How can I help you?');
			});

			it('should handle messages without system prompt', async () => {
				const { generateText } = await import('ai');
				const paramsNoSystem = {
					...mockParams,
					messages: [{ role: 'user', content: 'Hello' }]
				};

				generateText.mockResolvedValueOnce({
					text: 'Hi there!',
					usage: { promptTokens: 5, completionTokens: 3, totalTokens: 8 }
				});

				await provider.generateText(paramsNoSystem);

				expect(generateText).toHaveBeenCalledWith({
					model: expect.objectContaining({
						id: 'gemini-2.0-flash-exp'
					}),
					system: undefined,
					messages: [{ role: 'user', content: 'Hello' }],
					maxOutputTokens: 100,
					temperature: 0.7
				});
			});
		});

		describe('streamText with system messages', () => {
			it('should pass system prompt separately to AI SDK', async () => {
				const { streamText } = await import('ai');
				const mockStream = { stream: 'mock-stream' };
				streamText.mockResolvedValueOnce(mockStream);

				const result = await provider.streamText(mockParams);

				expect(streamText).toHaveBeenCalledWith({
					model: expect.objectContaining({
						id: 'gemini-2.0-flash-exp'
					}),
					system: 'You are a helpful assistant',
					messages: [{ role: 'user', content: 'Hello' }],
					maxOutputTokens: 100,
					temperature: 0.7
				});
				expect(result).toBe(mockStream);
			});
		});

		describe('generateObject with system messages', () => {
			const mockObjectParams = {
				...mockParams,
				schema: { type: 'object', properties: {} },
				objectName: 'testObject'
			};

			it('should include system prompt in fallback generateObject call', async () => {
				// Mock parent to throw JSON error
				jest
					.spyOn(
						Object.getPrototypeOf(Object.getPrototypeOf(provider)),
						'generateObject'
					)
					.mockRejectedValueOnce(new Error('Failed to parse JSON'));

				// Mock direct generateObject call
				generateObject.mockResolvedValueOnce({
					object: { result: 'success' },
					usage: { promptTokens: 15, completionTokens: 10, totalTokens: 25 }
				});

				const result = await provider.generateObject(mockObjectParams);

				expect(generateObject).toHaveBeenCalledWith({
					model: expect.objectContaining({
						id: 'gemini-2.0-flash-exp'
					}),
					system: expect.stringContaining('You are a helpful assistant'),
					messages: [{ role: 'user', content: 'Hello' }],
					schema: mockObjectParams.schema,
					mode: 'json',
					maxOutputTokens: 100,
					temperature: 0.7
				});
				expect(result.object).toEqual({ result: 'success' });
			});
		});
	});

	// Note: Error handling for module loading is tested in integration tests
	// since dynamic imports are difficult to mock properly in unit tests

	describe('authentication scenarios', () => {
		it('should use api-key auth type with API key', async () => {
			await provider.getClient({ apiKey: 'gemini-test-key' });

			expect(createGeminiProvider).toHaveBeenCalledWith({
				authType: 'api-key',
				apiKey: 'gemini-test-key'
			});
		});

		it('should use oauth-personal auth type without API key', async () => {
			await provider.getClient({});

			expect(createGeminiProvider).toHaveBeenCalledWith({
				authType: 'oauth-personal'
			});
		});

		it('should handle empty string API key as no API key', async () => {
			await provider.getClient({ apiKey: '' });

			expect(createGeminiProvider).toHaveBeenCalledWith({
				authType: 'oauth-personal'
			});
		});
	});
});

```

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

```typescript
import * as vscode from 'vscode';
import { logger } from './logger';
import {
	getNotificationType,
	getToastDuration,
	shouldShowNotification
} from './notificationPreferences';

export enum ErrorSeverity {
	LOW = 'low',
	MEDIUM = 'medium',
	HIGH = 'high',
	CRITICAL = 'critical'
}

export enum ErrorCategory {
	MCP_CONNECTION = 'mcp_connection',
	CONFIGURATION = 'configuration',
	TASK_LOADING = 'task_loading',
	UI_RENDERING = 'ui_rendering',
	VALIDATION = 'validation',
	NETWORK = 'network',
	INTERNAL = 'internal',
	TASK_MASTER_API = 'TASK_MASTER_API',
	DATA_VALIDATION = 'DATA_VALIDATION',
	DATA_PARSING = 'DATA_PARSING',
	TASK_DATA_CORRUPTION = 'TASK_DATA_CORRUPTION',
	VSCODE_API = 'VSCODE_API',
	WEBVIEW = 'WEBVIEW',
	EXTENSION_HOST = 'EXTENSION_HOST',
	USER_INTERACTION = 'USER_INTERACTION',
	DRAG_DROP = 'DRAG_DROP',
	COMPONENT_RENDER = 'COMPONENT_RENDER',
	PERMISSION = 'PERMISSION',
	FILE_SYSTEM = 'FILE_SYSTEM',
	UNKNOWN = 'UNKNOWN'
}

export enum NotificationType {
	VSCODE_INFO = 'VSCODE_INFO',
	VSCODE_WARNING = 'VSCODE_WARNING',
	VSCODE_ERROR = 'VSCODE_ERROR',
	TOAST_SUCCESS = 'TOAST_SUCCESS',
	TOAST_INFO = 'TOAST_INFO',
	TOAST_WARNING = 'TOAST_WARNING',
	TOAST_ERROR = 'TOAST_ERROR',
	CONSOLE_ONLY = 'CONSOLE_ONLY',
	SILENT = 'SILENT'
}

export interface ErrorContext {
	// Core error information
	category: ErrorCategory;
	severity: ErrorSeverity;
	message: string;
	originalError?: Error | unknown;

	// Contextual information
	operation?: string; // What operation was being performed
	taskId?: string; // Related task ID if applicable
	userId?: string; // User context if applicable
	sessionId?: string; // Session context

	// Technical details
	stackTrace?: string;
	userAgent?: string;
	timestamp?: number;

	// Recovery information
	isRecoverable?: boolean;
	suggestedActions?: string[];
	documentationLink?: string;

	// Notification preferences
	notificationType?: NotificationType;
	showToUser?: boolean;
	logToConsole?: boolean;
	logToFile?: boolean;
}

export interface ErrorDetails {
	code: string;
	message: string;
	category: ErrorCategory;
	severity: ErrorSeverity;
	timestamp: Date;
	context?: Record<string, any>;
	stack?: string;
	userAction?: string;
	recovery?: {
		automatic: boolean;
		action?: () => Promise<void>;
		description?: string;
	};
}

export interface ErrorLogEntry {
	id: string;
	error: ErrorDetails;
	resolved: boolean;
	resolvedAt?: Date;
	attempts: number;
	lastAttempt?: Date;
}

/**
 * Base class for all Task Master errors
 */
export abstract class TaskMasterError extends Error {
	public readonly code: string;
	public readonly category: ErrorCategory;
	public readonly severity: ErrorSeverity;
	public readonly timestamp: Date;
	public readonly context?: Record<string, any>;
	public readonly userAction?: string;
	public readonly recovery?: {
		automatic: boolean;
		action?: () => Promise<void>;
		description?: string;
	};

	constructor(
		message: string,
		code: string,
		category: ErrorCategory,
		severity: ErrorSeverity = ErrorSeverity.MEDIUM,
		context?: Record<string, any>,
		userAction?: string,
		recovery?: {
			automatic: boolean;
			action?: () => Promise<void>;
			description?: string;
		}
	) {
		super(message);
		this.name = this.constructor.name;
		this.code = code;
		this.category = category;
		this.severity = severity;
		this.timestamp = new Date();
		this.context = context;
		this.userAction = userAction;
		this.recovery = recovery;

		// Capture stack trace
		if (Error.captureStackTrace) {
			Error.captureStackTrace(this, this.constructor);
		}
	}

	public toErrorDetails(): ErrorDetails {
		return {
			code: this.code,
			message: this.message,
			category: this.category,
			severity: this.severity,
			timestamp: this.timestamp,
			context: this.context,
			stack: this.stack,
			userAction: this.userAction,
			recovery: this.recovery
		};
	}
}

/**
 * MCP Connection related errors
 */
export class MCPConnectionError extends TaskMasterError {
	constructor(
		message: string,
		code = 'MCP_CONNECTION_FAILED',
		context?: Record<string, any>,
		recovery?: {
			automatic: boolean;
			action?: () => Promise<void>;
			description?: string;
		}
	) {
		super(
			message,
			code,
			ErrorCategory.MCP_CONNECTION,
			ErrorSeverity.HIGH,
			context,
			'Check your Task Master configuration and ensure the MCP server is accessible.',
			recovery
		);
	}
}

/**
 * Configuration related errors
 */
export class ConfigurationError extends TaskMasterError {
	constructor(
		message: string,
		code = 'CONFIGURATION_INVALID',
		context?: Record<string, any>
	) {
		super(
			message,
			code,
			ErrorCategory.CONFIGURATION,
			ErrorSeverity.MEDIUM,
			context,
			'Check your Task Master configuration in VS Code settings.'
		);
	}
}

/**
 * Task loading related errors
 */
export class TaskLoadingError extends TaskMasterError {
	constructor(
		message: string,
		code = 'TASK_LOADING_FAILED',
		context?: Record<string, any>,
		recovery?: {
			automatic: boolean;
			action?: () => Promise<void>;
			description?: string;
		}
	) {
		super(
			message,
			code,
			ErrorCategory.TASK_LOADING,
			ErrorSeverity.MEDIUM,
			context,
			'Try refreshing the task list or check your project configuration.',
			recovery
		);
	}
}

/**
 * UI rendering related errors
 */
export class UIRenderingError extends TaskMasterError {
	constructor(
		message: string,
		code = 'UI_RENDERING_FAILED',
		context?: Record<string, any>
	) {
		super(
			message,
			code,
			ErrorCategory.UI_RENDERING,
			ErrorSeverity.LOW,
			context,
			'Try closing and reopening the Kanban board.'
		);
	}
}

/**
 * Network related errors
 */
export class NetworkError extends TaskMasterError {
	constructor(
		message: string,
		code = 'NETWORK_ERROR',
		context?: Record<string, any>,
		recovery?: {
			automatic: boolean;
			action?: () => Promise<void>;
			description?: string;
		}
	) {
		super(
			message,
			code,
			ErrorCategory.NETWORK,
			ErrorSeverity.MEDIUM,
			context,
			'Check your network connection and firewall settings.',
			recovery
		);
	}
}

/**
 * Centralized error handler
 */
export class ErrorHandler {
	private static instance: ErrorHandler | null = null;
	private errorLog: ErrorLogEntry[] = [];
	private maxLogSize = 1000;
	private errorListeners: ((error: ErrorDetails) => void)[] = [];

	private constructor() {
		this.setupGlobalErrorHandlers();
	}

	static getInstance(): ErrorHandler {
		if (!ErrorHandler.instance) {
			ErrorHandler.instance = new ErrorHandler();
		}
		return ErrorHandler.instance;
	}

	/**
	 * Handle an error with comprehensive logging and recovery
	 */
	async handleError(
		error: Error | TaskMasterError,
		context?: Record<string, any>
	): Promise<void> {
		const errorDetails = this.createErrorDetails(error, context);
		const logEntry = this.logError(errorDetails);

		// Notify listeners
		this.notifyErrorListeners(errorDetails);

		// Show user notification based on severity
		await this.showUserNotification(errorDetails);

		// Attempt recovery if available
		if (errorDetails.recovery?.automatic && errorDetails.recovery.action) {
			try {
				await errorDetails.recovery.action();
				this.markErrorResolved(logEntry.id);
			} catch (recoveryError) {
				logger.error('Error recovery failed:', recoveryError);
				logEntry.attempts++;
				logEntry.lastAttempt = new Date();
			}
		}

		// Log to console with appropriate level
		this.logToConsole(errorDetails);
	}

	/**
	 * Handle critical errors that should stop execution
	 */
	async handleCriticalError(
		error: Error | TaskMasterError,
		context?: Record<string, any>
	): Promise<void> {
		const errorDetails = this.createErrorDetails(error, context);
		errorDetails.severity = ErrorSeverity.CRITICAL;

		await this.handleError(error, context);

		// Show critical error dialog
		const action = await vscode.window.showErrorMessage(
			`Critical Error in Task Master: ${errorDetails.message}`,
			'View Details',
			'Report Issue',
			'Restart Extension'
		);

		switch (action) {
			case 'View Details':
				await this.showErrorDetails(errorDetails);
				break;
			case 'Report Issue':
				await this.openIssueReport(errorDetails);
				break;
			case 'Restart Extension':
				await vscode.commands.executeCommand('workbench.action.reloadWindow');
				break;
		}
	}

	/**
	 * Add error event listener
	 */
	onError(listener: (error: ErrorDetails) => void): void {
		this.errorListeners.push(listener);
	}

	/**
	 * Remove error event listener
	 */
	removeErrorListener(listener: (error: ErrorDetails) => void): void {
		const index = this.errorListeners.indexOf(listener);
		if (index !== -1) {
			this.errorListeners.splice(index, 1);
		}
	}

	/**
	 * Get error log
	 */
	getErrorLog(
		category?: ErrorCategory,
		severity?: ErrorSeverity
	): ErrorLogEntry[] {
		let filteredLog = this.errorLog;

		if (category) {
			filteredLog = filteredLog.filter(
				(entry) => entry.error.category === category
			);
		}

		if (severity) {
			filteredLog = filteredLog.filter(
				(entry) => entry.error.severity === severity
			);
		}

		return filteredLog.slice().reverse(); // Most recent first
	}

	/**
	 * Clear error log
	 */
	clearErrorLog(): void {
		this.errorLog = [];
	}

	/**
	 * Export error log for debugging
	 */
	exportErrorLog(): string {
		return JSON.stringify(this.errorLog, null, 2);
	}

	/**
	 * Create error details from error instance
	 */
	private createErrorDetails(
		error: Error | TaskMasterError,
		context?: Record<string, any>
	): ErrorDetails {
		if (error instanceof TaskMasterError) {
			const details = error.toErrorDetails();
			if (context) {
				details.context = { ...details.context, ...context };
			}
			return details;
		}

		// Handle standard Error objects
		return {
			code: 'UNKNOWN_ERROR',
			message: error.message || 'An unknown error occurred',
			category: ErrorCategory.INTERNAL,
			severity: ErrorSeverity.MEDIUM,
			timestamp: new Date(),
			context: { ...context, errorName: error.name },
			stack: error.stack
		};
	}

	/**
	 * Log error to internal log
	 */
	private logError(errorDetails: ErrorDetails): ErrorLogEntry {
		const logEntry: ErrorLogEntry = {
			id: `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
			error: errorDetails,
			resolved: false,
			attempts: 0
		};

		this.errorLog.push(logEntry);

		// Maintain log size limit
		if (this.errorLog.length > this.maxLogSize) {
			this.errorLog = this.errorLog.slice(-this.maxLogSize);
		}

		return logEntry;
	}

	/**
	 * Mark error as resolved
	 */
	private markErrorResolved(errorId: string): void {
		const entry = this.errorLog.find((e) => e.id === errorId);
		if (entry) {
			entry.resolved = true;
			entry.resolvedAt = new Date();
		}
	}

	/**
	 * Show user notification based on error severity and user preferences
	 */
	private async showUserNotification(
		errorDetails: ErrorDetails
	): Promise<void> {
		// Check if user wants to see this notification
		if (!shouldShowNotification(errorDetails.category, errorDetails.severity)) {
			return;
		}

		const notificationType = getNotificationType(
			errorDetails.category,
			errorDetails.severity
		);
		const message = errorDetails.userAction
			? `${errorDetails.message} ${errorDetails.userAction}`
			: errorDetails.message;

		// Handle different notification types based on user preferences
		switch (notificationType) {
			case 'VSCODE_ERROR':
				await vscode.window.showErrorMessage(message);
				break;
			case 'VSCODE_WARNING':
				await vscode.window.showWarningMessage(message);
				break;
			case 'VSCODE_INFO':
				await vscode.window.showInformationMessage(message);
				break;
			case 'TOAST_SUCCESS':
			case 'TOAST_INFO':
			case 'TOAST_WARNING':
			case 'TOAST_ERROR':
				// These will be handled by the webview toast system
				// The error listener in extension.ts will send these to webview
				break;
			case 'CONSOLE_ONLY':
			case 'SILENT':
				// No user notification, just console logging
				break;
			default:
				// Fallback to severity-based notifications
				switch (errorDetails.severity) {
					case ErrorSeverity.CRITICAL:
						await vscode.window.showErrorMessage(message);
						break;
					case ErrorSeverity.HIGH:
						await vscode.window.showErrorMessage(message);
						break;
					case ErrorSeverity.MEDIUM:
						await vscode.window.showWarningMessage(message);
						break;
					case ErrorSeverity.LOW:
						await vscode.window.showInformationMessage(message);
						break;
				}
		}
	}

	/**
	 * Log to console with appropriate level
	 */
	private logToConsole(errorDetails: ErrorDetails): void {
		const logMessage = `[${errorDetails.category}] ${errorDetails.code}: ${errorDetails.message}`;

		switch (errorDetails.severity) {
			case ErrorSeverity.CRITICAL:
			case ErrorSeverity.HIGH:
				logger.error(logMessage, errorDetails);
				break;
			case ErrorSeverity.MEDIUM:
				logger.warn(logMessage, errorDetails);
				break;
			case ErrorSeverity.LOW:
				console.info(logMessage, errorDetails);
				break;
		}
	}

	/**
	 * Show detailed error information
	 */
	private async showErrorDetails(errorDetails: ErrorDetails): Promise<void> {
		const details = [
			`Error Code: ${errorDetails.code}`,
			`Category: ${errorDetails.category}`,
			`Severity: ${errorDetails.severity}`,
			`Time: ${errorDetails.timestamp.toISOString()}`,
			`Message: ${errorDetails.message}`
		];

		if (errorDetails.context) {
			details.push(`Context: ${JSON.stringify(errorDetails.context, null, 2)}`);
		}

		if (errorDetails.stack) {
			details.push(`Stack Trace: ${errorDetails.stack}`);
		}

		const content = details.join('\n\n');

		// Create temporary document to show error details
		const doc = await vscode.workspace.openTextDocument({
			content,
			language: 'plaintext'
		});

		await vscode.window.showTextDocument(doc);
	}

	/**
	 * Open GitHub issue report
	 */
	private async openIssueReport(errorDetails: ErrorDetails): Promise<void> {
		const issueTitle = encodeURIComponent(
			`Error: ${errorDetails.code} - ${errorDetails.message}`
		);
		const issueBody = encodeURIComponent(`
**Error Details:**
- Code: ${errorDetails.code}
- Category: ${errorDetails.category}
- Severity: ${errorDetails.severity}
- Time: ${errorDetails.timestamp.toISOString()}

**Message:**
${errorDetails.message}

**Context:**
${errorDetails.context ? JSON.stringify(errorDetails.context, null, 2) : 'None'}

**Steps to Reproduce:**
1. 
2. 
3. 

**Expected Behavior:**


**Additional Notes:**

    `);

		const issueUrl = `https://github.com/eyaltoledano/claude-task-master/issues/new?title=${issueTitle}&body=${issueBody}`;
		await vscode.env.openExternal(vscode.Uri.parse(issueUrl));
	}

	/**
	 * Notify error listeners
	 */
	private notifyErrorListeners(errorDetails: ErrorDetails): void {
		this.errorListeners.forEach((listener) => {
			try {
				listener(errorDetails);
			} catch (error) {
				logger.error('Error in error listener:', error);
			}
		});
	}

	/**
	 * Setup global error handlers
	 */
	private setupGlobalErrorHandlers(): void {
		// Handle unhandled promise rejections
		process.on('unhandledRejection', (reason, promise) => {
			// Create a concrete error class for internal errors
			class InternalError extends TaskMasterError {
				constructor(
					message: string,
					code: string,
					severity: ErrorSeverity,
					context?: Record<string, any>
				) {
					super(message, code, ErrorCategory.INTERNAL, severity, context);
				}
			}

			const error = new InternalError(
				'Unhandled Promise Rejection',
				'UNHANDLED_REJECTION',
				ErrorSeverity.HIGH,
				{ reason: String(reason), promise: String(promise) }
			);
			this.handleError(error);
		});

		// Handle uncaught exceptions
		process.on('uncaughtException', (error) => {
			// Create a concrete error class for internal errors
			class InternalError extends TaskMasterError {
				constructor(
					message: string,
					code: string,
					severity: ErrorSeverity,
					context?: Record<string, any>
				) {
					super(message, code, ErrorCategory.INTERNAL, severity, context);
				}
			}

			const taskMasterError = new InternalError(
				'Uncaught Exception',
				'UNCAUGHT_EXCEPTION',
				ErrorSeverity.CRITICAL,
				{ originalError: error.message, stack: error.stack }
			);
			this.handleCriticalError(taskMasterError);
		});
	}
}

/**
 * Utility functions for error handling
 */
export function getErrorHandler(): ErrorHandler {
	return ErrorHandler.getInstance();
}

export function createRecoveryAction(
	action: () => Promise<void>,
	description: string
) {
	return {
		automatic: false,
		action,
		description
	};
}

export function createAutoRecoveryAction(
	action: () => Promise<void>,
	description: string
) {
	return {
		automatic: true,
		action,
		description
	};
}

// Default error categorization rules
export const ERROR_CATEGORIZATION_RULES: Record<string, ErrorCategory> = {
	// Network patterns
	ECONNREFUSED: ErrorCategory.NETWORK,
	ENOTFOUND: ErrorCategory.NETWORK,
	ETIMEDOUT: ErrorCategory.NETWORK,
	'Network request failed': ErrorCategory.NETWORK,
	'fetch failed': ErrorCategory.NETWORK,

	// MCP patterns
	MCP: ErrorCategory.MCP_CONNECTION,
	'Task Master': ErrorCategory.TASK_MASTER_API,
	polling: ErrorCategory.TASK_MASTER_API,

	// VS Code patterns
	vscode: ErrorCategory.VSCODE_API,
	webview: ErrorCategory.WEBVIEW,
	extension: ErrorCategory.EXTENSION_HOST,

	// Data patterns
	JSON: ErrorCategory.DATA_PARSING,
	parse: ErrorCategory.DATA_PARSING,
	validation: ErrorCategory.DATA_VALIDATION,
	invalid: ErrorCategory.DATA_VALIDATION,

	// Permission patterns
	EACCES: ErrorCategory.PERMISSION,
	EPERM: ErrorCategory.PERMISSION,
	permission: ErrorCategory.PERMISSION,

	// File system patterns
	ENOENT: ErrorCategory.FILE_SYSTEM,
	EISDIR: ErrorCategory.FILE_SYSTEM,
	file: ErrorCategory.FILE_SYSTEM
};

// Severity mapping based on error categories
export const CATEGORY_SEVERITY_MAPPING: Record<ErrorCategory, ErrorSeverity> = {
	[ErrorCategory.NETWORK]: ErrorSeverity.MEDIUM,
	[ErrorCategory.MCP_CONNECTION]: ErrorSeverity.HIGH,
	[ErrorCategory.TASK_MASTER_API]: ErrorSeverity.HIGH,
	[ErrorCategory.DATA_VALIDATION]: ErrorSeverity.MEDIUM,
	[ErrorCategory.DATA_PARSING]: ErrorSeverity.HIGH,
	[ErrorCategory.TASK_DATA_CORRUPTION]: ErrorSeverity.CRITICAL,
	[ErrorCategory.VSCODE_API]: ErrorSeverity.HIGH,
	[ErrorCategory.WEBVIEW]: ErrorSeverity.MEDIUM,
	[ErrorCategory.EXTENSION_HOST]: ErrorSeverity.CRITICAL,
	[ErrorCategory.USER_INTERACTION]: ErrorSeverity.LOW,
	[ErrorCategory.DRAG_DROP]: ErrorSeverity.MEDIUM,
	[ErrorCategory.COMPONENT_RENDER]: ErrorSeverity.MEDIUM,
	[ErrorCategory.PERMISSION]: ErrorSeverity.CRITICAL,
	[ErrorCategory.FILE_SYSTEM]: ErrorSeverity.HIGH,
	[ErrorCategory.CONFIGURATION]: ErrorSeverity.MEDIUM,
	[ErrorCategory.UNKNOWN]: ErrorSeverity.HIGH,
	// Legacy mappings for existing categories
	[ErrorCategory.TASK_LOADING]: ErrorSeverity.HIGH,
	[ErrorCategory.UI_RENDERING]: ErrorSeverity.MEDIUM,
	[ErrorCategory.VALIDATION]: ErrorSeverity.MEDIUM,
	[ErrorCategory.INTERNAL]: ErrorSeverity.HIGH
};

// Notification type mapping based on severity
export const SEVERITY_NOTIFICATION_MAPPING: Record<
	ErrorSeverity,
	NotificationType
> = {
	[ErrorSeverity.LOW]: NotificationType.TOAST_INFO,
	[ErrorSeverity.MEDIUM]: NotificationType.TOAST_WARNING,
	[ErrorSeverity.HIGH]: NotificationType.VSCODE_WARNING,
	[ErrorSeverity.CRITICAL]: NotificationType.VSCODE_ERROR
};

/**
 * Automatically categorize an error based on its message and type
 */
export function categorizeError(
	error: Error | unknown,
	operation?: string
): ErrorCategory {
	const errorMessage = error instanceof Error ? error.message : String(error);
	const errorStack = error instanceof Error ? error.stack : undefined;
	const searchText =
		`${errorMessage} ${errorStack || ''} ${operation || ''}`.toLowerCase();

	for (const [pattern, category] of Object.entries(
		ERROR_CATEGORIZATION_RULES
	)) {
		if (searchText.includes(pattern.toLowerCase())) {
			return category;
		}
	}

	return ErrorCategory.UNKNOWN;
}

export function getSuggestedSeverity(category: ErrorCategory): ErrorSeverity {
	return CATEGORY_SEVERITY_MAPPING[category] || ErrorSeverity.HIGH;
}

export function getSuggestedNotificationType(
	severity: ErrorSeverity
): NotificationType {
	return (
		SEVERITY_NOTIFICATION_MAPPING[severity] || NotificationType.CONSOLE_ONLY
	);
}

export function createErrorContext(
	error: Error | unknown,
	operation?: string,
	overrides?: Partial<ErrorContext>
): ErrorContext {
	const category = categorizeError(error, operation);
	const severity = getSuggestedSeverity(category);
	const notificationType = getSuggestedNotificationType(severity);

	const baseContext: ErrorContext = {
		category,
		severity,
		message: error instanceof Error ? error.message : String(error),
		originalError: error,
		operation,
		timestamp: Date.now(),
		stackTrace: error instanceof Error ? error.stack : undefined,
		isRecoverable: severity !== ErrorSeverity.CRITICAL,
		notificationType,
		showToUser:
			severity === ErrorSeverity.HIGH || severity === ErrorSeverity.CRITICAL,
		logToConsole: true,
		logToFile:
			severity === ErrorSeverity.HIGH || severity === ErrorSeverity.CRITICAL
	};

	return { ...baseContext, ...overrides };
}

```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/update-task-by-id.test.js:
--------------------------------------------------------------------------------

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

jest.unstable_mockModule('fs', () => {
	const mockFs = {
		existsSync: jest.fn(() => true),
		writeFileSync: jest.fn(),
		readFileSync: jest.fn(),
		unlinkSync: jest.fn()
	};
	return { default: mockFs, ...mockFs };
});

jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
	readJSON: jest.fn(),
	writeJSON: jest.fn(),
	log: jest.fn(),
	isSilentMode: jest.fn(() => false),
	findProjectRoot: jest.fn(() => '/project'),
	flattenTasksWithSubtasks: jest.fn(() => []),
	truncate: jest.fn((t) => t),
	isEmpty: jest.fn(() => false),
	resolveEnvVariable: jest.fn(),
	findTaskById: jest.fn(),
	getCurrentTag: jest.fn(() => 'master'),
	resolveTag: jest.fn(() => 'master'),
	addComplexityToTask: jest.fn((task, complexity) => ({ ...task, complexity })),
	getTasksForTag: jest.fn((data, tag) => data[tag]?.tasks || []),
	setTasksForTag: jest.fn(),
	ensureTagMetadata: jest.fn((tagObj) => tagObj)
}));

jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
	displayBanner: jest.fn(),
	getStatusWithColor: jest.fn((s) => s),
	startLoadingIndicator: jest.fn(() => ({ stop: jest.fn() })),
	stopLoadingIndicator: jest.fn(),
	succeedLoadingIndicator: jest.fn(),
	failLoadingIndicator: jest.fn(),
	warnLoadingIndicator: jest.fn(),
	infoLoadingIndicator: jest.fn(),
	displayAiUsageSummary: jest.fn(),
	displayContextAnalysis: jest.fn()
}));

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

jest.unstable_mockModule(
	'../../../../../scripts/modules/ai-services-unified.js',
	() => ({
		generateTextService: jest
			.fn()
			.mockResolvedValue({ mainResult: { content: '{}' }, telemetryData: {} }),
		generateObjectService: jest.fn().mockResolvedValue({
			mainResult: {
				task: {
					id: 1,
					title: 'Updated Task',
					description: 'Updated description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: null,
					testStrategy: null,
					subtasks: []
				}
			},
			telemetryData: {}
		})
	})
);

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

// Mock @tm/bridge module
jest.unstable_mockModule('@tm/bridge', () => ({
	tryUpdateViaRemote: jest.fn().mockResolvedValue(null)
}));

// Mock bridge-utils module
jest.unstable_mockModule(
	'../../../../../scripts/modules/bridge-utils.js',
	() => ({
		createBridgeLogger: jest.fn(() => ({
			logger: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn()
			},
			report: jest.fn(),
			isMCP: false
		}))
	})
);

// Mock prompt-manager module
jest.unstable_mockModule(
	'../../../../../scripts/modules/prompt-manager.js',
	() => ({
		getPromptManager: jest.fn().mockReturnValue({
			loadPrompt: jest.fn((promptId, params) => ({
				systemPrompt:
					'You are an AI assistant that helps update a software development task with new requirements and information.',
				userPrompt: `Update the following task based on the provided information: ${params?.updatePrompt || 'User prompt for task update'}`,
				metadata: {
					templateId: 'update-task',
					version: '1.0.0',
					variant: 'default',
					parameters: params || {}
				}
			}))
		})
	})
);

// Mock contextGatherer module
jest.unstable_mockModule(
	'../../../../../scripts/modules/utils/contextGatherer.js',
	() => ({
		ContextGatherer: jest.fn().mockImplementation(() => ({
			gather: jest.fn().mockResolvedValue({
				fullContext: '',
				summary: ''
			})
		}))
	})
);

// Mock fuzzyTaskSearch module
jest.unstable_mockModule(
	'../../../../../scripts/modules/utils/fuzzyTaskSearch.js',
	() => ({
		FuzzyTaskSearch: jest.fn().mockImplementation(() => ({
			search: jest.fn().mockReturnValue([]),
			findRelevantTasks: jest.fn().mockReturnValue([]),
			getTaskIds: jest.fn().mockReturnValue([])
		}))
	})
);

const { readJSON, log } = await import(
	'../../../../../scripts/modules/utils.js'
);
const { tryUpdateViaRemote } = await import('@tm/bridge');
const { createBridgeLogger } = await import(
	'../../../../../scripts/modules/bridge-utils.js'
);
const { getPromptManager } = await import(
	'../../../../../scripts/modules/prompt-manager.js'
);
const { ContextGatherer } = await import(
	'../../../../../scripts/modules/utils/contextGatherer.js'
);
const { FuzzyTaskSearch } = await import(
	'../../../../../scripts/modules/utils/fuzzyTaskSearch.js'
);
const { default: updateTaskById } = await import(
	'../../../../../scripts/modules/task-manager/update-task-by-id.js'
);

// Import test fixtures for consistent sample data
import {
	taggedEmptyTasks,
	taggedOneTask
} from '../../../../fixtures/sample-tasks.js';

describe('updateTaskById validation', () => {
	beforeEach(() => {
		jest.clearAllMocks();
		jest.spyOn(process, 'exit').mockImplementation(() => {
			throw new Error('process.exit called');
		});
	});

	test('throws error if prompt is empty', async () => {
		await expect(
			updateTaskById(
				'tasks/tasks.json',
				1,
				'',
				false,
				{ tag: 'master' },
				'json'
			)
		).rejects.toThrow('Prompt cannot be empty');
	});

	test('throws error if task file missing', async () => {
		const fs = await import('fs');
		fs.existsSync.mockReturnValue(false);
		await expect(
			updateTaskById(
				'tasks/tasks.json',
				1,
				'prompt',
				false,
				{
					tag: 'master'
				},
				'json'
			)
		).rejects.toThrow('Tasks file not found');
	});

	test('throws error when task ID not found', async () => {
		const fs = await import('fs');
		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue(taggedEmptyTasks);
		await expect(
			updateTaskById(
				'tasks/tasks.json',
				42,
				'prompt',
				false,
				{
					tag: 'master'
				},
				'json'
			)
		).rejects.toThrow('Task with ID 42 not found');
		// Note: The error is reported through the bridge logger (report),
		// not the log function, so we don't assert on log being called
	});
});

describe('updateTaskById success path with generateObjectService', () => {
	let fs;
	let generateObjectService;

	beforeEach(async () => {
		jest.clearAllMocks();
		jest.spyOn(process, 'exit').mockImplementation(() => {
			throw new Error('process.exit called');
		});
		fs = await import('fs');
		const aiServices = await import(
			'../../../../../scripts/modules/ai-services-unified.js'
		);
		generateObjectService = aiServices.generateObjectService;
	});

	test('successfully updates task with all fields from generateObjectService', async () => {
		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Original Task',
					description: 'Original description',
					status: 'pending',
					dependencies: [],
					priority: 'low',
					details: null,
					testStrategy: null,
					subtasks: []
				}
			]
		});

		const updatedTaskData = {
			id: 1,
			title: 'Updated Task',
			description: 'Updated description',
			status: 'pending',
			dependencies: [2],
			priority: 'high',
			details: 'New implementation details',
			testStrategy: 'Unit tests required',
			subtasks: [
				{
					id: 1,
					title: 'Subtask 1',
					description: 'First subtask',
					status: 'pending',
					dependencies: []
				}
			]
		};

		generateObjectService.mockResolvedValue({
			mainResult: {
				task: updatedTaskData
			},
			telemetryData: {
				model: 'claude-3-5-sonnet-20241022',
				inputTokens: 100,
				outputTokens: 200
			}
		});

		const result = await updateTaskById(
			'tasks/tasks.json',
			1,
			'Update task with new requirements',
			false,
			{ tag: 'master' },
			'json'
		);

		// Verify generateObjectService was called (not generateTextService)
		expect(generateObjectService).toHaveBeenCalled();
		const callArgs = generateObjectService.mock.calls[0][0];

		// Verify correct arguments were passed
		expect(callArgs).toMatchObject({
			role: 'main',
			commandName: 'update-task',
			objectName: 'task'
		});
		expect(callArgs.schema).toBeDefined();
		expect(callArgs.systemPrompt).toContain(
			'update a software development task'
		);
		expect(callArgs.prompt).toContain('Update task with new requirements');

		// Verify the returned task contains all expected fields
		expect(result).toEqual({
			updatedTask: expect.objectContaining({
				id: 1,
				title: 'Updated Task',
				description: 'Updated description',
				status: 'pending',
				dependencies: [2],
				priority: 'high',
				details: 'New implementation details',
				testStrategy: 'Unit tests required',
				subtasks: expect.arrayContaining([
					expect.objectContaining({
						id: 1,
						title: 'Subtask 1',
						description: 'First subtask',
						status: 'pending'
					})
				])
			}),
			telemetryData: expect.objectContaining({
				model: 'claude-3-5-sonnet-20241022',
				inputTokens: 100,
				outputTokens: 200
			}),
			tagInfo: undefined
		});
	});

	test('handles generateObjectService with malformed mainResult', async () => {
		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Task',
					description: 'Description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: null,
					testStrategy: null,
					subtasks: []
				}
			]
		});

		generateObjectService.mockResolvedValue({
			mainResult: {
				task: null // Malformed: task is null
			},
			telemetryData: {}
		});

		await expect(
			updateTaskById(
				'tasks/tasks.json',
				1,
				'Update task',
				false,
				{ tag: 'master' },
				'json'
			)
		).rejects.toThrow('Received invalid task object from AI');
	});

	test('handles generateObjectService with missing required fields', async () => {
		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Task',
					description: 'Description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: null,
					testStrategy: null,
					subtasks: []
				}
			]
		});

		generateObjectService.mockResolvedValue({
			mainResult: {
				task: {
					id: 1,
					// Missing title and description
					status: 'pending',
					dependencies: [],
					priority: 'medium'
				}
			},
			telemetryData: {}
		});

		await expect(
			updateTaskById(
				'tasks/tasks.json',
				1,
				'Update task',
				false,
				{ tag: 'master' },
				'json'
			)
		).rejects.toThrow('Updated task missing required fields');
	});
});

describe('Remote Update via Bridge', () => {
	let fs;
	let generateObjectService;

	beforeEach(async () => {
		jest.clearAllMocks();
		jest.spyOn(process, 'exit').mockImplementation(() => {
			throw new Error('process.exit called');
		});
		fs = await import('fs');
		const aiServices = await import(
			'../../../../../scripts/modules/ai-services-unified.js'
		);
		generateObjectService = aiServices.generateObjectService;
	});

	test('should use remote update result when tryUpdateViaRemote succeeds', async () => {
		// Arrange - Mock successful remote update
		const remoteResult = {
			success: true,
			message: 'Task updated successfully via remote',
			data: {
				task: {
					id: 1,
					title: 'Updated via Remote',
					description: 'Updated description from remote',
					status: 'in-progress',
					dependencies: [],
					priority: 'high',
					details: 'Remote update details',
					testStrategy: 'Remote test strategy',
					subtasks: []
				}
			}
		};
		tryUpdateViaRemote.mockResolvedValue(remoteResult);

		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Original Task',
					description: 'Original description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: 'Original details',
					testStrategy: 'Original test strategy',
					subtasks: []
				}
			]
		});

		// Act
		const result = await updateTaskById(
			'tasks/tasks.json',
			1,
			'Update this task',
			false,
			{ tag: 'master' },
			'json'
		);

		// Assert - Should use remote result and NOT call local AI service
		expect(tryUpdateViaRemote).toHaveBeenCalled();
		expect(generateObjectService).not.toHaveBeenCalled();
		expect(result).toEqual(remoteResult);
	});

	test('should fallback to local update when tryUpdateViaRemote returns null', async () => {
		// Arrange - Mock remote returning null (no remote available)
		tryUpdateViaRemote.mockResolvedValue(null);

		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Task',
					description: 'Description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: 'Details',
					testStrategy: 'Test strategy',
					subtasks: []
				}
			]
		});

		generateObjectService.mockResolvedValue({
			mainResult: {
				task: {
					id: 1,
					title: 'Updated Task',
					description: 'Updated description',
					status: 'in-progress',
					dependencies: [],
					priority: 'high',
					details: 'Updated details',
					testStrategy: 'Updated test strategy',
					subtasks: []
				}
			},
			telemetryData: {}
		});

		// Act
		await updateTaskById(
			'tasks/tasks.json',
			1,
			'Update this task',
			false,
			{ tag: 'master' },
			'json'
		);

		// Assert - Should fallback to local update
		expect(tryUpdateViaRemote).toHaveBeenCalled();
		expect(generateObjectService).toHaveBeenCalled();
	});

	test('should propagate error when tryUpdateViaRemote throws error', async () => {
		// Arrange - Mock remote throwing error (it re-throws, doesn't return null)
		tryUpdateViaRemote.mockImplementation(() =>
			Promise.reject(new Error('Remote update service unavailable'))
		);

		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Task',
					description: 'Description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: 'Details',
					testStrategy: 'Test strategy',
					subtasks: []
				}
			]
		});

		generateObjectService.mockResolvedValue({
			mainResult: {
				task: {
					id: 1,
					title: 'Updated Task',
					description: 'Updated description',
					status: 'in-progress',
					dependencies: [],
					priority: 'high',
					details: 'Updated details',
					testStrategy: 'Updated test strategy',
					subtasks: []
				}
			},
			telemetryData: {}
		});

		// Act & Assert - Should propagate the error (not fallback to local)
		await expect(
			updateTaskById(
				'tasks/tasks.json',
				1,
				'Update this task',
				false,
				{ tag: 'master' },
				'json'
			)
		).rejects.toThrow('Remote update service unavailable');

		expect(tryUpdateViaRemote).toHaveBeenCalled();
		// Local update should NOT be called when remote throws
		expect(generateObjectService).not.toHaveBeenCalled();
	});
});

describe('Prompt Manager Integration', () => {
	let fs;
	let generateObjectService;

	beforeEach(async () => {
		jest.clearAllMocks();
		jest.spyOn(process, 'exit').mockImplementation(() => {
			throw new Error('process.exit called');
		});
		fs = await import('fs');
		const aiServices = await import(
			'../../../../../scripts/modules/ai-services-unified.js'
		);
		generateObjectService = aiServices.generateObjectService;
		tryUpdateViaRemote.mockResolvedValue(null); // No remote
	});

	test('should use prompt manager to load update prompts', async () => {
		// Arrange
		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Task',
					description: 'Description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: 'Details',
					testStrategy: 'Test strategy',
					subtasks: []
				}
			]
		});

		generateObjectService.mockResolvedValue({
			mainResult: {
				task: {
					id: 1,
					title: 'Updated Task',
					description: 'Updated description',
					status: 'in-progress',
					dependencies: [],
					priority: 'high',
					details: 'Updated details',
					testStrategy: 'Updated test strategy',
					subtasks: []
				}
			},
			telemetryData: {}
		});

		// Act
		await updateTaskById(
			'tasks/tasks.json',
			1,
			'Update this task with new requirements',
			false,
			{ tag: 'master', projectRoot: '/mock/project' },
			'json'
		);

		// Assert - Prompt manager should be called
		expect(getPromptManager).toHaveBeenCalled();
		const promptManagerInstance = getPromptManager.mock.results[0].value;
		expect(promptManagerInstance.loadPrompt).toHaveBeenCalled();
	});
});

describe('Context Gathering Integration', () => {
	let fs;
	let generateObjectService;

	beforeEach(async () => {
		jest.clearAllMocks();
		jest.spyOn(process, 'exit').mockImplementation(() => {
			throw new Error('process.exit called');
		});
		fs = await import('fs');
		const aiServices = await import(
			'../../../../../scripts/modules/ai-services-unified.js'
		);
		generateObjectService = aiServices.generateObjectService;
		tryUpdateViaRemote.mockResolvedValue(null); // No remote
	});

	test('should gather project context when projectRoot is provided', async () => {
		// Arrange
		const mockContextGatherer = {
			gather: jest.fn().mockResolvedValue({
				fullContext: 'Project context from files',
				summary: 'Context summary'
			})
		};
		ContextGatherer.mockImplementation(() => mockContextGatherer);

		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Task',
					description: 'Description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: 'Details',
					testStrategy: 'Test strategy',
					subtasks: []
				}
			]
		});

		generateObjectService.mockResolvedValue({
			mainResult: {
				task: {
					id: 1,
					title: 'Updated Task',
					description: 'Updated description',
					status: 'in-progress',
					dependencies: [],
					priority: 'high',
					details: 'Updated details',
					testStrategy: 'Updated test strategy',
					subtasks: []
				}
			},
			telemetryData: {}
		});

		// Act
		await updateTaskById(
			'tasks/tasks.json',
			1,
			'Update with context',
			false,
			{ tag: 'master', projectRoot: '/mock/project' },
			'json'
		);

		// Assert - Context gatherer should be instantiated and used
		expect(ContextGatherer).toHaveBeenCalledWith('/mock/project', 'master');
		expect(mockContextGatherer.gather).toHaveBeenCalled();
	});
});

describe('Fuzzy Task Search Integration', () => {
	let fs;
	let generateObjectService;

	beforeEach(async () => {
		jest.clearAllMocks();
		jest.spyOn(process, 'exit').mockImplementation(() => {
			throw new Error('process.exit called');
		});
		fs = await import('fs');
		const aiServices = await import(
			'../../../../../scripts/modules/ai-services-unified.js'
		);
		generateObjectService = aiServices.generateObjectService;
		tryUpdateViaRemote.mockResolvedValue(null); // No remote
	});

	test('should use fuzzy search to find related tasks for context', async () => {
		// Arrange
		const mockFuzzySearch = {
			findRelevantTasks: jest.fn().mockReturnValue([
				{ id: 2, title: 'Related Task 1', score: 0.9 },
				{ id: 3, title: 'Related Task 2', score: 0.85 }
			]),
			getTaskIds: jest.fn().mockReturnValue(['2', '3'])
		};
		FuzzyTaskSearch.mockImplementation(() => mockFuzzySearch);

		fs.existsSync.mockReturnValue(true);
		readJSON.mockReturnValue({
			tag: 'master',
			tasks: [
				{
					id: 1,
					title: 'Task to update',
					description: 'Description',
					status: 'pending',
					dependencies: [],
					priority: 'medium',
					details: 'Details',
					testStrategy: 'Test strategy',
					subtasks: []
				},
				{
					id: 2,
					title: 'Related Task 1',
					description: 'Related description',
					status: 'done',
					dependencies: [],
					priority: 'medium',
					details: 'Related details',
					testStrategy: 'Related test strategy',
					subtasks: []
				},
				{
					id: 3,
					title: 'Related Task 2',
					description: 'Another related description',
					status: 'pending',
					dependencies: [],
					priority: 'low',
					details: 'More details',
					testStrategy: 'Test strategy',
					subtasks: []
				}
			]
		});

		generateObjectService.mockResolvedValue({
			mainResult: {
				task: {
					id: 1,
					title: 'Updated Task',
					description: 'Updated description',
					status: 'in-progress',
					dependencies: [],
					priority: 'high',
					details: 'Updated details',
					testStrategy: 'Updated test strategy',
					subtasks: []
				}
			},
			telemetryData: {}
		});

		// Act
		await updateTaskById(
			'tasks/tasks.json',
			1,
			'Update with related task context',
			false,
			{ tag: 'master' },
			'json'
		);

		// Assert - Fuzzy search should be instantiated and used
		expect(FuzzyTaskSearch).toHaveBeenCalled();
		expect(mockFuzzySearch.findRelevantTasks).toHaveBeenCalledWith(
			expect.stringContaining('Task to update'),
			expect.objectContaining({
				maxResults: 5,
				includeSelf: true
			})
		);
		expect(mockFuzzySearch.getTaskIds).toHaveBeenCalled();
	});
});

```

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

```typescript
/**
 * @fileoverview Task Service
 * Core service for task operations - handles business logic between storage and API
 */

import type {
	Task,
	TaskFilter,
	TaskStatus,
	StorageType
} from '../../../common/types/index.js';
import type { IStorage } from '../../../common/interfaces/storage.interface.js';
import { ConfigManager } from '../../config/managers/config-manager.js';
import { StorageFactory } from '../../storage/services/storage-factory.js';
import { TaskEntity } from '../entities/task.entity.js';
import { ERROR_CODES, TaskMasterError } from '../../../common/errors/task-master-error.js';
import { getLogger } from '../../../common/logger/factory.js';
import type { ExpandTaskResult } from '../../integration/services/task-expansion.service.js';

/**
 * Result returned by getTaskList
 */
export interface TaskListResult {
	/** The filtered list of tasks */
	tasks: Task[];
	/** Total number of tasks before filtering */
	total: number;
	/** Number of tasks after filtering */
	filtered: number;
	/** The tag/brief name for these tasks (brief name for API storage, tag for file storage) */
	tag?: string;
	/** Storage type being used */
	storageType: StorageType;
}

/**
 * Options for getTaskList
 */
export interface GetTaskListOptions {
	/** Optional tag override (uses active tag from config if not provided) */
	tag?: string;
	/** Filter criteria */
	filter?: TaskFilter;
	/** Include subtasks in response */
	includeSubtasks?: boolean;
}

/**
 * TaskService handles all task-related operations
 * This is where business logic lives - it coordinates between ConfigManager and Storage
 */
export class TaskService {
	private configManager: ConfigManager;
	private storage: IStorage;
	private initialized = false;
	private logger = getLogger('TaskService');

	constructor(configManager: ConfigManager) {
		this.configManager = configManager;

		// Storage will be created during initialization
		this.storage = null as any;
	}

	/**
	 * Initialize the service
	 */
	async initialize(): Promise<void> {
		if (this.initialized) return;

		// Create storage based on configuration
		const storageConfig = this.configManager.getStorageConfig();
		const projectRoot = this.configManager.getProjectRoot();

		this.storage = await StorageFactory.createFromStorageConfig(
			storageConfig,
			projectRoot
		);

		// Initialize storage
		await this.storage.initialize();

		this.initialized = true;
	}

	/**
	 * Get list of tasks
	 * This is the main method that retrieves tasks from storage and applies filters
	 */
	async getTaskList(options: GetTaskListOptions = {}): Promise<TaskListResult> {
		// Determine which tag to use
		const activeTag = this.configManager.getActiveTag();
		const tag = options.tag || activeTag;

		try {
			// Determine if we can push filters to storage layer
			const canPushStatusFilter =
				options.filter?.status &&
				!options.filter.priority &&
				!options.filter.tags &&
				!options.filter.assignee &&
				!options.filter.search &&
				options.filter.hasSubtasks === undefined;

			// Build storage-level options
			const storageOptions: any = {};

			// Push status filter to storage if it's the only filter
			if (canPushStatusFilter) {
				const statuses = Array.isArray(options.filter!.status)
					? options.filter!.status
					: [options.filter!.status];
				// Only push single status to storage (multiple statuses need in-memory filtering)
				if (statuses.length === 1) {
					storageOptions.status = statuses[0];
				}
			}

			// Push subtask exclusion to storage
			if (options.includeSubtasks === false) {
				storageOptions.excludeSubtasks = true;
			}

			// Load tasks from storage with pushed-down filters
			const rawTasks = await this.storage.loadTasks(tag, storageOptions);

			// Get total count without status filters, but preserve subtask exclusion
			const baseOptions: any = {};
			if (options.includeSubtasks === false) {
				baseOptions.excludeSubtasks = true;
			}

			const allTasks =
				storageOptions.status !== undefined
					? await this.storage.loadTasks(tag, baseOptions)
					: rawTasks;

			// Convert to TaskEntity for business logic operations
			const taskEntities = TaskEntity.fromArray(rawTasks);

			// Apply remaining filters in-memory if needed
			let filteredEntities = taskEntities;
			if (options.filter && !canPushStatusFilter) {
				filteredEntities = this.applyFilters(taskEntities, options.filter);
			} else if (
				options.filter?.status &&
				Array.isArray(options.filter.status) &&
				options.filter.status.length > 1
			) {
				// Multiple statuses - filter in-memory
				filteredEntities = this.applyFilters(taskEntities, options.filter);
			}

			// Convert back to plain objects
			const tasks = filteredEntities.map((entity) => entity.toJSON());

			// For API storage, use brief name. For file storage, use tag.
			// This way consumers don't need to know about the difference.
			const storageType = this.getStorageType();
			const tagOrBrief =
				storageType === 'api'
					? this.storage.getCurrentBriefName() || tag
					: tag;

			return {
				tasks,
				total: allTasks.length,
				filtered: filteredEntities.length,
				tag: tagOrBrief, // For API: brief name, For file: tag
				storageType
			};
		} catch (error) {
			// If it's a user-facing error (like NO_BRIEF_SELECTED), don't log it as an internal error
			if (
				error instanceof TaskMasterError &&
				error.is(ERROR_CODES.NO_BRIEF_SELECTED)
			) {
				// Just re-throw user-facing errors without wrapping
				throw error;
			}

			// Log internal errors
			this.logger.error('Failed to get task list', error);
			throw new TaskMasterError(
				'Failed to get task list',
				ERROR_CODES.INTERNAL_ERROR,
				{
					operation: 'getTaskList',
					tag,
					hasFilter: !!options.filter
				},
				error as Error
			);
		}
	}

	/**
	 * Get a single task by ID - delegates to storage layer
	 */
	async getTask(taskId: string, tag?: string): Promise<Task | null> {
		// Use provided tag or get active tag
		const activeTag = tag || this.getActiveTag();

		try {
			// Delegate to storage layer which handles the specific logic for tasks vs subtasks
			return await this.storage.loadTask(String(taskId), activeTag);
		} catch (error) {
			// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
			if (
				error instanceof TaskMasterError &&
				error.is(ERROR_CODES.NO_BRIEF_SELECTED)
			) {
				throw error;
			}

			throw new TaskMasterError(
				`Failed to get task ${taskId}`,
				ERROR_CODES.STORAGE_ERROR,
				{
					operation: 'getTask',
					resource: 'task',
					taskId: String(taskId),
					tag: activeTag
				},
				error as Error
			);
		}
	}

	/**
	 * Get tasks filtered by status
	 */
	async getTasksByStatus(
		status: TaskStatus | TaskStatus[],
		tag?: string
	): Promise<Task[]> {
		const statuses = Array.isArray(status) ? status : [status];

		const result = await this.getTaskList({
			tag,
			filter: { status: statuses }
		});

		return result.tasks;
	}

	/**
	 * Get statistics about tasks
	 */
	async getTaskStats(tag?: string): Promise<{
		total: number;
		byStatus: Record<TaskStatus, number>;
		withSubtasks: number;
		blocked: number;
		storageType: StorageType;
	}> {
		const result = await this.getTaskList({
			tag,
			includeSubtasks: true
		});

		const stats = {
			total: result.total,
			byStatus: {} as Record<TaskStatus, number>,
			withSubtasks: 0,
			blocked: 0,
			storageType: result.storageType
		};

		// Initialize all statuses
		const allStatuses: TaskStatus[] = [
			'pending',
			'in-progress',
			'done',
			'deferred',
			'cancelled',
			'blocked',
			'review'
		];

		allStatuses.forEach((status) => {
			stats.byStatus[status] = 0;
		});

		// Count tasks
		result.tasks.forEach((task) => {
			stats.byStatus[task.status]++;

			if (task.subtasks && task.subtasks.length > 0) {
				stats.withSubtasks++;
			}

			if (task.status === 'blocked') {
				stats.blocked++;
			}
		});

		return stats;
	}

	/**
	 * Get next available task to work on
	 * Prioritizes eligible subtasks from in-progress parent tasks before falling back to top-level tasks
	 */
	async getNextTask(tag?: string): Promise<Task | null> {
		const result = await this.getTaskList({
			tag,
			filter: {
				status: ['pending', 'in-progress', 'done']
			}
		});

		const allTasks = result.tasks;
		const priorityValues = { critical: 4, high: 3, medium: 2, low: 1 };

		// Helper to convert subtask dependencies to full dotted notation
		const toFullSubId = (
			parentId: string,
			maybeDotId: string | number
		): string => {
			if (typeof maybeDotId === 'string' && maybeDotId.includes('.')) {
				return maybeDotId;
			}
			return `${parentId}.${maybeDotId}`;
		};

		// Build completed IDs set (both tasks and subtasks)
		const completedIds = new Set<string>();
		allTasks.forEach((t) => {
			if (t.status === 'done') {
				completedIds.add(String(t.id));
			}
			if (Array.isArray(t.subtasks)) {
				t.subtasks.forEach((st) => {
					if (st.status === 'done') {
						completedIds.add(`${t.id}.${st.id}`);
					}
				});
			}
		});

		// 1) Look for eligible subtasks from in-progress parent tasks
		const candidateSubtasks: Array<Task & { parentId?: string }> = [];

		allTasks
			.filter((t) => t.status === 'in-progress' && Array.isArray(t.subtasks))
			.forEach((parent) => {
				parent.subtasks!.forEach((st) => {
					const stStatus = (st.status || 'pending').toLowerCase();
					if (stStatus !== 'pending' && stStatus !== 'in-progress') return;

					const fullDeps =
						st.dependencies?.map((d) => toFullSubId(String(parent.id), d)) ??
						[];
					const depsSatisfied =
						fullDeps.length === 0 ||
						fullDeps.every((depId) => completedIds.has(String(depId)));

					if (depsSatisfied) {
						candidateSubtasks.push({
							id: `${parent.id}.${st.id}`,
							title: st.title || `Subtask ${st.id}`,
							status: st.status || 'pending',
							priority: st.priority || parent.priority || 'medium',
							dependencies: fullDeps,
							parentId: String(parent.id),
							description: st.description,
							details: st.details,
							testStrategy: st.testStrategy,
							subtasks: []
						} as Task & { parentId: string });
					}
				});
			});

		if (candidateSubtasks.length > 0) {
			// Sort by priority → dependency count → parent ID → subtask ID
			candidateSubtasks.sort((a, b) => {
				const pa =
					priorityValues[a.priority as keyof typeof priorityValues] ?? 2;
				const pb =
					priorityValues[b.priority as keyof typeof priorityValues] ?? 2;
				if (pb !== pa) return pb - pa;

				if (a.dependencies!.length !== b.dependencies!.length) {
					return a.dependencies!.length - b.dependencies!.length;
				}

				// Compare parent then subtask ID numerically
				const [aPar, aSub] = String(a.id).split('.').map(Number);
				const [bPar, bSub] = String(b.id).split('.').map(Number);
				if (aPar !== bPar) return aPar - bPar;
				return aSub - bSub;
			});

			return candidateSubtasks[0];
		}

		// 2) Fall back to top-level tasks (original logic)
		const eligibleTasks = allTasks.filter((task) => {
			const status = (task.status || 'pending').toLowerCase();
			if (status !== 'pending' && status !== 'in-progress') return false;

			const deps = task.dependencies ?? [];
			return deps.every((depId) => completedIds.has(String(depId)));
		});

		if (eligibleTasks.length === 0) return null;

		// Sort by priority → dependency count → task ID
		const nextTask = eligibleTasks.sort((a, b) => {
			const pa = priorityValues[a.priority as keyof typeof priorityValues] ?? 2;
			const pb = priorityValues[b.priority as keyof typeof priorityValues] ?? 2;
			if (pb !== pa) return pb - pa;

			const da = (a.dependencies ?? []).length;
			const db = (b.dependencies ?? []).length;
			if (da !== db) return da - db;

			return Number(a.id) - Number(b.id);
		})[0];

		return nextTask;
	}

	/**
	 * Apply filters to task entities
	 */
	private applyFilters(tasks: TaskEntity[], filter: TaskFilter): TaskEntity[] {
		return tasks.filter((task) => {
			// Status filter
			if (filter.status) {
				const statuses = Array.isArray(filter.status)
					? filter.status
					: [filter.status];
				if (!statuses.includes(task.status)) {
					return false;
				}
			}

			// Priority filter
			if (filter.priority) {
				const priorities = Array.isArray(filter.priority)
					? filter.priority
					: [filter.priority];
				if (!priorities.includes(task.priority)) {
					return false;
				}
			}

			// Tags filter
			if (filter.tags && filter.tags.length > 0) {
				if (
					!task.tags ||
					!filter.tags.some((tag) => task.tags?.includes(tag))
				) {
					return false;
				}
			}

			// Assignee filter
			if (filter.assignee) {
				if (task.assignee !== filter.assignee) {
					return false;
				}
			}

			// Search filter
			if (filter.search) {
				const searchLower = filter.search.toLowerCase();
				const inTitle = task.title.toLowerCase().includes(searchLower);
				const inDescription = task.description
					.toLowerCase()
					.includes(searchLower);
				const inDetails = task.details.toLowerCase().includes(searchLower);

				if (!inTitle && !inDescription && !inDetails) {
					return false;
				}
			}

			// Has subtasks filter
			if (filter.hasSubtasks !== undefined) {
				const hasSubtasks = task.subtasks.length > 0;
				if (hasSubtasks !== filter.hasSubtasks) {
					return false;
				}
			}

			return true;
		});
	}

	/**
	 * Get current storage type (resolved at runtime)
	 * Returns the actual storage type being used, never 'auto'
	 */
	getStorageType(): 'file' | 'api' {
		// Storage interface guarantees this method exists
		return this.storage.getStorageType();
	}

	/**
	 * Get the storage instance
	 * Internal use only - used by other services in the tasks module
	 */
	getStorage(): IStorage {
		return this.storage;
	}

	/**
	 * Get current active tag
	 */
	getActiveTag(): string {
		return this.configManager.getActiveTag();
	}

	/**
	 * Set active tag
	 */
	async setActiveTag(tag: string): Promise<void> {
		await this.configManager.setActiveTag(tag);
	}

	/**
	 * Update a task with new data (direct structural update)
	 * @param taskId - Task ID (supports numeric, alphanumeric, and subtask IDs)
	 * @param updates - Partial task object with fields to update
	 * @param tag - Optional tag context
	 */
	async updateTask(
		taskId: string | number,
		updates: Partial<Task>,
		tag?: string
	): Promise<void> {
		// Ensure we have storage
		if (!this.storage) {
			throw new TaskMasterError(
				'Storage not initialized',
				ERROR_CODES.STORAGE_ERROR
			);
		}

		// Auto-initialize if needed
		if (!this.initialized) {
			await this.initialize();
		}

		// Use provided tag or get active tag
		const activeTag = tag || this.getActiveTag();
		const taskIdStr = String(taskId);

		try {
			// Direct update - no AI processing
			await this.storage.updateTask(taskIdStr, updates, activeTag);
		} catch (error) {
			// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
			if (
				error instanceof TaskMasterError &&
				error.is(ERROR_CODES.NO_BRIEF_SELECTED)
			) {
				throw error;
			}

			throw new TaskMasterError(
				`Failed to update task ${taskId}`,
				ERROR_CODES.STORAGE_ERROR,
				{
					operation: 'updateTask',
					resource: 'task',
					taskId: taskIdStr,
					tag: activeTag
				},
				error as Error
			);
		}
	}

	/**
	 * Update a task using AI-powered prompt (natural language update)
	 * @param taskId - Task ID (supports numeric, alphanumeric, and subtask IDs)
	 * @param prompt - Natural language prompt describing the update
	 * @param tag - Optional tag context
	 * @param options - Optional update options
	 * @param options.useResearch - Use research AI for file storage updates
	 * @param options.mode - Update mode for API storage: 'append', 'update', or 'rewrite'
	 */
	async updateTaskWithPrompt(
		taskId: string | number,
		prompt: string,
		tag?: string,
		options?: { mode?: 'append' | 'update' | 'rewrite'; useResearch?: boolean }
	): Promise<void> {
		// Ensure we have storage
		if (!this.storage) {
			throw new TaskMasterError(
				'Storage not initialized',
				ERROR_CODES.STORAGE_ERROR
			);
		}

		// Auto-initialize if needed
		if (!this.initialized) {
			await this.initialize();
		}

		// Use provided tag or get active tag
		const activeTag = tag || this.getActiveTag();
		const taskIdStr = String(taskId);

		try {
			// AI-powered update - send prompt to storage layer
			// API storage: sends prompt to backend for server-side AI processing
			// File storage: must use client-side AI logic before calling this
			await this.storage.updateTaskWithPrompt(
				taskIdStr,
				prompt,
				activeTag,
				options
			);
		} catch (error) {
			// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
			if (
				error instanceof TaskMasterError
			) {
				throw error;
			}

			throw new TaskMasterError(
				`Failed to update task ${taskId} with prompt`,
				ERROR_CODES.STORAGE_ERROR,
				{
					operation: 'updateTaskWithPrompt',
					resource: 'task',
					taskId: taskIdStr,
					tag: activeTag,
					promptLength: prompt.length
				},
				error as Error
			);
		}
	}

	/**
	 * Expand a task into subtasks using AI-powered generation
	 * @param taskId - Task ID to expand (supports numeric and alphanumeric IDs)
	 * @param tag - Optional tag context
	 * @param options - Optional expansion options
	 * @param options.numSubtasks - Number of subtasks to generate
	 * @param options.useResearch - Use research AI for generation
	 * @param options.additionalContext - Additional context for generation
	 * @param options.force - Force regeneration even if subtasks exist
	 * @returns ExpandTaskResult when using API storage, void for file storage
	 */
	async expandTaskWithPrompt(
		taskId: string | number,
		tag?: string,
		options?: {
			numSubtasks?: number;
			useResearch?: boolean;
			additionalContext?: string;
			force?: boolean;
		}
	): Promise<ExpandTaskResult | void> {
		// Ensure we have storage
		if (!this.storage) {
			throw new TaskMasterError(
				'Storage not initialized',
				ERROR_CODES.STORAGE_ERROR
			);
		}

		// Auto-initialize if needed
		if (!this.initialized) {
			await this.initialize();
		}

		// Use provided tag or get active tag
		const activeTag = tag || this.getActiveTag();
		const taskIdStr = String(taskId);

		try {
			// AI-powered expansion - send to storage layer
			// API storage: sends request to backend for server-side AI processing
			// File storage: must use client-side AI logic before calling this
			return await this.storage.expandTaskWithPrompt(
				taskIdStr,
				activeTag,
				options
			);
		} catch (error) {
			// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
			if (
				error instanceof TaskMasterError
			) {
				throw error;
			}

			throw new TaskMasterError(
				`Failed to expand task ${taskId}`,
				ERROR_CODES.STORAGE_ERROR,
				{
					operation: 'expandTaskWithPrompt',
					resource: 'task',
					taskId: taskIdStr,
					tag: activeTag,
					numSubtasks: options?.numSubtasks
				},
				error as Error
			);
		}
	}

	/**
	 * Update task status - delegates to storage layer which handles storage-specific logic
	 */
	async updateTaskStatus(
		taskId: string | number,
		newStatus: TaskStatus,
		tag?: string
	): Promise<{
		success: boolean;
		oldStatus: TaskStatus;
		newStatus: TaskStatus;
		taskId: string;
	}> {
		// Ensure we have storage
		if (!this.storage) {
			throw new TaskMasterError(
				'Storage not initialized',
				ERROR_CODES.STORAGE_ERROR
			);
		}

		// Use provided tag or get active tag
		const activeTag = tag || this.getActiveTag();
		const taskIdStr = String(taskId);

		try {
			// Delegate to storage layer which handles the specific logic for tasks vs subtasks
			return await this.storage.updateTaskStatus(
				taskIdStr,
				newStatus,
				activeTag
			);
		} catch (error) {
			// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
			if (
				error instanceof TaskMasterError &&
				error.is(ERROR_CODES.NO_BRIEF_SELECTED)
			) {
				throw error;
			}

			throw new TaskMasterError(
				`Failed to update task status for ${taskIdStr}`,
				ERROR_CODES.STORAGE_ERROR,
				{
					operation: 'updateTaskStatus',
					resource: 'task',
					taskId: taskIdStr,
					newStatus,
					tag: activeTag
				},
				error as Error
			);
		}
	}

	/**
	 * Get all tags with detailed statistics including task counts
	 * Delegates to storage layer which handles file vs API implementation
	 */
	async getTagsWithStats() {
		// Ensure we have storage
		if (!this.storage) {
			throw new TaskMasterError(
				'Storage not initialized',
				ERROR_CODES.STORAGE_ERROR
			);
		}

		// Auto-initialize if needed
		if (!this.initialized) {
			await this.initialize();
		}

		try {
			return await this.storage.getTagsWithStats();
		} catch (error) {
			// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
			if (
				error instanceof TaskMasterError &&
				error.is(ERROR_CODES.NO_BRIEF_SELECTED)
			) {
				throw error;
			}

			throw new TaskMasterError(
				'Failed to get tags with stats',
				ERROR_CODES.STORAGE_ERROR,
				{
					operation: 'getTagsWithStats',
					resource: 'tags'
				},
				error as Error
			);
		}
	}
}

```

--------------------------------------------------------------------------------
/tests/unit/utils.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Utils module tests
 */

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

// Mock modules first before any imports
jest.mock('fs', () => ({
	existsSync: jest.fn((filePath) => {
		// Prevent Jest internal file access
		if (
			filePath.includes('jest-message-util') ||
			filePath.includes('node_modules')
		) {
			return false;
		}
		return false; // Default to false for config discovery prevention
	}),
	readFileSync: jest.fn(() => '{}'),
	writeFileSync: jest.fn(),
	mkdirSync: jest.fn()
}));

jest.mock('path', () => ({
	join: jest.fn((...paths) => paths.join('/')),
	dirname: jest.fn((filePath) => filePath.split('/').slice(0, -1).join('/')),
	resolve: jest.fn((...paths) => paths.join('/')),
	basename: jest.fn((filePath) => filePath.split('/').pop()),
	parse: jest.fn((filePath) => {
		const parts = filePath.split('/');
		const fileName = parts[parts.length - 1];
		const extIndex = fileName.lastIndexOf('.');
		return {
			dir: parts.length > 1 ? parts.slice(0, -1).join('/') : '',
			name: extIndex > 0 ? fileName.substring(0, extIndex) : fileName,
			ext: extIndex > 0 ? fileName.substring(extIndex) : '',
			base: fileName
		};
	}),
	format: jest.fn((pathObj) => {
		const dir = pathObj.dir || '';
		const base = pathObj.base || `${pathObj.name || ''}${pathObj.ext || ''}`;
		return dir ? `${dir}/${base}` : base;
	})
}));

jest.mock('chalk', () => ({
	red: jest.fn((text) => text),
	blue: jest.fn((text) => text),
	green: jest.fn((text) => text),
	yellow: jest.fn((text) => text),
	white: jest.fn((text) => ({
		bold: jest.fn((text) => text)
	})),
	reset: jest.fn((text) => text),
	dim: jest.fn((text) => text) // Add dim function to prevent chalk errors
}));

// Mock console to prevent Jest internal access
const mockConsole = {
	log: jest.fn(),
	info: jest.fn(),
	warn: jest.fn(),
	error: jest.fn()
};
global.console = mockConsole;

// Mock path-utils to prevent file system discovery issues
jest.mock('../../src/utils/path-utils.js', () => ({
	__esModule: true,
	findProjectRoot: jest.fn(() => '/mock/project'),
	findConfigPath: jest.fn(() => null), // Always return null to prevent config discovery
	findTasksPath: jest.fn(() => '/mock/tasks.json'),
	findComplexityReportPath: jest.fn(() => null),
	resolveTasksOutputPath: jest.fn(() => '/mock/tasks.json'),
	resolveComplexityReportOutputPath: jest.fn(() => '/mock/report.json')
}));

// Import the actual module to test
import {
	truncate,
	log,
	readJSON,
	writeJSON,
	sanitizePrompt,
	readComplexityReport,
	findTaskInComplexityReport,
	taskExists,
	formatTaskId,
	findCycles,
	toKebabCase,
	slugifyTagForFilePath,
	getTagAwareFilePath
} from '../../scripts/modules/utils.js';

// Import the mocked modules for use in tests
import fs from 'fs';
import path from 'path';

// Mock config-manager to provide config values
const mockGetLogLevel = jest.fn(() => 'info'); // Default log level for tests
const mockGetDebugFlag = jest.fn(() => false); // Default debug flag for tests
jest.mock('../../scripts/modules/config-manager.js', () => ({
	getLogLevel: mockGetLogLevel,
	getDebugFlag: mockGetDebugFlag
	// Mock other getters if needed by utils.js functions under test
}));

// Test implementation of detectCamelCaseFlags
function testDetectCamelCaseFlags(args) {
	const camelCaseFlags = [];
	for (const arg of args) {
		if (arg.startsWith('--')) {
			const flagName = arg.split('=')[0].slice(2); // Remove -- and anything after =

			// Skip single-word flags - they can't be camelCase
			if (!flagName.includes('-') && !/[A-Z]/.test(flagName)) {
				continue;
			}

			// Check for camelCase pattern (lowercase followed by uppercase)
			if (/[a-z][A-Z]/.test(flagName)) {
				const kebabVersion = toKebabCase(flagName);
				if (kebabVersion !== flagName) {
					camelCaseFlags.push({
						original: flagName,
						kebabCase: kebabVersion
					});
				}
			}
		}
	}
	return camelCaseFlags;
}

describe('Utils Module', () => {
	beforeEach(() => {
		// Clear all mocks before each test
		jest.clearAllMocks();
		// Restore the original path.join mock
		jest.spyOn(path, 'join').mockImplementation((...paths) => paths.join('/'));
	});

	describe('truncate function', () => {
		test('should return the original string if shorter than maxLength', () => {
			const result = truncate('Hello', 10);
			expect(result).toBe('Hello');
		});

		test('should truncate the string and add ellipsis if longer than maxLength', () => {
			const result = truncate(
				'This is a long string that needs truncation',
				20
			);
			expect(result).toBe('This is a long st...');
		});

		test('should handle empty string', () => {
			const result = truncate('', 10);
			expect(result).toBe('');
		});

		test('should return null when input is null', () => {
			const result = truncate(null, 10);
			expect(result).toBe(null);
		});

		test('should return undefined when input is undefined', () => {
			const result = truncate(undefined, 10);
			expect(result).toBe(undefined);
		});

		test('should handle maxLength of 0 or negative', () => {
			// When maxLength is 0, slice(0, -3) returns 'He'
			const result1 = truncate('Hello', 0);
			expect(result1).toBe('He...');

			// When maxLength is negative, slice(0, -8) returns nothing
			const result2 = truncate('Hello', -5);
			expect(result2).toBe('...');
		});
	});

	describe.skip('log function', () => {
		// const originalConsoleLog = console.log; // Keep original for potential restore if needed
		beforeEach(() => {
			// Mock console.log for each test
			// console.log = jest.fn(); // REMOVE console.log spy
			mockGetLogLevel.mockClear(); // Clear mock calls
		});

		afterEach(() => {
			// Restore original console.log after each test
			// console.log = originalConsoleLog; // REMOVE console.log restore
		});

		test('should log messages according to log level from config-manager', () => {
			// Test with info level (default from mock)
			mockGetLogLevel.mockReturnValue('info');

			// Spy on console.log JUST for this test to verify calls
			const consoleSpy = jest
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			log('debug', 'Debug message');
			log('info', 'Info message');
			log('warn', 'Warning message');
			log('error', 'Error message');

			// Debug should not be logged (level 0 < 1)
			expect(consoleSpy).not.toHaveBeenCalledWith(
				expect.stringContaining('Debug message')
			);

			// Info and above should be logged
			expect(consoleSpy).toHaveBeenCalledWith(
				expect.stringContaining('Info message')
			);
			expect(consoleSpy).toHaveBeenCalledWith(
				expect.stringContaining('Warning message')
			);
			expect(consoleSpy).toHaveBeenCalledWith(
				expect.stringContaining('Error message')
			);

			// Verify the formatting includes text prefixes
			expect(consoleSpy).toHaveBeenCalledWith(
				expect.stringContaining('[INFO]')
			);
			expect(consoleSpy).toHaveBeenCalledWith(
				expect.stringContaining('[WARN]')
			);
			expect(consoleSpy).toHaveBeenCalledWith(
				expect.stringContaining('[ERROR]')
			);

			// Verify getLogLevel was called by log function
			expect(mockGetLogLevel).toHaveBeenCalled();

			// Restore spy for this test
			consoleSpy.mockRestore();
		});

		test('should not log messages below the configured log level', () => {
			// Set log level to error via mock
			mockGetLogLevel.mockReturnValue('error');

			// Spy on console.log JUST for this test
			const consoleSpy = jest
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			log('debug', 'Debug message');
			log('info', 'Info message');
			log('warn', 'Warning message');
			log('error', 'Error message');

			// Only error should be logged
			expect(consoleSpy).not.toHaveBeenCalledWith(
				expect.stringContaining('Debug message')
			);
			expect(consoleSpy).not.toHaveBeenCalledWith(
				expect.stringContaining('Info message')
			);
			expect(consoleSpy).not.toHaveBeenCalledWith(
				expect.stringContaining('Warning message')
			);
			expect(consoleSpy).toHaveBeenCalledWith(
				expect.stringContaining('Error message')
			);

			// Verify getLogLevel was called
			expect(mockGetLogLevel).toHaveBeenCalled();

			// Restore spy for this test
			consoleSpy.mockRestore();
		});

		test('should join multiple arguments into a single message', () => {
			mockGetLogLevel.mockReturnValue('info');
			// Spy on console.log JUST for this test
			const consoleSpy = jest
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			log('info', 'Message', 'with', 'multiple', 'parts');
			expect(consoleSpy).toHaveBeenCalledWith(
				expect.stringContaining('Message with multiple parts')
			);

			// Restore spy for this test
			consoleSpy.mockRestore();
		});
	});

	describe.skip('readJSON function', () => {
		test('should read and parse a valid JSON file', () => {
			const testData = { key: 'value', nested: { prop: true } };
			fsReadFileSyncSpy.mockReturnValue(JSON.stringify(testData));

			const result = readJSON('test.json');

			expect(fsReadFileSyncSpy).toHaveBeenCalledWith('test.json', 'utf8');
			expect(result).toEqual(testData);
		});

		test('should handle file not found errors', () => {
			fsReadFileSyncSpy.mockImplementation(() => {
				throw new Error('ENOENT: no such file or directory');
			});

			// Mock console.error
			const consoleSpy = jest
				.spyOn(console, 'error')
				.mockImplementation(() => {});

			const result = readJSON('nonexistent.json');

			expect(result).toBeNull();

			// Restore console.error
			consoleSpy.mockRestore();
		});

		test('should handle invalid JSON format', () => {
			fsReadFileSyncSpy.mockReturnValue('{ invalid json: }');

			// Mock console.error
			const consoleSpy = jest
				.spyOn(console, 'error')
				.mockImplementation(() => {});

			const result = readJSON('invalid.json');

			expect(result).toBeNull();

			// Restore console.error
			consoleSpy.mockRestore();
		});
	});

	describe.skip('writeJSON function', () => {
		test('should write JSON data to a file', () => {
			const testData = { key: 'value', nested: { prop: true } };

			writeJSON('output.json', testData);

			expect(fsWriteFileSyncSpy).toHaveBeenCalledWith(
				'output.json',
				JSON.stringify(testData, null, 2),
				'utf8'
			);
		});

		test('should handle file write errors', () => {
			const testData = { key: 'value' };

			fsWriteFileSyncSpy.mockImplementation(() => {
				throw new Error('Permission denied');
			});

			// Mock console.error
			const consoleSpy = jest
				.spyOn(console, 'error')
				.mockImplementation(() => {});

			// Function shouldn't throw, just log error
			expect(() => writeJSON('protected.json', testData)).not.toThrow();

			// Restore console.error
			consoleSpy.mockRestore();
		});
	});

	describe('sanitizePrompt function', () => {
		test('should escape double quotes in prompts', () => {
			const prompt = 'This is a "quoted" prompt with "multiple" quotes';
			const expected =
				'This is a \\"quoted\\" prompt with \\"multiple\\" quotes';

			expect(sanitizePrompt(prompt)).toBe(expected);
		});

		test('should handle prompts with no special characters', () => {
			const prompt = 'This is a regular prompt without quotes';

			expect(sanitizePrompt(prompt)).toBe(prompt);
		});

		test('should handle empty strings', () => {
			expect(sanitizePrompt('')).toBe('');
		});
	});

	describe('readComplexityReport function', () => {
		test('should read and parse a valid complexity report', () => {
			const testReport = {
				meta: { generatedAt: new Date().toISOString() },
				complexityAnalysis: [{ taskId: 1, complexityScore: 7 }]
			};

			jest.spyOn(fs, 'existsSync').mockReturnValue(true);
			jest
				.spyOn(fs, 'readFileSync')
				.mockReturnValue(JSON.stringify(testReport));
			jest.spyOn(path, 'join').mockReturnValue('/path/to/report.json');

			const result = readComplexityReport();

			expect(fs.existsSync).toHaveBeenCalled();
			expect(fs.readFileSync).toHaveBeenCalledWith(
				'/path/to/report.json',
				'utf8'
			);
			expect(result).toEqual(testReport);
		});

		test('should handle missing report file', () => {
			jest.spyOn(fs, 'existsSync').mockReturnValue(false);
			jest.spyOn(path, 'join').mockReturnValue('/path/to/report.json');

			const result = readComplexityReport();

			expect(result).toBeNull();
			expect(fs.readFileSync).not.toHaveBeenCalled();
		});

		test('should handle custom report path', () => {
			const testReport = {
				meta: { generatedAt: new Date().toISOString() },
				complexityAnalysis: [{ taskId: 1, complexityScore: 7 }]
			};

			jest.spyOn(fs, 'existsSync').mockReturnValue(true);
			jest
				.spyOn(fs, 'readFileSync')
				.mockReturnValue(JSON.stringify(testReport));

			const customPath = '/custom/path/report.json';
			const result = readComplexityReport(customPath);

			expect(fs.existsSync).toHaveBeenCalledWith(customPath);
			expect(fs.readFileSync).toHaveBeenCalledWith(customPath, 'utf8');
			expect(result).toEqual(testReport);
		});
	});

	describe('findTaskInComplexityReport function', () => {
		test('should find a task by ID in a valid report', () => {
			const testReport = {
				complexityAnalysis: [
					{ taskId: 1, complexityScore: 7 },
					{ taskId: 2, complexityScore: 4 },
					{ taskId: 3, complexityScore: 9 }
				]
			};

			const result = findTaskInComplexityReport(testReport, 2);

			expect(result).toEqual({ taskId: 2, complexityScore: 4 });
		});

		test('should return null for non-existent task ID', () => {
			const testReport = {
				complexityAnalysis: [
					{ taskId: 1, complexityScore: 7 },
					{ taskId: 2, complexityScore: 4 }
				]
			};

			const result = findTaskInComplexityReport(testReport, 99);

			// Fixing the expectation to match actual implementation
			// The function might return null or undefined based on implementation
			expect(result).toBeFalsy();
		});

		test('should handle invalid report structure', () => {
			// Test with null report
			expect(findTaskInComplexityReport(null, 1)).toBeNull();

			// Test with missing complexityAnalysis
			expect(findTaskInComplexityReport({}, 1)).toBeNull();

			// Test with non-array complexityAnalysis
			expect(
				findTaskInComplexityReport({ complexityAnalysis: {} }, 1)
			).toBeNull();
		});
	});

	describe('taskExists function', () => {
		const sampleTasks = [
			{ id: 1, title: 'Task 1' },
			{ id: 2, title: 'Task 2' },
			{
				id: 3,
				title: 'Task with subtasks',
				subtasks: [
					{ id: 1, title: 'Subtask 1' },
					{ id: 2, title: 'Subtask 2' }
				]
			}
		];

		test('should return true for existing task IDs', () => {
			expect(taskExists(sampleTasks, 1)).toBe(true);
			expect(taskExists(sampleTasks, 2)).toBe(true);
			expect(taskExists(sampleTasks, '2')).toBe(true); // String ID should work too
		});

		test('should return true for existing subtask IDs', () => {
			expect(taskExists(sampleTasks, '3.1')).toBe(true);
			expect(taskExists(sampleTasks, '3.2')).toBe(true);
		});

		test('should return false for non-existent task IDs', () => {
			expect(taskExists(sampleTasks, 99)).toBe(false);
			expect(taskExists(sampleTasks, '99')).toBe(false);
		});

		test('should return false for non-existent subtask IDs', () => {
			expect(taskExists(sampleTasks, '3.99')).toBe(false);
			expect(taskExists(sampleTasks, '99.1')).toBe(false);
		});

		test('should handle invalid inputs', () => {
			expect(taskExists(null, 1)).toBe(false);
			expect(taskExists(undefined, 1)).toBe(false);
			expect(taskExists([], 1)).toBe(false);
			expect(taskExists(sampleTasks, null)).toBe(false);
			expect(taskExists(sampleTasks, undefined)).toBe(false);
		});
	});

	describe('formatTaskId function', () => {
		test('should format numeric task IDs as strings', () => {
			expect(formatTaskId(1)).toBe('1');
			expect(formatTaskId(42)).toBe('42');
		});

		test('should preserve string task IDs', () => {
			expect(formatTaskId('1')).toBe('1');
			expect(formatTaskId('task-1')).toBe('task-1');
		});

		test('should preserve dot notation for subtask IDs', () => {
			expect(formatTaskId('1.2')).toBe('1.2');
			expect(formatTaskId('42.7')).toBe('42.7');
		});

		test('should handle edge cases', () => {
			// These should return as-is, though your implementation may differ
			expect(formatTaskId(null)).toBe(null);
			expect(formatTaskId(undefined)).toBe(undefined);
			expect(formatTaskId('')).toBe('');
		});
	});

	describe('findCycles function', () => {
		test('should detect simple cycles in dependency graph', () => {
			// A -> B -> A (cycle)
			const dependencyMap = new Map([
				['A', ['B']],
				['B', ['A']]
			]);

			const cycles = findCycles('A', dependencyMap);

			expect(cycles.length).toBeGreaterThan(0);
			expect(cycles).toContain('A');
		});

		test('should detect complex cycles in dependency graph', () => {
			// A -> B -> C -> A (cycle)
			const dependencyMap = new Map([
				['A', ['B']],
				['B', ['C']],
				['C', ['A']]
			]);

			const cycles = findCycles('A', dependencyMap);

			expect(cycles.length).toBeGreaterThan(0);
			expect(cycles).toContain('A');
		});

		test('should return empty array for acyclic graphs', () => {
			// A -> B -> C (no cycle)
			const dependencyMap = new Map([
				['A', ['B']],
				['B', ['C']],
				['C', []]
			]);

			const cycles = findCycles('A', dependencyMap);

			expect(cycles.length).toBe(0);
		});

		test('should handle empty dependency maps', () => {
			const dependencyMap = new Map();

			const cycles = findCycles('A', dependencyMap);

			expect(cycles.length).toBe(0);
		});

		test('should handle nodes with no dependencies', () => {
			const dependencyMap = new Map([
				['A', []],
				['B', []],
				['C', []]
			]);

			const cycles = findCycles('A', dependencyMap);

			expect(cycles.length).toBe(0);
		});

		test('should identify the breaking edge in a cycle', () => {
			// A -> B -> C -> D -> B (cycle)
			const dependencyMap = new Map([
				['A', ['B']],
				['B', ['C']],
				['C', ['D']],
				['D', ['B']]
			]);

			const cycles = findCycles('A', dependencyMap);

			expect(cycles).toContain('B');
		});
	});
});

describe('CLI Flag Format Validation', () => {
	test('toKebabCase should convert camelCase to kebab-case', () => {
		expect(toKebabCase('promptText')).toBe('prompt-text');
		expect(toKebabCase('userID')).toBe('user-id');
		expect(toKebabCase('numTasks')).toBe('num-tasks');
		expect(toKebabCase('alreadyKebabCase')).toBe('already-kebab-case');
	});

	test('detectCamelCaseFlags should identify camelCase flags', () => {
		const args = [
			'node',
			'task-master',
			'add-task',
			'--promptText=test',
			'--userID=123'
		];
		const flags = testDetectCamelCaseFlags(args);

		expect(flags).toHaveLength(2);
		expect(flags).toContainEqual({
			original: 'promptText',
			kebabCase: 'prompt-text'
		});
		expect(flags).toContainEqual({
			original: 'userID',
			kebabCase: 'user-id'
		});
	});

	test('detectCamelCaseFlags should not flag kebab-case flags', () => {
		const args = [
			'node',
			'task-master',
			'add-task',
			'--prompt-text=test',
			'--user-id=123'
		];
		const flags = testDetectCamelCaseFlags(args);

		expect(flags).toHaveLength(0);
	});

	test('detectCamelCaseFlags should respect single-word flags', () => {
		const args = [
			'node',
			'task-master',
			'add-task',
			'--prompt=test',
			'--file=test.json',
			'--priority=high',
			'--promptText=test'
		];
		const flags = testDetectCamelCaseFlags(args);

		// Should only flag promptText, not the single-word flags
		expect(flags).toHaveLength(1);
		expect(flags).toContainEqual({
			original: 'promptText',
			kebabCase: 'prompt-text'
		});
	});
});

test('slugifyTagForFilePath should create filesystem-safe tag names', () => {
	expect(slugifyTagForFilePath('feature/user-auth')).toBe('feature-user-auth');
	expect(slugifyTagForFilePath('Feature Branch')).toBe('feature-branch');
	expect(slugifyTagForFilePath('test@special#chars')).toBe(
		'test-special-chars'
	);
	expect(slugifyTagForFilePath('UPPERCASE')).toBe('uppercase');
	expect(slugifyTagForFilePath('multiple---hyphens')).toBe('multiple-hyphens');
	expect(slugifyTagForFilePath('--leading-trailing--')).toBe(
		'leading-trailing'
	);
	expect(slugifyTagForFilePath('')).toBe('unknown-tag');
	expect(slugifyTagForFilePath(null)).toBe('unknown-tag');
	expect(slugifyTagForFilePath(undefined)).toBe('unknown-tag');
});

test('getTagAwareFilePath should use slugified tags in file paths', () => {
	const basePath = '.taskmaster/reports/complexity-report.json';
	const projectRoot = '/test/project';

	// Master tag should not be slugified
	expect(getTagAwareFilePath(basePath, 'master', projectRoot)).toBe(
		'/test/project/.taskmaster/reports/complexity-report.json'
	);

	// Null/undefined tags should use base path
	expect(getTagAwareFilePath(basePath, null, projectRoot)).toBe(
		'/test/project/.taskmaster/reports/complexity-report.json'
	);

	// Regular tag should be slugified
	expect(getTagAwareFilePath(basePath, 'feature-branch', projectRoot)).toBe(
		'/test/project/.taskmaster/reports/complexity-report_feature-branch.json'
	);

	// Tag with special characters should be slugified
	expect(getTagAwareFilePath(basePath, 'feature/user-auth', projectRoot)).toBe(
		'/test/project/.taskmaster/reports/complexity-report_feature-user-auth.json'
	);

	// Tag with spaces and special characters
	expect(
		getTagAwareFilePath(basePath, 'Feature Branch @Test', projectRoot)
	).toBe(
		'/test/project/.taskmaster/reports/complexity-report_feature-branch-test.json'
	);
});

```
Page 32/50FirstPrevNextLast