This is page 48 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
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/storage/adapters/api-storage.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview API-based storage implementation using repository pattern
3 | * This provides storage via repository abstraction for flexibility
4 | */
5 |
6 | import type { SupabaseClient } from '@supabase/supabase-js';
7 | import {
8 | ERROR_CODES,
9 | TaskMasterError
10 | } from '../../../common/errors/task-master-error.js';
11 | import type {
12 | IStorage,
13 | LoadTasksOptions,
14 | StorageStats,
15 | UpdateStatusResult
16 | } from '../../../common/interfaces/storage.interface.js';
17 | import { getLogger } from '../../../common/logger/factory.js';
18 | import type {
19 | Task,
20 | TaskMetadata,
21 | TaskStatus,
22 | TaskTag
23 | } from '../../../common/types/index.js';
24 | import { AuthManager } from '../../auth/managers/auth-manager.js';
25 | import { BriefsDomain } from '../../briefs/briefs-domain.js';
26 | import {
27 | type ExpandTaskResult,
28 | TaskExpansionService
29 | } from '../../integration/services/task-expansion.service.js';
30 | import { TaskRetrievalService } from '../../integration/services/task-retrieval.service.js';
31 | import { SupabaseRepository } from '../../tasks/repositories/supabase/index.js';
32 | import type { TaskRepository } from '../../tasks/repositories/task-repository.interface.js';
33 | import { ApiClient } from '../utils/api-client.js';
34 |
35 | /**
36 | * API storage configuration
37 | */
38 | export interface ApiStorageConfig {
39 | /** Supabase client instance */
40 | supabaseClient?: SupabaseClient;
41 | /** Custom repository implementation */
42 | repository?: TaskRepository;
43 | /** Project ID for scoping */
44 | projectId: string;
45 | /** Enable request retries */
46 | enableRetry?: boolean;
47 | /** Maximum retry attempts */
48 | maxRetries?: number;
49 | }
50 |
51 | /**
52 | * Response from the update task with prompt API endpoint
53 | */
54 | interface UpdateTaskWithPromptResponse {
55 | success: boolean;
56 | task: {
57 | id: string;
58 | displayId: string | null;
59 | title: string;
60 | description: string | null;
61 | status: string;
62 | priority: string | null;
63 | };
64 | message: string;
65 | }
66 |
67 | /**
68 | * ApiStorage implementation using repository pattern
69 | * Provides flexibility to swap between different backend implementations
70 | */
71 | export class ApiStorage implements IStorage {
72 | private readonly repository: TaskRepository;
73 | private readonly projectId: string;
74 | private readonly enableRetry: boolean;
75 | private readonly maxRetries: number;
76 | private initialized = false;
77 | private tagsCache: Map<string, TaskTag> = new Map();
78 | private apiClient?: ApiClient;
79 | private expansionService?: TaskExpansionService;
80 | private retrievalService?: TaskRetrievalService;
81 | private readonly logger = getLogger('ApiStorage');
82 |
83 | constructor(config: ApiStorageConfig) {
84 | this.validateConfig(config);
85 |
86 | // Use provided repository or create Supabase repository
87 | if (config.repository) {
88 | this.repository = config.repository;
89 | } else if (config.supabaseClient) {
90 | // TODO: SupabaseRepository doesn't implement all TaskRepository methods yet
91 | // Cast for now until full implementation is complete
92 | this.repository = new SupabaseRepository(
93 | config.supabaseClient
94 | ) as unknown as TaskRepository;
95 | } else {
96 | throw new TaskMasterError(
97 | 'Either repository or supabaseClient must be provided',
98 | ERROR_CODES.MISSING_CONFIGURATION
99 | );
100 | }
101 |
102 | this.projectId = config.projectId;
103 | this.enableRetry = config.enableRetry ?? true;
104 | this.maxRetries = config.maxRetries ?? 3;
105 | }
106 |
107 | /**
108 | * Validate API storage configuration
109 | */
110 | private validateConfig(config: ApiStorageConfig): void {
111 | if (!config.projectId) {
112 | throw new TaskMasterError(
113 | 'Project ID is required for API storage',
114 | ERROR_CODES.MISSING_CONFIGURATION
115 | );
116 | }
117 |
118 | if (!config.repository && !config.supabaseClient) {
119 | throw new TaskMasterError(
120 | 'Either repository or supabaseClient must be provided',
121 | ERROR_CODES.MISSING_CONFIGURATION
122 | );
123 | }
124 | }
125 |
126 | /**
127 | * Initialize the API storage
128 | */
129 | async initialize(): Promise<void> {
130 | if (this.initialized) return;
131 |
132 | try {
133 | // Load initial tags
134 | await this.loadTagsIntoCache();
135 | this.initialized = true;
136 | } catch (error) {
137 | throw new TaskMasterError(
138 | 'Failed to initialize API storage',
139 | ERROR_CODES.STORAGE_ERROR,
140 | { operation: 'initialize' },
141 | error as Error
142 | );
143 | }
144 | }
145 |
146 | /**
147 | * Get the storage type
148 | */
149 | getStorageType(): 'api' {
150 | return 'api';
151 | }
152 |
153 | /**
154 | * Get the current brief name
155 | * @returns The brief name if a brief is selected, null otherwise
156 | */
157 | getCurrentBriefName(): string | null {
158 | const authManager = AuthManager.getInstance();
159 | const context = authManager.getContext();
160 | return context?.briefName || null;
161 | }
162 |
163 | /**
164 | * Get all briefs (tags) with detailed statistics including task counts
165 | * In API storage, tags are called "briefs"
166 | * Delegates to BriefsDomain for brief statistics calculation
167 | */
168 | async getTagsWithStats(): Promise<{
169 | tags: Array<{
170 | name: string;
171 | isCurrent: boolean;
172 | taskCount: number;
173 | completedTasks: number;
174 | statusBreakdown: Record<string, number>;
175 | subtaskCounts?: {
176 | totalSubtasks: number;
177 | subtasksByStatus: Record<string, number>;
178 | };
179 | created?: string;
180 | description?: string;
181 | status?: string;
182 | briefId?: string;
183 | }>;
184 | currentTag: string | null;
185 | totalTags: number;
186 | }> {
187 | await this.ensureInitialized();
188 |
189 | try {
190 | // Delegate to BriefsDomain which owns brief operations
191 | const briefsDomain = new BriefsDomain();
192 | return await briefsDomain.getBriefsWithStats(
193 | this.repository,
194 | this.projectId
195 | );
196 | } catch (error) {
197 | throw new TaskMasterError(
198 | 'Failed to get tags with stats from API',
199 | ERROR_CODES.STORAGE_ERROR,
200 | { operation: 'getTagsWithStats' },
201 | error as Error
202 | );
203 | }
204 | }
205 |
206 | /**
207 | * Load tags into cache
208 | * In our API-based system, "tags" represent briefs
209 | */
210 | private async loadTagsIntoCache(): Promise<void> {
211 | try {
212 | const authManager = AuthManager.getInstance();
213 | const context = authManager.getContext();
214 |
215 | // If we have a selected brief, create a virtual "tag" for it
216 | if (context?.briefId) {
217 | // Create a virtual tag representing the current brief
218 | const briefTag: TaskTag = {
219 | name: context.briefId,
220 | tasks: [], // Will be populated when tasks are loaded
221 | metadata: {
222 | briefId: context.briefId,
223 | briefName: context.briefName,
224 | organizationId: context.orgId
225 | }
226 | };
227 |
228 | this.tagsCache.clear();
229 | this.tagsCache.set(context.briefId, briefTag);
230 | }
231 | } catch (error) {
232 | // If no brief is selected, that's okay - user needs to select one first
233 | console.debug('No brief selected, starting with empty cache');
234 | }
235 | }
236 |
237 | /**
238 | * Load tasks from API
239 | * In our system, the tag parameter represents a brief ID
240 | */
241 | async loadTasks(tag?: string, options?: LoadTasksOptions): Promise<Task[]> {
242 | await this.ensureInitialized();
243 |
244 | try {
245 | const context =
246 | AuthManager.getInstance().ensureBriefSelected('loadTasks');
247 |
248 | // Load tasks from the current brief context with filters pushed to repository
249 | const tasks = await this.retryOperation(() =>
250 | this.repository.getTasks(this.projectId, options)
251 | );
252 |
253 | // Update the tag cache with the loaded task IDs
254 | const briefTag = this.tagsCache.get(context.briefId);
255 | if (briefTag) {
256 | briefTag.tasks = tasks.map((task) => task.id);
257 | }
258 |
259 | return tasks;
260 | } catch (error) {
261 | this.wrapError(error, 'Failed to load tasks from API', {
262 | operation: 'loadTasks',
263 | tag,
264 | context: 'brief-based loading'
265 | });
266 | }
267 | }
268 |
269 | /**
270 | * Save tasks to API
271 | */
272 | async saveTasks(tasks: Task[], tag?: string): Promise<void> {
273 | await this.ensureInitialized();
274 |
275 | try {
276 | if (tag) {
277 | // Update tag with task IDs
278 | const tagData = this.tagsCache.get(tag) || {
279 | name: tag,
280 | tasks: [],
281 | metadata: {}
282 | };
283 | tagData.tasks = tasks.map((t) => t.id);
284 |
285 | // Save or update tag
286 | if (this.tagsCache.has(tag)) {
287 | await this.repository.updateTag(this.projectId, tag, tagData);
288 | } else {
289 | await this.repository.createTag(this.projectId, tagData);
290 | }
291 |
292 | this.tagsCache.set(tag, tagData);
293 | }
294 |
295 | // Save tasks using bulk operation
296 | await this.retryOperation(() =>
297 | this.repository.bulkCreateTasks(this.projectId, tasks)
298 | );
299 | } catch (error) {
300 | throw new TaskMasterError(
301 | 'Failed to save tasks to API',
302 | ERROR_CODES.STORAGE_ERROR,
303 | { operation: 'saveTasks', tag, taskCount: tasks.length },
304 | error as Error
305 | );
306 | }
307 | }
308 |
309 | /**
310 | * Load a single task by ID (supports UUID or display ID like HAM-123)
311 | */
312 | async loadTask(taskId: string, tag?: string): Promise<Task | null> {
313 | await this.ensureInitialized();
314 |
315 | try {
316 | const retrievalService = this.getRetrievalService();
317 | return await this.retryOperation(() => retrievalService.getTask(taskId));
318 | } catch (error) {
319 | this.wrapError(error, 'Failed to load task from API', {
320 | operation: 'loadTask',
321 | taskId,
322 | tag
323 | });
324 | }
325 | }
326 |
327 | /**
328 | * Save a single task
329 | */
330 | async saveTask(task: Task, tag?: string): Promise<void> {
331 | await this.ensureInitialized();
332 |
333 | try {
334 | // Check if task exists
335 | const existing = await this.repository.getTask(this.projectId, task.id);
336 |
337 | if (existing) {
338 | await this.retryOperation(() =>
339 | this.repository.updateTask(this.projectId, task.id, task)
340 | );
341 | } else {
342 | await this.retryOperation(() =>
343 | this.repository.createTask(this.projectId, task)
344 | );
345 | }
346 |
347 | // Update tag if specified
348 | if (tag) {
349 | const tagData = this.tagsCache.get(tag);
350 | if (tagData && !tagData.tasks.includes(task.id)) {
351 | tagData.tasks.push(task.id);
352 | await this.repository.updateTag(this.projectId, tag, tagData);
353 | }
354 | }
355 | } catch (error) {
356 | throw new TaskMasterError(
357 | 'Failed to save task to API',
358 | ERROR_CODES.STORAGE_ERROR,
359 | { operation: 'saveTask', taskId: task.id, tag },
360 | error as Error
361 | );
362 | }
363 | }
364 |
365 | /**
366 | * Delete a task
367 | */
368 | async deleteTask(taskId: string, tag?: string): Promise<void> {
369 | await this.ensureInitialized();
370 |
371 | try {
372 | await this.retryOperation(() =>
373 | this.repository.deleteTask(this.projectId, taskId)
374 | );
375 |
376 | // Remove from tag if specified
377 | if (tag) {
378 | const tagData = this.tagsCache.get(tag);
379 | if (tagData) {
380 | tagData.tasks = tagData.tasks.filter((id) => id !== taskId);
381 | await this.repository.updateTag(this.projectId, tag, tagData);
382 | }
383 | }
384 | } catch (error) {
385 | throw new TaskMasterError(
386 | 'Failed to delete task from API',
387 | ERROR_CODES.STORAGE_ERROR,
388 | { operation: 'deleteTask', taskId, tag },
389 | error as Error
390 | );
391 | }
392 | }
393 |
394 | /**
395 | * List available tags (briefs in our system)
396 | */
397 | async listTags(): Promise<string[]> {
398 | await this.ensureInitialized();
399 |
400 | try {
401 | const authManager = AuthManager.getInstance();
402 | const context = authManager.getContext();
403 |
404 | // In our API-based system, we only have one "tag" at a time - the current brief
405 | if (context?.briefId) {
406 | // Ensure the current brief is in our cache
407 | await this.loadTagsIntoCache();
408 | return [context.briefId];
409 | }
410 |
411 | // No brief selected, return empty array
412 | return [];
413 | } catch (error) {
414 | throw new TaskMasterError(
415 | 'Failed to list tags from API',
416 | ERROR_CODES.STORAGE_ERROR,
417 | { operation: 'listTags' },
418 | error as Error
419 | );
420 | }
421 | }
422 |
423 | /**
424 | * Load metadata
425 | */
426 | async loadMetadata(tag?: string): Promise<TaskMetadata | null> {
427 | await this.ensureInitialized();
428 |
429 | try {
430 | if (tag) {
431 | const tagData = this.tagsCache.get(tag);
432 | return (tagData?.metadata as TaskMetadata) || null;
433 | }
434 |
435 | // Return global metadata if no tag specified
436 | // This could be stored in a special system tag
437 | const systemTag = await this.repository.getTag(this.projectId, '_system');
438 | return (systemTag?.metadata as TaskMetadata) || null;
439 | } catch (error) {
440 | throw new TaskMasterError(
441 | 'Failed to load metadata from API',
442 | ERROR_CODES.STORAGE_ERROR,
443 | { operation: 'loadMetadata', tag },
444 | error as Error
445 | );
446 | }
447 | }
448 |
449 | /**
450 | * Save metadata
451 | */
452 | async saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void> {
453 | await this.ensureInitialized();
454 |
455 | try {
456 | if (tag) {
457 | const tagData = this.tagsCache.get(tag) || {
458 | name: tag,
459 | tasks: [],
460 | metadata: {}
461 | };
462 | tagData.metadata = metadata as any;
463 |
464 | if (this.tagsCache.has(tag)) {
465 | await this.repository.updateTag(this.projectId, tag, tagData);
466 | } else {
467 | await this.repository.createTag(this.projectId, tagData);
468 | }
469 |
470 | this.tagsCache.set(tag, tagData);
471 | } else {
472 | // Save to system tag
473 | const systemTag: TaskTag = {
474 | name: '_system',
475 | tasks: [],
476 | metadata: metadata as any
477 | };
478 |
479 | const existing = await this.repository.getTag(
480 | this.projectId,
481 | '_system'
482 | );
483 | if (existing) {
484 | await this.repository.updateTag(this.projectId, '_system', systemTag);
485 | } else {
486 | await this.repository.createTag(this.projectId, systemTag);
487 | }
488 | }
489 | } catch (error) {
490 | throw new TaskMasterError(
491 | 'Failed to save metadata to API',
492 | ERROR_CODES.STORAGE_ERROR,
493 | { operation: 'saveMetadata', tag },
494 | error as Error
495 | );
496 | }
497 | }
498 |
499 | /**
500 | * Check if storage exists
501 | */
502 | async exists(): Promise<boolean> {
503 | try {
504 | await this.initialize();
505 | return true;
506 | } catch {
507 | return false;
508 | }
509 | }
510 |
511 | /**
512 | * Append tasks to existing storage
513 | */
514 | async appendTasks(tasks: Task[], tag?: string): Promise<void> {
515 | await this.ensureInitialized();
516 |
517 | try {
518 | // Use bulk create - repository should handle duplicates
519 | await this.retryOperation(() =>
520 | this.repository.bulkCreateTasks(this.projectId, tasks)
521 | );
522 |
523 | // Update tag if specified
524 | if (tag) {
525 | const tagData = this.tagsCache.get(tag) || {
526 | name: tag,
527 | tasks: [],
528 | metadata: {}
529 | };
530 |
531 | const newTaskIds = tasks.map((t) => t.id);
532 | tagData.tasks = [...new Set([...tagData.tasks, ...newTaskIds])];
533 |
534 | if (this.tagsCache.has(tag)) {
535 | await this.repository.updateTag(this.projectId, tag, tagData);
536 | } else {
537 | await this.repository.createTag(this.projectId, tagData);
538 | }
539 |
540 | this.tagsCache.set(tag, tagData);
541 | }
542 | } catch (error) {
543 | throw new TaskMasterError(
544 | 'Failed to append tasks to API',
545 | ERROR_CODES.STORAGE_ERROR,
546 | { operation: 'appendTasks', tag, taskCount: tasks.length },
547 | error as Error
548 | );
549 | }
550 | }
551 |
552 | /**
553 | * Update a specific task
554 | */
555 | async updateTask(
556 | taskId: string,
557 | updates: Partial<Task>,
558 | tag?: string
559 | ): Promise<void> {
560 | await this.ensureInitialized();
561 |
562 | try {
563 | await this.retryOperation(() =>
564 | this.repository.updateTask(this.projectId, taskId, updates)
565 | );
566 | } catch (error) {
567 | throw new TaskMasterError(
568 | 'Failed to update task via API',
569 | ERROR_CODES.STORAGE_ERROR,
570 | { operation: 'updateTask', taskId, tag },
571 | error as Error
572 | );
573 | }
574 | }
575 |
576 | /**
577 | * Update task with AI-powered prompt
578 | * Sends prompt to backend for server-side AI processing
579 | */
580 | async updateTaskWithPrompt(
581 | taskId: string,
582 | prompt: string,
583 | tag?: string,
584 | options?: { useResearch?: boolean; mode?: 'append' | 'update' | 'rewrite' }
585 | ): Promise<void> {
586 | await this.ensureInitialized();
587 |
588 | const mode = options?.mode ?? 'append';
589 |
590 | try {
591 | // Use the API client - all auth, error handling, etc. is centralized
592 | const apiClient = this.getApiClient();
593 |
594 | const result = await apiClient.patch<UpdateTaskWithPromptResponse>(
595 | `/ai/api/v1/tasks/${taskId}/prompt`,
596 | { prompt, mode }
597 | );
598 |
599 | if (!result.success) {
600 | // API returned success: false
601 | throw new Error(
602 | result.message ||
603 | `Update failed for task ${taskId}. The server did not provide details.`
604 | );
605 | }
606 |
607 | // Log success with task details
608 | this.logger.info(
609 | `Successfully updated task ${result.task.displayId || result.task.id} using AI prompt (mode: ${mode})`
610 | );
611 | this.logger.info(` Title: ${result.task.title}`);
612 | this.logger.info(` Status: ${result.task.status}`);
613 | if (result.message) {
614 | this.logger.info(` ${result.message}`);
615 | }
616 | } catch (error) {
617 | // If it's already a TaskMasterError, just add context and re-throw
618 | if (error instanceof TaskMasterError) {
619 | throw error.withContext({
620 | operation: 'updateTaskWithPrompt',
621 | taskId,
622 | tag,
623 | promptLength: prompt.length,
624 | mode
625 | });
626 | }
627 |
628 | // For other errors, wrap them
629 | const errorMessage =
630 | error instanceof Error ? error.message : String(error);
631 | throw new TaskMasterError(
632 | errorMessage,
633 | ERROR_CODES.STORAGE_ERROR,
634 | {
635 | operation: 'updateTaskWithPrompt',
636 | taskId,
637 | tag,
638 | promptLength: prompt.length,
639 | mode
640 | },
641 | error as Error
642 | );
643 | }
644 | }
645 |
646 | /**
647 | * Expand task into subtasks with AI-powered generation
648 | * Sends task to backend for server-side AI processing
649 | */
650 | async expandTaskWithPrompt(
651 | taskId: string,
652 | _tag?: string,
653 | options?: {
654 | numSubtasks?: number;
655 | useResearch?: boolean;
656 | additionalContext?: string;
657 | force?: boolean;
658 | }
659 | ): Promise<ExpandTaskResult> {
660 | await this.ensureInitialized();
661 |
662 | const expansionService = this.getExpansionService();
663 | return await expansionService.expandTask(taskId, options);
664 | }
665 |
666 | /**
667 | * Update task or subtask status by ID - for API storage
668 | */
669 | async updateTaskStatus(
670 | taskId: string,
671 | newStatus: TaskStatus,
672 | tag?: string
673 | ): Promise<UpdateStatusResult> {
674 | await this.ensureInitialized();
675 |
676 | try {
677 | AuthManager.getInstance().ensureBriefSelected('updateTaskStatus');
678 |
679 | const existingTask = await this.retryOperation(() =>
680 | this.repository.getTask(this.projectId, taskId)
681 | );
682 |
683 | if (!existingTask) {
684 | throw new Error(`Task ${taskId} not found`);
685 | }
686 |
687 | const oldStatus = existingTask.status;
688 | if (oldStatus === newStatus) {
689 | return {
690 | success: true,
691 | oldStatus,
692 | newStatus,
693 | taskId
694 | };
695 | }
696 |
697 | // Update the task/subtask status
698 | await this.retryOperation(() =>
699 | this.repository.updateTask(this.projectId, taskId, {
700 | status: newStatus,
701 | updatedAt: new Date().toISOString()
702 | })
703 | );
704 |
705 | // Note: Parent status auto-adjustment is handled by the backend API service
706 | // which has its own business logic for managing task relationships
707 |
708 | return {
709 | success: true,
710 | oldStatus,
711 | newStatus,
712 | taskId
713 | };
714 | } catch (error) {
715 | this.wrapError(error, 'Failed to update task status via API', {
716 | operation: 'updateTaskStatus',
717 | taskId,
718 | newStatus,
719 | tag
720 | });
721 | }
722 | }
723 |
724 | /**
725 | * Get all available tags
726 | */
727 | async getAllTags(): Promise<string[]> {
728 | return this.listTags();
729 | }
730 |
731 | /**
732 | * Create a new tag (brief)
733 | * Not supported with API storage - users must create briefs via web interface
734 | */
735 | async createTag(
736 | tagName: string,
737 | _options?: { copyFrom?: string; description?: string }
738 | ): Promise<void> {
739 | throw new TaskMasterError(
740 | 'Tag creation is not supported with API storage. Please create briefs through Hamster Studio.',
741 | ERROR_CODES.NOT_IMPLEMENTED,
742 | { storageType: 'api', operation: 'createTag', tagName }
743 | );
744 | }
745 |
746 | /**
747 | * Delete all tasks for a tag
748 | */
749 | async deleteTag(tag: string): Promise<void> {
750 | await this.ensureInitialized();
751 |
752 | try {
753 | await this.retryOperation(() =>
754 | this.repository.deleteTag(this.projectId, tag)
755 | );
756 |
757 | this.tagsCache.delete(tag);
758 | } catch (error) {
759 | throw new TaskMasterError(
760 | 'Failed to delete tag via API',
761 | ERROR_CODES.STORAGE_ERROR,
762 | { operation: 'deleteTag', tag },
763 | error as Error
764 | );
765 | }
766 | }
767 |
768 | /**
769 | * Rename a tag
770 | */
771 | async renameTag(oldTag: string, newTag: string): Promise<void> {
772 | await this.ensureInitialized();
773 |
774 | try {
775 | const tagData = this.tagsCache.get(oldTag);
776 | if (!tagData) {
777 | throw new Error(`Tag ${oldTag} not found`);
778 | }
779 |
780 | // Create new tag with same data
781 | const newTagData = { ...tagData, name: newTag };
782 | await this.repository.createTag(this.projectId, newTagData);
783 |
784 | // Delete old tag
785 | await this.repository.deleteTag(this.projectId, oldTag);
786 |
787 | // Update cache
788 | this.tagsCache.delete(oldTag);
789 | this.tagsCache.set(newTag, newTagData);
790 | } catch (error) {
791 | throw new TaskMasterError(
792 | 'Failed to rename tag via API',
793 | ERROR_CODES.STORAGE_ERROR,
794 | { operation: 'renameTag', oldTag, newTag },
795 | error as Error
796 | );
797 | }
798 | }
799 |
800 | /**
801 | * Copy a tag
802 | */
803 | async copyTag(sourceTag: string, targetTag: string): Promise<void> {
804 | await this.ensureInitialized();
805 |
806 | try {
807 | const sourceData = this.tagsCache.get(sourceTag);
808 | if (!sourceData) {
809 | throw new Error(`Source tag ${sourceTag} not found`);
810 | }
811 |
812 | // Create new tag with copied data
813 | const targetData = { ...sourceData, name: targetTag };
814 | await this.repository.createTag(this.projectId, targetData);
815 |
816 | // Update cache
817 | this.tagsCache.set(targetTag, targetData);
818 | } catch (error) {
819 | throw new TaskMasterError(
820 | 'Failed to copy tag via API',
821 | ERROR_CODES.STORAGE_ERROR,
822 | { operation: 'copyTag', sourceTag, targetTag },
823 | error as Error
824 | );
825 | }
826 | }
827 |
828 | /**
829 | * Get storage statistics
830 | */
831 | async getStats(): Promise<StorageStats> {
832 | await this.ensureInitialized();
833 |
834 | try {
835 | const tasks = await this.repository.getTasks(this.projectId);
836 | const tags = await this.repository.getTags(this.projectId);
837 |
838 | const tagStats = tags.map((tag) => ({
839 | tag: tag.name,
840 | taskCount: tag.tasks.length,
841 | lastModified: new Date().toISOString() // TODO: Get actual last modified from tag data
842 | }));
843 |
844 | return {
845 | totalTasks: tasks.length,
846 | totalTags: tags.length,
847 | storageSize: 0, // Not applicable for API storage
848 | lastModified: new Date().toISOString(),
849 | tagStats
850 | };
851 | } catch (error) {
852 | throw new TaskMasterError(
853 | 'Failed to get stats from API',
854 | ERROR_CODES.STORAGE_ERROR,
855 | { operation: 'getStats' },
856 | error as Error
857 | );
858 | }
859 | }
860 |
861 | /**
862 | * Create backup
863 | */
864 | async backup(): Promise<string> {
865 | await this.ensureInitialized();
866 |
867 | try {
868 | // Export all data
869 | await this.repository.getTasks(this.projectId);
870 | await this.repository.getTags(this.projectId);
871 |
872 | // TODO: In a real implementation, this would:
873 | // 1. Create backup data structure with tasks and tags
874 | // 2. Save the backup to a storage service
875 | // For now, return a backup identifier
876 | return `backup-${this.projectId}-${Date.now()}`;
877 | } catch (error) {
878 | throw new TaskMasterError(
879 | 'Failed to create backup via API',
880 | ERROR_CODES.STORAGE_ERROR,
881 | { operation: 'backup' },
882 | error as Error
883 | );
884 | }
885 | }
886 |
887 | /**
888 | * Restore from backup
889 | */
890 | async restore(backupId: string): Promise<void> {
891 | await this.ensureInitialized();
892 |
893 | // This would restore from a backup service
894 | // Implementation depends on backup strategy
895 | throw new TaskMasterError(
896 | 'Restore not implemented for API storage',
897 | ERROR_CODES.NOT_IMPLEMENTED,
898 | { operation: 'restore', backupId }
899 | );
900 | }
901 |
902 | /**
903 | * Clear all data
904 | */
905 | async clear(): Promise<void> {
906 | await this.ensureInitialized();
907 |
908 | try {
909 | // Delete all tasks
910 | const tasks = await this.repository.getTasks(this.projectId);
911 | if (tasks.length > 0) {
912 | await this.repository.bulkDeleteTasks(
913 | this.projectId,
914 | tasks.map((t) => t.id)
915 | );
916 | }
917 |
918 | // Delete all tags
919 | const tags = await this.repository.getTags(this.projectId);
920 | for (const tag of tags) {
921 | await this.repository.deleteTag(this.projectId, tag.name);
922 | }
923 |
924 | // Clear cache
925 | this.tagsCache.clear();
926 | } catch (error) {
927 | throw new TaskMasterError(
928 | 'Failed to clear data via API',
929 | ERROR_CODES.STORAGE_ERROR,
930 | { operation: 'clear' },
931 | error as Error
932 | );
933 | }
934 | }
935 |
936 | /**
937 | * Close connection
938 | */
939 | async close(): Promise<void> {
940 | this.initialized = false;
941 | this.tagsCache.clear();
942 | }
943 |
944 | /**
945 | * Ensure storage is initialized
946 | */
947 | private async ensureInitialized(): Promise<void> {
948 | if (!this.initialized) {
949 | await this.initialize();
950 | }
951 | }
952 |
953 | /**
954 | * Get or create API client instance with auth
955 | */
956 | private getApiClient(): ApiClient {
957 | if (!this.apiClient) {
958 | const apiEndpoint =
959 | process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN;
960 |
961 | if (!apiEndpoint) {
962 | throw new TaskMasterError(
963 | 'API endpoint not configured. Please set TM_PUBLIC_BASE_DOMAIN environment variable.',
964 | ERROR_CODES.MISSING_CONFIGURATION,
965 | { operation: 'getApiClient' }
966 | );
967 | }
968 |
969 | const context =
970 | AuthManager.getInstance().ensureBriefSelected('getApiClient');
971 | const authManager = AuthManager.getInstance();
972 |
973 | this.apiClient = new ApiClient({
974 | baseUrl: apiEndpoint,
975 | authManager,
976 | accountId: context.orgId
977 | });
978 | }
979 |
980 | return this.apiClient;
981 | }
982 |
983 | /**
984 | * Get or create TaskExpansionService instance
985 | */
986 | private getExpansionService(): TaskExpansionService {
987 | if (!this.expansionService) {
988 | const apiClient = this.getApiClient();
989 | const authManager = AuthManager.getInstance();
990 |
991 | this.expansionService = new TaskExpansionService(
992 | this.repository,
993 | this.projectId,
994 | apiClient,
995 | authManager
996 | );
997 | }
998 |
999 | return this.expansionService;
1000 | }
1001 |
1002 | /**
1003 | * Get or create TaskRetrievalService instance
1004 | */
1005 | private getRetrievalService(): TaskRetrievalService {
1006 | if (!this.retrievalService) {
1007 | const apiClient = this.getApiClient();
1008 | const authManager = AuthManager.getInstance();
1009 |
1010 | this.retrievalService = new TaskRetrievalService(
1011 | this.repository,
1012 | this.projectId,
1013 | apiClient,
1014 | authManager
1015 | );
1016 | }
1017 |
1018 | return this.retrievalService;
1019 | }
1020 |
1021 | /**
1022 | * Retry an operation with exponential backoff
1023 | */
1024 | private async retryOperation<T>(
1025 | operation: () => Promise<T>,
1026 | attempt = 1
1027 | ): Promise<T> {
1028 | try {
1029 | return await operation();
1030 | } catch (error) {
1031 | if (this.enableRetry && attempt < this.maxRetries) {
1032 | const delay = Math.pow(2, attempt) * 1000;
1033 | await new Promise((resolve) => setTimeout(resolve, delay));
1034 | return this.retryOperation(operation, attempt + 1);
1035 | }
1036 | throw error;
1037 | }
1038 | }
1039 |
1040 | /**
1041 | * Wrap an error unless it's already a NO_BRIEF_SELECTED error
1042 | */
1043 | private wrapError(
1044 | error: unknown,
1045 | message: string,
1046 | context: Record<string, unknown>
1047 | ): never {
1048 | // If it's already a NO_BRIEF_SELECTED error, don't wrap it
1049 | if (
1050 | error instanceof TaskMasterError &&
1051 | error.is(ERROR_CODES.NO_BRIEF_SELECTED)
1052 | ) {
1053 | throw error;
1054 | }
1055 |
1056 | throw new TaskMasterError(
1057 | message,
1058 | ERROR_CODES.STORAGE_ERROR,
1059 | context,
1060 | error as Error
1061 | );
1062 | }
1063 | }
1064 |
```
--------------------------------------------------------------------------------
/docs/models.md:
--------------------------------------------------------------------------------
```markdown
1 | # Available Models as of November 18, 2025
2 |
3 | ## Main Models
4 |
5 | | Provider | Model Name | SWE Score | Input Cost | Output Cost |
6 | | ----------- | ---------------------------------------------- | --------- | ---------- | ----------- |
7 | | anthropic | claude-sonnet-4-20250514 | 0.727 | 3 | 15 |
8 | | anthropic | claude-opus-4-20250514 | 0.725 | 15 | 75 |
9 | | anthropic | claude-3-7-sonnet-20250219 | 0.623 | 3 | 15 |
10 | | anthropic | claude-3-5-sonnet-20241022 | 0.49 | 3 | 15 |
11 | | anthropic | claude-sonnet-4-5-20250929 | 0.73 | 3 | 15 |
12 | | anthropic | claude-haiku-4-5-20251001 | 0.45 | 1 | 5 |
13 | | claude-code | opus | 0.725 | 0 | 0 |
14 | | claude-code | sonnet | 0.727 | 0 | 0 |
15 | | claude-code | haiku | 0.45 | 0 | 0 |
16 | | codex-cli | gpt-5 | 0.749 | 0 | 0 |
17 | | codex-cli | gpt-5-codex | 0.749 | 0 | 0 |
18 | | mcp | mcp-sampling | — | 0 | 0 |
19 | | gemini-cli | gemini-3-pro-preview | 0.762 | 0 | 0 |
20 | | gemini-cli | gemini-2.5-pro | 0.72 | 0 | 0 |
21 | | gemini-cli | gemini-2.5-flash | 0.71 | 0 | 0 |
22 | | grok-cli | grok-4-latest | 0.7 | 0 | 0 |
23 | | grok-cli | grok-3-latest | 0.65 | 0 | 0 |
24 | | grok-cli | grok-3-fast | 0.6 | 0 | 0 |
25 | | grok-cli | grok-3-mini-fast | 0.55 | 0 | 0 |
26 | | openai | gpt-4o | 0.332 | 2.5 | 10 |
27 | | openai | o1 | 0.489 | 15 | 60 |
28 | | openai | o3 | 0.5 | 2 | 8 |
29 | | openai | o3-mini | 0.493 | 1.1 | 4.4 |
30 | | openai | o4-mini | 0.45 | 1.1 | 4.4 |
31 | | openai | o1-mini | 0.4 | 1.1 | 4.4 |
32 | | openai | o1-pro | — | 150 | 600 |
33 | | openai | gpt-4-5-preview | 0.38 | 75 | 150 |
34 | | openai | gpt-4-1-mini | — | 0.4 | 1.6 |
35 | | openai | gpt-4-1-nano | — | 0.1 | 0.4 |
36 | | openai | gpt-4o-mini | 0.3 | 0.15 | 0.6 |
37 | | openai | gpt-5 | 0.749 | 5 | 20 |
38 | | google | gemini-3-pro-preview | 0.762 | 2 | 12 |
39 | | google | gemini-2.5-pro-preview-05-06 | 0.638 | — | — |
40 | | google | gemini-2.5-pro-preview-03-25 | 0.638 | — | — |
41 | | google | gemini-2.5-flash-preview-04-17 | 0.604 | — | — |
42 | | google | gemini-2.0-flash | 0.518 | 0.15 | 0.6 |
43 | | google | gemini-2.0-flash-lite | — | — | — |
44 | | xai | grok-3 | — | 3 | 15 |
45 | | xai | grok-3-fast | — | 5 | 25 |
46 | | xai | grok-4 | — | 3 | 15 |
47 | | groq | moonshotai/kimi-k2-instruct | 0.66 | 1 | 3 |
48 | | groq | llama-3.3-70b-versatile | 0.55 | 0.59 | 0.79 |
49 | | groq | llama-3.1-8b-instant | 0.32 | 0.05 | 0.08 |
50 | | groq | llama-4-scout | 0.45 | 0.11 | 0.34 |
51 | | groq | llama-4-maverick | 0.52 | 0.5 | 0.77 |
52 | | groq | mixtral-8x7b-32768 | 0.35 | 0.24 | 0.24 |
53 | | groq | qwen-qwq-32b-preview | 0.4 | 0.18 | 0.18 |
54 | | groq | deepseek-r1-distill-llama-70b | 0.52 | 0.75 | 0.99 |
55 | | groq | gemma2-9b-it | 0.3 | 0.2 | 0.2 |
56 | | groq | whisper-large-v3 | — | 0.11 | 0 |
57 | | perplexity | sonar-pro | — | 3 | 15 |
58 | | perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
59 | | perplexity | sonar-reasoning | 0.211 | 1 | 5 |
60 | | openrouter | google/gemini-2.5-flash-preview-05-20 | — | 0.15 | 0.6 |
61 | | openrouter | google/gemini-2.5-flash-preview-05-20:thinking | — | 0.15 | 3.5 |
62 | | openrouter | google/gemini-2.5-pro-exp-03-25 | — | 0 | 0 |
63 | | openrouter | deepseek/deepseek-chat-v3-0324 | — | 0.27 | 1.1 |
64 | | openrouter | openai/gpt-4.1 | — | 2 | 8 |
65 | | openrouter | openai/gpt-4.1-mini | — | 0.4 | 1.6 |
66 | | openrouter | openai/gpt-4.1-nano | — | 0.1 | 0.4 |
67 | | openrouter | openai/o3 | — | 10 | 40 |
68 | | openrouter | openai/codex-mini | — | 1.5 | 6 |
69 | | openrouter | openai/gpt-4o-mini | — | 0.15 | 0.6 |
70 | | openrouter | openai/o4-mini | 0.45 | 1.1 | 4.4 |
71 | | openrouter | openai/o4-mini-high | — | 1.1 | 4.4 |
72 | | openrouter | openai/o1-pro | — | 150 | 600 |
73 | | openrouter | meta-llama/llama-3.3-70b-instruct | — | 120 | 600 |
74 | | openrouter | meta-llama/llama-4-maverick | — | 0.18 | 0.6 |
75 | | openrouter | meta-llama/llama-4-scout | — | 0.08 | 0.3 |
76 | | openrouter | qwen/qwen-max | — | 1.6 | 6.4 |
77 | | openrouter | qwen/qwen-turbo | — | 0.05 | 0.2 |
78 | | openrouter | qwen/qwen3-235b-a22b | — | 0.14 | 2 |
79 | | openrouter | mistralai/mistral-small-3.1-24b-instruct | — | 0.1 | 0.3 |
80 | | openrouter | mistralai/devstral-small | — | 0.1 | 0.3 |
81 | | openrouter | mistralai/mistral-nemo | — | 0.03 | 0.07 |
82 | | zai | glm-4.6 | 0.68 | 0.6 | 2.2 |
83 | | zai | glm-4.5 | 0.65 | 0.6 | 2.2 |
84 | | zai | glm-4.5-air | 0.62 | 0.2 | 1.1 |
85 | | zai-coding | glm-4.6 | 0.68 | 0 | 0 |
86 | | zai-coding | glm-4.5 | 0.65 | 0 | 0 |
87 | | zai-coding | glm-4.5-air | 0.62 | 0 | 0 |
88 | | ollama | gpt-oss:latest | 0.607 | 0 | 0 |
89 | | ollama | gpt-oss:20b | 0.607 | 0 | 0 |
90 | | ollama | gpt-oss:120b | 0.624 | 0 | 0 |
91 | | ollama | devstral:latest | — | 0 | 0 |
92 | | ollama | qwen3:latest | — | 0 | 0 |
93 | | ollama | qwen3:14b | — | 0 | 0 |
94 | | ollama | qwen3:32b | — | 0 | 0 |
95 | | ollama | mistral-small3.1:latest | — | 0 | 0 |
96 | | ollama | llama3.3:latest | — | 0 | 0 |
97 | | ollama | phi4:latest | — | 0 | 0 |
98 | | azure | gpt-4o | 0.332 | 2.5 | 10 |
99 | | azure | gpt-4o-mini | 0.3 | 0.15 | 0.6 |
100 | | azure | gpt-4-1 | — | 2 | 10 |
101 | | bedrock | us.anthropic.claude-3-haiku-20240307-v1:0 | 0.4 | 0.25 | 1.25 |
102 | | bedrock | us.anthropic.claude-3-opus-20240229-v1:0 | 0.725 | 15 | 75 |
103 | | bedrock | us.anthropic.claude-3-5-sonnet-20240620-v1:0 | 0.49 | 3 | 15 |
104 | | bedrock | us.anthropic.claude-3-5-sonnet-20241022-v2:0 | 0.49 | 3 | 15 |
105 | | bedrock | us.anthropic.claude-3-7-sonnet-20250219-v1:0 | 0.623 | 3 | 15 |
106 | | bedrock | us.anthropic.claude-3-5-haiku-20241022-v1:0 | 0.4 | 0.8 | 4 |
107 | | bedrock | us.anthropic.claude-opus-4-20250514-v1:0 | 0.725 | 15 | 75 |
108 | | bedrock | us.anthropic.claude-sonnet-4-20250514-v1:0 | 0.727 | 3 | 15 |
109 |
110 | ## Research Models
111 |
112 | | Provider | Model Name | SWE Score | Input Cost | Output Cost |
113 | | ----------- | -------------------------------------------- | --------- | ---------- | ----------- |
114 | | claude-code | opus | 0.725 | 0 | 0 |
115 | | claude-code | sonnet | 0.727 | 0 | 0 |
116 | | claude-code | haiku | 0.45 | 0 | 0 |
117 | | codex-cli | gpt-5 | 0.749 | 0 | 0 |
118 | | codex-cli | gpt-5-codex | 0.749 | 0 | 0 |
119 | | mcp | mcp-sampling | — | 0 | 0 |
120 | | gemini-cli | gemini-3-pro-preview | 0.762 | 0 | 0 |
121 | | gemini-cli | gemini-2.5-pro | 0.72 | 0 | 0 |
122 | | gemini-cli | gemini-2.5-flash | 0.71 | 0 | 0 |
123 | | grok-cli | grok-4-latest | 0.7 | 0 | 0 |
124 | | grok-cli | grok-3-latest | 0.65 | 0 | 0 |
125 | | grok-cli | grok-3-fast | 0.6 | 0 | 0 |
126 | | grok-cli | grok-3-mini-fast | 0.55 | 0 | 0 |
127 | | openai | gpt-4o-search-preview | 0.33 | 2.5 | 10 |
128 | | openai | gpt-4o-mini-search-preview | 0.3 | 0.15 | 0.6 |
129 | | google | gemini-3-pro-preview | 0.762 | 2 | 12 |
130 | | xai | grok-3 | — | 3 | 15 |
131 | | xai | grok-3-fast | — | 5 | 25 |
132 | | xai | grok-4 | — | 3 | 15 |
133 | | groq | llama-3.3-70b-versatile | 0.55 | 0.59 | 0.79 |
134 | | groq | llama-4-scout | 0.45 | 0.11 | 0.34 |
135 | | groq | llama-4-maverick | 0.52 | 0.5 | 0.77 |
136 | | groq | qwen-qwq-32b-preview | 0.4 | 0.18 | 0.18 |
137 | | groq | deepseek-r1-distill-llama-70b | 0.52 | 0.75 | 0.99 |
138 | | perplexity | sonar-pro | — | 3 | 15 |
139 | | perplexity | sonar | — | 1 | 1 |
140 | | perplexity | sonar-deep-research | 0.211 | 2 | 8 |
141 | | perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
142 | | perplexity | sonar-reasoning | 0.211 | 1 | 5 |
143 | | zai | glm-4.6 | 0.68 | 0.6 | 2.2 |
144 | | zai | glm-4.5 | 0.65 | 0.6 | 2.2 |
145 | | zai | glm-4.5-air | 0.62 | 0.2 | 1.1 |
146 | | zai-coding | glm-4.6 | 0.68 | 0 | 0 |
147 | | zai-coding | glm-4.5 | 0.65 | 0 | 0 |
148 | | zai-coding | glm-4.5-air | 0.62 | 0 | 0 |
149 | | bedrock | us.anthropic.claude-3-opus-20240229-v1:0 | 0.725 | 15 | 75 |
150 | | bedrock | us.anthropic.claude-3-5-sonnet-20240620-v1:0 | 0.49 | 3 | 15 |
151 | | bedrock | us.anthropic.claude-3-5-sonnet-20241022-v2:0 | 0.49 | 3 | 15 |
152 | | bedrock | us.anthropic.claude-3-7-sonnet-20250219-v1:0 | 0.623 | 3 | 15 |
153 | | bedrock | us.anthropic.claude-opus-4-20250514-v1:0 | 0.725 | 15 | 75 |
154 | | bedrock | us.anthropic.claude-sonnet-4-20250514-v1:0 | 0.727 | 3 | 15 |
155 | | bedrock | us.deepseek.r1-v1:0 | — | 1.35 | 5.4 |
156 |
157 | ## Fallback Models
158 |
159 | | Provider | Model Name | SWE Score | Input Cost | Output Cost |
160 | | ----------- | ---------------------------------------------- | --------- | ---------- | ----------- |
161 | | anthropic | claude-sonnet-4-20250514 | 0.727 | 3 | 15 |
162 | | anthropic | claude-opus-4-20250514 | 0.725 | 15 | 75 |
163 | | anthropic | claude-3-7-sonnet-20250219 | 0.623 | 3 | 15 |
164 | | anthropic | claude-3-5-sonnet-20241022 | 0.49 | 3 | 15 |
165 | | anthropic | claude-sonnet-4-5-20250929 | 0.73 | 3 | 15 |
166 | | anthropic | claude-haiku-4-5-20251001 | 0.45 | 1 | 5 |
167 | | claude-code | opus | 0.725 | 0 | 0 |
168 | | claude-code | sonnet | 0.727 | 0 | 0 |
169 | | claude-code | haiku | 0.45 | 0 | 0 |
170 | | codex-cli | gpt-5 | 0.749 | 0 | 0 |
171 | | codex-cli | gpt-5-codex | 0.749 | 0 | 0 |
172 | | mcp | mcp-sampling | — | 0 | 0 |
173 | | gemini-cli | gemini-3-pro-preview | 0.762 | 0 | 0 |
174 | | gemini-cli | gemini-2.5-pro | 0.72 | 0 | 0 |
175 | | gemini-cli | gemini-2.5-flash | 0.71 | 0 | 0 |
176 | | grok-cli | grok-4-latest | 0.7 | 0 | 0 |
177 | | grok-cli | grok-3-latest | 0.65 | 0 | 0 |
178 | | grok-cli | grok-3-fast | 0.6 | 0 | 0 |
179 | | grok-cli | grok-3-mini-fast | 0.55 | 0 | 0 |
180 | | openai | gpt-4o | 0.332 | 2.5 | 10 |
181 | | openai | o3 | 0.5 | 2 | 8 |
182 | | openai | o4-mini | 0.45 | 1.1 | 4.4 |
183 | | openai | gpt-5 | 0.749 | 5 | 20 |
184 | | google | gemini-3-pro-preview | 0.762 | 2 | 12 |
185 | | google | gemini-2.5-pro-preview-05-06 | 0.638 | — | — |
186 | | google | gemini-2.5-pro-preview-03-25 | 0.638 | — | — |
187 | | google | gemini-2.5-flash-preview-04-17 | 0.604 | — | — |
188 | | google | gemini-2.0-flash | 0.518 | 0.15 | 0.6 |
189 | | google | gemini-2.0-flash-lite | — | — | — |
190 | | xai | grok-3 | — | 3 | 15 |
191 | | xai | grok-3-fast | — | 5 | 25 |
192 | | xai | grok-4 | — | 3 | 15 |
193 | | groq | moonshotai/kimi-k2-instruct | 0.66 | 1 | 3 |
194 | | groq | llama-3.3-70b-versatile | 0.55 | 0.59 | 0.79 |
195 | | groq | llama-3.1-8b-instant | 0.32 | 0.05 | 0.08 |
196 | | groq | llama-4-scout | 0.45 | 0.11 | 0.34 |
197 | | groq | llama-4-maverick | 0.52 | 0.5 | 0.77 |
198 | | groq | mixtral-8x7b-32768 | 0.35 | 0.24 | 0.24 |
199 | | groq | qwen-qwq-32b-preview | 0.4 | 0.18 | 0.18 |
200 | | groq | gemma2-9b-it | 0.3 | 0.2 | 0.2 |
201 | | perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
202 | | perplexity | sonar-reasoning | 0.211 | 1 | 5 |
203 | | openrouter | google/gemini-2.5-flash-preview-05-20 | — | 0.15 | 0.6 |
204 | | openrouter | google/gemini-2.5-flash-preview-05-20:thinking | — | 0.15 | 3.5 |
205 | | openrouter | google/gemini-2.5-pro-exp-03-25 | — | 0 | 0 |
206 | | openrouter | openai/gpt-4.1 | — | 2 | 8 |
207 | | openrouter | openai/gpt-4.1-mini | — | 0.4 | 1.6 |
208 | | openrouter | openai/gpt-4.1-nano | — | 0.1 | 0.4 |
209 | | openrouter | openai/o3 | — | 10 | 40 |
210 | | openrouter | openai/codex-mini | — | 1.5 | 6 |
211 | | openrouter | openai/gpt-4o-mini | — | 0.15 | 0.6 |
212 | | openrouter | openai/o4-mini | 0.45 | 1.1 | 4.4 |
213 | | openrouter | openai/o4-mini-high | — | 1.1 | 4.4 |
214 | | openrouter | openai/o1-pro | — | 150 | 600 |
215 | | openrouter | meta-llama/llama-3.3-70b-instruct | — | 120 | 600 |
216 | | openrouter | meta-llama/llama-4-maverick | — | 0.18 | 0.6 |
217 | | openrouter | meta-llama/llama-4-scout | — | 0.08 | 0.3 |
218 | | openrouter | qwen/qwen-max | — | 1.6 | 6.4 |
219 | | openrouter | qwen/qwen-turbo | — | 0.05 | 0.2 |
220 | | openrouter | qwen/qwen3-235b-a22b | — | 0.14 | 2 |
221 | | openrouter | mistralai/mistral-small-3.1-24b-instruct | — | 0.1 | 0.3 |
222 | | openrouter | mistralai/mistral-nemo | — | 0.03 | 0.07 |
223 | | zai | glm-4.6 | 0.68 | 0.6 | 2.2 |
224 | | zai | glm-4.5 | 0.65 | 0.6 | 2.2 |
225 | | zai | glm-4.5-air | 0.62 | 0.2 | 1.1 |
226 | | zai-coding | glm-4.6 | 0.68 | 0 | 0 |
227 | | zai-coding | glm-4.5 | 0.65 | 0 | 0 |
228 | | zai-coding | glm-4.5-air | 0.62 | 0 | 0 |
229 | | ollama | gpt-oss:latest | 0.607 | 0 | 0 |
230 | | ollama | gpt-oss:20b | 0.607 | 0 | 0 |
231 | | ollama | gpt-oss:120b | 0.624 | 0 | 0 |
232 | | ollama | devstral:latest | — | 0 | 0 |
233 | | ollama | qwen3:latest | — | 0 | 0 |
234 | | ollama | qwen3:14b | — | 0 | 0 |
235 | | ollama | qwen3:32b | — | 0 | 0 |
236 | | ollama | mistral-small3.1:latest | — | 0 | 0 |
237 | | ollama | llama3.3:latest | — | 0 | 0 |
238 | | ollama | phi4:latest | — | 0 | 0 |
239 | | azure | gpt-4o | 0.332 | 2.5 | 10 |
240 | | azure | gpt-4o-mini | 0.3 | 0.15 | 0.6 |
241 | | azure | gpt-4-1 | — | 2 | 10 |
242 | | bedrock | us.anthropic.claude-3-haiku-20240307-v1:0 | 0.4 | 0.25 | 1.25 |
243 | | bedrock | us.anthropic.claude-3-opus-20240229-v1:0 | 0.725 | 15 | 75 |
244 | | bedrock | us.anthropic.claude-3-5-sonnet-20240620-v1:0 | 0.49 | 3 | 15 |
245 | | bedrock | us.anthropic.claude-3-5-sonnet-20241022-v2:0 | 0.49 | 3 | 15 |
246 | | bedrock | us.anthropic.claude-3-7-sonnet-20250219-v1:0 | 0.623 | 3 | 15 |
247 | | bedrock | us.anthropic.claude-3-5-haiku-20241022-v1:0 | 0.4 | 0.8 | 4 |
248 | | bedrock | us.anthropic.claude-opus-4-20250514-v1:0 | 0.725 | 15 | 75 |
249 | | bedrock | us.anthropic.claude-sonnet-4-20250514-v1:0 | 0.727 | 3 | 15 |
250 |
251 | ## Unsupported Models
252 |
253 | | Provider | Model Name | Reason |
254 | | ---------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
255 | | openrouter | deepseek/deepseek-chat-v3-0324:free | 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. |
256 | | openrouter | mistralai/mistral-small-3.1-24b-instruct:free | 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. |
257 | | openrouter | thudm/glm-4-32b:free | 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. |
258 |
```
--------------------------------------------------------------------------------
/scripts/modules/task-manager/scope-adjustment.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * scope-adjustment.js
3 | * Core logic for dynamic task complexity adjustment (scope-up and scope-down)
4 | */
5 |
6 | import { z } from 'zod';
7 | import {
8 | log,
9 | readJSON,
10 | writeJSON,
11 | getCurrentTag,
12 | readComplexityReport,
13 | findTaskInComplexityReport
14 | } from '../utils.js';
15 | import {
16 | generateObjectService,
17 | generateTextService
18 | } from '../ai-services-unified.js';
19 | import { findTaskById, taskExists } from '../task-manager.js';
20 | import analyzeTaskComplexity from './analyze-task-complexity.js';
21 | import { findComplexityReportPath } from '../../../src/utils/path-utils.js';
22 |
23 | /**
24 | * Valid strength levels for scope adjustments
25 | */
26 | const VALID_STRENGTHS = ['light', 'regular', 'heavy'];
27 |
28 | /**
29 | * Statuses that should be preserved during subtask regeneration
30 | * These represent work that has been started or intentionally set by the user
31 | */
32 | const PRESERVE_STATUSES = [
33 | 'done',
34 | 'in-progress',
35 | 'review',
36 | 'cancelled',
37 | 'deferred',
38 | 'blocked'
39 | ];
40 |
41 | /**
42 | * Statuses that should be regenerated during subtask regeneration
43 | * These represent work that hasn't been started yet
44 | */
45 | const REGENERATE_STATUSES = ['pending'];
46 |
47 | /**
48 | * Validates strength parameter
49 | * @param {string} strength - The strength level to validate
50 | * @returns {boolean} True if valid, false otherwise
51 | */
52 | export function validateStrength(strength) {
53 | return VALID_STRENGTHS.includes(strength);
54 | }
55 |
56 | /**
57 | * Re-analyzes the complexity of a single task after scope adjustment
58 | * @param {Object} task - The task to analyze
59 | * @param {string} tasksPath - Path to tasks.json
60 | * @param {Object} context - Context containing projectRoot, tag, session
61 | * @returns {Promise<number|null>} New complexity score or null if analysis failed
62 | */
63 | async function reanalyzeTaskComplexity(task, tasksPath, context) {
64 | const { projectRoot, tag, session } = context;
65 |
66 | try {
67 | // Create a minimal tasks data structure for analysis
68 | const tasksForAnalysis = {
69 | tasks: [task],
70 | metadata: { analyzedAt: new Date().toISOString() }
71 | };
72 |
73 | // Find the complexity report path for this tag
74 | const complexityReportPath = findComplexityReportPath(
75 | null,
76 | { projectRoot, tag },
77 | null
78 | );
79 |
80 | if (!complexityReportPath) {
81 | log('warn', 'No complexity report found - cannot re-analyze complexity');
82 | return null;
83 | }
84 |
85 | // Use analyze-task-complexity to re-analyze just this task
86 | const analysisOptions = {
87 | file: tasksPath,
88 | output: complexityReportPath,
89 | id: task.id.toString(), // Analyze only this specific task
90 | projectRoot,
91 | tag,
92 | _filteredTasksData: tasksForAnalysis, // Pass pre-filtered data
93 | _originalTaskCount: 1
94 | };
95 |
96 | // Run the analysis with proper context
97 | await analyzeTaskComplexity(analysisOptions, { session });
98 |
99 | // Read the updated complexity report to get the new score
100 | const updatedReport = readComplexityReport(complexityReportPath);
101 | if (updatedReport) {
102 | const taskAnalysis = findTaskInComplexityReport(updatedReport, task.id);
103 | if (taskAnalysis) {
104 | log(
105 | 'info',
106 | `Re-analyzed task ${task.id} complexity: ${taskAnalysis.complexityScore}/10`
107 | );
108 | return taskAnalysis.complexityScore;
109 | }
110 | }
111 |
112 | log(
113 | 'warn',
114 | `Could not find updated complexity analysis for task ${task.id}`
115 | );
116 | return null;
117 | } catch (error) {
118 | log('error', `Failed to re-analyze task complexity: ${error.message}`);
119 | return null;
120 | }
121 | }
122 |
123 | /**
124 | * Gets the current complexity score for a task from the complexity report
125 | * @param {number} taskId - Task ID to look up
126 | * @param {Object} context - Context containing projectRoot, tag
127 | * @returns {number|null} Current complexity score or null if not found
128 | */
129 | function getCurrentComplexityScore(taskId, context) {
130 | const { projectRoot, tag } = context;
131 |
132 | try {
133 | // Find the complexity report path for this tag
134 | const complexityReportPath = findComplexityReportPath(
135 | null,
136 | { projectRoot, tag },
137 | null
138 | );
139 |
140 | if (!complexityReportPath) {
141 | return null;
142 | }
143 |
144 | // Read the current complexity report
145 | const complexityReport = readComplexityReport(complexityReportPath);
146 | if (!complexityReport) {
147 | return null;
148 | }
149 |
150 | // Find this task's current complexity
151 | const taskAnalysis = findTaskInComplexityReport(complexityReport, taskId);
152 | return taskAnalysis ? taskAnalysis.complexityScore : null;
153 | } catch (error) {
154 | log('debug', `Could not read current complexity score: ${error.message}`);
155 | return null;
156 | }
157 | }
158 |
159 | /**
160 | * Regenerates subtasks for a task based on new complexity while preserving completed work
161 | * @param {Object} task - The updated task object
162 | * @param {string} tasksPath - Path to tasks.json
163 | * @param {Object} context - Context containing projectRoot, tag, session
164 | * @param {string} direction - Direction of scope change (up/down) for logging
165 | * @param {string} strength - Strength level ('light', 'regular', 'heavy')
166 | * @param {number|null} originalComplexity - Original complexity score for smarter adjustments
167 | * @returns {Promise<Object>} Object with updated task and regeneration info
168 | */
169 | async function regenerateSubtasksForComplexity(
170 | task,
171 | tasksPath,
172 | context,
173 | direction,
174 | strength = 'regular',
175 | originalComplexity = null
176 | ) {
177 | const { projectRoot, tag, session } = context;
178 |
179 | // Check if task has subtasks
180 | if (
181 | !task.subtasks ||
182 | !Array.isArray(task.subtasks) ||
183 | task.subtasks.length === 0
184 | ) {
185 | return {
186 | updatedTask: task,
187 | regenerated: false,
188 | preserved: 0,
189 | generated: 0
190 | };
191 | }
192 |
193 | // Identify subtasks to preserve vs regenerate
194 | const preservedSubtasks = task.subtasks.filter((subtask) =>
195 | PRESERVE_STATUSES.includes(subtask.status)
196 | );
197 | const pendingSubtasks = task.subtasks.filter((subtask) =>
198 | REGENERATE_STATUSES.includes(subtask.status)
199 | );
200 |
201 | // If no pending subtasks, nothing to regenerate
202 | if (pendingSubtasks.length === 0) {
203 | return {
204 | updatedTask: task,
205 | regenerated: false,
206 | preserved: preservedSubtasks.length,
207 | generated: 0
208 | };
209 | }
210 |
211 | // Calculate appropriate number of total subtasks based on direction, complexity, strength, and original complexity
212 | let targetSubtaskCount;
213 | const preservedCount = preservedSubtasks.length;
214 | const currentPendingCount = pendingSubtasks.length;
215 |
216 | // Use original complexity to inform decisions (if available)
217 | const complexityFactor = originalComplexity
218 | ? Math.max(0.5, originalComplexity / 10)
219 | : 1.0;
220 | const complexityInfo = originalComplexity
221 | ? ` (original complexity: ${originalComplexity}/10)`
222 | : '';
223 |
224 | if (direction === 'up') {
225 | // Scope up: More subtasks for increased complexity
226 | if (strength === 'light') {
227 | const base = Math.max(
228 | 5,
229 | preservedCount + Math.ceil(currentPendingCount * 1.1)
230 | );
231 | targetSubtaskCount = Math.ceil(base * (0.8 + 0.4 * complexityFactor));
232 | } else if (strength === 'regular') {
233 | const base = Math.max(
234 | 6,
235 | preservedCount + Math.ceil(currentPendingCount * 1.3)
236 | );
237 | targetSubtaskCount = Math.ceil(base * (0.8 + 0.4 * complexityFactor));
238 | } else {
239 | // heavy
240 | const base = Math.max(
241 | 8,
242 | preservedCount + Math.ceil(currentPendingCount * 1.6)
243 | );
244 | targetSubtaskCount = Math.ceil(base * (0.8 + 0.6 * complexityFactor));
245 | }
246 | } else {
247 | // Scope down: Fewer subtasks for decreased complexity
248 | // High complexity tasks get reduced more aggressively
249 | const aggressiveFactor =
250 | originalComplexity >= 8 ? 0.7 : originalComplexity >= 6 ? 0.85 : 1.0;
251 |
252 | if (strength === 'light') {
253 | const base = Math.max(
254 | 3,
255 | preservedCount + Math.ceil(currentPendingCount * 0.8)
256 | );
257 | targetSubtaskCount = Math.ceil(base * aggressiveFactor);
258 | } else if (strength === 'regular') {
259 | const base = Math.max(
260 | 3,
261 | preservedCount + Math.ceil(currentPendingCount * 0.5)
262 | );
263 | targetSubtaskCount = Math.ceil(base * aggressiveFactor);
264 | } else {
265 | // heavy
266 | // Heavy scope-down should be much more aggressive - aim for only core functionality
267 | // Very high complexity tasks (9-10) get reduced to almost nothing
268 | const ultraAggressiveFactor =
269 | originalComplexity >= 9 ? 0.3 : originalComplexity >= 7 ? 0.5 : 0.7;
270 | const base = Math.max(
271 | 2,
272 | preservedCount + Math.ceil(currentPendingCount * 0.25)
273 | );
274 | targetSubtaskCount = Math.max(1, Math.ceil(base * ultraAggressiveFactor));
275 | }
276 | }
277 |
278 | log(
279 | 'debug',
280 | `Complexity-aware subtask calculation${complexityInfo}: ${currentPendingCount} pending -> target ${targetSubtaskCount} total`
281 | );
282 | log(
283 | 'debug',
284 | `Complexity-aware calculation${complexityInfo}: ${currentPendingCount} pending -> ${targetSubtaskCount} total subtasks (${strength} ${direction})`
285 | );
286 |
287 | const newSubtasksNeeded = Math.max(1, targetSubtaskCount - preservedCount);
288 |
289 | try {
290 | // Generate new subtasks using AI to match the new complexity level
291 | const systemPrompt = `You are an expert project manager who creates task breakdowns that match complexity levels.`;
292 |
293 | const prompt = `Based on this updated task, generate ${newSubtasksNeeded} NEW subtasks that reflect the ${direction === 'up' ? 'increased' : 'decreased'} complexity level:
294 |
295 | **Task Title**: ${task.title}
296 | **Task Description**: ${task.description}
297 | **Implementation Details**: ${task.details}
298 | **Test Strategy**: ${task.testStrategy}
299 |
300 | **Complexity Direction**: This task was recently scoped ${direction} (${strength} strength) to ${direction === 'up' ? 'increase' : 'decrease'} complexity.
301 | ${originalComplexity ? `**Original Complexity**: ${originalComplexity}/10 - consider this when determining appropriate scope level.` : ''}
302 |
303 | ${preservedCount > 0 ? `**Preserved Subtasks**: ${preservedCount} existing subtasks with work already done will be kept.` : ''}
304 |
305 | Generate subtasks that:
306 | ${
307 | direction === 'up'
308 | ? strength === 'heavy'
309 | ? `- Add comprehensive implementation steps with advanced features
310 | - Include extensive error handling, validation, and edge cases
311 | - Cover multiple integration scenarios and advanced testing
312 | - Provide thorough documentation and optimization approaches`
313 | : strength === 'regular'
314 | ? `- Add more detailed implementation steps
315 | - Include additional error handling and validation
316 | - Cover more edge cases and advanced features
317 | - Provide more comprehensive testing approaches`
318 | : `- Add some additional implementation details
319 | - Include basic error handling considerations
320 | - Cover a few common edge cases
321 | - Enhance testing approaches slightly`
322 | : strength === 'heavy'
323 | ? `- Focus ONLY on absolutely essential core functionality
324 | - Strip out ALL non-critical features (error handling, advanced testing, etc.)
325 | - Provide only the minimum viable implementation
326 | - Eliminate any complex integrations or advanced scenarios
327 | - Aim for the simplest possible working solution`
328 | : strength === 'regular'
329 | ? `- Focus on core functionality only
330 | - Simplify implementation steps
331 | - Remove non-essential features
332 | - Streamline to basic requirements`
333 | : `- Focus mainly on core functionality
334 | - Slightly simplify implementation steps
335 | - Remove some non-essential features
336 | - Streamline most requirements`
337 | }
338 |
339 | Return a JSON object with a "subtasks" array. Each subtask should have:
340 | - id: Sequential NUMBER starting from 1 (e.g., 1, 2, 3 - NOT "1", "2", "3")
341 | - title: Clear, specific title
342 | - description: Detailed description
343 | - dependencies: Array of dependency IDs as STRINGS (use format ["${task.id}.1", "${task.id}.2"] for siblings, or empty array [] for no dependencies)
344 | - details: Implementation guidance
345 | - status: "pending"
346 | - testStrategy: Testing approach
347 |
348 | IMPORTANT:
349 | - The 'id' field must be a NUMBER, not a string!
350 | - Dependencies must be strings, not numbers!
351 |
352 | Ensure the JSON is valid and properly formatted.`;
353 |
354 | // Define subtask schema
355 | const subtaskSchema = z.object({
356 | subtasks: z.array(
357 | z.object({
358 | id: z.int().positive(),
359 | title: z.string().min(5),
360 | description: z.string().min(10),
361 | dependencies: z.array(z.string()),
362 | details: z.string().min(20),
363 | status: z.string(),
364 | testStrategy: z.string()
365 | })
366 | )
367 | });
368 |
369 | const aiResult = await generateObjectService({
370 | role: context.research ? 'research' : 'main',
371 | session: context.session,
372 | systemPrompt,
373 | prompt,
374 | schema: subtaskSchema,
375 | objectName: 'subtask_regeneration',
376 | commandName: context.commandName || `subtask-regen-${direction}`,
377 | outputType: context.outputType || 'cli'
378 | });
379 |
380 | const generatedSubtasks = aiResult.mainResult.subtasks || [];
381 |
382 | // Post-process generated subtasks to ensure defaults
383 | const processedGeneratedSubtasks = generatedSubtasks.map((subtask) => ({
384 | ...subtask,
385 | status: subtask.status || 'pending',
386 | testStrategy: subtask.testStrategy || ''
387 | }));
388 |
389 | // Ensure new subtasks have unique sequential IDs after the preserved ones
390 | const maxPreservedId = preservedSubtasks.reduce(
391 | (max, st) => Math.max(max, st.id || 0),
392 | 0
393 | );
394 | let nextId = maxPreservedId + 1;
395 | const idMapping = new Map();
396 | const normalizedGeneratedSubtasks = processedGeneratedSubtasks
397 | .map((st) => {
398 | const originalId = st.id;
399 | const newId = nextId++;
400 | idMapping.set(originalId, newId);
401 | return {
402 | ...st,
403 | id: newId
404 | };
405 | })
406 | .map((st) => ({
407 | ...st,
408 | dependencies: (st.dependencies || []).map((dep) => {
409 | if (typeof dep !== 'string' || !dep.startsWith(`${task.id}.`)) {
410 | return dep;
411 | }
412 | const [, siblingIdPart] = dep.split('.');
413 | const originalSiblingId = Number.parseInt(siblingIdPart, 10);
414 | const remappedSiblingId = idMapping.get(originalSiblingId);
415 | return remappedSiblingId ? `${task.id}.${remappedSiblingId}` : dep;
416 | })
417 | }));
418 |
419 | // Update task with preserved subtasks + newly generated ones
420 | task.subtasks = [...preservedSubtasks, ...normalizedGeneratedSubtasks];
421 |
422 | return {
423 | updatedTask: task,
424 | regenerated: true,
425 | preserved: preservedSubtasks.length,
426 | generated: normalizedGeneratedSubtasks.length
427 | };
428 | } catch (error) {
429 | log(
430 | 'warn',
431 | `Failed to regenerate subtasks for task ${task.id}: ${error.message}`
432 | );
433 | // Don't fail the whole operation if subtask regeneration fails
434 | return {
435 | updatedTask: task,
436 | regenerated: false,
437 | preserved: preservedSubtasks.length,
438 | generated: 0,
439 | error: error.message
440 | };
441 | }
442 | }
443 |
444 | /**
445 | * Generates AI prompt for scope adjustment
446 | * @param {Object} task - The task to adjust
447 | * @param {string} direction - 'up' or 'down'
448 | * @param {string} strength - 'light', 'regular', or 'heavy'
449 | * @param {string} customPrompt - Optional custom instructions
450 | * @returns {string} The generated prompt
451 | */
452 | function generateScopePrompt(task, direction, strength, customPrompt) {
453 | const isUp = direction === 'up';
454 | const strengthDescriptions = {
455 | light: isUp ? 'minor enhancements' : 'slight simplifications',
456 | regular: isUp
457 | ? 'moderate complexity increases'
458 | : 'moderate simplifications',
459 | heavy: isUp ? 'significant complexity additions' : 'major simplifications'
460 | };
461 |
462 | let basePrompt = `You are tasked with adjusting the complexity of a task.
463 |
464 | CURRENT TASK:
465 | Title: ${task.title}
466 | Description: ${task.description}
467 | Details: ${task.details}
468 | Test Strategy: ${task.testStrategy || 'Not specified'}
469 |
470 | ADJUSTMENT REQUIREMENTS:
471 | - Direction: ${isUp ? 'INCREASE' : 'DECREASE'} complexity
472 | - Strength: ${strength} (${strengthDescriptions[strength]})
473 | - Preserve the core purpose and functionality of the task
474 | - Maintain consistency with the existing task structure`;
475 |
476 | if (isUp) {
477 | basePrompt += `
478 | - Add more detailed requirements, edge cases, or advanced features
479 | - Include additional implementation considerations
480 | - Enhance error handling and validation requirements
481 | - Expand testing strategies with more comprehensive scenarios`;
482 | } else {
483 | basePrompt += `
484 | - Focus on core functionality and essential requirements
485 | - Remove or simplify non-essential features
486 | - Streamline implementation details
487 | - Simplify testing to focus on basic functionality`;
488 | }
489 |
490 | if (customPrompt) {
491 | basePrompt += `\n\nCUSTOM INSTRUCTIONS:\n${customPrompt}`;
492 | }
493 |
494 | basePrompt += `\n\nReturn a JSON object with the updated task containing these fields:
495 | - title: Updated task title
496 | - description: Updated task description
497 | - details: Updated implementation details
498 | - testStrategy: Updated test strategy
499 | - priority: Task priority ('low', 'medium', or 'high')
500 |
501 | Ensure the JSON is valid and properly formatted.`;
502 |
503 | return basePrompt;
504 | }
505 |
506 | /**
507 | * Adjusts task complexity using AI
508 | * @param {Object} task - The task to adjust
509 | * @param {string} direction - 'up' or 'down'
510 | * @param {string} strength - 'light', 'regular', or 'heavy'
511 | * @param {string} customPrompt - Optional custom instructions
512 | * @param {Object} context - Context object with projectRoot, tag, etc.
513 | * @returns {Promise<Object>} Updated task data and telemetry
514 | */
515 | async function adjustTaskComplexity(
516 | task,
517 | direction,
518 | strength,
519 | customPrompt,
520 | context
521 | ) {
522 | const systemPrompt = `You are an expert software project manager who helps adjust task complexity while maintaining clarity and actionability.`;
523 |
524 | const prompt = generateScopePrompt(task, direction, strength, customPrompt);
525 |
526 | // Define the task schema for structured response using Zod
527 | const taskSchema = z.object({
528 | title: z
529 | .string()
530 | .min(1)
531 | .describe('Updated task title reflecting scope adjustment'),
532 | description: z
533 | .string()
534 | .min(1)
535 | .describe('Updated task description with adjusted scope'),
536 | details: z
537 | .string()
538 | .min(1)
539 | .describe('Updated implementation details with adjusted complexity'),
540 | testStrategy: z
541 | .string()
542 | .min(1)
543 | .describe('Updated testing approach for the adjusted scope'),
544 | priority: z.enum(['low', 'medium', 'high']).describe('Task priority level')
545 | });
546 |
547 | const aiResult = await generateObjectService({
548 | role: context.research ? 'research' : 'main',
549 | session: context.session,
550 | systemPrompt,
551 | prompt,
552 | schema: taskSchema,
553 | objectName: 'updated_task',
554 | commandName: context.commandName || `scope-${direction}`,
555 | outputType: context.outputType || 'cli'
556 | });
557 |
558 | const updatedTaskData = aiResult.mainResult;
559 |
560 | // Ensure priority has a value (in case AI didn't provide one)
561 | const processedTaskData = {
562 | ...updatedTaskData,
563 | priority: updatedTaskData.priority || task.priority || 'medium'
564 | };
565 |
566 | return {
567 | updatedTask: {
568 | ...task,
569 | ...processedTaskData
570 | },
571 | telemetryData: aiResult.telemetryData
572 | };
573 | }
574 |
575 | /**
576 | * Increases task complexity (scope-up)
577 | * @param {string} tasksPath - Path to tasks.json file
578 | * @param {Array<number>} taskIds - Array of task IDs to scope up
579 | * @param {string} strength - Strength level ('light', 'regular', 'heavy')
580 | * @param {string} customPrompt - Optional custom instructions
581 | * @param {Object} context - Context object with projectRoot, tag, etc.
582 | * @param {string} outputFormat - Output format ('text' or 'json')
583 | * @returns {Promise<Object>} Results of the scope-up operation
584 | */
585 | export async function scopeUpTask(
586 | tasksPath,
587 | taskIds,
588 | strength = 'regular',
589 | customPrompt = null,
590 | context = {},
591 | outputFormat = 'text'
592 | ) {
593 | // Validate inputs
594 | if (!validateStrength(strength)) {
595 | throw new Error(
596 | `Invalid strength level: ${strength}. Must be one of: ${VALID_STRENGTHS.join(', ')}`
597 | );
598 | }
599 |
600 | const { projectRoot = '.', tag = 'master' } = context;
601 |
602 | // Read tasks data
603 | const data = readJSON(tasksPath, projectRoot, tag);
604 | const tasks = data?.tasks || [];
605 |
606 | // Validate all task IDs exist
607 | for (const taskId of taskIds) {
608 | if (!taskExists(tasks, taskId)) {
609 | throw new Error(`Task with ID ${taskId} not found`);
610 | }
611 | }
612 |
613 | const updatedTasks = [];
614 | let combinedTelemetryData = null;
615 |
616 | // Process each task
617 | for (const taskId of taskIds) {
618 | const taskResult = findTaskById(tasks, taskId);
619 | const task = taskResult.task;
620 | if (!task) {
621 | throw new Error(`Task with ID ${taskId} not found`);
622 | }
623 |
624 | if (outputFormat === 'text') {
625 | log('info', `Scoping up task ${taskId}: ${task.title}`);
626 | }
627 |
628 | // Get original complexity score (if available)
629 | const originalComplexity = getCurrentComplexityScore(taskId, context);
630 | if (originalComplexity && outputFormat === 'text') {
631 | log('info', `Original complexity: ${originalComplexity}/10`);
632 | }
633 |
634 | const adjustResult = await adjustTaskComplexity(
635 | task,
636 | 'up',
637 | strength,
638 | customPrompt,
639 | context
640 | );
641 |
642 | // Regenerate subtasks based on new complexity while preserving completed work
643 | const subtaskResult = await regenerateSubtasksForComplexity(
644 | adjustResult.updatedTask,
645 | tasksPath,
646 | context,
647 | 'up',
648 | strength,
649 | originalComplexity
650 | );
651 |
652 | // Log subtask regeneration info if in text mode
653 | if (outputFormat === 'text' && subtaskResult.regenerated) {
654 | log(
655 | 'info',
656 | `Regenerated ${subtaskResult.generated} pending subtasks (preserved ${subtaskResult.preserved} completed)`
657 | );
658 | }
659 |
660 | // Update task in data
661 | const taskIndex = data.tasks.findIndex((t) => t.id === taskId);
662 | if (taskIndex !== -1) {
663 | data.tasks[taskIndex] = subtaskResult.updatedTask;
664 | updatedTasks.push(subtaskResult.updatedTask);
665 | }
666 |
667 | // Re-analyze complexity after scoping (if we have a session for AI calls)
668 | if (context.session && originalComplexity) {
669 | try {
670 | // Write the updated task first so complexity analysis can read it
671 | writeJSON(tasksPath, data, projectRoot, tag);
672 |
673 | // Re-analyze complexity
674 | const newComplexity = await reanalyzeTaskComplexity(
675 | subtaskResult.updatedTask,
676 | tasksPath,
677 | context
678 | );
679 | if (newComplexity && outputFormat === 'text') {
680 | const complexityChange = newComplexity - originalComplexity;
681 | const arrow =
682 | complexityChange > 0 ? '↗️' : complexityChange < 0 ? '↘️' : '➡️';
683 | log(
684 | 'info',
685 | `New complexity: ${originalComplexity}/10 ${arrow} ${newComplexity}/10 (${complexityChange > 0 ? '+' : ''}${complexityChange})`
686 | );
687 | }
688 | } catch (error) {
689 | if (outputFormat === 'text') {
690 | log('warn', `Could not re-analyze complexity: ${error.message}`);
691 | }
692 | }
693 | }
694 |
695 | // Combine telemetry data
696 | if (adjustResult.telemetryData) {
697 | if (!combinedTelemetryData) {
698 | combinedTelemetryData = { ...adjustResult.telemetryData };
699 | } else {
700 | // Sum up costs and tokens
701 | combinedTelemetryData.inputTokens +=
702 | adjustResult.telemetryData.inputTokens || 0;
703 | combinedTelemetryData.outputTokens +=
704 | adjustResult.telemetryData.outputTokens || 0;
705 | combinedTelemetryData.totalTokens +=
706 | adjustResult.telemetryData.totalTokens || 0;
707 | combinedTelemetryData.totalCost +=
708 | adjustResult.telemetryData.totalCost || 0;
709 | }
710 | }
711 | }
712 |
713 | // Write updated data
714 | writeJSON(tasksPath, data, projectRoot, tag);
715 |
716 | if (outputFormat === 'text') {
717 | log('info', `Successfully scoped up ${updatedTasks.length} task(s)`);
718 | }
719 |
720 | return {
721 | updatedTasks,
722 | telemetryData: combinedTelemetryData
723 | };
724 | }
725 |
726 | /**
727 | * Decreases task complexity (scope-down)
728 | * @param {string} tasksPath - Path to tasks.json file
729 | * @param {Array<number>} taskIds - Array of task IDs to scope down
730 | * @param {string} strength - Strength level ('light', 'regular', 'heavy')
731 | * @param {string} customPrompt - Optional custom instructions
732 | * @param {Object} context - Context object with projectRoot, tag, etc.
733 | * @param {string} outputFormat - Output format ('text' or 'json')
734 | * @returns {Promise<Object>} Results of the scope-down operation
735 | */
736 | export async function scopeDownTask(
737 | tasksPath,
738 | taskIds,
739 | strength = 'regular',
740 | customPrompt = null,
741 | context = {},
742 | outputFormat = 'text'
743 | ) {
744 | // Validate inputs
745 | if (!validateStrength(strength)) {
746 | throw new Error(
747 | `Invalid strength level: ${strength}. Must be one of: ${VALID_STRENGTHS.join(', ')}`
748 | );
749 | }
750 |
751 | const { projectRoot = '.', tag = 'master' } = context;
752 |
753 | // Read tasks data
754 | const data = readJSON(tasksPath, projectRoot, tag);
755 | const tasks = data?.tasks || [];
756 |
757 | // Validate all task IDs exist
758 | for (const taskId of taskIds) {
759 | if (!taskExists(tasks, taskId)) {
760 | throw new Error(`Task with ID ${taskId} not found`);
761 | }
762 | }
763 |
764 | const updatedTasks = [];
765 | let combinedTelemetryData = null;
766 |
767 | // Process each task
768 | for (const taskId of taskIds) {
769 | const taskResult = findTaskById(tasks, taskId);
770 | const task = taskResult.task;
771 | if (!task) {
772 | throw new Error(`Task with ID ${taskId} not found`);
773 | }
774 |
775 | if (outputFormat === 'text') {
776 | log('info', `Scoping down task ${taskId}: ${task.title}`);
777 | }
778 |
779 | // Get original complexity score (if available)
780 | const originalComplexity = getCurrentComplexityScore(taskId, context);
781 | if (originalComplexity && outputFormat === 'text') {
782 | log('info', `Original complexity: ${originalComplexity}/10`);
783 | }
784 |
785 | const adjustResult = await adjustTaskComplexity(
786 | task,
787 | 'down',
788 | strength,
789 | customPrompt,
790 | context
791 | );
792 |
793 | // Regenerate subtasks based on new complexity while preserving completed work
794 | const subtaskResult = await regenerateSubtasksForComplexity(
795 | adjustResult.updatedTask,
796 | tasksPath,
797 | context,
798 | 'down',
799 | strength,
800 | originalComplexity
801 | );
802 |
803 | // Log subtask regeneration info if in text mode
804 | if (outputFormat === 'text' && subtaskResult.regenerated) {
805 | log(
806 | 'info',
807 | `Regenerated ${subtaskResult.generated} pending subtasks (preserved ${subtaskResult.preserved} completed)`
808 | );
809 | }
810 |
811 | // Update task in data
812 | const taskIndex = data.tasks.findIndex((t) => t.id === taskId);
813 | if (taskIndex !== -1) {
814 | data.tasks[taskIndex] = subtaskResult.updatedTask;
815 | updatedTasks.push(subtaskResult.updatedTask);
816 | }
817 |
818 | // Re-analyze complexity after scoping (if we have a session for AI calls)
819 | if (context.session && originalComplexity) {
820 | try {
821 | // Write the updated task first so complexity analysis can read it
822 | writeJSON(tasksPath, data, projectRoot, tag);
823 |
824 | // Re-analyze complexity
825 | const newComplexity = await reanalyzeTaskComplexity(
826 | subtaskResult.updatedTask,
827 | tasksPath,
828 | context
829 | );
830 | if (newComplexity && outputFormat === 'text') {
831 | const complexityChange = newComplexity - originalComplexity;
832 | const arrow =
833 | complexityChange > 0 ? '↗️' : complexityChange < 0 ? '↘️' : '➡️';
834 | log(
835 | 'info',
836 | `New complexity: ${originalComplexity}/10 ${arrow} ${newComplexity}/10 (${complexityChange > 0 ? '+' : ''}${complexityChange})`
837 | );
838 | }
839 | } catch (error) {
840 | if (outputFormat === 'text') {
841 | log('warn', `Could not re-analyze complexity: ${error.message}`);
842 | }
843 | }
844 | }
845 |
846 | // Combine telemetry data
847 | if (adjustResult.telemetryData) {
848 | if (!combinedTelemetryData) {
849 | combinedTelemetryData = { ...adjustResult.telemetryData };
850 | } else {
851 | // Sum up costs and tokens
852 | combinedTelemetryData.inputTokens +=
853 | adjustResult.telemetryData.inputTokens || 0;
854 | combinedTelemetryData.outputTokens +=
855 | adjustResult.telemetryData.outputTokens || 0;
856 | combinedTelemetryData.totalTokens +=
857 | adjustResult.telemetryData.totalTokens || 0;
858 | combinedTelemetryData.totalCost +=
859 | adjustResult.telemetryData.totalCost || 0;
860 | }
861 | }
862 | }
863 |
864 | // Write updated data
865 | writeJSON(tasksPath, data, projectRoot, tag);
866 |
867 | if (outputFormat === 'text') {
868 | log('info', `Successfully scoped down ${updatedTasks.length} task(s)`);
869 | }
870 |
871 | return {
872 | updatedTasks,
873 | telemetryData: combinedTelemetryData
874 | };
875 | }
876 |
```