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 |
```