#
tokens: 44813/50000 3/975 files (page 47/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 47 of 69. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&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/supported-models.json:
--------------------------------------------------------------------------------

```json
   1 | {
   2 | 	"anthropic": [
   3 | 		{
   4 | 			"id": "claude-sonnet-4-20250514",
   5 | 			"swe_score": 0.727,
   6 | 			"cost_per_1m_tokens": {
   7 | 				"input": 3.0,
   8 | 				"output": 15.0
   9 | 			},
  10 | 			"allowed_roles": ["main", "fallback"],
  11 | 			"max_tokens": 64000,
  12 | 			"supported": true
  13 | 		},
  14 | 		{
  15 | 			"id": "claude-opus-4-20250514",
  16 | 			"swe_score": 0.725,
  17 | 			"cost_per_1m_tokens": {
  18 | 				"input": 15.0,
  19 | 				"output": 75.0
  20 | 			},
  21 | 			"allowed_roles": ["main", "fallback"],
  22 | 			"max_tokens": 32000,
  23 | 			"supported": true
  24 | 		},
  25 | 		{
  26 | 			"id": "claude-3-7-sonnet-20250219",
  27 | 			"swe_score": 0.623,
  28 | 			"cost_per_1m_tokens": {
  29 | 				"input": 3.0,
  30 | 				"output": 15.0
  31 | 			},
  32 | 			"allowed_roles": ["main", "fallback"],
  33 | 			"max_tokens": 120000,
  34 | 			"supported": true
  35 | 		},
  36 | 		{
  37 | 			"id": "claude-3-5-sonnet-20241022",
  38 | 			"swe_score": 0.49,
  39 | 			"cost_per_1m_tokens": {
  40 | 				"input": 3.0,
  41 | 				"output": 15.0
  42 | 			},
  43 | 			"allowed_roles": ["main", "fallback"],
  44 | 			"max_tokens": 8192,
  45 | 			"supported": true
  46 | 		},
  47 | 		{
  48 | 			"id": "claude-sonnet-4-5-20250929",
  49 | 			"swe_score": 0.73,
  50 | 			"cost_per_1m_tokens": {
  51 | 				"input": 3.0,
  52 | 				"output": 15.0
  53 | 			},
  54 | 			"allowed_roles": ["main", "fallback"],
  55 | 			"max_tokens": 64000,
  56 | 			"supported": true
  57 | 		},
  58 | 		{
  59 | 			"id": "claude-haiku-4-5-20251001",
  60 | 			"swe_score": 0.45,
  61 | 			"cost_per_1m_tokens": {
  62 | 				"input": 1.0,
  63 | 				"output": 5.0
  64 | 			},
  65 | 			"allowed_roles": ["main", "fallback"],
  66 | 			"max_tokens": 200000,
  67 | 			"supported": true
  68 | 		}
  69 | 	],
  70 | 	"claude-code": [
  71 | 		{
  72 | 			"id": "opus",
  73 | 			"swe_score": 0.725,
  74 | 			"cost_per_1m_tokens": {
  75 | 				"input": 0,
  76 | 				"output": 0
  77 | 			},
  78 | 			"allowed_roles": ["main", "fallback", "research"],
  79 | 			"max_tokens": 32000,
  80 | 			"supported": true
  81 | 		},
  82 | 		{
  83 | 			"id": "sonnet",
  84 | 			"swe_score": 0.727,
  85 | 			"cost_per_1m_tokens": {
  86 | 				"input": 0,
  87 | 				"output": 0
  88 | 			},
  89 | 			"allowed_roles": ["main", "fallback", "research"],
  90 | 			"max_tokens": 64000,
  91 | 			"supported": true
  92 | 		},
  93 | 		{
  94 | 			"id": "haiku",
  95 | 			"swe_score": 0.45,
  96 | 			"cost_per_1m_tokens": {
  97 | 				"input": 0,
  98 | 				"output": 0
  99 | 			},
 100 | 			"allowed_roles": ["main", "fallback", "research"],
 101 | 			"max_tokens": 200000,
 102 | 			"supported": true
 103 | 		}
 104 | 	],
 105 | 	"codex-cli": [
 106 | 		{
 107 | 			"id": "gpt-5",
 108 | 			"swe_score": 0.749,
 109 | 			"cost_per_1m_tokens": {
 110 | 				"input": 0,
 111 | 				"output": 0
 112 | 			},
 113 | 			"allowed_roles": ["main", "fallback", "research"],
 114 | 			"max_tokens": 128000,
 115 | 			"supported": true
 116 | 		},
 117 | 		{
 118 | 			"id": "gpt-5-codex",
 119 | 			"swe_score": 0.749,
 120 | 			"cost_per_1m_tokens": {
 121 | 				"input": 0,
 122 | 				"output": 0
 123 | 			},
 124 | 			"allowed_roles": ["main", "fallback", "research"],
 125 | 			"max_tokens": 128000,
 126 | 			"supported": true
 127 | 		}
 128 | 	],
 129 | 	"mcp": [
 130 | 		{
 131 | 			"id": "mcp-sampling",
 132 | 			"swe_score": null,
 133 | 			"cost_per_1m_tokens": {
 134 | 				"input": 0,
 135 | 				"output": 0
 136 | 			},
 137 | 			"allowed_roles": ["main", "fallback", "research"],
 138 | 			"max_tokens": 100000,
 139 | 			"supported": true
 140 | 		}
 141 | 	],
 142 | 	"gemini-cli": [
 143 | 		{
 144 | 			"id": "gemini-3-pro-preview",
 145 | 			"swe_score": 0.762,
 146 | 			"cost_per_1m_tokens": {
 147 | 				"input": 0,
 148 | 				"output": 0
 149 | 			},
 150 | 			"allowed_roles": ["main", "fallback", "research"],
 151 | 			"max_tokens": 65536,
 152 | 			"supported": true
 153 | 		},
 154 | 		{
 155 | 			"id": "gemini-2.5-pro",
 156 | 			"swe_score": 0.72,
 157 | 			"cost_per_1m_tokens": {
 158 | 				"input": 0,
 159 | 				"output": 0
 160 | 			},
 161 | 			"allowed_roles": ["main", "fallback", "research"],
 162 | 			"max_tokens": 65536,
 163 | 			"supported": true
 164 | 		},
 165 | 		{
 166 | 			"id": "gemini-2.5-flash",
 167 | 			"swe_score": 0.71,
 168 | 			"cost_per_1m_tokens": {
 169 | 				"input": 0,
 170 | 				"output": 0
 171 | 			},
 172 | 			"allowed_roles": ["main", "fallback", "research"],
 173 | 			"max_tokens": 65536,
 174 | 			"supported": true
 175 | 		}
 176 | 	],
 177 | 	"grok-cli": [
 178 | 		{
 179 | 			"id": "grok-4-latest",
 180 | 			"name": "Grok 4 Latest",
 181 | 			"swe_score": 0.7,
 182 | 			"cost_per_1m_tokens": {
 183 | 				"input": 0,
 184 | 				"output": 0
 185 | 			},
 186 | 			"allowed_roles": ["main", "fallback", "research"],
 187 | 			"max_tokens": 131072,
 188 | 			"supported": true
 189 | 		},
 190 | 		{
 191 | 			"id": "grok-3-latest",
 192 | 			"name": "Grok 3 Latest",
 193 | 			"swe_score": 0.65,
 194 | 			"cost_per_1m_tokens": {
 195 | 				"input": 0,
 196 | 				"output": 0
 197 | 			},
 198 | 			"allowed_roles": ["main", "fallback", "research"],
 199 | 			"max_tokens": 131072,
 200 | 			"supported": true
 201 | 		},
 202 | 		{
 203 | 			"id": "grok-3-fast",
 204 | 			"name": "Grok 3 Fast",
 205 | 			"swe_score": 0.6,
 206 | 			"cost_per_1m_tokens": {
 207 | 				"input": 0,
 208 | 				"output": 0
 209 | 			},
 210 | 			"allowed_roles": ["main", "fallback", "research"],
 211 | 			"max_tokens": 131072,
 212 | 			"supported": true
 213 | 		},
 214 | 		{
 215 | 			"id": "grok-3-mini-fast",
 216 | 			"name": "Grok 3 Mini Fast",
 217 | 			"swe_score": 0.55,
 218 | 			"cost_per_1m_tokens": {
 219 | 				"input": 0,
 220 | 				"output": 0
 221 | 			},
 222 | 			"allowed_roles": ["main", "fallback", "research"],
 223 | 			"max_tokens": 32768,
 224 | 			"supported": true
 225 | 		}
 226 | 	],
 227 | 	"openai": [
 228 | 		{
 229 | 			"id": "gpt-4o",
 230 | 			"swe_score": 0.332,
 231 | 			"cost_per_1m_tokens": {
 232 | 				"input": 2.5,
 233 | 				"output": 10.0
 234 | 			},
 235 | 			"allowed_roles": ["main", "fallback"],
 236 | 			"max_tokens": 16384,
 237 | 			"supported": true
 238 | 		},
 239 | 		{
 240 | 			"id": "o1",
 241 | 			"swe_score": 0.489,
 242 | 			"cost_per_1m_tokens": {
 243 | 				"input": 15.0,
 244 | 				"output": 60.0
 245 | 			},
 246 | 			"allowed_roles": ["main"],
 247 | 			"supported": true
 248 | 		},
 249 | 		{
 250 | 			"id": "o3",
 251 | 			"swe_score": 0.5,
 252 | 			"cost_per_1m_tokens": {
 253 | 				"input": 2.0,
 254 | 				"output": 8.0
 255 | 			},
 256 | 			"allowed_roles": ["main", "fallback"],
 257 | 			"max_tokens": 100000,
 258 | 			"supported": true
 259 | 		},
 260 | 		{
 261 | 			"id": "o3-mini",
 262 | 			"swe_score": 0.493,
 263 | 			"cost_per_1m_tokens": {
 264 | 				"input": 1.1,
 265 | 				"output": 4.4
 266 | 			},
 267 | 			"allowed_roles": ["main"],
 268 | 			"max_tokens": 100000,
 269 | 			"supported": true
 270 | 		},
 271 | 		{
 272 | 			"id": "o4-mini",
 273 | 			"swe_score": 0.45,
 274 | 			"cost_per_1m_tokens": {
 275 | 				"input": 1.1,
 276 | 				"output": 4.4
 277 | 			},
 278 | 			"allowed_roles": ["main", "fallback"],
 279 | 			"supported": true
 280 | 		},
 281 | 		{
 282 | 			"id": "o1-mini",
 283 | 			"swe_score": 0.4,
 284 | 			"cost_per_1m_tokens": {
 285 | 				"input": 1.1,
 286 | 				"output": 4.4
 287 | 			},
 288 | 			"allowed_roles": ["main"],
 289 | 			"supported": true
 290 | 		},
 291 | 		{
 292 | 			"id": "o1-pro",
 293 | 			"swe_score": 0,
 294 | 			"cost_per_1m_tokens": {
 295 | 				"input": 150.0,
 296 | 				"output": 600.0
 297 | 			},
 298 | 			"allowed_roles": ["main"],
 299 | 			"supported": true
 300 | 		},
 301 | 		{
 302 | 			"id": "gpt-4-5-preview",
 303 | 			"swe_score": 0.38,
 304 | 			"cost_per_1m_tokens": {
 305 | 				"input": 75.0,
 306 | 				"output": 150.0
 307 | 			},
 308 | 			"allowed_roles": ["main"],
 309 | 			"supported": true
 310 | 		},
 311 | 		{
 312 | 			"id": "gpt-4-1-mini",
 313 | 			"swe_score": 0,
 314 | 			"cost_per_1m_tokens": {
 315 | 				"input": 0.4,
 316 | 				"output": 1.6
 317 | 			},
 318 | 			"allowed_roles": ["main"],
 319 | 			"supported": true
 320 | 		},
 321 | 		{
 322 | 			"id": "gpt-4-1-nano",
 323 | 			"swe_score": 0,
 324 | 			"cost_per_1m_tokens": {
 325 | 				"input": 0.1,
 326 | 				"output": 0.4
 327 | 			},
 328 | 			"allowed_roles": ["main"],
 329 | 			"supported": true
 330 | 		},
 331 | 		{
 332 | 			"id": "gpt-4o-mini",
 333 | 			"swe_score": 0.3,
 334 | 			"cost_per_1m_tokens": {
 335 | 				"input": 0.15,
 336 | 				"output": 0.6
 337 | 			},
 338 | 			"allowed_roles": ["main"],
 339 | 			"supported": true
 340 | 		},
 341 | 		{
 342 | 			"id": "gpt-4o-search-preview",
 343 | 			"swe_score": 0.33,
 344 | 			"cost_per_1m_tokens": {
 345 | 				"input": 2.5,
 346 | 				"output": 10.0
 347 | 			},
 348 | 			"allowed_roles": ["research"],
 349 | 			"supported": true
 350 | 		},
 351 | 		{
 352 | 			"id": "gpt-4o-mini-search-preview",
 353 | 			"swe_score": 0.3,
 354 | 			"cost_per_1m_tokens": {
 355 | 				"input": 0.15,
 356 | 				"output": 0.6
 357 | 			},
 358 | 			"allowed_roles": ["research"],
 359 | 			"supported": true
 360 | 		},
 361 | 		{
 362 | 			"id": "gpt-5",
 363 | 			"swe_score": 0.749,
 364 | 			"cost_per_1m_tokens": {
 365 | 				"input": 5.0,
 366 | 				"output": 20.0
 367 | 			},
 368 | 			"allowed_roles": ["main", "fallback"],
 369 | 			"max_tokens": 100000,
 370 | 			"temperature": 1,
 371 | 			"supported": true
 372 | 		}
 373 | 	],
 374 | 	"google": [
 375 | 		{
 376 | 			"id": "gemini-3-pro-preview",
 377 | 			"swe_score": 0.762,
 378 | 			"cost_per_1m_tokens": {
 379 | 				"input": 2.0,
 380 | 				"output": 12.0
 381 | 			},
 382 | 			"allowed_roles": ["main", "fallback", "research"],
 383 | 			"max_tokens": 1000000,
 384 | 			"supported": true
 385 | 		},
 386 | 		{
 387 | 			"id": "gemini-2.5-pro-preview-05-06",
 388 | 			"swe_score": 0.638,
 389 | 			"cost_per_1m_tokens": null,
 390 | 			"allowed_roles": ["main", "fallback"],
 391 | 			"max_tokens": 1048000,
 392 | 			"supported": true
 393 | 		},
 394 | 		{
 395 | 			"id": "gemini-2.5-pro-preview-03-25",
 396 | 			"swe_score": 0.638,
 397 | 			"cost_per_1m_tokens": null,
 398 | 			"allowed_roles": ["main", "fallback"],
 399 | 			"max_tokens": 1048000,
 400 | 			"supported": true
 401 | 		},
 402 | 		{
 403 | 			"id": "gemini-2.5-flash-preview-04-17",
 404 | 			"swe_score": 0.604,
 405 | 			"cost_per_1m_tokens": null,
 406 | 			"allowed_roles": ["main", "fallback"],
 407 | 			"max_tokens": 1048000,
 408 | 			"supported": true
 409 | 		},
 410 | 		{
 411 | 			"id": "gemini-2.0-flash",
 412 | 			"swe_score": 0.518,
 413 | 			"cost_per_1m_tokens": {
 414 | 				"input": 0.15,
 415 | 				"output": 0.6
 416 | 			},
 417 | 			"allowed_roles": ["main", "fallback"],
 418 | 			"max_tokens": 1048000,
 419 | 			"supported": true
 420 | 		},
 421 | 		{
 422 | 			"id": "gemini-2.0-flash-lite",
 423 | 			"swe_score": 0,
 424 | 			"cost_per_1m_tokens": null,
 425 | 			"allowed_roles": ["main", "fallback"],
 426 | 			"max_tokens": 1048000,
 427 | 			"supported": true
 428 | 		}
 429 | 	],
 430 | 	"xai": [
 431 | 		{
 432 | 			"id": "grok-3",
 433 | 			"name": "Grok 3",
 434 | 			"swe_score": null,
 435 | 			"cost_per_1m_tokens": {
 436 | 				"input": 3,
 437 | 				"output": 15
 438 | 			},
 439 | 			"allowed_roles": ["main", "fallback", "research"],
 440 | 			"max_tokens": 131072,
 441 | 			"supported": true
 442 | 		},
 443 | 		{
 444 | 			"id": "grok-3-fast",
 445 | 			"name": "Grok 3 Fast",
 446 | 			"swe_score": 0,
 447 | 			"cost_per_1m_tokens": {
 448 | 				"input": 5,
 449 | 				"output": 25
 450 | 			},
 451 | 			"allowed_roles": ["main", "fallback", "research"],
 452 | 			"max_tokens": 131072,
 453 | 			"supported": true
 454 | 		},
 455 | 		{
 456 | 			"id": "grok-4",
 457 | 			"name": "Grok 4",
 458 | 			"swe_score": null,
 459 | 			"cost_per_1m_tokens": {
 460 | 				"input": 3,
 461 | 				"output": 15
 462 | 			},
 463 | 			"allowed_roles": ["main", "fallback", "research"],
 464 | 			"max_tokens": 131072,
 465 | 			"supported": true
 466 | 		}
 467 | 	],
 468 | 	"groq": [
 469 | 		{
 470 | 			"id": "moonshotai/kimi-k2-instruct",
 471 | 			"swe_score": 0.66,
 472 | 			"cost_per_1m_tokens": {
 473 | 				"input": 1.0,
 474 | 				"output": 3.0
 475 | 			},
 476 | 			"allowed_roles": ["main", "fallback"],
 477 | 			"max_tokens": 16384,
 478 | 			"supported": true
 479 | 		},
 480 | 		{
 481 | 			"id": "llama-3.3-70b-versatile",
 482 | 			"swe_score": 0.55,
 483 | 			"cost_per_1m_tokens": {
 484 | 				"input": 0.59,
 485 | 				"output": 0.79
 486 | 			},
 487 | 			"allowed_roles": ["main", "fallback", "research"],
 488 | 			"max_tokens": 32768,
 489 | 			"supported": true
 490 | 		},
 491 | 		{
 492 | 			"id": "llama-3.1-8b-instant",
 493 | 			"swe_score": 0.32,
 494 | 			"cost_per_1m_tokens": {
 495 | 				"input": 0.05,
 496 | 				"output": 0.08
 497 | 			},
 498 | 			"allowed_roles": ["main", "fallback"],
 499 | 			"max_tokens": 131072,
 500 | 			"supported": true
 501 | 		},
 502 | 		{
 503 | 			"id": "llama-4-scout",
 504 | 			"swe_score": 0.45,
 505 | 			"cost_per_1m_tokens": {
 506 | 				"input": 0.11,
 507 | 				"output": 0.34
 508 | 			},
 509 | 			"allowed_roles": ["main", "fallback", "research"],
 510 | 			"max_tokens": 32768,
 511 | 			"supported": true
 512 | 		},
 513 | 		{
 514 | 			"id": "llama-4-maverick",
 515 | 			"swe_score": 0.52,
 516 | 			"cost_per_1m_tokens": {
 517 | 				"input": 0.5,
 518 | 				"output": 0.77
 519 | 			},
 520 | 			"allowed_roles": ["main", "fallback", "research"],
 521 | 			"max_tokens": 32768,
 522 | 			"supported": true
 523 | 		},
 524 | 		{
 525 | 			"id": "mixtral-8x7b-32768",
 526 | 			"swe_score": 0.35,
 527 | 			"cost_per_1m_tokens": {
 528 | 				"input": 0.24,
 529 | 				"output": 0.24
 530 | 			},
 531 | 			"allowed_roles": ["main", "fallback"],
 532 | 			"max_tokens": 32768,
 533 | 			"supported": true
 534 | 		},
 535 | 		{
 536 | 			"id": "qwen-qwq-32b-preview",
 537 | 			"swe_score": 0.4,
 538 | 			"cost_per_1m_tokens": {
 539 | 				"input": 0.18,
 540 | 				"output": 0.18
 541 | 			},
 542 | 			"allowed_roles": ["main", "fallback", "research"],
 543 | 			"max_tokens": 32768,
 544 | 			"supported": true
 545 | 		},
 546 | 		{
 547 | 			"id": "deepseek-r1-distill-llama-70b",
 548 | 			"swe_score": 0.52,
 549 | 			"cost_per_1m_tokens": {
 550 | 				"input": 0.75,
 551 | 				"output": 0.99
 552 | 			},
 553 | 			"allowed_roles": ["main", "research"],
 554 | 			"max_tokens": 8192,
 555 | 			"supported": true
 556 | 		},
 557 | 		{
 558 | 			"id": "gemma2-9b-it",
 559 | 			"swe_score": 0.3,
 560 | 			"cost_per_1m_tokens": {
 561 | 				"input": 0.2,
 562 | 				"output": 0.2
 563 | 			},
 564 | 			"allowed_roles": ["main", "fallback"],
 565 | 			"max_tokens": 8192,
 566 | 			"supported": true
 567 | 		},
 568 | 		{
 569 | 			"id": "whisper-large-v3",
 570 | 			"swe_score": 0,
 571 | 			"cost_per_1m_tokens": {
 572 | 				"input": 0.11,
 573 | 				"output": 0
 574 | 			},
 575 | 			"allowed_roles": ["main"],
 576 | 			"max_tokens": 0,
 577 | 			"supported": true
 578 | 		}
 579 | 	],
 580 | 	"perplexity": [
 581 | 		{
 582 | 			"id": "sonar-pro",
 583 | 			"swe_score": 0,
 584 | 			"cost_per_1m_tokens": {
 585 | 				"input": 3,
 586 | 				"output": 15
 587 | 			},
 588 | 			"allowed_roles": ["main", "research"],
 589 | 			"max_tokens": 8700,
 590 | 			"supported": true
 591 | 		},
 592 | 		{
 593 | 			"id": "sonar",
 594 | 			"swe_score": 0,
 595 | 			"cost_per_1m_tokens": {
 596 | 				"input": 1,
 597 | 				"output": 1
 598 | 			},
 599 | 			"allowed_roles": ["research"],
 600 | 			"max_tokens": 8700,
 601 | 			"supported": true
 602 | 		},
 603 | 		{
 604 | 			"id": "sonar-deep-research",
 605 | 			"swe_score": 0.211,
 606 | 			"cost_per_1m_tokens": {
 607 | 				"input": 2,
 608 | 				"output": 8
 609 | 			},
 610 | 			"allowed_roles": ["research"],
 611 | 			"max_tokens": 8700,
 612 | 			"supported": true
 613 | 		},
 614 | 		{
 615 | 			"id": "sonar-reasoning-pro",
 616 | 			"swe_score": 0.211,
 617 | 			"cost_per_1m_tokens": {
 618 | 				"input": 2,
 619 | 				"output": 8
 620 | 			},
 621 | 			"allowed_roles": ["main", "research", "fallback"],
 622 | 			"max_tokens": 8700,
 623 | 			"supported": true
 624 | 		},
 625 | 		{
 626 | 			"id": "sonar-reasoning",
 627 | 			"swe_score": 0.211,
 628 | 			"cost_per_1m_tokens": {
 629 | 				"input": 1,
 630 | 				"output": 5
 631 | 			},
 632 | 			"allowed_roles": ["main", "research", "fallback"],
 633 | 			"max_tokens": 8700,
 634 | 			"supported": true
 635 | 		}
 636 | 	],
 637 | 	"openrouter": [
 638 | 		{
 639 | 			"id": "google/gemini-2.5-flash-preview-05-20",
 640 | 			"swe_score": 0,
 641 | 			"cost_per_1m_tokens": {
 642 | 				"input": 0.15,
 643 | 				"output": 0.6
 644 | 			},
 645 | 			"allowed_roles": ["main", "fallback"],
 646 | 			"max_tokens": 1048576,
 647 | 			"supported": true
 648 | 		},
 649 | 		{
 650 | 			"id": "google/gemini-2.5-flash-preview-05-20:thinking",
 651 | 			"swe_score": 0,
 652 | 			"cost_per_1m_tokens": {
 653 | 				"input": 0.15,
 654 | 				"output": 3.5
 655 | 			},
 656 | 			"allowed_roles": ["main", "fallback"],
 657 | 			"max_tokens": 1048576,
 658 | 			"supported": true
 659 | 		},
 660 | 		{
 661 | 			"id": "google/gemini-2.5-pro-exp-03-25",
 662 | 			"swe_score": 0,
 663 | 			"cost_per_1m_tokens": {
 664 | 				"input": 0,
 665 | 				"output": 0
 666 | 			},
 667 | 			"allowed_roles": ["main", "fallback"],
 668 | 			"max_tokens": 1000000,
 669 | 			"supported": true
 670 | 		},
 671 | 		{
 672 | 			"id": "deepseek/deepseek-chat-v3-0324:free",
 673 | 			"swe_score": 0,
 674 | 			"cost_per_1m_tokens": {
 675 | 				"input": 0,
 676 | 				"output": 0
 677 | 			},
 678 | 			"allowed_roles": ["main", "fallback"],
 679 | 			"max_tokens": 163840,
 680 | 			"supported": false,
 681 | 			"reason": "Free OpenRouter models are not supported due to severe rate limits, lack of tool use support, and other reliability issues that make them impractical for production use."
 682 | 		},
 683 | 		{
 684 | 			"id": "deepseek/deepseek-chat-v3-0324",
 685 | 			"swe_score": 0,
 686 | 			"cost_per_1m_tokens": {
 687 | 				"input": 0.27,
 688 | 				"output": 1.1
 689 | 			},
 690 | 			"allowed_roles": ["main"],
 691 | 			"max_tokens": 64000,
 692 | 			"supported": true
 693 | 		},
 694 | 		{
 695 | 			"id": "openai/gpt-4.1",
 696 | 			"swe_score": 0,
 697 | 			"cost_per_1m_tokens": {
 698 | 				"input": 2,
 699 | 				"output": 8
 700 | 			},
 701 | 			"allowed_roles": ["main", "fallback"],
 702 | 			"max_tokens": 1000000,
 703 | 			"supported": true
 704 | 		},
 705 | 		{
 706 | 			"id": "openai/gpt-4.1-mini",
 707 | 			"swe_score": 0,
 708 | 			"cost_per_1m_tokens": {
 709 | 				"input": 0.4,
 710 | 				"output": 1.6
 711 | 			},
 712 | 			"allowed_roles": ["main", "fallback"],
 713 | 			"max_tokens": 1000000,
 714 | 			"supported": true
 715 | 		},
 716 | 		{
 717 | 			"id": "openai/gpt-4.1-nano",
 718 | 			"swe_score": 0,
 719 | 			"cost_per_1m_tokens": {
 720 | 				"input": 0.1,
 721 | 				"output": 0.4
 722 | 			},
 723 | 			"allowed_roles": ["main", "fallback"],
 724 | 			"max_tokens": 1000000,
 725 | 			"supported": true
 726 | 		},
 727 | 		{
 728 | 			"id": "openai/o3",
 729 | 			"swe_score": 0,
 730 | 			"cost_per_1m_tokens": {
 731 | 				"input": 10,
 732 | 				"output": 40
 733 | 			},
 734 | 			"allowed_roles": ["main", "fallback"],
 735 | 			"max_tokens": 200000,
 736 | 			"supported": true
 737 | 		},
 738 | 		{
 739 | 			"id": "openai/codex-mini",
 740 | 			"swe_score": 0,
 741 | 			"cost_per_1m_tokens": {
 742 | 				"input": 1.5,
 743 | 				"output": 6
 744 | 			},
 745 | 			"allowed_roles": ["main", "fallback"],
 746 | 			"max_tokens": 100000,
 747 | 			"supported": true
 748 | 		},
 749 | 		{
 750 | 			"id": "openai/gpt-4o-mini",
 751 | 			"swe_score": 0,
 752 | 			"cost_per_1m_tokens": {
 753 | 				"input": 0.15,
 754 | 				"output": 0.6
 755 | 			},
 756 | 			"allowed_roles": ["main", "fallback"],
 757 | 			"max_tokens": 100000,
 758 | 			"supported": true
 759 | 		},
 760 | 		{
 761 | 			"id": "openai/o4-mini",
 762 | 			"swe_score": 0.45,
 763 | 			"cost_per_1m_tokens": {
 764 | 				"input": 1.1,
 765 | 				"output": 4.4
 766 | 			},
 767 | 			"allowed_roles": ["main", "fallback"],
 768 | 			"max_tokens": 100000,
 769 | 			"supported": true
 770 | 		},
 771 | 		{
 772 | 			"id": "openai/o4-mini-high",
 773 | 			"swe_score": 0,
 774 | 			"cost_per_1m_tokens": {
 775 | 				"input": 1.1,
 776 | 				"output": 4.4
 777 | 			},
 778 | 			"allowed_roles": ["main", "fallback"],
 779 | 			"max_tokens": 100000,
 780 | 			"supported": true
 781 | 		},
 782 | 		{
 783 | 			"id": "openai/o1-pro",
 784 | 			"swe_score": 0,
 785 | 			"cost_per_1m_tokens": {
 786 | 				"input": 150,
 787 | 				"output": 600
 788 | 			},
 789 | 			"allowed_roles": ["main", "fallback"],
 790 | 			"max_tokens": 100000,
 791 | 			"supported": true
 792 | 		},
 793 | 		{
 794 | 			"id": "meta-llama/llama-3.3-70b-instruct",
 795 | 			"swe_score": 0,
 796 | 			"cost_per_1m_tokens": {
 797 | 				"input": 120,
 798 | 				"output": 600
 799 | 			},
 800 | 			"allowed_roles": ["main", "fallback"],
 801 | 			"max_tokens": 1048576,
 802 | 			"supported": true
 803 | 		},
 804 | 		{
 805 | 			"id": "meta-llama/llama-4-maverick",
 806 | 			"swe_score": 0,
 807 | 			"cost_per_1m_tokens": {
 808 | 				"input": 0.18,
 809 | 				"output": 0.6
 810 | 			},
 811 | 			"allowed_roles": ["main", "fallback"],
 812 | 			"max_tokens": 1000000,
 813 | 			"supported": true
 814 | 		},
 815 | 		{
 816 | 			"id": "meta-llama/llama-4-scout",
 817 | 			"swe_score": 0,
 818 | 			"cost_per_1m_tokens": {
 819 | 				"input": 0.08,
 820 | 				"output": 0.3
 821 | 			},
 822 | 			"allowed_roles": ["main", "fallback"],
 823 | 			"max_tokens": 1000000,
 824 | 			"supported": true
 825 | 		},
 826 | 		{
 827 | 			"id": "qwen/qwen-max",
 828 | 			"swe_score": 0,
 829 | 			"cost_per_1m_tokens": {
 830 | 				"input": 1.6,
 831 | 				"output": 6.4
 832 | 			},
 833 | 			"allowed_roles": ["main", "fallback"],
 834 | 			"max_tokens": 32768,
 835 | 			"supported": true
 836 | 		},
 837 | 		{
 838 | 			"id": "qwen/qwen-turbo",
 839 | 			"swe_score": 0,
 840 | 			"cost_per_1m_tokens": {
 841 | 				"input": 0.05,
 842 | 				"output": 0.2
 843 | 			},
 844 | 			"allowed_roles": ["main", "fallback"],
 845 | 			"max_tokens": 32768,
 846 | 			"supported": true
 847 | 		},
 848 | 		{
 849 | 			"id": "qwen/qwen3-235b-a22b",
 850 | 			"swe_score": 0,
 851 | 			"cost_per_1m_tokens": {
 852 | 				"input": 0.14,
 853 | 				"output": 2
 854 | 			},
 855 | 			"allowed_roles": ["main", "fallback"],
 856 | 			"max_tokens": 24000,
 857 | 			"supported": true
 858 | 		},
 859 | 		{
 860 | 			"id": "mistralai/mistral-small-3.1-24b-instruct:free",
 861 | 			"swe_score": 0,
 862 | 			"cost_per_1m_tokens": {
 863 | 				"input": 0,
 864 | 				"output": 0
 865 | 			},
 866 | 			"allowed_roles": ["main", "fallback"],
 867 | 			"max_tokens": 96000,
 868 | 			"supported": false,
 869 | 			"reason": "Free OpenRouter models are not supported due to severe rate limits, lack of tool use support, and other reliability issues that make them impractical for production use."
 870 | 		},
 871 | 		{
 872 | 			"id": "mistralai/mistral-small-3.1-24b-instruct",
 873 | 			"swe_score": 0,
 874 | 			"cost_per_1m_tokens": {
 875 | 				"input": 0.1,
 876 | 				"output": 0.3
 877 | 			},
 878 | 			"allowed_roles": ["main", "fallback"],
 879 | 			"max_tokens": 128000,
 880 | 			"supported": true
 881 | 		},
 882 | 		{
 883 | 			"id": "mistralai/devstral-small",
 884 | 			"swe_score": 0,
 885 | 			"cost_per_1m_tokens": {
 886 | 				"input": 0.1,
 887 | 				"output": 0.3
 888 | 			},
 889 | 			"allowed_roles": ["main"],
 890 | 			"max_tokens": 110000,
 891 | 			"supported": true
 892 | 		},
 893 | 		{
 894 | 			"id": "mistralai/mistral-nemo",
 895 | 			"swe_score": 0,
 896 | 			"cost_per_1m_tokens": {
 897 | 				"input": 0.03,
 898 | 				"output": 0.07
 899 | 			},
 900 | 			"allowed_roles": ["main", "fallback"],
 901 | 			"max_tokens": 100000,
 902 | 			"supported": true
 903 | 		},
 904 | 		{
 905 | 			"id": "thudm/glm-4-32b:free",
 906 | 			"swe_score": 0,
 907 | 			"cost_per_1m_tokens": {
 908 | 				"input": 0,
 909 | 				"output": 0
 910 | 			},
 911 | 			"allowed_roles": ["main", "fallback"],
 912 | 			"max_tokens": 32768,
 913 | 			"supported": false,
 914 | 			"reason": "Free OpenRouter models are not supported due to severe rate limits, lack of tool use support, and other reliability issues that make them impractical for production use."
 915 | 		}
 916 | 	],
 917 | 	"zai": [
 918 | 		{
 919 | 			"id": "glm-4.6",
 920 | 			"swe_score": 0.68,
 921 | 			"cost_per_1m_tokens": {
 922 | 				"input": 0.6,
 923 | 				"output": 2.2
 924 | 			},
 925 | 			"allowed_roles": ["main", "fallback", "research"],
 926 | 			"max_tokens": 204800,
 927 | 			"supported": true
 928 | 		},
 929 | 		{
 930 | 			"id": "glm-4.5",
 931 | 			"swe_score": 0.65,
 932 | 			"cost_per_1m_tokens": {
 933 | 				"input": 0.6,
 934 | 				"output": 2.2
 935 | 			},
 936 | 			"allowed_roles": ["main", "fallback", "research"],
 937 | 			"max_tokens": 131072,
 938 | 			"supported": true
 939 | 		},
 940 | 		{
 941 | 			"id": "glm-4.5-air",
 942 | 			"swe_score": 0.62,
 943 | 			"cost_per_1m_tokens": {
 944 | 				"input": 0.2,
 945 | 				"output": 1.1
 946 | 			},
 947 | 			"allowed_roles": ["main", "fallback", "research"],
 948 | 			"max_tokens": 131072,
 949 | 			"supported": true
 950 | 		}
 951 | 	],
 952 | 	"zai-coding": [
 953 | 		{
 954 | 			"id": "glm-4.6",
 955 | 			"swe_score": 0.68,
 956 | 			"cost_per_1m_tokens": {
 957 | 				"input": 0,
 958 | 				"output": 0
 959 | 			},
 960 | 			"allowed_roles": ["main", "fallback", "research"],
 961 | 			"max_tokens": 204800,
 962 | 			"supported": true
 963 | 		},
 964 | 		{
 965 | 			"id": "glm-4.5",
 966 | 			"swe_score": 0.65,
 967 | 			"cost_per_1m_tokens": {
 968 | 				"input": 0,
 969 | 				"output": 0
 970 | 			},
 971 | 			"allowed_roles": ["main", "fallback", "research"],
 972 | 			"max_tokens": 131072,
 973 | 			"supported": true
 974 | 		},
 975 | 		{
 976 | 			"id": "glm-4.5-air",
 977 | 			"swe_score": 0.62,
 978 | 			"cost_per_1m_tokens": {
 979 | 				"input": 0,
 980 | 				"output": 0
 981 | 			},
 982 | 			"allowed_roles": ["main", "fallback", "research"],
 983 | 			"max_tokens": 131072,
 984 | 			"supported": true
 985 | 		}
 986 | 	],
 987 | 	"ollama": [
 988 | 		{
 989 | 			"id": "gpt-oss:latest",
 990 | 			"swe_score": 0.607,
 991 | 			"cost_per_1m_tokens": {
 992 | 				"input": 0,
 993 | 				"output": 0
 994 | 			},
 995 | 			"allowed_roles": ["main", "fallback"],
 996 | 			"max_tokens": 128000,
 997 | 			"supported": true
 998 | 		},
 999 | 		{
1000 | 			"id": "gpt-oss:20b",
1001 | 			"swe_score": 0.607,
1002 | 			"cost_per_1m_tokens": {
1003 | 				"input": 0,
1004 | 				"output": 0
1005 | 			},
1006 | 			"allowed_roles": ["main", "fallback"],
1007 | 			"max_tokens": 128000,
1008 | 			"supported": true
1009 | 		},
1010 | 		{
1011 | 			"id": "gpt-oss:120b",
1012 | 			"swe_score": 0.624,
1013 | 			"cost_per_1m_tokens": {
1014 | 				"input": 0,
1015 | 				"output": 0
1016 | 			},
1017 | 			"allowed_roles": ["main", "fallback"],
1018 | 			"max_tokens": 128000,
1019 | 			"supported": true
1020 | 		},
1021 | 		{
1022 | 			"id": "devstral:latest",
1023 | 			"swe_score": 0,
1024 | 			"cost_per_1m_tokens": {
1025 | 				"input": 0,
1026 | 				"output": 0
1027 | 			},
1028 | 			"allowed_roles": ["main", "fallback"],
1029 | 			"supported": true
1030 | 		},
1031 | 		{
1032 | 			"id": "qwen3:latest",
1033 | 			"swe_score": 0,
1034 | 			"cost_per_1m_tokens": {
1035 | 				"input": 0,
1036 | 				"output": 0
1037 | 			},
1038 | 			"allowed_roles": ["main", "fallback"],
1039 | 			"supported": true
1040 | 		},
1041 | 		{
1042 | 			"id": "qwen3:14b",
1043 | 			"swe_score": 0,
1044 | 			"cost_per_1m_tokens": {
1045 | 				"input": 0,
1046 | 				"output": 0
1047 | 			},
1048 | 			"allowed_roles": ["main", "fallback"],
1049 | 			"supported": true
1050 | 		},
1051 | 		{
1052 | 			"id": "qwen3:32b",
1053 | 			"swe_score": 0,
1054 | 			"cost_per_1m_tokens": {
1055 | 				"input": 0,
1056 | 				"output": 0
1057 | 			},
1058 | 			"allowed_roles": ["main", "fallback"],
1059 | 			"supported": true
1060 | 		},
1061 | 		{
1062 | 			"id": "mistral-small3.1:latest",
1063 | 			"swe_score": 0,
1064 | 			"cost_per_1m_tokens": {
1065 | 				"input": 0,
1066 | 				"output": 0
1067 | 			},
1068 | 			"allowed_roles": ["main", "fallback"],
1069 | 			"supported": true
1070 | 		},
1071 | 		{
1072 | 			"id": "llama3.3:latest",
1073 | 			"swe_score": 0,
1074 | 			"cost_per_1m_tokens": {
1075 | 				"input": 0,
1076 | 				"output": 0
1077 | 			},
1078 | 			"allowed_roles": ["main", "fallback"],
1079 | 			"supported": true
1080 | 		},
1081 | 		{
1082 | 			"id": "phi4:latest",
1083 | 			"swe_score": 0,
1084 | 			"cost_per_1m_tokens": {
1085 | 				"input": 0,
1086 | 				"output": 0
1087 | 			},
1088 | 			"allowed_roles": ["main", "fallback"],
1089 | 			"supported": true
1090 | 		}
1091 | 	],
1092 | 	"azure": [
1093 | 		{
1094 | 			"id": "gpt-4o",
1095 | 			"swe_score": 0.332,
1096 | 			"cost_per_1m_tokens": {
1097 | 				"input": 2.5,
1098 | 				"output": 10
1099 | 			},
1100 | 			"allowed_roles": ["main", "fallback"],
1101 | 			"max_tokens": 16384,
1102 | 			"supported": true
1103 | 		},
1104 | 		{
1105 | 			"id": "gpt-4o-mini",
1106 | 			"swe_score": 0.3,
1107 | 			"cost_per_1m_tokens": {
1108 | 				"input": 0.15,
1109 | 				"output": 0.6
1110 | 			},
1111 | 			"allowed_roles": ["main", "fallback"],
1112 | 			"max_tokens": 16384,
1113 | 			"supported": true
1114 | 		},
1115 | 		{
1116 | 			"id": "gpt-4-1",
1117 | 			"swe_score": 0,
1118 | 			"cost_per_1m_tokens": {
1119 | 				"input": 2.0,
1120 | 				"output": 10.0
1121 | 			},
1122 | 			"allowed_roles": ["main", "fallback"],
1123 | 			"max_tokens": 16384,
1124 | 			"supported": true
1125 | 		}
1126 | 	],
1127 | 	"bedrock": [
1128 | 		{
1129 | 			"id": "us.anthropic.claude-3-haiku-20240307-v1:0",
1130 | 			"swe_score": 0.4,
1131 | 			"cost_per_1m_tokens": {
1132 | 				"input": 0.25,
1133 | 				"output": 1.25
1134 | 			},
1135 | 			"allowed_roles": ["main", "fallback"],
1136 | 			"supported": true
1137 | 		},
1138 | 		{
1139 | 			"id": "us.anthropic.claude-3-opus-20240229-v1:0",
1140 | 			"swe_score": 0.725,
1141 | 			"cost_per_1m_tokens": {
1142 | 				"input": 15,
1143 | 				"output": 75
1144 | 			},
1145 | 			"allowed_roles": ["main", "fallback", "research"],
1146 | 			"supported": true
1147 | 		},
1148 | 		{
1149 | 			"id": "us.anthropic.claude-3-5-sonnet-20240620-v1:0",
1150 | 			"swe_score": 0.49,
1151 | 			"cost_per_1m_tokens": {
1152 | 				"input": 3,
1153 | 				"output": 15
1154 | 			},
1155 | 			"allowed_roles": ["main", "fallback", "research"],
1156 | 			"supported": true
1157 | 		},
1158 | 		{
1159 | 			"id": "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
1160 | 			"swe_score": 0.49,
1161 | 			"cost_per_1m_tokens": {
1162 | 				"input": 3,
1163 | 				"output": 15
1164 | 			},
1165 | 			"allowed_roles": ["main", "fallback", "research"],
1166 | 			"supported": true
1167 | 		},
1168 | 		{
1169 | 			"id": "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
1170 | 			"swe_score": 0.623,
1171 | 			"cost_per_1m_tokens": {
1172 | 				"input": 3,
1173 | 				"output": 15
1174 | 			},
1175 | 			"allowed_roles": ["main", "fallback", "research"],
1176 | 			"max_tokens": 65536,
1177 | 			"supported": true
1178 | 		},
1179 | 		{
1180 | 			"id": "us.anthropic.claude-3-5-haiku-20241022-v1:0",
1181 | 			"swe_score": 0.4,
1182 | 			"cost_per_1m_tokens": {
1183 | 				"input": 0.8,
1184 | 				"output": 4
1185 | 			},
1186 | 			"allowed_roles": ["main", "fallback"],
1187 | 			"supported": true
1188 | 		},
1189 | 		{
1190 | 			"id": "us.anthropic.claude-opus-4-20250514-v1:0",
1191 | 			"swe_score": 0.725,
1192 | 			"cost_per_1m_tokens": {
1193 | 				"input": 15,
1194 | 				"output": 75
1195 | 			},
1196 | 			"allowed_roles": ["main", "fallback", "research"],
1197 | 			"supported": true
1198 | 		},
1199 | 		{
1200 | 			"id": "us.anthropic.claude-sonnet-4-20250514-v1:0",
1201 | 			"swe_score": 0.727,
1202 | 			"cost_per_1m_tokens": {
1203 | 				"input": 3,
1204 | 				"output": 15
1205 | 			},
1206 | 			"allowed_roles": ["main", "fallback", "research"],
1207 | 			"supported": true
1208 | 		},
1209 | 		{
1210 | 			"id": "us.deepseek.r1-v1:0",
1211 | 			"swe_score": 0,
1212 | 			"cost_per_1m_tokens": {
1213 | 				"input": 1.35,
1214 | 				"output": 5.4
1215 | 			},
1216 | 			"allowed_roles": ["research"],
1217 | 			"max_tokens": 65536,
1218 | 			"supported": true
1219 | 		}
1220 | 	]
1221 | }
1222 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/storage/adapters/file-storage/file-storage.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Refactored file-based storage implementation for Task Master
  3 |  */
  4 | 
  5 | import path from 'node:path';
  6 | import type {
  7 | 	IStorage,
  8 | 	LoadTasksOptions,
  9 | 	StorageStats,
 10 | 	UpdateStatusResult
 11 | } from '../../../../common/interfaces/storage.interface.js';
 12 | import type {
 13 | 	Task,
 14 | 	TaskMetadata,
 15 | 	TaskStatus
 16 | } from '../../../../common/types/index.js';
 17 | import {
 18 | 	ERROR_CODES,
 19 | 	TaskMasterError
 20 | } from '../../../../common/errors/task-master-error.js';
 21 | import { ComplexityReportManager } from '../../../reports/managers/complexity-report-manager.js';
 22 | import { FileOperations } from './file-operations.js';
 23 | import { FormatHandler } from './format-handler.js';
 24 | import { PathResolver } from './path-resolver.js';
 25 | 
 26 | /**
 27 |  * File-based storage implementation using a single tasks.json file with separated concerns
 28 |  */
 29 | export class FileStorage implements IStorage {
 30 | 	private formatHandler: FormatHandler;
 31 | 	private fileOps: FileOperations;
 32 | 	private pathResolver: PathResolver;
 33 | 	private complexityManager: ComplexityReportManager;
 34 | 
 35 | 	constructor(projectPath: string) {
 36 | 		this.formatHandler = new FormatHandler();
 37 | 		this.fileOps = new FileOperations();
 38 | 		this.pathResolver = new PathResolver(projectPath);
 39 | 		this.complexityManager = new ComplexityReportManager(projectPath);
 40 | 	}
 41 | 
 42 | 	/**
 43 | 	 * Initialize storage by creating necessary directories
 44 | 	 */
 45 | 	async initialize(): Promise<void> {
 46 | 		await this.fileOps.ensureDir(this.pathResolver.getTasksDir());
 47 | 	}
 48 | 
 49 | 	/**
 50 | 	 * Close storage and cleanup resources
 51 | 	 */
 52 | 	async close(): Promise<void> {
 53 | 		await this.fileOps.cleanup();
 54 | 	}
 55 | 
 56 | 	/**
 57 | 	 * Get the storage type
 58 | 	 */
 59 | 	getStorageType(): 'file' {
 60 | 		return 'file';
 61 | 	}
 62 | 
 63 | 	/**
 64 | 	 * Get the current brief name (not applicable for file storage)
 65 | 	 * @returns null (file storage doesn't use briefs)
 66 | 	 */
 67 | 	getCurrentBriefName(): null {
 68 | 		return null;
 69 | 	}
 70 | 
 71 | 	/**
 72 | 	 * Get statistics about the storage
 73 | 	 */
 74 | 	async getStats(): Promise<StorageStats> {
 75 | 		const filePath = this.pathResolver.getTasksPath();
 76 | 
 77 | 		try {
 78 | 			const stats = await this.fileOps.getStats(filePath);
 79 | 			const data = await this.fileOps.readJson(filePath);
 80 | 			const tags = this.formatHandler.extractTags(data);
 81 | 
 82 | 			let totalTasks = 0;
 83 | 			const tagStats = tags.map((tag) => {
 84 | 				const tasks = this.formatHandler.extractTasks(data, tag);
 85 | 				const taskCount = tasks.length;
 86 | 				totalTasks += taskCount;
 87 | 
 88 | 				return {
 89 | 					tag,
 90 | 					taskCount,
 91 | 					lastModified: stats.mtime.toISOString()
 92 | 				};
 93 | 			});
 94 | 
 95 | 			return {
 96 | 				totalTasks,
 97 | 				totalTags: tags.length,
 98 | 				lastModified: stats.mtime.toISOString(),
 99 | 				storageSize: 0, // Could calculate actual file sizes if needed
100 | 				tagStats
101 | 			};
102 | 		} catch (error: any) {
103 | 			if (error.code === 'ENOENT') {
104 | 				return {
105 | 					totalTasks: 0,
106 | 					totalTags: 0,
107 | 					lastModified: new Date().toISOString(),
108 | 					storageSize: 0,
109 | 					tagStats: []
110 | 				};
111 | 			}
112 | 			throw new Error(`Failed to get storage stats: ${error.message}`);
113 | 		}
114 | 	}
115 | 
116 | 	/**
117 | 	 * Load tasks from the single tasks.json file for a specific tag
118 | 	 * Enriches tasks with complexity data from the complexity report
119 | 	 */
120 | 	async loadTasks(tag?: string, options?: LoadTasksOptions): Promise<Task[]> {
121 | 		const filePath = this.pathResolver.getTasksPath();
122 | 		const resolvedTag = tag || 'master';
123 | 
124 | 		try {
125 | 			const rawData = await this.fileOps.readJson(filePath);
126 | 			let tasks = this.formatHandler.extractTasks(rawData, resolvedTag);
127 | 
128 | 			// Apply filters if provided
129 | 			if (options) {
130 | 				// Filter by status if specified
131 | 				if (options.status) {
132 | 					tasks = tasks.filter((task) => task.status === options.status);
133 | 				}
134 | 
135 | 				// Exclude subtasks if specified
136 | 				if (options.excludeSubtasks) {
137 | 					tasks = tasks.map((task) => ({
138 | 						...task,
139 | 						subtasks: []
140 | 					}));
141 | 				}
142 | 			}
143 | 
144 | 			return await this.enrichTasksWithComplexity(tasks, resolvedTag);
145 | 		} catch (error: any) {
146 | 			if (error.code === 'ENOENT') {
147 | 				return []; // File doesn't exist, return empty array
148 | 			}
149 | 			throw new Error(`Failed to load tasks: ${error.message}`);
150 | 		}
151 | 	}
152 | 
153 | 	/**
154 | 	 * Load a single task by ID from the tasks.json file
155 | 	 * Handles both regular tasks and subtasks (with dotted notation like "1.2")
156 | 	 */
157 | 	async loadTask(taskId: string, tag?: string): Promise<Task | null> {
158 | 		const tasks = await this.loadTasks(tag);
159 | 
160 | 		// Check if this is a subtask (contains a dot)
161 | 		if (taskId.includes('.')) {
162 | 			const [parentId, subtaskId] = taskId.split('.');
163 | 			const parentTask = tasks.find((t) => String(t.id) === parentId);
164 | 
165 | 			if (!parentTask || !parentTask.subtasks) {
166 | 				return null;
167 | 			}
168 | 
169 | 			const subtask = parentTask.subtasks.find(
170 | 				(st) => String(st.id) === subtaskId
171 | 			);
172 | 			if (!subtask) {
173 | 				return null;
174 | 			}
175 | 
176 | 			const toFullSubId = (maybeDotId: string | number): string => {
177 | 				const depId = String(maybeDotId);
178 | 				return depId.includes('.') ? depId : `${parentTask.id}.${depId}`;
179 | 			};
180 | 			const resolvedDependencies =
181 | 				subtask.dependencies?.map((dep) => toFullSubId(dep)) ?? [];
182 | 
183 | 			// Return a Task-like object for the subtask with the full dotted ID
184 | 			// Following the same pattern as findTaskById in utils.js
185 | 			const subtaskResult = {
186 | 				...subtask,
187 | 				id: taskId, // Use the full dotted ID
188 | 				title: subtask.title || `Subtask ${subtaskId}`,
189 | 				description: subtask.description || '',
190 | 				status: subtask.status || 'pending',
191 | 				priority: subtask.priority || parentTask.priority || 'medium',
192 | 				dependencies: resolvedDependencies,
193 | 				details: subtask.details || '',
194 | 				testStrategy: subtask.testStrategy || '',
195 | 				subtasks: [],
196 | 				tags: parentTask.tags || [],
197 | 				assignee: subtask.assignee || parentTask.assignee,
198 | 				complexity: subtask.complexity || parentTask.complexity,
199 | 				createdAt: subtask.createdAt || parentTask.createdAt,
200 | 				updatedAt: subtask.updatedAt || parentTask.updatedAt,
201 | 				// Add reference to parent task for context (like utils.js does)
202 | 				parentTask: {
203 | 					id: parentTask.id,
204 | 					title: parentTask.title,
205 | 					status: parentTask.status
206 | 				},
207 | 				isSubtask: true
208 | 			};
209 | 
210 | 			return subtaskResult;
211 | 		}
212 | 
213 | 		// Handle regular task lookup
214 | 		return tasks.find((task) => String(task.id) === String(taskId)) || null;
215 | 	}
216 | 
217 | 	/**
218 | 	 * Save tasks for a specific tag in the single tasks.json file
219 | 	 */
220 | 	async saveTasks(tasks: Task[], tag?: string): Promise<void> {
221 | 		const filePath = this.pathResolver.getTasksPath();
222 | 		const resolvedTag = tag || 'master';
223 | 
224 | 		// Ensure directory exists
225 | 		await this.fileOps.ensureDir(this.pathResolver.getTasksDir());
226 | 
227 | 		// Get existing data from the file
228 | 		let existingData: any = {};
229 | 		try {
230 | 			existingData = await this.fileOps.readJson(filePath);
231 | 		} catch (error: any) {
232 | 			if (error.code !== 'ENOENT') {
233 | 				throw new Error(`Failed to read existing tasks: ${error.message}`);
234 | 			}
235 | 			// File doesn't exist, start with empty data
236 | 		}
237 | 
238 | 		// Create metadata for this tag
239 | 		const metadata: TaskMetadata = {
240 | 			version: '1.0.0',
241 | 			lastModified: new Date().toISOString(),
242 | 			taskCount: tasks.length,
243 | 			completedCount: tasks.filter((t) => t.status === 'done').length,
244 | 			tags: [resolvedTag]
245 | 		};
246 | 
247 | 		// Normalize tasks
248 | 		const normalizedTasks = this.normalizeTaskIds(tasks);
249 | 
250 | 		// Update the specific tag in the existing data structure
251 | 		if (
252 | 			this.formatHandler.detectFormat(existingData) === 'legacy' ||
253 | 			Object.keys(existingData).some(
254 | 				(key) => key !== 'tasks' && key !== 'metadata'
255 | 			)
256 | 		) {
257 | 			// Legacy format - update/add the tag
258 | 			existingData[resolvedTag] = {
259 | 				tasks: normalizedTasks,
260 | 				metadata
261 | 			};
262 | 		} else if (resolvedTag === 'master') {
263 | 			// Standard format for master tag
264 | 			existingData = {
265 | 				tasks: normalizedTasks,
266 | 				metadata
267 | 			};
268 | 		} else {
269 | 			// Convert to legacy format when adding non-master tags
270 | 			const masterTasks = existingData.tasks || [];
271 | 			const masterMetadata = existingData.metadata || metadata;
272 | 
273 | 			existingData = {
274 | 				master: {
275 | 					tasks: masterTasks,
276 | 					metadata: masterMetadata
277 | 				},
278 | 				[resolvedTag]: {
279 | 					tasks: normalizedTasks,
280 | 					metadata
281 | 				}
282 | 			};
283 | 		}
284 | 
285 | 		// Write the updated file
286 | 		await this.fileOps.writeJson(filePath, existingData);
287 | 	}
288 | 
289 | 	/**
290 | 	 * Normalize task IDs - keep Task IDs as strings, Subtask IDs as numbers
291 | 	 */
292 | 	private normalizeTaskIds(tasks: Task[]): Task[] {
293 | 		return tasks.map((task) => ({
294 | 			...task,
295 | 			id: String(task.id), // Task IDs are strings
296 | 			dependencies: task.dependencies?.map((dep) => String(dep)) || [],
297 | 			subtasks:
298 | 				task.subtasks?.map((subtask) => ({
299 | 					...subtask,
300 | 					id: Number(subtask.id), // Subtask IDs are numbers
301 | 					parentId: String(subtask.parentId) // Parent ID is string (Task ID)
302 | 				})) || []
303 | 		}));
304 | 	}
305 | 
306 | 	/**
307 | 	 * Check if the tasks file exists
308 | 	 */
309 | 	async exists(_tag?: string): Promise<boolean> {
310 | 		const filePath = this.pathResolver.getTasksPath();
311 | 		return this.fileOps.exists(filePath);
312 | 	}
313 | 
314 | 	/**
315 | 	 * Get all available tags from the single tasks.json file
316 | 	 */
317 | 	async getAllTags(): Promise<string[]> {
318 | 		try {
319 | 			const filePath = this.pathResolver.getTasksPath();
320 | 			const data = await this.fileOps.readJson(filePath);
321 | 			return this.formatHandler.extractTags(data);
322 | 		} catch (error: any) {
323 | 			if (error.code === 'ENOENT') {
324 | 				return []; // File doesn't exist
325 | 			}
326 | 			throw new Error(`Failed to get tags: ${error.message}`);
327 | 		}
328 | 	}
329 | 
330 | 	/**
331 | 	 * Load metadata from the single tasks.json file for a specific tag
332 | 	 */
333 | 	async loadMetadata(tag?: string): Promise<TaskMetadata | null> {
334 | 		const filePath = this.pathResolver.getTasksPath();
335 | 		const resolvedTag = tag || 'master';
336 | 
337 | 		try {
338 | 			const rawData = await this.fileOps.readJson(filePath);
339 | 			return this.formatHandler.extractMetadata(rawData, resolvedTag);
340 | 		} catch (error: any) {
341 | 			if (error.code === 'ENOENT') {
342 | 				return null;
343 | 			}
344 | 			throw new Error(`Failed to load metadata: ${error.message}`);
345 | 		}
346 | 	}
347 | 
348 | 	/**
349 | 	 * Save metadata (stored with tasks)
350 | 	 */
351 | 	async saveMetadata(_metadata: TaskMetadata, tag?: string): Promise<void> {
352 | 		const tasks = await this.loadTasks(tag);
353 | 		await this.saveTasks(tasks, tag);
354 | 	}
355 | 
356 | 	/**
357 | 	 * Append tasks to existing storage
358 | 	 */
359 | 	async appendTasks(tasks: Task[], tag?: string): Promise<void> {
360 | 		const existingTasks = await this.loadTasks(tag);
361 | 		const allTasks = [...existingTasks, ...tasks];
362 | 		await this.saveTasks(allTasks, tag);
363 | 	}
364 | 
365 | 	/**
366 | 	 * Update a specific task
367 | 	 */
368 | 	async updateTask(
369 | 		taskId: string,
370 | 		updates: Partial<Task>,
371 | 		tag?: string
372 | 	): Promise<void> {
373 | 		const tasks = await this.loadTasks(tag);
374 | 		const taskIndex = tasks.findIndex((t) => String(t.id) === String(taskId));
375 | 
376 | 		if (taskIndex === -1) {
377 | 			throw new Error(`Task ${taskId} not found`);
378 | 		}
379 | 
380 | 		tasks[taskIndex] = {
381 | 			...tasks[taskIndex],
382 | 			...updates,
383 | 			id: String(taskId) // Keep consistent with normalizeTaskIds
384 | 		};
385 | 		await this.saveTasks(tasks, tag);
386 | 	}
387 | 
388 | 	/**
389 | 	 * Update task with AI-powered prompt
390 | 	 * For file storage, this should NOT be called - client must handle AI processing first
391 | 	 */
392 | 	async updateTaskWithPrompt(
393 | 		_taskId: string,
394 | 		_prompt: string,
395 | 		_tag?: string,
396 | 		_options?: { useResearch?: boolean; mode?: 'append' | 'update' | 'rewrite' }
397 | 	): Promise<void> {
398 | 		throw new Error(
399 | 			'File storage does not support updateTaskWithPrompt. ' +
400 | 				'Client-side AI logic must process the prompt before calling updateTask().'
401 | 		);
402 | 	}
403 | 
404 | 	/**
405 | 	 * Expand task into subtasks with AI-powered generation
406 | 	 * For file storage, this should NOT be called - client must handle AI processing first
407 | 	 */
408 | 	async expandTaskWithPrompt(
409 | 		_taskId: string,
410 | 		_tag?: string,
411 | 		_options?: {
412 | 			numSubtasks?: number;
413 | 			useResearch?: boolean;
414 | 			additionalContext?: string;
415 | 			force?: boolean;
416 | 		}
417 | 	): Promise<void> {
418 | 		throw new Error(
419 | 			'File storage does not support expandTaskWithPrompt. ' +
420 | 				'Client-side AI logic must process the expansion before calling updateTask().'
421 | 		);
422 | 	}
423 | 
424 | 	/**
425 | 	 * Update task or subtask status by ID - handles file storage logic with parent/subtask relationships
426 | 	 */
427 | 	async updateTaskStatus(
428 | 		taskId: string,
429 | 		newStatus: TaskStatus,
430 | 		tag?: string
431 | 	): Promise<UpdateStatusResult> {
432 | 		const tasks = await this.loadTasks(tag);
433 | 
434 | 		// Check if this is a subtask (contains a dot)
435 | 		if (taskId.includes('.')) {
436 | 			return this.updateSubtaskStatusInFile(tasks, taskId, newStatus, tag);
437 | 		}
438 | 
439 | 		// Handle regular task update
440 | 		const taskIndex = tasks.findIndex((t) => String(t.id) === String(taskId));
441 | 
442 | 		if (taskIndex === -1) {
443 | 			throw new Error(`Task ${taskId} not found`);
444 | 		}
445 | 
446 | 		const oldStatus = tasks[taskIndex].status;
447 | 		if (oldStatus === newStatus) {
448 | 			return {
449 | 				success: true,
450 | 				oldStatus,
451 | 				newStatus,
452 | 				taskId: String(taskId)
453 | 			};
454 | 		}
455 | 
456 | 		tasks[taskIndex] = {
457 | 			...tasks[taskIndex],
458 | 			status: newStatus,
459 | 			updatedAt: new Date().toISOString()
460 | 		};
461 | 
462 | 		await this.saveTasks(tasks, tag);
463 | 
464 | 		return {
465 | 			success: true,
466 | 			oldStatus,
467 | 			newStatus,
468 | 			taskId: String(taskId)
469 | 		};
470 | 	}
471 | 
472 | 	/**
473 | 	 * Update subtask status within file storage - handles parent status auto-adjustment
474 | 	 */
475 | 	private async updateSubtaskStatusInFile(
476 | 		tasks: Task[],
477 | 		subtaskId: string,
478 | 		newStatus: TaskStatus,
479 | 		tag?: string
480 | 	): Promise<UpdateStatusResult> {
481 | 		// Parse the subtask ID to get parent ID and subtask ID
482 | 		const parts = subtaskId.split('.');
483 | 		if (parts.length !== 2) {
484 | 			throw new Error(
485 | 				`Invalid subtask ID format: ${subtaskId}. Expected format: parentId.subtaskId`
486 | 			);
487 | 		}
488 | 
489 | 		const [parentId, subIdRaw] = parts;
490 | 		const subId = subIdRaw.trim();
491 | 		if (!/^\d+$/.test(subId)) {
492 | 			throw new Error(
493 | 				`Invalid subtask ID: ${subId}. Subtask ID must be a positive integer.`
494 | 			);
495 | 		}
496 | 		const subtaskNumericId = Number(subId);
497 | 
498 | 		// Find the parent task
499 | 		const parentTaskIndex = tasks.findIndex(
500 | 			(t) => String(t.id) === String(parentId)
501 | 		);
502 | 
503 | 		if (parentTaskIndex === -1) {
504 | 			throw new Error(`Parent task ${parentId} not found`);
505 | 		}
506 | 
507 | 		const parentTask = tasks[parentTaskIndex];
508 | 
509 | 		// Find the subtask within the parent task
510 | 		const subtaskIndex = parentTask.subtasks.findIndex(
511 | 			(st) => st.id === subtaskNumericId || String(st.id) === subId
512 | 		);
513 | 
514 | 		if (subtaskIndex === -1) {
515 | 			throw new Error(
516 | 				`Subtask ${subtaskId} not found in parent task ${parentId}`
517 | 			);
518 | 		}
519 | 
520 | 		const oldStatus = parentTask.subtasks[subtaskIndex].status || 'pending';
521 | 		if (oldStatus === newStatus) {
522 | 			return {
523 | 				success: true,
524 | 				oldStatus,
525 | 				newStatus,
526 | 				taskId: subtaskId
527 | 			};
528 | 		}
529 | 
530 | 		const now = new Date().toISOString();
531 | 
532 | 		// Update the subtask status
533 | 		parentTask.subtasks[subtaskIndex] = {
534 | 			...parentTask.subtasks[subtaskIndex],
535 | 			status: newStatus,
536 | 			updatedAt: now
537 | 		};
538 | 
539 | 		// Auto-adjust parent status based on subtask statuses
540 | 		const subs = parentTask.subtasks;
541 | 		let parentNewStatus = parentTask.status;
542 | 		if (subs.length > 0) {
543 | 			const norm = (s: any) => s.status || 'pending';
544 | 			const isDoneLike = (s: any) => {
545 | 				const st = norm(s);
546 | 				return st === 'done' || st === 'completed';
547 | 			};
548 | 			const allDone = subs.every(isDoneLike);
549 | 			const anyInProgress = subs.some((s) => norm(s) === 'in-progress');
550 | 			const anyDone = subs.some(isDoneLike);
551 | 			const allPending = subs.every((s) => norm(s) === 'pending');
552 | 
553 | 			if (allDone) parentNewStatus = 'done';
554 | 			else if (anyInProgress || anyDone) parentNewStatus = 'in-progress';
555 | 			else if (allPending) parentNewStatus = 'pending';
556 | 		}
557 | 
558 | 		// Always bump updatedAt; update status only if changed
559 | 		tasks[parentTaskIndex] = {
560 | 			...parentTask,
561 | 			...(parentNewStatus !== parentTask.status
562 | 				? { status: parentNewStatus }
563 | 				: {}),
564 | 			updatedAt: now
565 | 		};
566 | 
567 | 		await this.saveTasks(tasks, tag);
568 | 
569 | 		return {
570 | 			success: true,
571 | 			oldStatus,
572 | 			newStatus,
573 | 			taskId: subtaskId
574 | 		};
575 | 	}
576 | 
577 | 	/**
578 | 	 * Delete a task
579 | 	 */
580 | 	async deleteTask(taskId: string, tag?: string): Promise<void> {
581 | 		const tasks = await this.loadTasks(tag);
582 | 		const filteredTasks = tasks.filter((t) => String(t.id) !== String(taskId));
583 | 
584 | 		if (filteredTasks.length === tasks.length) {
585 | 			throw new Error(`Task ${taskId} not found`);
586 | 		}
587 | 
588 | 		await this.saveTasks(filteredTasks, tag);
589 | 	}
590 | 
591 | 	/**
592 | 	 * Create a new tag in the tasks.json file
593 | 	 */
594 | 	async createTag(
595 | 		tagName: string,
596 | 		options?: { copyFrom?: string; description?: string }
597 | 	): Promise<void> {
598 | 		const filePath = this.pathResolver.getTasksPath();
599 | 
600 | 		try {
601 | 			const existingData = await this.fileOps.readJson(filePath);
602 | 			const format = this.formatHandler.detectFormat(existingData);
603 | 
604 | 			if (format === 'legacy') {
605 | 				// Legacy format - add new tag key
606 | 				if (tagName in existingData) {
607 | 					throw new TaskMasterError(
608 | 						`Tag ${tagName} already exists`,
609 | 						ERROR_CODES.VALIDATION_ERROR
610 | 					);
611 | 				}
612 | 
613 | 				// Get tasks to copy if specified
614 | 				let tasksToCopy = [];
615 | 				if (options?.copyFrom) {
616 | 					if (
617 | 						options.copyFrom in existingData &&
618 | 						existingData[options.copyFrom].tasks
619 | 					) {
620 | 						tasksToCopy = JSON.parse(
621 | 							JSON.stringify(existingData[options.copyFrom].tasks)
622 | 						);
623 | 					}
624 | 				}
625 | 
626 | 				// Create new tag structure
627 | 				existingData[tagName] = {
628 | 					tasks: tasksToCopy,
629 | 					metadata: {
630 | 						created: new Date().toISOString(),
631 | 						updatedAt: new Date().toISOString(),
632 | 						description:
633 | 							options?.description ||
634 | 							`Tag created on ${new Date().toLocaleDateString()}`,
635 | 						tags: [tagName]
636 | 					}
637 | 				};
638 | 
639 | 				await this.fileOps.writeJson(filePath, existingData);
640 | 			} else {
641 | 				// Standard format - need to convert to legacy format first
642 | 				const masterTasks = existingData.tasks || [];
643 | 				const masterMetadata = existingData.metadata || {};
644 | 
645 | 				// Get tasks to copy (from master in this case)
646 | 				let tasksToCopy = [];
647 | 				if (options?.copyFrom === 'master' || !options?.copyFrom) {
648 | 					tasksToCopy = JSON.parse(JSON.stringify(masterTasks));
649 | 				}
650 | 
651 | 				const newData = {
652 | 					master: {
653 | 						tasks: masterTasks,
654 | 						metadata: { ...masterMetadata, tags: ['master'] }
655 | 					},
656 | 					[tagName]: {
657 | 						tasks: tasksToCopy,
658 | 						metadata: {
659 | 							created: new Date().toISOString(),
660 | 							updatedAt: new Date().toISOString(),
661 | 							description:
662 | 								options?.description ||
663 | 								`Tag created on ${new Date().toLocaleDateString()}`,
664 | 							tags: [tagName]
665 | 						}
666 | 					}
667 | 				};
668 | 
669 | 				await this.fileOps.writeJson(filePath, newData);
670 | 			}
671 | 		} catch (error: any) {
672 | 			if (error.code === 'ENOENT') {
673 | 				throw new Error('Tasks file not found - initialize project first');
674 | 			}
675 | 			throw error;
676 | 		}
677 | 	}
678 | 
679 | 	/**
680 | 	 * Delete a tag from the single tasks.json file
681 | 	 */
682 | 	async deleteTag(tag: string): Promise<void> {
683 | 		const filePath = this.pathResolver.getTasksPath();
684 | 
685 | 		try {
686 | 			const existingData = await this.fileOps.readJson(filePath);
687 | 
688 | 			if (this.formatHandler.detectFormat(existingData) === 'legacy') {
689 | 				// Legacy format - remove the tag key
690 | 				if (tag in existingData) {
691 | 					delete existingData[tag];
692 | 					await this.fileOps.writeJson(filePath, existingData);
693 | 				} else {
694 | 					throw new Error(`Tag ${tag} not found`);
695 | 				}
696 | 			} else if (tag === 'master') {
697 | 				// Standard format - delete the entire file for master tag
698 | 				await this.fileOps.deleteFile(filePath);
699 | 			} else {
700 | 				throw new Error(`Tag ${tag} not found in standard format`);
701 | 			}
702 | 		} catch (error: any) {
703 | 			if (error.code === 'ENOENT') {
704 | 				throw new Error(`Tag ${tag} not found - file doesn't exist`);
705 | 			}
706 | 			throw error;
707 | 		}
708 | 	}
709 | 
710 | 	/**
711 | 	 * Rename a tag within the single tasks.json file
712 | 	 */
713 | 	async renameTag(oldTag: string, newTag: string): Promise<void> {
714 | 		const filePath = this.pathResolver.getTasksPath();
715 | 
716 | 		try {
717 | 			const existingData = await this.fileOps.readJson(filePath);
718 | 
719 | 			if (this.formatHandler.detectFormat(existingData) === 'legacy') {
720 | 				// Legacy format - rename the tag key
721 | 				if (oldTag in existingData) {
722 | 					existingData[newTag] = existingData[oldTag];
723 | 					delete existingData[oldTag];
724 | 
725 | 					// Update metadata tags array
726 | 					if (existingData[newTag].metadata) {
727 | 						existingData[newTag].metadata.tags = [newTag];
728 | 					}
729 | 
730 | 					await this.fileOps.writeJson(filePath, existingData);
731 | 				} else {
732 | 					throw new Error(`Tag ${oldTag} not found`);
733 | 				}
734 | 			} else if (oldTag === 'master') {
735 | 				// Convert standard format to legacy when renaming master
736 | 				const masterTasks = existingData.tasks || [];
737 | 				const masterMetadata = existingData.metadata || {};
738 | 
739 | 				const newData = {
740 | 					[newTag]: {
741 | 						tasks: masterTasks,
742 | 						metadata: { ...masterMetadata, tags: [newTag] }
743 | 					}
744 | 				};
745 | 
746 | 				await this.fileOps.writeJson(filePath, newData);
747 | 			} else {
748 | 				throw new Error(`Tag ${oldTag} not found in standard format`);
749 | 			}
750 | 		} catch (error: any) {
751 | 			if (error.code === 'ENOENT') {
752 | 				throw new Error(`Tag ${oldTag} not found - file doesn't exist`);
753 | 			}
754 | 			throw error;
755 | 		}
756 | 	}
757 | 
758 | 	/**
759 | 	 * Copy a tag within the single tasks.json file
760 | 	 */
761 | 	async copyTag(sourceTag: string, targetTag: string): Promise<void> {
762 | 		const tasks = await this.loadTasks(sourceTag);
763 | 
764 | 		if (tasks.length === 0) {
765 | 			throw new Error(`Source tag ${sourceTag} not found or has no tasks`);
766 | 		}
767 | 
768 | 		await this.saveTasks(tasks, targetTag);
769 | 	}
770 | 
771 | 	/**
772 | 	 * Get all tags with detailed statistics including task counts
773 | 	 * For file storage, reads tags from tasks.json and calculates statistics
774 | 	 */
775 | 	async getTagsWithStats(): Promise<{
776 | 		tags: Array<{
777 | 			name: string;
778 | 			isCurrent: boolean;
779 | 			taskCount: number;
780 | 			completedTasks: number;
781 | 			statusBreakdown: Record<string, number>;
782 | 			subtaskCounts?: {
783 | 				totalSubtasks: number;
784 | 				subtasksByStatus: Record<string, number>;
785 | 			};
786 | 			created?: string;
787 | 			description?: string;
788 | 		}>;
789 | 		currentTag: string | null;
790 | 		totalTags: number;
791 | 	}> {
792 | 		const availableTags = await this.getAllTags();
793 | 
794 | 		// Get active tag from state.json
795 | 		const activeTag = await this.getActiveTagFromState();
796 | 
797 | 		const tagsWithStats = await Promise.all(
798 | 			availableTags.map(async (tagName) => {
799 | 				try {
800 | 					// Load tasks for this tag
801 | 					const tasks = await this.loadTasks(tagName);
802 | 
803 | 					// Calculate statistics
804 | 					const statusBreakdown: Record<string, number> = {};
805 | 					let completedTasks = 0;
806 | 
807 | 					const subtaskCounts = {
808 | 						totalSubtasks: 0,
809 | 						subtasksByStatus: {} as Record<string, number>
810 | 					};
811 | 
812 | 					tasks.forEach((task) => {
813 | 						// Count task status
814 | 						const status = task.status || 'pending';
815 | 						statusBreakdown[status] = (statusBreakdown[status] || 0) + 1;
816 | 
817 | 						if (status === 'done') {
818 | 							completedTasks++;
819 | 						}
820 | 
821 | 						// Count subtasks
822 | 						if (task.subtasks && task.subtasks.length > 0) {
823 | 							subtaskCounts.totalSubtasks += task.subtasks.length;
824 | 
825 | 							task.subtasks.forEach((subtask) => {
826 | 								const subStatus = subtask.status || 'pending';
827 | 								subtaskCounts.subtasksByStatus[subStatus] =
828 | 									(subtaskCounts.subtasksByStatus[subStatus] || 0) + 1;
829 | 							});
830 | 						}
831 | 					});
832 | 
833 | 					// Load metadata to get created date and description
834 | 					const metadata = await this.loadMetadata(tagName);
835 | 
836 | 					return {
837 | 						name: tagName,
838 | 						isCurrent: tagName === activeTag,
839 | 						taskCount: tasks.length,
840 | 						completedTasks,
841 | 						statusBreakdown,
842 | 						subtaskCounts:
843 | 							subtaskCounts.totalSubtasks > 0 ? subtaskCounts : undefined,
844 | 						created: metadata?.created,
845 | 						description: metadata?.description
846 | 					};
847 | 				} catch (error) {
848 | 					// If we can't load tasks for a tag, return it with 0 tasks
849 | 					return {
850 | 						name: tagName,
851 | 						isCurrent: tagName === activeTag,
852 | 						taskCount: 0,
853 | 						completedTasks: 0,
854 | 						statusBreakdown: {}
855 | 					};
856 | 				}
857 | 			})
858 | 		);
859 | 
860 | 		return {
861 | 			tags: tagsWithStats,
862 | 			currentTag: activeTag,
863 | 			totalTags: tagsWithStats.length
864 | 		};
865 | 	}
866 | 
867 | 	/**
868 | 	 * Get the active tag from state.json
869 | 	 * @returns The active tag name or 'master' as default
870 | 	 */
871 | 	private async getActiveTagFromState(): Promise<string> {
872 | 		try {
873 | 			const statePath = path.join(
874 | 				this.pathResolver.getBasePath(),
875 | 				'state.json'
876 | 			);
877 | 			const stateData = await this.fileOps.readJson(statePath);
878 | 			return stateData?.currentTag || 'master';
879 | 		} catch (error) {
880 | 			// If state.json doesn't exist or can't be read, default to 'master'
881 | 			return 'master';
882 | 		}
883 | 	}
884 | 
885 | 	/**
886 | 	 * Enrich tasks with complexity data from the complexity report
887 | 	 * Private helper method called by loadTasks()
888 | 	 */
889 | 	private async enrichTasksWithComplexity(
890 | 		tasks: Task[],
891 | 		tag: string
892 | 	): Promise<Task[]> {
893 | 		// Get all task IDs for bulk lookup
894 | 		const taskIds = tasks.map((t) => t.id);
895 | 
896 | 		// Load complexity data for all tasks at once (more efficient)
897 | 		const complexityMap = await this.complexityManager.getComplexityForTasks(
898 | 			taskIds,
899 | 			tag
900 | 		);
901 | 
902 | 		// If no complexity data found, return tasks as-is
903 | 		if (complexityMap.size === 0) {
904 | 			return tasks;
905 | 		}
906 | 
907 | 		// Enrich each task with its complexity data
908 | 		return tasks.map((task) => {
909 | 			const complexityData = complexityMap.get(String(task.id));
910 | 			if (!complexityData) {
911 | 				return task;
912 | 			}
913 | 
914 | 			// Merge complexity data into the task
915 | 			return {
916 | 				...task,
917 | 				complexity: complexityData.complexityScore,
918 | 				recommendedSubtasks: complexityData.recommendedSubtasks,
919 | 				expansionPrompt: complexityData.expansionPrompt,
920 | 				complexityReasoning: complexityData.complexityReasoning
921 | 			};
922 | 		});
923 | 	}
924 | }
925 | 
926 | // Export as default for convenience
927 | export default FileStorage;
928 | 
```

--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Configuration
  2 | 
  3 | Taskmaster uses two primary methods for configuration:
  4 | 
  5 | 1.  **`.taskmaster/config.json` File (Recommended - New Structure)**
  6 | 
  7 |     - This JSON file stores most configuration settings, including AI model selections, parameters, logging levels, and project defaults.
  8 |     - **Location:** This file is created in the `.taskmaster/` directory when you run the `task-master models --setup` interactive setup or initialize a new project with `task-master init`.
  9 |     - **Migration:** Existing projects with `.taskmasterconfig` in the root will continue to work, but should be migrated to the new structure using `task-master migrate`.
 10 |     - **Management:** Use the `task-master models --setup` command (or `models` MCP tool) to interactively create and manage this file. You can also set specific models directly using `task-master models --set-<role>=<model_id>`, adding `--ollama` or `--openrouter` flags for custom models. Manual editing is possible but not recommended unless you understand the structure.
 11 |     - **Example Structure:**
 12 |       ```json
 13 |       {
 14 |         "models": {
 15 |           "main": {
 16 |             "provider": "anthropic",
 17 |             "modelId": "claude-3-7-sonnet-20250219",
 18 |             "maxTokens": 64000,
 19 |             "temperature": 0.2,
 20 |             "baseURL": "https://api.anthropic.com/v1"
 21 |           },
 22 |           "research": {
 23 |             "provider": "perplexity",
 24 |             "modelId": "sonar-pro",
 25 |             "maxTokens": 8700,
 26 |             "temperature": 0.1,
 27 |             "baseURL": "https://api.perplexity.ai/v1"
 28 |           },
 29 |           "fallback": {
 30 |             "provider": "anthropic",
 31 |             "modelId": "claude-3-5-sonnet",
 32 |             "maxTokens": 64000,
 33 |             "temperature": 0.2
 34 |           }
 35 |         },
 36 |         "global": {
 37 |           "logLevel": "info",
 38 |           "debug": false,
 39 |           "defaultNumTasks": 10,
 40 |           "defaultSubtasks": 5,
 41 |           "defaultPriority": "medium",
 42 |           "defaultTag": "master",
 43 |           "projectName": "Your Project Name",
 44 |           "ollamaBaseURL": "http://localhost:11434/api",
 45 |           "azureBaseURL": "https://your-endpoint.azure.com/openai/deployments",
 46 |           "vertexProjectId": "your-gcp-project-id",
 47 |           "vertexLocation": "us-central1",
 48 | 	      "responseLanguage": "English"
 49 |         }
 50 |       }
 51 |       ```
 52 | 
 53 | > For MCP-specific setup and troubleshooting, see [Provider-Specific Configuration](#provider-specific-configuration).
 54 | 
 55 | 2.  **Legacy `.taskmasterconfig` File (Backward Compatibility)**
 56 | 
 57 |     - For projects that haven't migrated to the new structure yet.
 58 |     - **Location:** Project root directory.
 59 |     - **Migration:** Use `task-master migrate` to move this to `.taskmaster/config.json`.
 60 |     - **Deprecation:** While still supported, you'll see warnings encouraging migration to the new structure.
 61 | 
 62 | ## MCP Tool Loading Configuration
 63 | 
 64 | ### TASK_MASTER_TOOLS Environment Variable
 65 | 
 66 | The `TASK_MASTER_TOOLS` environment variable controls which tools are loaded by the Task Master MCP server. This allows you to optimize token usage based on your workflow needs.
 67 | 
 68 | > Note
 69 | > Prefer setting `TASK_MASTER_TOOLS` in your MCP client's `env` block (e.g., `.cursor/mcp.json`) or in CI/deployment env. The `.env` file is reserved for API keys/endpoints; avoid persisting non-secret settings there.
 70 | 
 71 | #### Configuration Options
 72 | 
 73 | - **`all`** (default): Loads all 36 available tools (~21,000 tokens)
 74 |   - Best for: Users who need the complete feature set
 75 |   - Use when: Working with complex projects requiring all Task Master features
 76 |   - Backward compatibility: This is the default to maintain compatibility with existing installations
 77 | 
 78 | - **`standard`**: Loads 15 commonly used tools (~10,000 tokens, 50% reduction)
 79 |   - Best for: Regular task management workflows
 80 |   - Tools included: All core tools plus project initialization, complexity analysis, task generation, and more
 81 |   - Use when: You need a balanced set of features with reduced token usage
 82 | 
 83 | - **`core`** (or `lean`): Loads 7 essential tools (~5,000 tokens, 70% reduction)
 84 |   - Best for: Daily development with minimal token overhead
 85 |   - Tools included: `get_tasks`, `next_task`, `get_task`, `set_task_status`, `update_subtask`, `parse_prd`, `expand_task`
 86 |   - Use when: Working in large contexts where token usage is critical
 87 |   - Note: "lean" is an alias for "core" (same tools, token estimate and recommended use). You can refer to it as either "core" or "lean" when configuring.
 88 | 
 89 | - **Custom list**: Comma-separated list of specific tool names
 90 |   - Best for: Specialized workflows requiring specific tools
 91 |   - Example: `"get_tasks,next_task,set_task_status"`
 92 |   - Use when: You know exactly which tools you need
 93 | 
 94 | #### How to Configure
 95 | 
 96 | 1. **In MCP configuration files** (`.cursor/mcp.json`, `.vscode/mcp.json`, etc.) - **Recommended**:
 97 | 
 98 |    ```jsonc
 99 |    {
100 |      "mcpServers": {
101 |        "task-master-ai": {
102 |          "env": {
103 |            "TASK_MASTER_TOOLS": "standard",  // Set tool loading mode
104 |            // API keys can still use .env for security
105 |          }
106 |        }
107 |      }
108 |    }
109 |    ```
110 | 
111 | 2. **Via Claude Code CLI**:
112 | 
113 |    ```bash
114 |    claude mcp add task-master-ai --scope user \
115 |      --env TASK_MASTER_TOOLS="core" \
116 |      -- npx -y task-master-ai@latest
117 |    ```
118 | 
119 | 3. **In CI/deployment environment variables**:
120 |    ```bash
121 |    export TASK_MASTER_TOOLS="standard"
122 |    node mcp-server/server.js
123 |    ```
124 | 
125 | #### Tool Loading Behavior
126 | 
127 | - When `TASK_MASTER_TOOLS` is unset or empty, the system defaults to `"all"`
128 | - Invalid tool names in a user-specified list are ignored (a warning is emitted for each)
129 | - If every tool name in a custom list is invalid, the system falls back to `"all"`
130 | - Tool names are case-insensitive (e.g., `"CORE"`, `"core"`, and `"Core"` are treated identically)
131 | 
132 | ## Environment Variables (`.env` file or MCP `env` block - For API Keys Only)
133 | 
134 | - Used **exclusively** for sensitive API keys and specific endpoint URLs.
135 | - **Location:**
136 |   - For CLI usage: Create a `.env` file in your project root.
137 |   - For MCP/Cursor usage: Configure keys in the `env` section of your `.cursor/mcp.json` file.
138 | - **Required API Keys (Depending on configured providers):**
139 |   - `ANTHROPIC_API_KEY`: Your Anthropic API key.
140 |   - `PERPLEXITY_API_KEY`: Your Perplexity API key.
141 |   - `OPENAI_API_KEY`: Your OpenAI API key.
142 |   - `GOOGLE_API_KEY`: Your Google API key (also used for Vertex AI provider).
143 |   - `MISTRAL_API_KEY`: Your Mistral API key.
144 |   - `AZURE_OPENAI_API_KEY`: Your Azure OpenAI API key (also requires `AZURE_OPENAI_ENDPOINT`).
145 |   - `OPENROUTER_API_KEY`: Your OpenRouter API key.
146 |   - `XAI_API_KEY`: Your X-AI API key.
147 | - **Optional Endpoint Overrides:**
148 |   - **Per-role `baseURL` in `.taskmasterconfig`:** You can add a `baseURL` property to any model role (`main`, `research`, `fallback`) to override the default API endpoint for that provider. If omitted, the provider's standard endpoint is used.
149 |   - **Environment Variable Overrides (`<PROVIDER>_BASE_URL`):** For greater flexibility, especially with third-party services, you can set an environment variable like `OPENAI_BASE_URL` or `MISTRAL_BASE_URL`. This will override any `baseURL` set in the configuration file for that provider. This is the recommended way to connect to OpenAI-compatible APIs.
150 |   - `AZURE_OPENAI_ENDPOINT`: Required if using Azure OpenAI key (can also be set as `baseURL` for the Azure model role).
151 |   - `OLLAMA_BASE_URL`: Override the default Ollama API URL (Default: `http://localhost:11434/api`).
152 |   - `VERTEX_PROJECT_ID`: Your Google Cloud project ID for Vertex AI. Required when using the 'vertex' provider.
153 |   - `VERTEX_LOCATION`: Google Cloud region for Vertex AI (e.g., 'us-central1'). Default is 'us-central1'.
154 |   - `GOOGLE_APPLICATION_CREDENTIALS`: Path to service account credentials JSON file for Google Cloud auth (alternative to API key for Vertex AI).
155 | 
156 | **Important:** Settings like model ID selections (`main`, `research`, `fallback`), `maxTokens`, `temperature`, `logLevel`, `defaultSubtasks`, `defaultPriority`, and `projectName` are **managed in `.taskmaster/config.json`** (or `.taskmasterconfig` for unmigrated projects), not environment variables.
157 | 
158 | ## Tagged Task Lists Configuration (v0.17+)
159 | 
160 | Taskmaster includes a tagged task lists system for multi-context task management.
161 | 
162 | ### Global Tag Settings
163 | 
164 | ```json
165 | "global": {
166 |   "defaultTag": "master"
167 | }
168 | ```
169 | 
170 | - **`defaultTag`** (string): Default tag context for new operations (default: "master")
171 | 
172 | ### Git Integration
173 | 
174 | Task Master provides manual git integration through the `--from-branch` option:
175 | 
176 | - **Manual Tag Creation**: Use `task-master add-tag --from-branch` to create a tag based on your current git branch name
177 | - **User Control**: No automatic tag switching - you control when and how tags are created
178 | - **Flexible Workflow**: Supports any git workflow without imposing rigid branch-tag mappings
179 | 
180 | ## State Management File
181 | 
182 | Taskmaster uses `.taskmaster/state.json` to track tagged system runtime information:
183 | 
184 | ```json
185 | {
186 |   "currentTag": "master",
187 |   "lastSwitched": "2025-06-11T20:26:12.598Z",
188 |   "migrationNoticeShown": true
189 | }
190 | ```
191 | 
192 | - **`currentTag`**: Currently active tag context
193 | - **`lastSwitched`**: Timestamp of last tag switch
194 | - **`migrationNoticeShown`**: Whether migration notice has been displayed
195 | 
196 | This file is automatically created during tagged system migration and should not be manually edited.
197 | 
198 | ## Example `.env` File (for API Keys)
199 | 
200 | ```
201 | # Required API keys for providers configured in .taskmaster/config.json
202 | ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
203 | PERPLEXITY_API_KEY=pplx-your-key-here
204 | # OPENAI_API_KEY=sk-your-key-here
205 | # GOOGLE_API_KEY=AIzaSy...
206 | # AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
207 | # etc.
208 | 
209 | # Optional Endpoint Overrides
210 | # Use a specific provider's base URL, e.g., for an OpenAI-compatible API
211 | # OPENAI_BASE_URL=https://api.third-party.com/v1
212 | #
213 | # Azure OpenAI Configuration
214 | # AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/ or https://your-endpoint-name.cognitiveservices.azure.com/openai/deployments
215 | # OLLAMA_BASE_URL=http://custom-ollama-host:11434/api
216 | 
217 | # Google Vertex AI Configuration (Required if using 'vertex' provider)
218 | # VERTEX_PROJECT_ID=your-gcp-project-id
219 | ```
220 | 
221 | ## Troubleshooting
222 | 
223 | ### Configuration Errors
224 | 
225 | - If Task Master reports errors about missing configuration or cannot find the config file, run `task-master models --setup` in your project root to create or repair the file.
226 | - For new projects, config will be created at `.taskmaster/config.json`. For legacy projects, you may want to use `task-master migrate` to move to the new structure.
227 | - Ensure API keys are correctly placed in your `.env` file (for CLI) or `.cursor/mcp.json` (for MCP) and are valid for the providers selected in your config file.
228 | 
229 | ### If `task-master init` doesn't respond:
230 | 
231 | Try running it with Node directly:
232 | 
233 | ```bash
234 | node node_modules/claude-task-master/scripts/init.js
235 | ```
236 | 
237 | Or clone the repository and run:
238 | 
239 | ```bash
240 | git clone https://github.com/eyaltoledano/claude-task-master.git
241 | cd claude-task-master
242 | node scripts/init.js
243 | ```
244 | 
245 | ## Provider-Specific Configuration
246 | 
247 | ### MCP (Model Context Protocol) Provider
248 | 
249 | 1. **Prerequisites**:
250 |    - An active MCP session with sampling capability
251 |    - MCP client with sampling support (e.g. VS Code)
252 |    - No API keys required (uses session-based authentication)
253 | 
254 | 2. **Configuration**:
255 |    ```json
256 |    {
257 |      "models": {
258 |        "main": {
259 |          "provider": "mcp",
260 |          "modelId": "mcp-sampling"
261 |        },
262 |        "research": {
263 |          "provider": "mcp",
264 |          "modelId": "mcp-sampling"
265 |        }
266 |      }
267 |    }
268 |    ```
269 | 
270 | 3. **Available Model IDs**:
271 |    - `mcp-sampling` - General text generation using MCP client sampling (supports all roles)
272 |    - `claude-3-5-sonnet-20241022` - High-performance model for general tasks (supports all roles)
273 |    - `claude-3-opus-20240229` - Enhanced reasoning model for complex tasks (supports all roles)
274 | 
275 | 4. **Features**:
276 |    - ✅ **Text Generation**: Standard AI text generation via MCP sampling
277 |    - ✅ **Object Generation**: Full schema-driven structured output generation
278 |    - ✅ **PRD Parsing**: Parse Product Requirements Documents into structured tasks
279 |    - ✅ **Task Creation**: AI-powered task creation with validation
280 |    - ✅ **Session Management**: Automatic session detection and context handling
281 |    - ✅ **Error Recovery**: Robust error handling and fallback mechanisms
282 | 
283 | 5. **Usage Requirements**:
284 |    - Must be running in an MCP context (session must be available)
285 |    - Session must provide `clientCapabilities.sampling` capability
286 | 
287 | 6. **Best Practices**:
288 |    - Always configure a non-MCP fallback provider
289 |    - Use `mcp` for main/research roles when in MCP environments
290 |    - Test sampling capability before production use
291 | 
292 | 7. **Setup Commands**:
293 |    ```bash
294 |    # Set MCP provider for main role
295 |    task-master models set-main --provider mcp --model claude-3-5-sonnet-20241022
296 | 
297 |    # Set MCP provider for research role
298 |    task-master models set-research --provider mcp --model claude-3-opus-20240229
299 | 
300 |    # Verify configuration
301 |    task-master models list
302 |    ```
303 | 
304 | 8. **Troubleshooting**:
305 |    - "MCP provider requires session context" → Ensure running in MCP environment
306 |    - See the [MCP Provider Guide](./mcp-provider-guide.md) for detailed troubleshooting
307 | 
308 | ### MCP Timeout Configuration
309 | 
310 | Long-running AI operations in taskmaster-ai can exceed the default 60-second MCP timeout. Operations like `parse_prd`, `expand_task`, `research`, and `analyze_project_complexity` may take 2-5 minutes to complete.
311 | 
312 | #### Adding Timeout Configuration
313 | 
314 | Add a `timeout` parameter to your MCP configuration to extend the timeout limit. The timeout configuration works identically across MCP clients including Cursor, Windsurf, and RooCode:
315 | 
316 | ```json
317 | {
318 |   "mcpServers": {
319 |     "task-master-ai": {
320 |       "command": "npx",
321 |       "args": ["-y", "--package=task-master-ai", "task-master-ai"],
322 |       "timeout": 300,
323 |       "env": {
324 |         "ANTHROPIC_API_KEY": "your-anthropic-api-key"
325 |       }
326 |     }
327 |   }
328 | }
329 | ```
330 | 
331 | **Configuration Details:**
332 | - **`timeout: 300`** - Sets timeout to 300 seconds (5 minutes)
333 | - **Value range**: 1-3600 seconds (1 second to 1 hour)
334 | - **Recommended**: 300 seconds provides sufficient time for most AI operations
335 | - **Format**: Integer value in seconds (not milliseconds)
336 | 
337 | #### Automatic Setup
338 | 
339 | When adding taskmaster rules for supported editors, the timeout configuration is automatically included:
340 | 
341 | ```bash
342 | # Automatically includes timeout configuration
343 | task-master rules add cursor
344 | task-master rules add roo
345 | task-master rules add windsurf
346 | task-master rules add vscode
347 | ```
348 | 
349 | #### Troubleshooting Timeouts
350 | 
351 | If you're still experiencing timeout errors:
352 | 
353 | 1. **Verify configuration**: Check that `timeout: 300` is present in your MCP config
354 | 2. **Restart editor**: Restart your editor after making configuration changes
355 | 3. **Increase timeout**: For very complex operations, try `timeout: 600` (10 minutes)
356 | 4. **Check API keys**: Ensure required API keys are properly configured
357 | 
358 | **Expected behavior:**
359 | - **Before fix**: Operations fail after 60 seconds with `MCP request timed out after 60000ms`
360 | - **After fix**: Operations complete successfully within the configured timeout limit
361 | 
362 | ### Google Vertex AI Configuration
363 | 
364 | Google Vertex AI is Google Cloud's enterprise AI platform and requires specific configuration:
365 | 
366 | 1. **Prerequisites**:
367 |    - A Google Cloud account with Vertex AI API enabled
368 |    - Either a Google API key with Vertex AI permissions OR a service account with appropriate roles
369 |    - A Google Cloud project ID
370 | 2. **Authentication Options**:
371 |    - **API Key**: Set the `GOOGLE_API_KEY` environment variable
372 |    - **Service Account**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to your service account JSON file
373 | 3. **Required Configuration**:
374 |    - Set `VERTEX_PROJECT_ID` to your Google Cloud project ID
375 |    - Set `VERTEX_LOCATION` to your preferred Google Cloud region (default: us-central1)
376 | 4. **Example Setup**:
377 | 
378 |    ```bash
379 |    # In .env file
380 |    GOOGLE_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXX
381 |    VERTEX_PROJECT_ID=my-gcp-project-123
382 |    VERTEX_LOCATION=us-central1
383 |    ```
384 | 
385 |    Or using service account:
386 | 
387 |    ```bash
388 |    # In .env file
389 |    GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
390 |    VERTEX_PROJECT_ID=my-gcp-project-123
391 |    VERTEX_LOCATION=us-central1
392 |    ```
393 | 
394 | 5. **In .taskmaster/config.json**:
395 |    ```json
396 |    "global": {
397 |      "vertexProjectId": "my-gcp-project-123",
398 |      "vertexLocation": "us-central1"
399 |    }
400 |    ```
401 | 
402 | ### Azure OpenAI Configuration
403 | 
404 | Azure OpenAI provides enterprise-grade OpenAI models through Microsoft's Azure cloud platform and requires specific configuration:
405 | 
406 | 1. **Prerequisites**:
407 |    - An Azure account with an active subscription
408 |    - Azure OpenAI service resource created in the Azure portal
409 |    - Azure OpenAI API key and endpoint URL
410 |    - Deployed models (e.g., gpt-4o, gpt-4o-mini, gpt-4.1, etc) in your Azure OpenAI resource
411 | 
412 | 2. **Authentication**:
413 |    - Set the `AZURE_OPENAI_API_KEY` environment variable with your Azure OpenAI API key
414 |    - Configure the endpoint URL using one of the methods below
415 | 
416 | 3. **Configuration Options**:
417 | 
418 |    **Option 1: Using Global Azure Base URL (affects all Azure models)**
419 |    ```json
420 |    // In .taskmaster/config.json
421 |    {
422 |      "models": {
423 |        "main": {
424 |          "provider": "azure",
425 |          "modelId": "gpt-4o",
426 |          "maxTokens": 16000,
427 |          "temperature": 0.7
428 |        },
429 |        "fallback": {
430 |          "provider": "azure",
431 |          "modelId": "gpt-4o-mini",
432 |          "maxTokens": 10000,
433 |          "temperature": 0.7
434 |        }
435 |      },
436 |      "global": {
437 |        "azureBaseURL": "https://your-resource-name.azure.com/openai/deployments"
438 |      }
439 |    }
440 |    ```
441 | 
442 |    **Option 2: Using Per-Model Base URLs (recommended for flexibility)**
443 |    ```json
444 |    // In .taskmaster/config.json
445 |    {
446 |      "models": {
447 |        "main": {
448 |          "provider": "azure",
449 |          "modelId": "gpt-4o",
450 |          "maxTokens": 16000,
451 |          "temperature": 0.7,
452 |          "baseURL": "https://your-resource-name.azure.com/openai/deployments"
453 |        },
454 |        "research": {
455 |          "provider": "perplexity",
456 |          "modelId": "sonar-pro",
457 |          "maxTokens": 8700,
458 |          "temperature": 0.1
459 |        },
460 |        "fallback": {
461 |          "provider": "azure",
462 |          "modelId": "gpt-4o-mini",
463 |          "maxTokens": 10000,
464 |          "temperature": 0.7,
465 |          "baseURL": "https://your-resource-name.azure.com/openai/deployments"
466 |        }
467 |      }
468 |    }
469 |    ```
470 | 
471 | 4. **Environment Variables**:
472 |    ```bash
473 |    # In .env file
474 |    AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
475 | 
476 |    # Optional: Override endpoint for all Azure models
477 |    AZURE_OPENAI_ENDPOINT=https://your-resource-name.azure.com/openai/deployments
478 |    ```
479 | 
480 | 5. **Important Notes**:
481 |    - **Model Deployment Names**: The `modelId` in your configuration should match the **deployment name** you created in Azure OpenAI Studio, not the underlying model name
482 |    - **Base URL Priority**: Per-model `baseURL` settings override the global `azureBaseURL` setting
483 |    - **Endpoint Format**: When using per-model `baseURL`, use the full path including `/openai/deployments`
484 | 
485 | 6. **Troubleshooting**:
486 | 
487 |    **"Resource not found" errors:**
488 |    - Ensure your `baseURL` includes the full path: `https://your-resource-name.openai.azure.com/openai/deployments`
489 |    - Verify that your deployment name in `modelId` exactly matches what's configured in Azure OpenAI Studio
490 |    - Check that your Azure OpenAI resource is in the correct region and properly deployed
491 | 
492 |    **Authentication errors:**
493 |    - Verify your `AZURE_OPENAI_API_KEY` is correct and has not expired
494 |    - Ensure your Azure OpenAI resource has the necessary permissions
495 |    - Check that your subscription has not been suspended or reached quota limits
496 | 
497 |    **Model availability errors:**
498 |    - Confirm the model is deployed in your Azure OpenAI resource
499 |    - Verify the deployment name matches your configuration exactly (case-sensitive)
500 |    - Ensure the model deployment is in a "Succeeded" state in Azure OpenAI Studio
501 |    - Ensure youre not getting rate limited by `maxTokens` maintain appropriate Tokens per Minute Rate Limit (TPM) in your deployment.
502 | 
503 | ### Codex CLI Provider
504 | 
505 | The Codex CLI provider integrates Task Master with OpenAI's Codex CLI, allowing you to use ChatGPT subscription models via OAuth authentication.
506 | 
507 | 1. **Prerequisites**:
508 |    - Node.js >= 18
509 |    - Codex CLI >= 0.42.0 (>= 0.44.0 recommended)
510 |    - ChatGPT subscription: Plus, Pro, Business, Edu, or Enterprise (for OAuth access to GPT-5 models)
511 | 
512 | 2. **Installation**:
513 |    ```bash
514 |    npm install -g @openai/codex
515 |    ```
516 | 
517 | 3. **Authentication** (OAuth - Primary Method):
518 |    ```bash
519 |    codex login
520 |    ```
521 |    This will open a browser window for OAuth authentication with your ChatGPT account. Once authenticated, Task Master will automatically use these credentials.
522 | 
523 | 4. **Optional API Key Method**:
524 |    While OAuth is the primary and recommended authentication method, you can optionally set an OpenAI API key:
525 |    ```bash
526 |    # In .env file
527 |    OPENAI_API_KEY=sk-your-openai-api-key-here
528 |    ```
529 |    **Note**: The API key will only be injected if explicitly provided. OAuth is always preferred.
530 | 
531 | 5. **Configuration**:
532 |    ```json
533 |    // In .taskmaster/config.json
534 |    {
535 |      "models": {
536 |        "main": {
537 |          "provider": "codex-cli",
538 |          "modelId": "gpt-5-codex",
539 |          "maxTokens": 128000,
540 |          "temperature": 0.2
541 |        },
542 |        "fallback": {
543 |          "provider": "codex-cli",
544 |          "modelId": "gpt-5",
545 |          "maxTokens": 128000,
546 |          "temperature": 0.2
547 |        }
548 |      },
549 |      "codexCli": {
550 |        "allowNpx": true,
551 |        "skipGitRepoCheck": true,
552 |        "approvalMode": "on-failure",
553 |        "sandboxMode": "workspace-write"
554 |      }
555 |    }
556 |    ```
557 | 
558 | 6. **Available Models**:
559 |    - `gpt-5` - Latest GPT-5 model (272K max input, 128K max output)
560 |    - `gpt-5-codex` - GPT-5 optimized for agentic software engineering (272K max input, 128K max output)
561 | 
562 | 7. **Codex CLI Settings (`codexCli` section)**:
563 | 
564 |    The `codexCli` section in your configuration file supports the following options:
565 | 
566 |    - **`allowNpx`** (boolean, default: `false`): Allow fallback to `npx @openai/codex` if CLI not found on PATH
567 |    - **`skipGitRepoCheck`** (boolean, default: `false`): Skip git repository safety check (recommended for CI/non-repo usage)
568 |    - **`approvalMode`** (string): Control command execution approval
569 |      - `"untrusted"`: Require approval for all commands
570 |      - `"on-failure"`: Only require approval after a command fails (default)
571 |      - `"on-request"`: Approve only when explicitly requested
572 |      - `"never"`: Never require approval (not recommended)
573 |    - **`sandboxMode`** (string): Control filesystem access
574 |      - `"read-only"`: Read-only access
575 |      - `"workspace-write"`: Allow writes to workspace (default)
576 |      - `"danger-full-access"`: Full filesystem access (use with caution)
577 |    - **`codexPath`** (string, optional): Custom path to codex CLI executable
578 |    - **`cwd`** (string, optional): Working directory for Codex CLI execution
579 |    - **`fullAuto`** (boolean, optional): Fully automatic mode (equivalent to `--full-auto` flag)
580 |    - **`dangerouslyBypassApprovalsAndSandbox`** (boolean, optional): Bypass all safety checks (dangerous!)
581 |    - **`color`** (string, optional): Color handling - `"always"`, `"never"`, or `"auto"`
582 |    - **`outputLastMessageFile`** (string, optional): Write last agent message to specified file
583 |    - **`verbose`** (boolean, optional): Enable verbose logging
584 |    - **`env`** (object, optional): Additional environment variables for Codex CLI
585 | 
586 | 8. **Command-Specific Settings** (optional):
587 |    You can override settings for specific Task Master commands:
588 |    ```json
589 |    {
590 |      "codexCli": {
591 |        "allowNpx": true,
592 |        "approvalMode": "on-failure",
593 |        "commandSpecific": {
594 |          "parse-prd": {
595 |            "approvalMode": "never",
596 |            "verbose": true
597 |          },
598 |          "expand": {
599 |            "sandboxMode": "read-only"
600 |          }
601 |        }
602 |      }
603 |    }
604 |    ```
605 | 
606 | 9. **Codebase Features**:
607 |    The Codex CLI provider is codebase-capable, meaning it can analyze and interact with your project files. Codebase analysis features are automatically enabled when using `codex-cli` as your provider and `enableCodebaseAnalysis` is set to `true` in your global configuration (default).
608 | 
609 | 10. **Setup Commands**:
610 |     ```bash
611 |     # Set Codex CLI for main role
612 |     task-master models --set-main gpt-5-codex --codex-cli
613 | 
614 |     # Set Codex CLI for fallback role
615 |     task-master models --set-fallback gpt-5 --codex-cli
616 | 
617 |     # Verify configuration
618 |     task-master models
619 |     ```
620 | 
621 | 11. **Troubleshooting**:
622 | 
623 |     **"codex: command not found" error:**
624 |     - Install Codex CLI globally: `npm install -g @openai/codex`
625 |     - Verify installation: `codex --version`
626 |     - Alternatively, enable `allowNpx: true` in your codexCli configuration
627 | 
628 |     **"Not logged in" errors:**
629 |     - Run `codex login` to authenticate with your ChatGPT account
630 |     - Verify authentication status: `codex` (opens interactive CLI)
631 | 
632 |     **"Old version" warnings:**
633 |     - Check version: `codex --version`
634 |     - Upgrade: `npm install -g @openai/codex@latest`
635 |     - Minimum version: 0.42.0, recommended: >= 0.44.0
636 | 
637 |     **"Model not available" errors:**
638 |     - Only `gpt-5` and `gpt-5-codex` are available via OAuth subscription
639 |     - Verify your ChatGPT subscription is active
640 |     - For other OpenAI models, use the standard `openai` provider with an API key
641 | 
642 |     **API key not being used:**
643 |     - API key is only injected when explicitly provided
644 |     - OAuth authentication is always preferred
645 |     - If you want to use an API key, ensure `OPENAI_API_KEY` is set in your `.env` file
646 | 
647 | 12. **Important Notes**:
648 |     - OAuth subscription required for model access (no API key needed for basic operation)
649 |     - Limited to OAuth-available models only (`gpt-5` and `gpt-5-codex`)
650 |     - Pricing information is not available for OAuth models (shows as "Unknown" in cost calculations)
651 |     - See [Codex CLI Provider Documentation](./providers/codex-cli.md) for more details
652 | 
```
Page 47/69FirstPrevNextLast