This is page 7 of 50. Use http://codebase.md/eyaltoledano/claude-task-master?page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── commands
│ │ └── dedupe.md
│ └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│ └── marketplace.json
├── .coderabbit.yaml
├── .cursor
│ ├── mcp.json
│ └── rules
│ ├── ai_providers.mdc
│ ├── ai_services.mdc
│ ├── architecture.mdc
│ ├── changeset.mdc
│ ├── commands.mdc
│ ├── context_gathering.mdc
│ ├── cursor_rules.mdc
│ ├── dependencies.mdc
│ ├── dev_workflow.mdc
│ ├── git_workflow.mdc
│ ├── glossary.mdc
│ ├── mcp.mdc
│ ├── new_features.mdc
│ ├── self_improve.mdc
│ ├── tags.mdc
│ ├── taskmaster.mdc
│ ├── tasks.mdc
│ ├── telemetry.mdc
│ ├── test_workflow.mdc
│ ├── tests.mdc
│ ├── ui.mdc
│ └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── enhancements---feature-requests.md
│ │ └── feedback.md
│ ├── PULL_REQUEST_TEMPLATE
│ │ ├── bugfix.md
│ │ ├── config.yml
│ │ ├── feature.md
│ │ └── integration.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── scripts
│ │ ├── auto-close-duplicates.mjs
│ │ ├── backfill-duplicate-comments.mjs
│ │ ├── check-pre-release-mode.mjs
│ │ ├── parse-metrics.mjs
│ │ ├── release.mjs
│ │ ├── tag-extension.mjs
│ │ ├── utils.mjs
│ │ └── validate-changesets.mjs
│ └── workflows
│ ├── auto-close-duplicates.yml
│ ├── backfill-duplicate-comments.yml
│ ├── ci.yml
│ ├── claude-dedupe-issues.yml
│ ├── claude-docs-trigger.yml
│ ├── claude-docs-updater.yml
│ ├── claude-issue-triage.yml
│ ├── claude.yml
│ ├── extension-ci.yml
│ ├── extension-release.yml
│ ├── log-issue-events.yml
│ ├── pre-release.yml
│ ├── release-check.yml
│ ├── release.yml
│ ├── update-models-md.yml
│ └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│ ├── hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── settings
│ │ └── mcp.json
│ └── steering
│ ├── dev_workflow.md
│ ├── kiro_rules.md
│ ├── self_improve.md
│ ├── taskmaster_hooks_workflow.md
│ └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│ ├── CLAUDE.md
│ ├── config.json
│ ├── docs
│ │ ├── autonomous-tdd-git-workflow.md
│ │ ├── MIGRATION-ROADMAP.md
│ │ ├── prd-tm-start.txt
│ │ ├── prd.txt
│ │ ├── README.md
│ │ ├── research
│ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│ │ │ ├── 2025-06-14_test-save-functionality.md
│ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│ │ ├── task-template-importing-prd.txt
│ │ ├── tdd-workflow-phase-0-spike.md
│ │ ├── tdd-workflow-phase-1-core-rails.md
│ │ ├── tdd-workflow-phase-1-orchestrator.md
│ │ ├── tdd-workflow-phase-2-pr-resumability.md
│ │ ├── tdd-workflow-phase-3-extensibility-guardrails.md
│ │ ├── test-prd.txt
│ │ └── tm-core-phase-1.txt
│ ├── reports
│ │ ├── task-complexity-report_autonomous-tdd-git-workflow.json
│ │ ├── task-complexity-report_cc-kiro-hooks.json
│ │ ├── task-complexity-report_tdd-phase-1-core-rails.json
│ │ ├── task-complexity-report_tdd-workflow-phase-0.json
│ │ ├── task-complexity-report_test-prd-tag.json
│ │ ├── task-complexity-report_tm-core-phase-1.json
│ │ ├── task-complexity-report.json
│ │ └── tm-core-complexity.json
│ ├── state.json
│ ├── tasks
│ │ ├── task_001_tm-start.txt
│ │ ├── task_002_tm-start.txt
│ │ ├── task_003_tm-start.txt
│ │ ├── task_004_tm-start.txt
│ │ ├── task_007_tm-start.txt
│ │ └── tasks.json
│ └── templates
│ ├── example_prd_rpg.md
│ └── example_prd.md
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── apps
│ ├── cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── command-registry.ts
│ │ │ ├── commands
│ │ │ │ ├── auth.command.ts
│ │ │ │ ├── autopilot
│ │ │ │ │ ├── abort.command.ts
│ │ │ │ │ ├── commit.command.ts
│ │ │ │ │ ├── complete.command.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next.command.ts
│ │ │ │ │ ├── resume.command.ts
│ │ │ │ │ ├── shared.ts
│ │ │ │ │ ├── start.command.ts
│ │ │ │ │ └── status.command.ts
│ │ │ │ ├── briefs.command.ts
│ │ │ │ ├── context.command.ts
│ │ │ │ ├── export.command.ts
│ │ │ │ ├── list.command.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── custom-providers.ts
│ │ │ │ │ ├── fetchers.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompts.ts
│ │ │ │ │ ├── setup.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── next.command.ts
│ │ │ │ ├── set-status.command.ts
│ │ │ │ ├── show.command.ts
│ │ │ │ ├── start.command.ts
│ │ │ │ └── tags.command.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── model-management.ts
│ │ │ ├── types
│ │ │ │ └── tag-management.d.ts
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── cardBox.component.ts
│ │ │ │ │ ├── dashboard.component.ts
│ │ │ │ │ ├── header.component.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next-task.component.ts
│ │ │ │ │ ├── suggested-steps.component.ts
│ │ │ │ │ └── task-detail.component.ts
│ │ │ │ ├── display
│ │ │ │ │ ├── messages.ts
│ │ │ │ │ └── tables.ts
│ │ │ │ ├── formatters
│ │ │ │ │ ├── complexity-formatters.ts
│ │ │ │ │ ├── dependency-formatters.ts
│ │ │ │ │ ├── priority-formatters.ts
│ │ │ │ │ ├── status-formatters.spec.ts
│ │ │ │ │ └── status-formatters.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── layout
│ │ │ │ ├── helpers.spec.ts
│ │ │ │ └── helpers.ts
│ │ │ └── utils
│ │ │ ├── auth-helpers.ts
│ │ │ ├── auto-update.ts
│ │ │ ├── brief-selection.ts
│ │ │ ├── display-helpers.ts
│ │ │ ├── error-handler.ts
│ │ │ ├── index.ts
│ │ │ ├── project-root.ts
│ │ │ ├── task-status.ts
│ │ │ ├── ui.spec.ts
│ │ │ └── ui.ts
│ │ ├── tests
│ │ │ ├── integration
│ │ │ │ └── commands
│ │ │ │ └── autopilot
│ │ │ │ └── workflow.test.ts
│ │ │ └── unit
│ │ │ ├── commands
│ │ │ │ ├── autopilot
│ │ │ │ │ └── shared.test.ts
│ │ │ │ ├── list.command.spec.ts
│ │ │ │ └── show.command.spec.ts
│ │ │ └── ui
│ │ │ └── dashboard.component.spec.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── docs
│ │ ├── archive
│ │ │ ├── ai-client-utils-example.mdx
│ │ │ ├── ai-development-workflow.mdx
│ │ │ ├── command-reference.mdx
│ │ │ ├── configuration.mdx
│ │ │ ├── cursor-setup.mdx
│ │ │ ├── examples.mdx
│ │ │ └── Installation.mdx
│ │ ├── best-practices
│ │ │ ├── advanced-tasks.mdx
│ │ │ ├── configuration-advanced.mdx
│ │ │ └── index.mdx
│ │ ├── capabilities
│ │ │ ├── cli-root-commands.mdx
│ │ │ ├── index.mdx
│ │ │ ├── mcp.mdx
│ │ │ ├── rpg-method.mdx
│ │ │ └── task-structure.mdx
│ │ ├── CHANGELOG.md
│ │ ├── command-reference.mdx
│ │ ├── configuration.mdx
│ │ ├── docs.json
│ │ ├── favicon.svg
│ │ ├── getting-started
│ │ │ ├── api-keys.mdx
│ │ │ ├── contribute.mdx
│ │ │ ├── faq.mdx
│ │ │ └── quick-start
│ │ │ ├── configuration-quick.mdx
│ │ │ ├── execute-quick.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── moving-forward.mdx
│ │ │ ├── prd-quick.mdx
│ │ │ ├── quick-start.mdx
│ │ │ ├── requirements.mdx
│ │ │ ├── rules-quick.mdx
│ │ │ └── tasks-quick.mdx
│ │ ├── introduction.mdx
│ │ ├── licensing.md
│ │ ├── logo
│ │ │ ├── dark.svg
│ │ │ ├── light.svg
│ │ │ └── task-master-logo.png
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── style.css
│ │ ├── tdd-workflow
│ │ │ ├── ai-agent-integration.mdx
│ │ │ └── quickstart.mdx
│ │ ├── vercel.json
│ │ └── whats-new.mdx
│ ├── extension
│ │ ├── .vscodeignore
│ │ ├── assets
│ │ │ ├── banner.png
│ │ │ ├── icon-dark.svg
│ │ │ ├── icon-light.svg
│ │ │ ├── icon.png
│ │ │ ├── screenshots
│ │ │ │ ├── kanban-board.png
│ │ │ │ └── task-details.png
│ │ │ └── sidebar-icon.svg
│ │ ├── CHANGELOG.md
│ │ ├── components.json
│ │ ├── docs
│ │ │ ├── extension-CI-setup.md
│ │ │ └── extension-development-guide.md
│ │ ├── esbuild.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── package.mjs
│ │ ├── package.publish.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── components
│ │ │ │ ├── ConfigView.tsx
│ │ │ │ ├── constants.ts
│ │ │ │ ├── TaskDetails
│ │ │ │ │ ├── AIActionsSection.tsx
│ │ │ │ │ ├── DetailsSection.tsx
│ │ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ │ ├── SubtasksSection.tsx
│ │ │ │ │ ├── TaskMetadataSidebar.tsx
│ │ │ │ │ └── useTaskDetails.ts
│ │ │ │ ├── TaskDetailsView.tsx
│ │ │ │ ├── TaskMasterLogo.tsx
│ │ │ │ └── ui
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── breadcrumb.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── collapsible.tsx
│ │ │ │ ├── CollapsibleSection.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── scroll-area.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── shadcn-io
│ │ │ │ │ └── kanban
│ │ │ │ │ └── index.tsx
│ │ │ │ └── textarea.tsx
│ │ │ ├── extension.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── utils.ts
│ │ │ ├── services
│ │ │ │ ├── config-service.ts
│ │ │ │ ├── error-handler.ts
│ │ │ │ ├── notification-preferences.ts
│ │ │ │ ├── polling-service.ts
│ │ │ │ ├── polling-strategies.ts
│ │ │ │ ├── sidebar-webview-manager.ts
│ │ │ │ ├── task-repository.ts
│ │ │ │ ├── terminal-manager.ts
│ │ │ │ └── webview-manager.ts
│ │ │ ├── test
│ │ │ │ └── extension.test.ts
│ │ │ ├── utils
│ │ │ │ ├── configManager.ts
│ │ │ │ ├── connectionManager.ts
│ │ │ │ ├── errorHandler.ts
│ │ │ │ ├── event-emitter.ts
│ │ │ │ ├── logger.ts
│ │ │ │ ├── mcpClient.ts
│ │ │ │ ├── notificationPreferences.ts
│ │ │ │ └── task-master-api
│ │ │ │ ├── cache
│ │ │ │ │ └── cache-manager.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mcp-client.ts
│ │ │ │ ├── transformers
│ │ │ │ │ └── task-transformer.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ └── webview
│ │ │ ├── App.tsx
│ │ │ ├── components
│ │ │ │ ├── AppContent.tsx
│ │ │ │ ├── EmptyState.tsx
│ │ │ │ ├── ErrorBoundary.tsx
│ │ │ │ ├── PollingStatus.tsx
│ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ ├── SidebarView.tsx
│ │ │ │ ├── TagDropdown.tsx
│ │ │ │ ├── TaskCard.tsx
│ │ │ │ ├── TaskEditModal.tsx
│ │ │ │ ├── TaskMasterKanban.tsx
│ │ │ │ ├── ToastContainer.tsx
│ │ │ │ └── ToastNotification.tsx
│ │ │ ├── constants
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ └── VSCodeContext.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useTaskQueries.ts
│ │ │ │ ├── useVSCodeMessages.ts
│ │ │ │ └── useWebviewHeight.ts
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── providers
│ │ │ │ └── QueryProvider.tsx
│ │ │ ├── reducers
│ │ │ │ └── appReducer.ts
│ │ │ ├── sidebar.tsx
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ └── utils
│ │ │ ├── logger.ts
│ │ │ └── toast.ts
│ │ └── tsconfig.json
│ └── mcp
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── tools
│ │ ├── autopilot
│ │ │ ├── abort.tool.ts
│ │ │ ├── commit.tool.ts
│ │ │ ├── complete.tool.ts
│ │ │ ├── finalize.tool.ts
│ │ │ ├── index.ts
│ │ │ ├── next.tool.ts
│ │ │ ├── resume.tool.ts
│ │ │ ├── start.tool.ts
│ │ │ └── status.tool.ts
│ │ ├── README-ZOD-V3.md
│ │ └── tasks
│ │ ├── get-task.tool.ts
│ │ ├── get-tasks.tool.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── assets
│ ├── .windsurfrules
│ ├── AGENTS.md
│ ├── claude
│ │ └── TM_COMMANDS_GUIDE.md
│ ├── config.json
│ ├── env.example
│ ├── example_prd_rpg.txt
│ ├── example_prd.txt
│ ├── GEMINI.md
│ ├── gitignore
│ ├── kiro-hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── roocode
│ │ ├── .roo
│ │ │ ├── rules-architect
│ │ │ │ └── architect-rules
│ │ │ ├── rules-ask
│ │ │ │ └── ask-rules
│ │ │ ├── rules-code
│ │ │ │ └── code-rules
│ │ │ ├── rules-debug
│ │ │ │ └── debug-rules
│ │ │ ├── rules-orchestrator
│ │ │ │ └── orchestrator-rules
│ │ │ └── rules-test
│ │ │ └── test-rules
│ │ └── .roomodes
│ ├── rules
│ │ ├── cursor_rules.mdc
│ │ ├── dev_workflow.mdc
│ │ ├── self_improve.mdc
│ │ ├── taskmaster_hooks_workflow.mdc
│ │ └── taskmaster.mdc
│ └── scripts_README.md
├── bin
│ └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE_CODE_PLUGIN.md
├── CLAUDE.md
├── context
│ ├── chats
│ │ ├── add-task-dependencies-1.md
│ │ └── max-min-tokens.txt.md
│ ├── fastmcp-core.txt
│ ├── fastmcp-docs.txt
│ ├── MCP_INTEGRATION.md
│ ├── mcp-js-sdk-docs.txt
│ ├── mcp-protocol-repo.txt
│ ├── mcp-protocol-schema-03262025.json
│ └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│ ├── claude-code-integration.md
│ ├── CLI-COMMANDER-PATTERN.md
│ ├── command-reference.md
│ ├── configuration.md
│ ├── contributor-docs
│ │ ├── testing-roo-integration.md
│ │ └── worktree-setup.md
│ ├── cross-tag-task-movement.md
│ ├── examples
│ │ ├── claude-code-usage.md
│ │ └── codex-cli-usage.md
│ ├── examples.md
│ ├── licensing.md
│ ├── mcp-provider-guide.md
│ ├── mcp-provider.md
│ ├── migration-guide.md
│ ├── models.md
│ ├── providers
│ │ ├── codex-cli.md
│ │ └── gemini-cli.md
│ ├── README.md
│ ├── scripts
│ │ └── models-json-to-markdown.js
│ ├── task-structure.md
│ └── tutorial.md
├── images
│ ├── hamster-hiring.png
│ └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│ ├── server.js
│ └── src
│ ├── core
│ │ ├── __tests__
│ │ │ └── context-manager.test.js
│ │ ├── context-manager.js
│ │ ├── direct-functions
│ │ │ ├── add-dependency.js
│ │ │ ├── add-subtask.js
│ │ │ ├── add-tag.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── cache-stats.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── complexity-report.js
│ │ │ ├── copy-tag.js
│ │ │ ├── create-tag-from-branch.js
│ │ │ ├── delete-tag.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── fix-dependencies.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── initialize-project.js
│ │ │ ├── list-tags.js
│ │ │ ├── models.js
│ │ │ ├── move-task-cross-tag.js
│ │ │ ├── move-task.js
│ │ │ ├── next-task.js
│ │ │ ├── parse-prd.js
│ │ │ ├── remove-dependency.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── rename-tag.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── rules.js
│ │ │ ├── scope-down.js
│ │ │ ├── scope-up.js
│ │ │ ├── set-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ ├── update-tasks.js
│ │ │ ├── use-tag.js
│ │ │ └── validate-dependencies.js
│ │ ├── task-master-core.js
│ │ └── utils
│ │ ├── env-utils.js
│ │ └── path-utils.js
│ ├── custom-sdk
│ │ ├── errors.js
│ │ ├── index.js
│ │ ├── json-extractor.js
│ │ ├── language-model.js
│ │ ├── message-converter.js
│ │ └── schema-converter.js
│ ├── index.js
│ ├── logger.js
│ ├── providers
│ │ └── mcp-provider.js
│ └── tools
│ ├── add-dependency.js
│ ├── add-subtask.js
│ ├── add-tag.js
│ ├── add-task.js
│ ├── analyze.js
│ ├── clear-subtasks.js
│ ├── complexity-report.js
│ ├── copy-tag.js
│ ├── delete-tag.js
│ ├── expand-all.js
│ ├── expand-task.js
│ ├── fix-dependencies.js
│ ├── generate.js
│ ├── get-operation-status.js
│ ├── index.js
│ ├── initialize-project.js
│ ├── list-tags.js
│ ├── models.js
│ ├── move-task.js
│ ├── next-task.js
│ ├── parse-prd.js
│ ├── README-ZOD-V3.md
│ ├── remove-dependency.js
│ ├── remove-subtask.js
│ ├── remove-task.js
│ ├── rename-tag.js
│ ├── research.js
│ ├── response-language.js
│ ├── rules.js
│ ├── scope-down.js
│ ├── scope-up.js
│ ├── set-task-status.js
│ ├── tool-registry.js
│ ├── update-subtask.js
│ ├── update-task.js
│ ├── update.js
│ ├── use-tag.js
│ ├── utils.js
│ └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│ ├── ai-sdk-provider-grok-cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── errors.test.ts
│ │ │ ├── errors.ts
│ │ │ ├── grok-cli-language-model.ts
│ │ │ ├── grok-cli-provider.test.ts
│ │ │ ├── grok-cli-provider.ts
│ │ │ ├── index.ts
│ │ │ ├── json-extractor.test.ts
│ │ │ ├── json-extractor.ts
│ │ │ ├── message-converter.test.ts
│ │ │ ├── message-converter.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── build-config
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ └── tsdown.base.ts
│ │ └── tsconfig.json
│ ├── claude-code-plugin
│ │ ├── .claude-plugin
│ │ │ └── plugin.json
│ │ ├── .gitignore
│ │ ├── agents
│ │ │ ├── task-checker.md
│ │ │ ├── task-executor.md
│ │ │ └── task-orchestrator.md
│ │ ├── CHANGELOG.md
│ │ ├── commands
│ │ │ ├── add-dependency.md
│ │ │ ├── add-subtask.md
│ │ │ ├── add-task.md
│ │ │ ├── analyze-complexity.md
│ │ │ ├── analyze-project.md
│ │ │ ├── auto-implement-tasks.md
│ │ │ ├── command-pipeline.md
│ │ │ ├── complexity-report.md
│ │ │ ├── convert-task-to-subtask.md
│ │ │ ├── expand-all-tasks.md
│ │ │ ├── expand-task.md
│ │ │ ├── fix-dependencies.md
│ │ │ ├── generate-tasks.md
│ │ │ ├── help.md
│ │ │ ├── init-project-quick.md
│ │ │ ├── init-project.md
│ │ │ ├── install-taskmaster.md
│ │ │ ├── learn.md
│ │ │ ├── list-tasks-by-status.md
│ │ │ ├── list-tasks-with-subtasks.md
│ │ │ ├── list-tasks.md
│ │ │ ├── next-task.md
│ │ │ ├── parse-prd-with-research.md
│ │ │ ├── parse-prd.md
│ │ │ ├── project-status.md
│ │ │ ├── quick-install-taskmaster.md
│ │ │ ├── remove-all-subtasks.md
│ │ │ ├── remove-dependency.md
│ │ │ ├── remove-subtask.md
│ │ │ ├── remove-subtasks.md
│ │ │ ├── remove-task.md
│ │ │ ├── setup-models.md
│ │ │ ├── show-task.md
│ │ │ ├── smart-workflow.md
│ │ │ ├── sync-readme.md
│ │ │ ├── tm-main.md
│ │ │ ├── to-cancelled.md
│ │ │ ├── to-deferred.md
│ │ │ ├── to-done.md
│ │ │ ├── to-in-progress.md
│ │ │ ├── to-pending.md
│ │ │ ├── to-review.md
│ │ │ ├── update-single-task.md
│ │ │ ├── update-task.md
│ │ │ ├── update-tasks-from-id.md
│ │ │ ├── validate-dependencies.md
│ │ │ └── view-models.md
│ │ ├── mcp.json
│ │ └── package.json
│ ├── tm-bridge
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── add-tag-bridge.ts
│ │ │ ├── bridge-types.ts
│ │ │ ├── bridge-utils.ts
│ │ │ ├── expand-bridge.ts
│ │ │ ├── index.ts
│ │ │ ├── tags-bridge.ts
│ │ │ ├── update-bridge.ts
│ │ │ └── use-tag-bridge.ts
│ │ └── tsconfig.json
│ └── tm-core
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docs
│ │ └── listTasks-architecture.md
│ ├── package.json
│ ├── POC-STATUS.md
│ ├── README.md
│ ├── src
│ │ ├── common
│ │ │ ├── constants
│ │ │ │ ├── index.ts
│ │ │ │ ├── paths.ts
│ │ │ │ └── providers.ts
│ │ │ ├── errors
│ │ │ │ ├── index.ts
│ │ │ │ └── task-master-error.ts
│ │ │ ├── interfaces
│ │ │ │ ├── configuration.interface.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── storage.interface.ts
│ │ │ ├── logger
│ │ │ │ ├── factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── logger.spec.ts
│ │ │ │ └── logger.ts
│ │ │ ├── mappers
│ │ │ │ ├── TaskMapper.test.ts
│ │ │ │ └── TaskMapper.ts
│ │ │ ├── types
│ │ │ │ ├── database.types.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── legacy.ts
│ │ │ │ └── repository-types.ts
│ │ │ └── utils
│ │ │ ├── git-utils.ts
│ │ │ ├── id-generator.ts
│ │ │ ├── index.ts
│ │ │ ├── path-helpers.ts
│ │ │ ├── path-normalizer.spec.ts
│ │ │ ├── path-normalizer.ts
│ │ │ ├── project-root-finder.spec.ts
│ │ │ ├── project-root-finder.ts
│ │ │ ├── run-id-generator.spec.ts
│ │ │ └── run-id-generator.ts
│ │ ├── index.ts
│ │ ├── modules
│ │ │ ├── ai
│ │ │ │ ├── index.ts
│ │ │ │ ├── interfaces
│ │ │ │ │ └── ai-provider.interface.ts
│ │ │ │ └── providers
│ │ │ │ ├── base-provider.ts
│ │ │ │ └── index.ts
│ │ │ ├── auth
│ │ │ │ ├── auth-domain.spec.ts
│ │ │ │ ├── auth-domain.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── auth-manager.spec.ts
│ │ │ │ │ └── auth-manager.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── context-store.ts
│ │ │ │ │ ├── oauth-service.ts
│ │ │ │ │ ├── organization.service.ts
│ │ │ │ │ ├── supabase-session-storage.spec.ts
│ │ │ │ │ └── supabase-session-storage.ts
│ │ │ │ └── types.ts
│ │ │ ├── briefs
│ │ │ │ ├── briefs-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── brief-service.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils
│ │ │ │ └── url-parser.ts
│ │ │ ├── commands
│ │ │ │ └── index.ts
│ │ │ ├── config
│ │ │ │ ├── config-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── config-manager.spec.ts
│ │ │ │ │ └── config-manager.ts
│ │ │ │ └── services
│ │ │ │ ├── config-loader.service.spec.ts
│ │ │ │ ├── config-loader.service.ts
│ │ │ │ ├── config-merger.service.spec.ts
│ │ │ │ ├── config-merger.service.ts
│ │ │ │ ├── config-persistence.service.spec.ts
│ │ │ │ ├── config-persistence.service.ts
│ │ │ │ ├── environment-config-provider.service.spec.ts
│ │ │ │ ├── environment-config-provider.service.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── runtime-state-manager.service.spec.ts
│ │ │ │ └── runtime-state-manager.service.ts
│ │ │ ├── dependencies
│ │ │ │ └── index.ts
│ │ │ ├── execution
│ │ │ │ ├── executors
│ │ │ │ │ ├── base-executor.ts
│ │ │ │ │ ├── claude-executor.ts
│ │ │ │ │ └── executor-factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── executor-service.ts
│ │ │ │ └── types.ts
│ │ │ ├── git
│ │ │ │ ├── adapters
│ │ │ │ │ ├── git-adapter.test.ts
│ │ │ │ │ └── git-adapter.ts
│ │ │ │ ├── git-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── services
│ │ │ │ ├── branch-name-generator.spec.ts
│ │ │ │ ├── branch-name-generator.ts
│ │ │ │ ├── commit-message-generator.test.ts
│ │ │ │ ├── commit-message-generator.ts
│ │ │ │ ├── scope-detector.test.ts
│ │ │ │ ├── scope-detector.ts
│ │ │ │ ├── template-engine.test.ts
│ │ │ │ └── template-engine.ts
│ │ │ ├── integration
│ │ │ │ ├── clients
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── supabase-client.ts
│ │ │ │ ├── integration-domain.ts
│ │ │ │ └── services
│ │ │ │ ├── export.service.ts
│ │ │ │ ├── task-expansion.service.ts
│ │ │ │ └── task-retrieval.service.ts
│ │ │ ├── reports
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ └── complexity-report-manager.ts
│ │ │ │ └── types.ts
│ │ │ ├── storage
│ │ │ │ ├── adapters
│ │ │ │ │ ├── activity-logger.ts
│ │ │ │ │ ├── api-storage.ts
│ │ │ │ │ └── file-storage
│ │ │ │ │ ├── file-operations.ts
│ │ │ │ │ ├── file-storage.ts
│ │ │ │ │ ├── format-handler.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── path-resolver.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── storage-factory.ts
│ │ │ │ └── utils
│ │ │ │ └── api-client.ts
│ │ │ ├── tasks
│ │ │ │ ├── entities
│ │ │ │ │ └── task.entity.ts
│ │ │ │ ├── parser
│ │ │ │ │ └── index.ts
│ │ │ │ ├── repositories
│ │ │ │ │ ├── supabase
│ │ │ │ │ │ ├── dependency-fetcher.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── supabase-repository.ts
│ │ │ │ │ └── task-repository.interface.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── preflight-checker.service.ts
│ │ │ │ │ ├── tag.service.ts
│ │ │ │ │ ├── task-execution-service.ts
│ │ │ │ │ ├── task-loader.service.ts
│ │ │ │ │ └── task-service.ts
│ │ │ │ └── tasks-domain.ts
│ │ │ ├── ui
│ │ │ │ └── index.ts
│ │ │ └── workflow
│ │ │ ├── managers
│ │ │ │ ├── workflow-state-manager.spec.ts
│ │ │ │ └── workflow-state-manager.ts
│ │ │ ├── orchestrators
│ │ │ │ ├── workflow-orchestrator.test.ts
│ │ │ │ └── workflow-orchestrator.ts
│ │ │ ├── services
│ │ │ │ ├── test-result-validator.test.ts
│ │ │ │ ├── test-result-validator.ts
│ │ │ │ ├── test-result-validator.types.ts
│ │ │ │ ├── workflow-activity-logger.ts
│ │ │ │ └── workflow.service.ts
│ │ │ ├── types.ts
│ │ │ └── workflow-domain.ts
│ │ ├── subpath-exports.test.ts
│ │ ├── tm-core.ts
│ │ └── utils
│ │ └── time.utils.ts
│ ├── tests
│ │ ├── auth
│ │ │ └── auth-refresh.test.ts
│ │ ├── integration
│ │ │ ├── auth-token-refresh.test.ts
│ │ │ ├── list-tasks.test.ts
│ │ │ └── storage
│ │ │ └── activity-logger.test.ts
│ │ ├── mocks
│ │ │ └── mock-provider.ts
│ │ ├── setup.ts
│ │ └── unit
│ │ ├── base-provider.test.ts
│ │ ├── executor.test.ts
│ │ └── smoke.test.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│ ├── create-worktree.sh
│ ├── dev.js
│ ├── init.js
│ ├── list-worktrees.sh
│ ├── modules
│ │ ├── ai-services-unified.js
│ │ ├── bridge-utils.js
│ │ ├── commands.js
│ │ ├── config-manager.js
│ │ ├── dependency-manager.js
│ │ ├── index.js
│ │ ├── prompt-manager.js
│ │ ├── supported-models.json
│ │ ├── sync-readme.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── find-next-task.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── is-task-dependent.js
│ │ │ ├── list-tasks.js
│ │ │ ├── migrate.js
│ │ │ ├── models.js
│ │ │ ├── move-task.js
│ │ │ ├── parse-prd
│ │ │ │ ├── index.js
│ │ │ │ ├── parse-prd-config.js
│ │ │ │ ├── parse-prd-helpers.js
│ │ │ │ ├── parse-prd-non-streaming.js
│ │ │ │ ├── parse-prd-streaming.js
│ │ │ │ └── parse-prd.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── scope-adjustment.js
│ │ │ ├── set-task-status.js
│ │ │ ├── tag-management.js
│ │ │ ├── task-exists.js
│ │ │ ├── update-single-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ └── update-tasks.js
│ │ ├── task-manager.js
│ │ ├── ui.js
│ │ ├── update-config-tokens.js
│ │ ├── utils
│ │ │ ├── contextGatherer.js
│ │ │ ├── fuzzyTaskSearch.js
│ │ │ └── git-utils.js
│ │ └── utils.js
│ ├── task-complexity-report.json
│ ├── test-claude-errors.js
│ └── test-claude.js
├── sonar-project.properties
├── src
│ ├── ai-providers
│ │ ├── anthropic.js
│ │ ├── azure.js
│ │ ├── base-provider.js
│ │ ├── bedrock.js
│ │ ├── claude-code.js
│ │ ├── codex-cli.js
│ │ ├── gemini-cli.js
│ │ ├── google-vertex.js
│ │ ├── google.js
│ │ ├── grok-cli.js
│ │ ├── groq.js
│ │ ├── index.js
│ │ ├── lmstudio.js
│ │ ├── ollama.js
│ │ ├── openai-compatible.js
│ │ ├── openai.js
│ │ ├── openrouter.js
│ │ ├── perplexity.js
│ │ ├── xai.js
│ │ ├── zai-coding.js
│ │ └── zai.js
│ ├── constants
│ │ ├── commands.js
│ │ ├── paths.js
│ │ ├── profiles.js
│ │ ├── rules-actions.js
│ │ ├── task-priority.js
│ │ └── task-status.js
│ ├── profiles
│ │ ├── amp.js
│ │ ├── base-profile.js
│ │ ├── claude.js
│ │ ├── cline.js
│ │ ├── codex.js
│ │ ├── cursor.js
│ │ ├── gemini.js
│ │ ├── index.js
│ │ ├── kilo.js
│ │ ├── kiro.js
│ │ ├── opencode.js
│ │ ├── roo.js
│ │ ├── trae.js
│ │ ├── vscode.js
│ │ ├── windsurf.js
│ │ └── zed.js
│ ├── progress
│ │ ├── base-progress-tracker.js
│ │ ├── cli-progress-factory.js
│ │ ├── parse-prd-tracker.js
│ │ ├── progress-tracker-builder.js
│ │ └── tracker-ui.js
│ ├── prompts
│ │ ├── add-task.json
│ │ ├── analyze-complexity.json
│ │ ├── expand-task.json
│ │ ├── parse-prd.json
│ │ ├── README.md
│ │ ├── research.json
│ │ ├── schemas
│ │ │ ├── parameter.schema.json
│ │ │ ├── prompt-template.schema.json
│ │ │ ├── README.md
│ │ │ └── variant.schema.json
│ │ ├── update-subtask.json
│ │ ├── update-task.json
│ │ └── update-tasks.json
│ ├── provider-registry
│ │ └── index.js
│ ├── schemas
│ │ ├── add-task.js
│ │ ├── analyze-complexity.js
│ │ ├── base-schemas.js
│ │ ├── expand-task.js
│ │ ├── parse-prd.js
│ │ ├── registry.js
│ │ ├── update-subtask.js
│ │ ├── update-task.js
│ │ └── update-tasks.js
│ ├── task-master.js
│ ├── ui
│ │ ├── confirm.js
│ │ ├── indicators.js
│ │ └── parse-prd.js
│ └── utils
│ ├── asset-resolver.js
│ ├── create-mcp-config.js
│ ├── format.js
│ ├── getVersion.js
│ ├── logger-utils.js
│ ├── manage-gitignore.js
│ ├── path-utils.js
│ ├── profiles.js
│ ├── rule-transformer.js
│ ├── stream-parser.js
│ └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│ ├── e2e
│ │ ├── e2e_helpers.sh
│ │ ├── parse_llm_output.cjs
│ │ ├── run_e2e.sh
│ │ ├── run_fallback_verification.sh
│ │ └── test_llm_analysis.sh
│ ├── fixtures
│ │ ├── .taskmasterconfig
│ │ ├── sample-claude-response.js
│ │ ├── sample-prd.txt
│ │ └── sample-tasks.js
│ ├── helpers
│ │ └── tool-counts.js
│ ├── integration
│ │ ├── claude-code-error-handling.test.js
│ │ ├── claude-code-optional.test.js
│ │ ├── cli
│ │ │ ├── commands.test.js
│ │ │ ├── complex-cross-tag-scenarios.test.js
│ │ │ └── move-cross-tag.test.js
│ │ ├── manage-gitignore.test.js
│ │ ├── mcp-server
│ │ │ └── direct-functions.test.js
│ │ ├── move-task-cross-tag.integration.test.js
│ │ ├── move-task-simple.integration.test.js
│ │ ├── profiles
│ │ │ ├── amp-init-functionality.test.js
│ │ │ ├── claude-init-functionality.test.js
│ │ │ ├── cline-init-functionality.test.js
│ │ │ ├── codex-init-functionality.test.js
│ │ │ ├── cursor-init-functionality.test.js
│ │ │ ├── gemini-init-functionality.test.js
│ │ │ ├── opencode-init-functionality.test.js
│ │ │ ├── roo-files-inclusion.test.js
│ │ │ ├── roo-init-functionality.test.js
│ │ │ ├── rules-files-inclusion.test.js
│ │ │ ├── trae-init-functionality.test.js
│ │ │ ├── vscode-init-functionality.test.js
│ │ │ └── windsurf-init-functionality.test.js
│ │ └── providers
│ │ └── temperature-support.test.js
│ ├── manual
│ │ ├── progress
│ │ │ ├── parse-prd-analysis.js
│ │ │ ├── test-parse-prd.js
│ │ │ └── TESTING_GUIDE.md
│ │ └── prompts
│ │ ├── prompt-test.js
│ │ └── README.md
│ ├── README.md
│ ├── setup.js
│ └── unit
│ ├── ai-providers
│ │ ├── base-provider.test.js
│ │ ├── claude-code.test.js
│ │ ├── codex-cli.test.js
│ │ ├── gemini-cli.test.js
│ │ ├── lmstudio.test.js
│ │ ├── mcp-components.test.js
│ │ ├── openai-compatible.test.js
│ │ ├── openai.test.js
│ │ ├── provider-registry.test.js
│ │ ├── zai-coding.test.js
│ │ ├── zai-provider.test.js
│ │ ├── zai-schema-introspection.test.js
│ │ └── zai.test.js
│ ├── ai-services-unified.test.js
│ ├── commands.test.js
│ ├── config-manager.test.js
│ ├── config-manager.test.mjs
│ ├── dependency-manager.test.js
│ ├── init.test.js
│ ├── initialize-project.test.js
│ ├── kebab-case-validation.test.js
│ ├── manage-gitignore.test.js
│ ├── mcp
│ │ └── tools
│ │ ├── __mocks__
│ │ │ └── move-task.js
│ │ ├── add-task.test.js
│ │ ├── analyze-complexity.test.js
│ │ ├── expand-all.test.js
│ │ ├── get-tasks.test.js
│ │ ├── initialize-project.test.js
│ │ ├── move-task-cross-tag-options.test.js
│ │ ├── move-task-cross-tag.test.js
│ │ ├── remove-task.test.js
│ │ └── tool-registration.test.js
│ ├── mcp-providers
│ │ ├── mcp-components.test.js
│ │ └── mcp-provider.test.js
│ ├── parse-prd.test.js
│ ├── profiles
│ │ ├── amp-integration.test.js
│ │ ├── claude-integration.test.js
│ │ ├── cline-integration.test.js
│ │ ├── codex-integration.test.js
│ │ ├── cursor-integration.test.js
│ │ ├── gemini-integration.test.js
│ │ ├── kilo-integration.test.js
│ │ ├── kiro-integration.test.js
│ │ ├── mcp-config-validation.test.js
│ │ ├── opencode-integration.test.js
│ │ ├── profile-safety-check.test.js
│ │ ├── roo-integration.test.js
│ │ ├── rule-transformer-cline.test.js
│ │ ├── rule-transformer-cursor.test.js
│ │ ├── rule-transformer-gemini.test.js
│ │ ├── rule-transformer-kilo.test.js
│ │ ├── rule-transformer-kiro.test.js
│ │ ├── rule-transformer-opencode.test.js
│ │ ├── rule-transformer-roo.test.js
│ │ ├── rule-transformer-trae.test.js
│ │ ├── rule-transformer-vscode.test.js
│ │ ├── rule-transformer-windsurf.test.js
│ │ ├── rule-transformer-zed.test.js
│ │ ├── rule-transformer.test.js
│ │ ├── selective-profile-removal.test.js
│ │ ├── subdirectory-support.test.js
│ │ ├── trae-integration.test.js
│ │ ├── vscode-integration.test.js
│ │ ├── windsurf-integration.test.js
│ │ └── zed-integration.test.js
│ ├── progress
│ │ └── base-progress-tracker.test.js
│ ├── prompt-manager.test.js
│ ├── prompts
│ │ ├── expand-task-prompt.test.js
│ │ └── prompt-migration.test.js
│ ├── scripts
│ │ └── modules
│ │ ├── commands
│ │ │ ├── move-cross-tag.test.js
│ │ │ └── README.md
│ │ ├── dependency-manager
│ │ │ ├── circular-dependencies.test.js
│ │ │ ├── cross-tag-dependencies.test.js
│ │ │ └── fix-dependencies-command.test.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.test.js
│ │ │ ├── add-task.test.js
│ │ │ ├── analyze-task-complexity.test.js
│ │ │ ├── clear-subtasks.test.js
│ │ │ ├── complexity-report-tag-isolation.test.js
│ │ │ ├── expand-all-tasks.test.js
│ │ │ ├── expand-task.test.js
│ │ │ ├── find-next-task.test.js
│ │ │ ├── generate-task-files.test.js
│ │ │ ├── list-tasks.test.js
│ │ │ ├── models-baseurl.test.js
│ │ │ ├── move-task-cross-tag.test.js
│ │ │ ├── move-task.test.js
│ │ │ ├── parse-prd-schema.test.js
│ │ │ ├── parse-prd.test.js
│ │ │ ├── remove-subtask.test.js
│ │ │ ├── remove-task.test.js
│ │ │ ├── research.test.js
│ │ │ ├── scope-adjustment.test.js
│ │ │ ├── set-task-status.test.js
│ │ │ ├── setup.js
│ │ │ ├── update-single-task-status.test.js
│ │ │ ├── update-subtask-by-id.test.js
│ │ │ ├── update-task-by-id.test.js
│ │ │ └── update-tasks.test.js
│ │ ├── ui
│ │ │ └── cross-tag-error-display.test.js
│ │ └── utils-tag-aware-paths.test.js
│ ├── task-finder.test.js
│ ├── task-manager
│ │ ├── clear-subtasks.test.js
│ │ ├── move-task.test.js
│ │ ├── tag-boundary.test.js
│ │ └── tag-management.test.js
│ ├── task-master.test.js
│ ├── ui
│ │ └── indicators.test.js
│ ├── ui.test.js
│ ├── utils-strip-ansi.test.js
│ └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
├── turbo.json
└── update-task-migration-plan.md
```
# Files
--------------------------------------------------------------------------------
/.github/workflows/extension-release.yml:
--------------------------------------------------------------------------------
```yaml
name: Extension Release
on:
push:
tags:
- "extension@*"
permissions:
contents: write
concurrency: extension-release-${{ github.ref }}
jobs:
publish-extension:
runs-on: ubuntu-latest
environment: extension-release
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Monorepo Dependencies
run: npm ci
timeout-minutes: 5
- name: Type Check Extension
working-directory: apps/extension
run: npm run typecheck
env:
FORCE_COLOR: 1
- name: Build Extension
working-directory: apps/extension
run: npm run build
env:
FORCE_COLOR: 1
- name: Package Extension
working-directory: apps/extension
run: npm run package
env:
FORCE_COLOR: 1
- name: Create VSIX Package
working-directory: apps/extension/vsix-build
run: npx vsce package --no-dependencies
env:
FORCE_COLOR: 1
- name: Get VSIX filename
id: vsix-info
working-directory: apps/extension/vsix-build
run: |
VSIX_FILE=$(find . -maxdepth 1 -name "*.vsix" -type f | head -n1 | xargs basename)
if [ -z "$VSIX_FILE" ]; then
echo "Error: No VSIX file found"
exit 1
fi
echo "vsix-filename=$VSIX_FILE" >> "$GITHUB_OUTPUT"
echo "Found VSIX: $VSIX_FILE"
- name: Publish to VS Code Marketplace
working-directory: apps/extension/vsix-build
run: npx vsce publish --packagePath "${{ steps.vsix-info.outputs.vsix-filename }}"
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
FORCE_COLOR: 1
- name: Install Open VSX CLI
run: npm install -g ovsx
- name: Publish to Open VSX Registry
working-directory: apps/extension/vsix-build
run: ovsx publish "${{ steps.vsix-info.outputs.vsix-filename }}"
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
FORCE_COLOR: 1
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: extension-release-${{ github.ref_name }}
path: |
apps/extension/vsix-build/*.vsix
apps/extension/dist/
retention-days: 90
notify-success:
needs: publish-extension
if: success()
runs-on: ubuntu-latest
steps:
- name: Success Notification
run: |
echo "🎉 Extension ${{ github.ref_name }} successfully published!"
echo "📦 Available on VS Code Marketplace"
echo "🌍 Available on Open VSX Registry"
echo "🏷️ GitHub release created: ${{ github.ref_name }}"
```
--------------------------------------------------------------------------------
/apps/cli/src/commands/autopilot/status.command.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Status Command - Show workflow progress
*/
import { WorkflowOrchestrator } from '@tm/core';
import { Command } from 'commander';
import {
AutopilotBaseOptions,
OutputFormatter,
hasWorkflowState,
loadWorkflowState
} from './shared.js';
import { getProjectRoot } from '../../utils/project-root.js';
type StatusOptions = AutopilotBaseOptions;
/**
* Status Command - Show current workflow status
*/
export class StatusCommand extends Command {
constructor() {
super('status');
this.description('Show current TDD workflow status and progress').action(
async (options: StatusOptions) => {
await this.execute(options);
}
);
}
private async execute(options: StatusOptions): Promise<void> {
// Inherit parent options
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
const mergedOptions: StatusOptions = {
...parentOpts,
...options,
projectRoot: getProjectRoot(
options.projectRoot || parentOpts?.projectRoot
)
};
const formatter = new OutputFormatter(mergedOptions.json || false);
try {
// Check for workflow state
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
if (!hasState) {
formatter.error('No active workflow', {
suggestion: 'Start a workflow with: autopilot start <taskId>'
});
process.exit(1);
}
// Load state
const state = await loadWorkflowState(mergedOptions.projectRoot!);
if (!state) {
formatter.error('Failed to load workflow state');
process.exit(1);
}
// Restore orchestrator
const orchestrator = new WorkflowOrchestrator(state.context);
orchestrator.restoreState(state);
// Get status information
const phase = orchestrator.getCurrentPhase();
const tddPhase = orchestrator.getCurrentTDDPhase();
const progress = orchestrator.getProgress();
const currentSubtask = orchestrator.getCurrentSubtask();
const errors = state.context.errors ?? [];
// Build status output
const status = {
taskId: state.context.taskId,
phase,
tddPhase,
branchName: state.context.branchName,
progress: {
completed: progress.completed,
total: progress.total,
current: progress.current,
percentage: progress.percentage
},
currentSubtask: currentSubtask
? {
id: currentSubtask.id,
title: currentSubtask.title,
status: currentSubtask.status,
attempts: currentSubtask.attempts,
maxAttempts: currentSubtask.maxAttempts
}
: null,
subtasks: state.context.subtasks.map((st) => ({
id: st.id,
title: st.title,
status: st.status,
attempts: st.attempts
})),
errors: errors.length > 0 ? errors : undefined,
metadata: state.context.metadata
};
if (mergedOptions.json) {
formatter.output(status);
} else {
formatter.success('Workflow status', status);
}
} catch (error) {
formatter.error((error as Error).message);
if (mergedOptions.verbose) {
console.error((error as Error).stack);
}
process.exit(1);
}
}
}
```
--------------------------------------------------------------------------------
/apps/cli/src/commands/autopilot/resume.command.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Resume Command - Restore and resume TDD workflow
*/
import { WorkflowOrchestrator } from '@tm/core';
import { Command } from 'commander';
import {
AutopilotBaseOptions,
OutputFormatter,
hasWorkflowState,
loadWorkflowState
} from './shared.js';
import { getProjectRoot } from '../../utils/project-root.js';
type ResumeOptions = AutopilotBaseOptions;
/**
* Resume Command - Restore workflow from saved state
*/
export class ResumeCommand extends Command {
constructor() {
super('resume');
this.description('Resume a previously started TDD workflow').action(
async (options: ResumeOptions) => {
await this.execute(options);
}
);
}
private async execute(options: ResumeOptions): Promise<void> {
// Inherit parent options (autopilot command)
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
const mergedOptions: ResumeOptions = {
...parentOpts,
...options,
projectRoot: getProjectRoot(
options.projectRoot || parentOpts?.projectRoot
)
};
const formatter = new OutputFormatter(mergedOptions.json || false);
try {
// Check for workflow state
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
if (!hasState) {
formatter.error('No workflow state found', {
suggestion: 'Start a new workflow with: autopilot start <taskId>'
});
process.exit(1);
}
// Load state
formatter.info('Loading workflow state...');
const state = await loadWorkflowState(mergedOptions.projectRoot!);
if (!state) {
formatter.error('Failed to load workflow state');
process.exit(1);
}
// Validate state can be resumed
const orchestrator = new WorkflowOrchestrator(state.context);
if (!orchestrator.canResumeFromState(state)) {
formatter.error('Invalid workflow state', {
suggestion:
'State file may be corrupted. Consider starting a new workflow.'
});
process.exit(1);
}
// Restore state
orchestrator.restoreState(state);
// Re-enable auto-persistence
const { saveWorkflowState } = await import('./shared.js');
orchestrator.enableAutoPersist(async (newState) => {
await saveWorkflowState(mergedOptions.projectRoot!, newState);
});
// Get progress
const progress = orchestrator.getProgress();
const currentSubtask = orchestrator.getCurrentSubtask();
// Output success
formatter.success('Workflow resumed', {
taskId: state.context.taskId,
phase: orchestrator.getCurrentPhase(),
tddPhase: orchestrator.getCurrentTDDPhase(),
branchName: state.context.branchName,
progress: {
completed: progress.completed,
total: progress.total,
percentage: progress.percentage
},
currentSubtask: currentSubtask
? {
id: currentSubtask.id,
title: currentSubtask.title,
attempts: currentSubtask.attempts
}
: null
});
} catch (error) {
formatter.error((error as Error).message);
if (mergedOptions.verbose) {
console.error((error as Error).stack);
}
process.exit(1);
}
}
}
```
--------------------------------------------------------------------------------
/tests/integration/providers/temperature-support.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Integration Tests for Provider Temperature Support
*
* This test suite verifies that all providers correctly declare their
* temperature support capabilities. CLI providers should have
* supportsTemperature = false, while standard API providers should
* have supportsTemperature = true.
*
* These tests are separated from unit tests to avoid coupling
* base provider tests with concrete provider implementations.
*/
import { ClaudeCodeProvider } from '../../../src/ai-providers/claude-code.js';
import { CodexCliProvider } from '../../../src/ai-providers/codex-cli.js';
import { GeminiCliProvider } from '../../../src/ai-providers/gemini-cli.js';
import { GrokCliProvider } from '../../../src/ai-providers/grok-cli.js';
import { AnthropicAIProvider } from '../../../src/ai-providers/anthropic.js';
import { OpenAIProvider } from '../../../src/ai-providers/openai.js';
import { GoogleAIProvider } from '../../../src/ai-providers/google.js';
import { PerplexityAIProvider } from '../../../src/ai-providers/perplexity.js';
import { XAIProvider } from '../../../src/ai-providers/xai.js';
import { GroqProvider } from '../../../src/ai-providers/groq.js';
import { OpenRouterAIProvider } from '../../../src/ai-providers/openrouter.js';
import { OllamaAIProvider } from '../../../src/ai-providers/ollama.js';
import { BedrockAIProvider } from '../../../src/ai-providers/bedrock.js';
import { AzureProvider } from '../../../src/ai-providers/azure.js';
import { VertexAIProvider } from '../../../src/ai-providers/google-vertex.js';
describe('Provider Temperature Support', () => {
describe('CLI Providers', () => {
it('should verify CLI providers have supportsTemperature = false', () => {
expect(new ClaudeCodeProvider().supportsTemperature).toBe(false);
expect(new CodexCliProvider().supportsTemperature).toBe(false);
expect(new GeminiCliProvider().supportsTemperature).toBe(false);
expect(new GrokCliProvider().supportsTemperature).toBe(false);
});
});
describe('Standard API Providers', () => {
it('should verify standard providers have supportsTemperature = true', () => {
expect(new AnthropicAIProvider().supportsTemperature).toBe(true);
expect(new OpenAIProvider().supportsTemperature).toBe(true);
expect(new GoogleAIProvider().supportsTemperature).toBe(true);
expect(new PerplexityAIProvider().supportsTemperature).toBe(true);
expect(new XAIProvider().supportsTemperature).toBe(true);
expect(new GroqProvider().supportsTemperature).toBe(true);
expect(new OpenRouterAIProvider().supportsTemperature).toBe(true);
});
});
describe('Special Case Providers', () => {
it('should verify Ollama provider has supportsTemperature = true', () => {
expect(new OllamaAIProvider().supportsTemperature).toBe(true);
});
it('should verify cloud providers have supportsTemperature = true', () => {
expect(new BedrockAIProvider().supportsTemperature).toBe(true);
expect(new AzureProvider().supportsTemperature).toBe(true);
expect(new VertexAIProvider().supportsTemperature).toBe(true);
});
});
});
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/update-task.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/update-task.js
* Tool to update a single task by ID with new information
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { updateTaskByIdDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the update-task tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerUpdateTaskTool(server) {
server.addTool({
name: 'update_task',
description:
'Updates a single task by ID with new information or context provided in the prompt.',
parameters: z.object({
id: z
.string() // ID can be number or string like "1.2"
.describe(
"ID of the task (e.g., '15') to update. Subtasks are supported using the update-subtask tool."
),
prompt: z
.string()
.describe('New information or context to incorporate into the task'),
research: z
.boolean()
.optional()
.describe('Use Perplexity AI for research-backed updates'),
append: z
.boolean()
.optional()
.describe(
'Append timestamped information to task details instead of full update'
),
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
const toolName = 'update_task';
try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(
`Executing ${toolName} tool with args: ${JSON.stringify(args)}`
);
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`);
} catch (error) {
log.error(`${toolName}: Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// 3. Call Direct Function - Include projectRoot
const result = await updateTaskByIdDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id,
prompt: args.prompt,
research: args.research,
append: args.append,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
// 4. Handle Result
log.info(
`${toolName}: Direct function result: success=${result.success}`
);
return handleApiResult(
result,
log,
'Error updating task',
undefined,
args.projectRoot
);
} catch (error) {
log.error(
`Critical error in ${toolName} tool execute: ${error.message}`
);
return createErrorResponse(
`Internal tool error (${toolName}): ${error.message}`
);
}
})
});
}
```
--------------------------------------------------------------------------------
/src/prompts/schemas/prompt-template.schema.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/eyaltoledano/claude-task-master/blob/main/src/prompts/schemas/prompt-template.schema.json",
"version": "1.0.0",
"title": "Task Master Prompt Template",
"description": "Schema for Task Master AI prompt template files",
"type": "object",
"required": ["id", "version", "description", "prompts"],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9-]+$",
"description": "Unique identifier for the prompt template"
},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$",
"description": "Semantic version of the prompt template"
},
"description": {
"type": "string",
"minLength": 1,
"description": "Brief description of what this prompt does"
},
"metadata": {
"$ref": "#/definitions/metadata"
},
"parameters": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/parameter"
}
},
"prompts": {
"type": "object",
"properties": {
"default": {
"$ref": "#/definitions/promptVariant"
}
},
"additionalProperties": {
"$ref": "#/definitions/conditionalPromptVariant"
}
}
},
"definitions": {
"parameter": {
"type": "object",
"required": ["type", "description"],
"properties": {
"type": {
"type": "string",
"enum": ["string", "number", "boolean", "array", "object"]
},
"description": {
"type": "string",
"minLength": 1
},
"required": {
"type": "boolean",
"default": false
},
"default": {
"description": "Default value for optional parameters"
},
"enum": {
"type": "array",
"description": "Valid values for string parameters"
},
"pattern": {
"type": "string",
"description": "Regular expression pattern for string validation"
},
"minimum": {
"type": "number",
"description": "Minimum value for number parameters"
},
"maximum": {
"type": "number",
"description": "Maximum value for number parameters"
}
}
},
"promptVariant": {
"type": "object",
"required": ["system", "user"],
"properties": {
"system": {
"type": "string",
"minLength": 1
},
"user": {
"type": "string",
"minLength": 1
}
}
},
"conditionalPromptVariant": {
"allOf": [
{ "$ref": "#/definitions/promptVariant" },
{
"type": "object",
"properties": {
"condition": {
"type": "string",
"description": "JavaScript expression for variant selection"
}
}
}
]
},
"metadata": {
"type": "object",
"properties": {
"author": { "type": "string" },
"created": { "type": "string", "format": "date-time" },
"updated": { "type": "string", "format": "date-time" },
"tags": {
"type": "array",
"items": { "type": "string" }
},
"category": {
"type": "string",
"enum": [
"task",
"analysis",
"research",
"parsing",
"update",
"expansion"
]
}
}
}
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/generate-task-files.js:
--------------------------------------------------------------------------------
```javascript
/**
* generate-task-files.js
* Direct function implementation for generating task files from tasks.json
*/
import { generateTaskFiles } from '../../../../scripts/modules/task-manager.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for generateTaskFiles with error handling.
*
* @param {Object} args - Command arguments containing tasksJsonPath and outputDir.
* @param {string} args.tasksJsonPath - Path to the tasks.json file.
* @param {string} args.outputDir - Path to the output directory.
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function generateTaskFilesDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, outputDir, projectRoot, tag } = args;
try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
// Check if paths were provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage }
};
}
if (!outputDir) {
const errorMessage = 'outputDir is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage }
};
}
// Use the provided paths
const tasksPath = tasksJsonPath;
const resolvedOutputDir = outputDir;
log.info(`Generating task files from ${tasksPath} to ${resolvedOutputDir}`);
// Execute core generateTaskFiles function in a separate try/catch
try {
// Enable silent mode to prevent logs from being written to stdout
enableSilentMode();
// Pass projectRoot and tag so the core respects context
generateTaskFiles(tasksPath, resolvedOutputDir, {
projectRoot,
tag,
mcpLog: log
});
// Restore normal logging after task generation
disableSilentMode();
} catch (genError) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in generateTaskFiles: ${genError.message}`);
return {
success: false,
error: { code: 'GENERATE_FILES_ERROR', message: genError.message }
};
}
// Return success with file paths
return {
success: true,
data: {
message: `Successfully generated task files`,
tasksPath: tasksPath,
outputDir: resolvedOutputDir,
taskFiles:
'Individual task files have been generated in the output directory'
}
};
} catch (error) {
// Make sure to restore normal logging if an outer error occurs
disableSilentMode();
log.error(`Error generating task files: ${error.message}`);
return {
success: false,
error: {
code: 'GENERATE_TASKS_ERROR',
message: error.message || 'Unknown error generating task files'
}
};
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/models.js:
--------------------------------------------------------------------------------
```javascript
/**
* models.js
* MCP tool for managing AI model configurations
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { modelsDirect } from '../core/task-master-core.js';
/**
* Register the models tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerModelsTool(server) {
server.addTool({
name: 'models',
description:
'Get information about available AI models or set model configurations. Run without arguments to get the current model configuration and API key status for the selected model providers.',
parameters: z.object({
setMain: z
.string()
.optional()
.describe(
'Set the primary model for task generation/updates. Model provider API key is required in the MCP config ENV.'
),
setResearch: z
.string()
.optional()
.describe(
'Set the model for research-backed operations. Model provider API key is required in the MCP config ENV.'
),
setFallback: z
.string()
.optional()
.describe(
'Set the model to use if the primary fails. Model provider API key is required in the MCP config ENV.'
),
listAvailableModels: z
.boolean()
.optional()
.describe(
'List all available models not currently in use. Input/output costs values are in dollars (3 is $3.00).'
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
openrouter: z
.boolean()
.optional()
.describe('Indicates the set model ID is a custom OpenRouter model.'),
ollama: z
.boolean()
.optional()
.describe('Indicates the set model ID is a custom Ollama model.'),
bedrock: z
.boolean()
.optional()
.describe('Indicates the set model ID is a custom AWS Bedrock model.'),
azure: z
.boolean()
.optional()
.describe('Indicates the set model ID is a custom Azure OpenAI model.'),
vertex: z
.boolean()
.optional()
.describe(
'Indicates the set model ID is a custom Google Vertex AI model.'
),
'openai-compatible': z
.boolean()
.optional()
.describe(
'Indicates the set model ID is a custom OpenAI-compatible model. Requires baseURL parameter.'
),
baseURL: z
.string()
.optional()
.describe(
'Custom base URL for providers that support it (e.g., https://api.example.com/v1).'
)
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(`Starting models tool with args: ${JSON.stringify(args)}`);
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
const result = await modelsDirect(
{ ...args, projectRoot: args.projectRoot },
log,
{ session }
);
return handleApiResult(
result,
log,
'Error managing models',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in models tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/tests/unit/mcp/tools/__mocks__/move-task.js:
--------------------------------------------------------------------------------
```javascript
/**
* Mock for move-task module
* Provides mock implementations for testing scenarios
*/
// Mock the moveTask function from the core module
const mockMoveTask = jest
.fn()
.mockImplementation(
async (tasksPath, sourceId, destinationId, generateFiles, options) => {
// Simulate successful move operation
return {
success: true,
sourceId,
destinationId,
message: `Successfully moved task ${sourceId} to ${destinationId}`,
...options
};
}
);
// Mock the moveTaskDirect function
const mockMoveTaskDirect = jest
.fn()
.mockImplementation(async (args, log, context = {}) => {
// Validate required parameters
if (!args.sourceId) {
return {
success: false,
error: {
message: 'Source ID is required',
code: 'MISSING_SOURCE_ID'
}
};
}
if (!args.destinationId) {
return {
success: false,
error: {
message: 'Destination ID is required',
code: 'MISSING_DESTINATION_ID'
}
};
}
// Simulate successful move
return {
success: true,
data: {
sourceId: args.sourceId,
destinationId: args.destinationId,
message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`,
tag: args.tag,
projectRoot: args.projectRoot
}
};
});
// Mock the moveTaskCrossTagDirect function
const mockMoveTaskCrossTagDirect = jest
.fn()
.mockImplementation(async (args, log, context = {}) => {
// Validate required parameters
if (!args.sourceIds) {
return {
success: false,
error: {
message: 'Source IDs are required',
code: 'MISSING_SOURCE_IDS'
}
};
}
if (!args.sourceTag) {
return {
success: false,
error: {
message: 'Source tag is required for cross-tag moves',
code: 'MISSING_SOURCE_TAG'
}
};
}
if (!args.targetTag) {
return {
success: false,
error: {
message: 'Target tag is required for cross-tag moves',
code: 'MISSING_TARGET_TAG'
}
};
}
if (args.sourceTag === args.targetTag) {
return {
success: false,
error: {
message: `Source and target tags are the same ("${args.sourceTag}")`,
code: 'SAME_SOURCE_TARGET_TAG'
}
};
}
// Simulate successful cross-tag move
return {
success: true,
data: {
sourceIds: args.sourceIds,
sourceTag: args.sourceTag,
targetTag: args.targetTag,
message: `Successfully moved tasks ${args.sourceIds} from ${args.sourceTag} to ${args.targetTag}`,
withDependencies: args.withDependencies || false,
ignoreDependencies: args.ignoreDependencies || false
}
};
});
// Mock the registerMoveTaskTool function
const mockRegisterMoveTaskTool = jest.fn().mockImplementation((server) => {
// Simulate tool registration
server.addTool({
name: 'move_task',
description: 'Move a task or subtask to a new position',
parameters: {},
execute: jest.fn()
});
});
// Export the mock functions
export {
mockMoveTask,
mockMoveTaskDirect,
mockMoveTaskCrossTagDirect,
mockRegisterMoveTaskTool
};
// Default export for the main moveTask function
export default mockMoveTask;
```
--------------------------------------------------------------------------------
/assets/GEMINI.md:
--------------------------------------------------------------------------------
```markdown
# Gemini CLI-Specific Instructions
> **Note:** This file works alongside `AGENTS.md` (generic AI agent instructions). AGENTS.md contains the core Task Master commands and workflows for all AI agents. This file contains only Gemini CLI-specific features and integrations.
## MCP Configuration for Gemini CLI
Configure Task Master MCP server in `~/.gemini/settings.json`:
```json
{
"mcpServers": {
"task-master-ai": {
"command": "npx",
"args": ["-y", "task-master-ai"]
}
}
}
```
**Note:** API keys are configured via `task-master models --setup`, not in MCP configuration.
## Gemini CLI-Specific Features
### Session Management
Built-in session commands:
- `/chat` - Start new conversation while keeping context
- `/checkpoint save <name>` - Save session state
- `/checkpoint load <name>` - Resume saved session
- `/memory show` - View loaded context
Both `AGENTS.md` and `GEMINI.md` are auto-loaded on every Gemini CLI session.
### Headless Mode for Automation
Non-interactive mode for scripts:
```bash
# Simple text response
gemini -p "What's the next task?"
# JSON output for parsing
gemini -p "List all pending tasks" --output-format json
# Stream events for long operations
gemini -p "Expand all tasks" --output-format stream-json
```
### Token Usage Monitoring
```bash
# In Gemini CLI session
/stats
# Shows: token usage, API costs, request counts
```
### Google Search Grounding
Leverage built-in Google Search as an alternative to Perplexity research mode:
- Best practices research
- Library documentation
- Security vulnerability checks
- Implementation patterns
## Important Differences from Other Agents
### No Slash Commands
Gemini CLI does not support custom slash commands (unlike Claude Code). Use natural language instead.
### No Tool Allowlist
Security is managed at the MCP level, not via agent configuration.
### Session Persistence
Use `/checkpoint` instead of git worktrees for managing multiple work contexts.
### Configuration Files
- Global: `~/.gemini/settings.json`
- Project: `.gemini/settings.json`
- **Not**: `.mcp.json` (that's for Claude Code)
## Recommended Model Configuration
For Gemini CLI users:
```bash
# Set Gemini as primary model
task-master models --set-main gemini-2.0-flash-exp
task-master models --set-fallback gemini-1.5-flash
# Optional: Use Perplexity for research (or rely on Google Search)
task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online
```
## Your Role with Gemini CLI
As a Gemini CLI assistant with Task Master:
1. **Use MCP tools naturally** - They integrate transparently in conversation
2. **Reference files with @** - Leverage Gemini's file inclusion
3. **Save checkpoints** - Offer to save state after significant progress
4. **Monitor usage** - Remind users about `/stats` for long sessions
5. **Use Google Search** - Leverage search grounding for research
**Key Principle:** Focus on natural conversation. Task Master MCP tools work seamlessly with Gemini CLI's interface.
---
*See AGENTS.md for complete Task Master commands, workflows, and best practices.*
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/parse-prd.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/parsePRD.js
* Tool to parse PRD document and generate tasks
*/
import { z } from 'zod';
import {
handleApiResult,
withNormalizedProjectRoot,
createErrorResponse,
checkProgressCapability
} from './utils.js';
import { parsePRDDirect } from '../core/task-master-core.js';
import {
PRD_FILE,
TASKMASTER_DOCS_DIR,
TASKMASTER_TASKS_FILE
} from '../../../src/constants/paths.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the parse_prd tool
* @param {Object} server - FastMCP server instance
*/
export function registerParsePRDTool(server) {
server.addTool({
name: 'parse_prd',
description: `Parse a Product Requirements Document (PRD) text file to automatically generate initial tasks. Reinitializing the project is not necessary to run this tool. It is recommended to run parse-prd after initializing the project and creating/importing a prd.txt file in the project root's ${TASKMASTER_DOCS_DIR} directory.`,
parameters: z.object({
input: z
.string()
.optional()
.default(PRD_FILE)
.describe('Absolute path to the PRD document file (.txt, .md, etc.)'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on'),
output: z
.string()
.optional()
.describe(
`Output path for tasks.json file (default: ${TASKMASTER_TASKS_FILE})`
),
numTasks: z
.string()
.optional()
.describe(
'Approximate number of top-level tasks to generate (default: 10). As the agent, if you have enough information, ensure to enter a number of tasks that would logically scale with project complexity. Setting to 0 will allow Taskmaster to determine the appropriate number of tasks based on the complexity of the PRD. Avoid entering numbers above 50 due to context window limitations.'
),
force: z
.boolean()
.optional()
.default(false)
.describe('Overwrite existing output file without prompting.'),
research: z
.boolean()
.optional()
.describe(
'Enable Taskmaster to use the research role for potentially more informed task generation. Requires appropriate API key.'
),
append: z
.boolean()
.optional()
.describe('Append generated tasks to existing file.')
}),
execute: withNormalizedProjectRoot(
async (args, { log, session, reportProgress }) => {
try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
const progressCapability = checkProgressCapability(
reportProgress,
log
);
const result = await parsePRDDirect(
{
...args,
tag: resolvedTag
},
log,
{ session, reportProgress: progressCapability }
);
return handleApiResult(
result,
log,
'Error parsing PRD',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in parse_prd: ${error.message}`);
return createErrorResponse(`Failed to parse PRD: ${error.message}`);
}
}
)
});
}
```
--------------------------------------------------------------------------------
/.github/workflows/pre-release.yml:
--------------------------------------------------------------------------------
```yaml
name: Pre-Release (RC)
on:
workflow_dispatch: # Allows manual triggering from GitHub UI/API
concurrency: pre-release-${{ github.ref_name }}
jobs:
rc:
runs-on: ubuntu-latest
# Only allow pre-releases on non-main branches
if: github.ref != 'refs/heads/main'
environment: extension-release
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
timeout-minutes: 2
- name: Enter RC mode (if not already in RC mode)
run: |
# Check if we're in pre-release mode with the "rc" tag
if [ -f .changeset/pre.json ]; then
MODE=$(jq -r '.mode' .changeset/pre.json 2>/dev/null || echo '')
TAG=$(jq -r '.tag' .changeset/pre.json 2>/dev/null || echo '')
if [ "$MODE" = "exit" ]; then
echo "Pre-release mode is in 'exit' state, re-entering RC mode..."
npx changeset pre enter rc
elif [ "$MODE" = "pre" ] && [ "$TAG" != "rc" ]; then
echo "In pre-release mode but with wrong tag ($TAG), switching to RC..."
npx changeset pre exit
npx changeset pre enter rc
elif [ "$MODE" = "pre" ] && [ "$TAG" = "rc" ]; then
echo "Already in RC pre-release mode"
else
echo "Unknown mode state: $MODE, entering RC mode..."
npx changeset pre enter rc
fi
else
echo "No pre.json found, entering RC mode..."
npx changeset pre enter rc
fi
- name: Version RC packages
run: npx changeset version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Run format
run: npm run format
env:
FORCE_COLOR: 1
- name: Build packages
run: npm run turbo:build
env:
NODE_ENV: production
FORCE_COLOR: 1
TM_PUBLIC_BASE_DOMAIN: ${{ secrets.TM_PUBLIC_BASE_DOMAIN }}
TM_PUBLIC_SUPABASE_URL: ${{ secrets.TM_PUBLIC_SUPABASE_URL }}
TM_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.TM_PUBLIC_SUPABASE_ANON_KEY }}
- name: Create Release Candidate Pull Request or Publish Release Candidate to npm
uses: changesets/action@v1
with:
publish: npx changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Commit & Push changes
uses: actions-js/push@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
message: "chore: rc version bump"
```
--------------------------------------------------------------------------------
/apps/extension/src/webview/components/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
```typescript
/**
* Error Boundary Component
*/
import React from 'react';
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
errorInfo?: React.ErrorInfo;
}
interface ErrorBoundaryProps {
children: React.ReactNode;
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}
export class ErrorBoundary extends React.Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('React Error Boundary caught:', error, errorInfo);
// Log to extension
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
// Send error to extension for centralized handling
if (window.acquireVsCodeApi) {
const vscode = window.acquireVsCodeApi();
vscode.postMessage({
type: 'reactError',
data: {
message: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
timestamp: Date.now()
}
});
}
}
render() {
if (this.state.hasError) {
return (
<div className="min-h-screen flex items-center justify-center bg-vscode-background">
<div className="max-w-md mx-auto text-center p-6">
<div className="w-16 h-16 mx-auto mb-4 text-red-400">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.732 19c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<h2 className="text-xl font-semibold text-vscode-foreground mb-2">
Something went wrong
</h2>
<p className="text-vscode-foreground/70 mb-4">
The Task Master Kanban board encountered an unexpected error.
</p>
<div className="space-y-2">
<button
onClick={() =>
this.setState({
hasError: false,
error: undefined,
errorInfo: undefined
})
}
className="w-full px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md transition-colors"
>
Try Again
</button>
<button
onClick={() => window.location.reload()}
className="w-full px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md transition-colors"
>
Reload Extension
</button>
</div>
{this.state.error && (
<details className="mt-4 text-left">
<summary className="text-sm text-vscode-foreground/50 cursor-pointer">
Error Details
</summary>
<pre className="mt-2 text-xs text-vscode-foreground/70 bg-vscode-input/30 p-2 rounded overflow-auto max-h-32">
{this.state.error.message}
{this.state.error.stack && `\n\n${this.state.error.stack}`}
</pre>
</details>
)}
</div>
</div>
);
}
return this.props.children;
}
}
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/auth/auth-domain.spec.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Auth Domain tests
*/
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { AuthDomain } from './auth-domain.js';
describe('AuthDomain', () => {
let authDomain: AuthDomain;
let originalEnv: NodeJS.ProcessEnv;
beforeEach(() => {
// Save original environment
originalEnv = { ...process.env };
authDomain = new AuthDomain();
});
afterEach(() => {
// Restore original environment
process.env = originalEnv;
vi.clearAllMocks();
});
describe('getBriefCreationUrl', () => {
it('should return null if no base domain is configured', () => {
// Clear environment variables
delete process.env.TM_BASE_DOMAIN;
delete process.env.TM_PUBLIC_BASE_DOMAIN;
// Create fresh instance with cleared env
const domain = new AuthDomain();
const url = domain.getBriefCreationUrl();
expect(url).toBeNull();
});
it('should return null if org slug is not available in context', () => {
// Set base domain but context will have no orgSlug
process.env.TM_BASE_DOMAIN = 'localhost:8080';
const domain = new AuthDomain();
// Mock getContext to return null (no context set)
vi.spyOn(domain, 'getContext').mockReturnValue(null);
const url = domain.getBriefCreationUrl();
expect(url).toBeNull();
});
it('should construct URL with http protocol for localhost', () => {
process.env.TM_BASE_DOMAIN = 'localhost:8080';
// Mock getContext to return a context with orgSlug
const domain = new AuthDomain();
vi.spyOn(domain, 'getContext').mockReturnValue({
orgSlug: 'test-org',
updatedAt: new Date().toISOString()
});
const url = domain.getBriefCreationUrl();
expect(url).toBe('http://localhost:8080/home/test-org/briefs/create');
});
it('should construct URL with https protocol for production domain', () => {
process.env.TM_BASE_DOMAIN = 'tryhamster.com';
const domain = new AuthDomain();
vi.spyOn(domain, 'getContext').mockReturnValue({
orgSlug: 'acme-corp',
updatedAt: new Date().toISOString()
});
const url = domain.getBriefCreationUrl();
expect(url).toBe('https://tryhamster.com/home/acme-corp/briefs/create');
});
it('should use existing protocol if base domain includes it', () => {
process.env.TM_BASE_DOMAIN = 'https://staging.hamster.dev';
const domain = new AuthDomain();
vi.spyOn(domain, 'getContext').mockReturnValue({
orgSlug: 'staging-org',
updatedAt: new Date().toISOString()
});
const url = domain.getBriefCreationUrl();
expect(url).toBe(
'https://staging.hamster.dev/home/staging-org/briefs/create'
);
});
it('should prefer TM_BASE_DOMAIN over TM_PUBLIC_BASE_DOMAIN', () => {
process.env.TM_BASE_DOMAIN = 'localhost:8080';
process.env.TM_PUBLIC_BASE_DOMAIN = 'tryhamster.com';
const domain = new AuthDomain();
vi.spyOn(domain, 'getContext').mockReturnValue({
orgSlug: 'my-org',
updatedAt: new Date().toISOString()
});
const url = domain.getBriefCreationUrl();
// Should use TM_BASE_DOMAIN (localhost), not TM_PUBLIC_BASE_DOMAIN
expect(url).toBe('http://localhost:8080/home/my-org/briefs/create');
});
});
});
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/rename-tag.js:
--------------------------------------------------------------------------------
```javascript
/**
* rename-tag.js
* Direct function implementation for renaming a tag
*/
import { renameTag } from '../../../../scripts/modules/task-manager/tag-management.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for renaming a tag with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.oldName - Current name of the tag to rename
* @param {string} args.newName - New name for the tag
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
* @param {string} [args.projectRoot] - Project root path
* @param {Object} log - Logger object
* @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function renameTagDirect(args, log, context = {}) {
// Destructure expected args
const { tasksJsonPath, oldName, newName, projectRoot } = args;
const { session } = context;
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
try {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('renameTagDirect called without tasksJsonPath');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Check required parameters
if (!oldName || typeof oldName !== 'string') {
log.error('Missing required parameter: oldName');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'Old tag name is required and must be a string'
}
};
}
if (!newName || typeof newName !== 'string') {
log.error('Missing required parameter: newName');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'New tag name is required and must be a string'
}
};
}
log.info(`Renaming tag from "${oldName}" to "${newName}"`);
// Call the renameTag function
const result = await renameTag(
tasksJsonPath,
oldName,
newName,
{}, // options (empty for now)
{
session,
mcpLog,
projectRoot
},
'json' // outputFormat - use 'json' to suppress CLI UI
);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
oldName: result.oldName,
newName: result.newName,
renamed: result.renamed,
taskCount: result.taskCount,
wasCurrentTag: result.wasCurrentTag,
message: `Successfully renamed tag from "${result.oldName}" to "${result.newName}"`
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in renameTagDirect: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'RENAME_TAG_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/apps/extension/src/webview/types/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Shared types for the webview application
*/
export interface TaskMasterTask {
id: string;
title: string;
description: string;
status: 'pending' | 'in-progress' | 'done' | 'deferred' | 'review';
priority: 'high' | 'medium' | 'low';
dependencies?: string[];
details?: string;
testStrategy?: string;
subtasks?: TaskMasterTask[];
complexityScore?: number;
}
export interface TaskUpdates {
title?: string;
description?: string;
details?: string;
priority?: TaskMasterTask['priority'];
testStrategy?: string;
dependencies?: string[];
}
export interface WebviewMessage {
type: string;
requestId?: string;
data?: any;
success?: boolean;
[key: string]: any;
}
export interface ToastNotification {
id: string;
type: 'success' | 'info' | 'warning' | 'error';
title: string;
message: string;
duration?: number;
}
export interface AppState {
tasks: TaskMasterTask[];
loading: boolean;
error?: string;
requestId: number;
isConnected: boolean;
connectionStatus: string;
editingTask?: { taskId: string | null; editData?: TaskMasterTask };
polling: {
isActive: boolean;
errorCount: number;
lastUpdate?: number;
isUserInteracting: boolean;
isOfflineMode: boolean;
reconnectAttempts: number;
maxReconnectAttempts: number;
lastSuccessfulConnection?: number;
connectionStatus: 'online' | 'offline' | 'reconnecting';
};
toastNotifications: ToastNotification[];
currentView: 'kanban' | 'task-details' | 'config';
selectedTaskId?: string;
// Tag-related state
currentTag: string;
availableTags: string[];
}
export type AppAction =
| { type: 'SET_TASKS'; payload: TaskMasterTask[] }
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string }
| { type: 'CLEAR_ERROR' }
| { type: 'INCREMENT_REQUEST_ID' }
| {
type: 'UPDATE_TASK_STATUS';
payload: { taskId: string; newStatus: TaskMasterTask['status'] };
}
| {
type: 'UPDATE_TASK_CONTENT';
payload: { taskId: string; updates: TaskUpdates };
}
| {
type: 'SET_CONNECTION_STATUS';
payload: { isConnected: boolean; status: string };
}
| {
type: 'SET_EDITING_TASK';
payload: { taskId: string | null; editData?: TaskMasterTask };
}
| {
type: 'SET_POLLING_STATUS';
payload: { isActive: boolean; errorCount?: number };
}
| { type: 'SET_USER_INTERACTING'; payload: boolean }
| { type: 'TASKS_UPDATED_FROM_POLLING'; payload: TaskMasterTask[] }
| {
type: 'SET_NETWORK_STATUS';
payload: {
isOfflineMode: boolean;
connectionStatus: 'online' | 'offline' | 'reconnecting';
reconnectAttempts?: number;
maxReconnectAttempts?: number;
lastSuccessfulConnection?: number;
};
}
| { type: 'LOAD_CACHED_TASKS'; payload: TaskMasterTask[] }
| { type: 'ADD_TOAST'; payload: ToastNotification }
| { type: 'REMOVE_TOAST'; payload: string }
| { type: 'CLEAR_ALL_TOASTS' }
| { type: 'NAVIGATE_TO_TASK'; payload: string }
| { type: 'NAVIGATE_TO_KANBAN' }
| { type: 'NAVIGATE_TO_CONFIG' }
| { type: 'SET_CURRENT_TAG'; payload: string }
| { type: 'SET_AVAILABLE_TAGS'; payload: string[] }
| {
type: 'SET_TAG_DATA';
payload: { currentTag: string; availableTags: string[] };
};
```
--------------------------------------------------------------------------------
/.github/workflows/extension-ci.yml:
--------------------------------------------------------------------------------
```yaml
name: Extension CI
on:
push:
branches:
- main
- next
paths:
- "apps/extension/**"
- ".github/workflows/extension-ci.yml"
pull_request:
branches:
- main
- next
paths:
- "apps/extension/**"
- ".github/workflows/extension-ci.yml"
permissions:
contents: read
jobs:
setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Monorepo Dependencies
run: npm ci
timeout-minutes: 5
typecheck:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Restore node_modules
uses: actions/cache@v4
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install if cache miss
run: npm ci
timeout-minutes: 3
- name: Type Check Extension
working-directory: apps/extension
run: npm run typecheck
env:
FORCE_COLOR: 1
build:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Restore node_modules
uses: actions/cache@v4
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install if cache miss
run: npm ci
timeout-minutes: 3
- name: Build Extension
working-directory: apps/extension
run: npm run build
env:
FORCE_COLOR: 1
- name: Package Extension
working-directory: apps/extension
run: npm run package
env:
FORCE_COLOR: 1
- name: Verify Package Contents
working-directory: apps/extension
run: |
echo "Checking vsix-build contents..."
ls -la vsix-build/
echo "Checking dist contents..."
ls -la vsix-build/dist/
echo "Checking package.json exists..."
test -f vsix-build/package.json
- name: Create VSIX Package (Test)
working-directory: apps/extension/vsix-build
run: npx vsce package --no-dependencies
env:
FORCE_COLOR: 1
- name: Upload Extension Artifact
uses: actions/upload-artifact@v4
with:
name: extension-package
path: |
apps/extension/vsix-build/*.vsix
apps/extension/dist/
retention-days: 30
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/add-task.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/add-task.js
* Tool to add a new task using AI
*/
import { z } from 'zod';
import {
createErrorResponse,
handleApiResult,
withNormalizedProjectRoot
} from './utils.js';
import { addTaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the addTask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerAddTaskTool(server) {
server.addTool({
name: 'add_task',
description: 'Add a new task using AI',
parameters: z.object({
prompt: z
.string()
.optional()
.describe(
'Description of the task to add (required if not using manual fields)'
),
title: z
.string()
.optional()
.describe('Task title (for manual task creation)'),
description: z
.string()
.optional()
.describe('Task description (for manual task creation)'),
details: z
.string()
.optional()
.describe('Implementation details (for manual task creation)'),
testStrategy: z
.string()
.optional()
.describe('Test strategy (for manual task creation)'),
dependencies: z
.string()
.optional()
.describe('Comma-separated list of task IDs this task depends on'),
priority: z
.string()
.optional()
.describe('Task priority (high, medium, low)'),
file: z
.string()
.optional()
.describe('Path to the tasks file (default: tasks/tasks.json)'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on'),
research: z
.boolean()
.optional()
.describe('Whether to use research capabilities for task creation')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(`Starting add-task with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call the direct functionP
const result = await addTaskDirect(
{
tasksJsonPath: tasksJsonPath,
prompt: args.prompt,
title: args.title,
description: args.description,
details: args.details,
testStrategy: args.testStrategy,
dependencies: args.dependencies,
priority: args.priority,
research: args.research,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
return handleApiResult(
result,
log,
'Error adding task',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in add-task tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/tests/integration/profiles/gemini-init-functionality.test.js:
--------------------------------------------------------------------------------
```javascript
import fs from 'fs';
import path from 'path';
import { geminiProfile } from '../../../src/profiles/gemini.js';
describe('Gemini Profile Initialization Functionality', () => {
let geminiProfileContent;
beforeAll(() => {
const geminiJsPath = path.join(
process.cwd(),
'src',
'profiles',
'gemini.js'
);
geminiProfileContent = fs.readFileSync(geminiJsPath, 'utf8');
});
test('gemini.js has correct profile configuration', () => {
// Check for explicit, non-default values in the source file
expect(geminiProfileContent).toContain("name: 'gemini'");
expect(geminiProfileContent).toContain("displayName: 'Gemini'");
expect(geminiProfileContent).toContain("url: 'codeassist.google'");
expect(geminiProfileContent).toContain(
"docsUrl: 'github.com/google-gemini/gemini-cli'"
);
expect(geminiProfileContent).toContain("profileDir: '.gemini'");
expect(geminiProfileContent).toContain("rulesDir: '.'"); // non-default
expect(geminiProfileContent).toContain("mcpConfigName: 'settings.json'"); // non-default
expect(geminiProfileContent).toContain('includeDefaultRules: false'); // non-default
expect(geminiProfileContent).toContain("'AGENT.md': 'AGENTS.md'");
expect(geminiProfileContent).toContain("'GEMINI.md': 'GEMINI.md'");
// Check the final computed properties on the profile object
expect(geminiProfile.profileName).toBe('gemini');
expect(geminiProfile.displayName).toBe('Gemini');
expect(geminiProfile.profileDir).toBe('.gemini');
expect(geminiProfile.rulesDir).toBe('.');
expect(geminiProfile.mcpConfig).toBe(true); // computed from mcpConfigName
expect(geminiProfile.mcpConfigName).toBe('settings.json');
expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json'); // computed
expect(geminiProfile.includeDefaultRules).toBe(false);
expect(geminiProfile.fileMap['AGENT.md']).toBe('AGENTS.md');
expect(geminiProfile.fileMap['GEMINI.md']).toBe('GEMINI.md');
});
test('gemini.js has no lifecycle functions', () => {
// Gemini profile should not have any lifecycle functions
expect(geminiProfileContent).not.toContain('function onAddRulesProfile');
expect(geminiProfileContent).not.toContain('function onRemoveRulesProfile');
expect(geminiProfileContent).not.toContain(
'function onPostConvertRulesProfile'
);
expect(geminiProfileContent).not.toContain('onAddRulesProfile:');
expect(geminiProfileContent).not.toContain('onRemoveRulesProfile:');
expect(geminiProfileContent).not.toContain('onPostConvertRulesProfile:');
});
test('gemini.js uses custom MCP config name', () => {
// Gemini uses settings.json instead of mcp.json
expect(geminiProfileContent).toContain("mcpConfigName: 'settings.json'");
// Should not contain mcp.json as a config value (comments are OK)
expect(geminiProfileContent).not.toMatch(
/mcpConfigName:\s*['"]mcp\.json['"]/
);
});
test('gemini.js has minimal implementation', () => {
// Verify the profile is minimal (no extra functions or logic)
const lines = geminiProfileContent.split('\n');
const nonEmptyLines = lines.filter((line) => line.trim().length > 0);
// Should be around 16 lines (import, export, and profile definition)
expect(nonEmptyLines.length).toBeLessThan(20);
});
});
```
--------------------------------------------------------------------------------
/tests/integration/profiles/rules-files-inclusion.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { execSync } from 'child_process';
describe('Rules Files Inclusion in Package', () => {
// This test verifies that the required rules files are included in the final package
test('package.json includes dist/** in the "files" array for bundled files', () => {
// Read the package.json file
const packageJsonPath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
// Check if dist/** is included in the files array (which contains bundled output including assets)
expect(packageJson.files).toContain('dist/**');
});
test('source rules files exist in assets/rules directory', () => {
// Verify that the actual rules files exist
const rulesDir = path.join(process.cwd(), 'assets', 'rules');
expect(fs.existsSync(rulesDir)).toBe(true);
// Check for the 4 files that currently exist
const expectedFiles = [
'dev_workflow.mdc',
'taskmaster.mdc',
'self_improve.mdc',
'cursor_rules.mdc'
];
expectedFiles.forEach((file) => {
const filePath = path.join(rulesDir, file);
expect(fs.existsSync(filePath)).toBe(true);
});
});
test('roo.js profile contains logic for Roo directory creation and file copying', () => {
// Read the roo.js profile file
const rooJsPath = path.join(process.cwd(), 'src', 'profiles', 'roo.js');
const rooJsContent = fs.readFileSync(rooJsPath, 'utf8');
// Check for the main handler function
expect(
rooJsContent.includes('onAddRulesProfile(targetDir, assetsDir)')
).toBe(true);
// Check for general recursive copy of assets/roocode
expect(
rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)')
).toBe(true);
// Check for updated path handling
expect(rooJsContent.includes("path.join(assetsDir, 'roocode')")).toBe(true);
// Check for .roomodes file copying logic (source and destination paths)
expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe(
true
);
expect(rooJsContent.includes("path.join(targetDir, '.roomodes')")).toBe(
true
);
// Check for mode-specific rule file copying logic
expect(rooJsContent.includes('for (const mode of ROO_MODES)')).toBe(true);
expect(
rooJsContent.includes(
'path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`)'
)
).toBe(true);
expect(
rooJsContent.includes(
"path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)"
)
).toBe(true);
// Check for import of ROO_MODES from profiles.js
expect(
rooJsContent.includes(
"import { ROO_MODES } from '../constants/profiles.js'"
)
).toBe(true);
// Verify mode variable is used in the template strings (this confirms modes are being processed)
expect(rooJsContent.includes('rules-${mode}')).toBe(true);
expect(rooJsContent.includes('${mode}-rules')).toBe(true);
});
test('source Roo files exist in assets directory', () => {
// Verify that the source files for Roo integration exist
expect(
fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roo'))
).toBe(true);
expect(
fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roomodes'))
).toBe(true);
});
});
```
--------------------------------------------------------------------------------
/tests/unit/profiles/codex-integration.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';
// Mock external modules
jest.mock('child_process', () => ({
execSync: jest.fn()
}));
// Mock console methods
jest.mock('console', () => ({
log: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
clear: jest.fn()
}));
describe('Codex Profile Integration', () => {
let tempDir;
beforeEach(() => {
jest.clearAllMocks();
// Create a temporary directory for testing
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
// Spy on fs methods
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
if (filePath.toString().includes('AGENTS.md')) {
return 'Sample AGENTS.md content for Codex integration';
}
return '{}';
});
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
});
afterEach(() => {
// Clean up the temporary directory
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch (err) {
console.error(`Error cleaning up: ${err.message}`);
}
});
// Test function that simulates the Codex profile file copying behavior
function mockCreateCodexStructure() {
// Codex profile copies AGENTS.md to AGENTS.md in project root (same name)
const sourceContent = 'Sample AGENTS.md content for Codex integration';
fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), sourceContent);
}
test('creates AGENTS.md file in project root', () => {
// Act
mockCreateCodexStructure();
// Assert
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, 'AGENTS.md'),
'Sample AGENTS.md content for Codex integration'
);
});
test('does not create any profile directories', () => {
// Act
mockCreateCodexStructure();
// Assert - Codex profile should not create any directories
// Only the temp directory creation calls should exist
const mkdirCalls = fs.mkdirSync.mock.calls.filter(
(call) => !call[0].includes('task-master-test-')
);
expect(mkdirCalls).toHaveLength(0);
});
test('does not create MCP configuration files', () => {
// Act
mockCreateCodexStructure();
// Assert - Codex profile should not create any MCP config files
const writeFileCalls = fs.writeFileSync.mock.calls;
const mcpConfigCalls = writeFileCalls.filter(
(call) =>
call[0].toString().includes('mcp.json') ||
call[0].toString().includes('mcp_settings.json')
);
expect(mcpConfigCalls).toHaveLength(0);
});
test('only creates the target integration guide file', () => {
// Act
mockCreateCodexStructure();
// Assert - Should only create AGENTS.md
const writeFileCalls = fs.writeFileSync.mock.calls;
expect(writeFileCalls).toHaveLength(1);
expect(writeFileCalls[0][0]).toBe(path.join(tempDir, 'AGENTS.md'));
});
test('uses the same filename as source (AGENTS.md)', () => {
// Act
mockCreateCodexStructure();
// Assert - Codex should keep the same filename unlike Claude which renames it
const writeFileCalls = fs.writeFileSync.mock.calls;
expect(writeFileCalls[0][0]).toContain('AGENTS.md');
expect(writeFileCalls[0][0]).not.toContain('CLAUDE.md');
});
});
```
--------------------------------------------------------------------------------
/tests/unit/progress/base-progress-tracker.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock cli-progress factory before importing BaseProgressTracker
jest.unstable_mockModule(
'../../../src/progress/cli-progress-factory.js',
() => ({
newMultiBar: jest.fn(() => ({
create: jest.fn(() => ({
update: jest.fn()
})),
stop: jest.fn()
}))
})
);
const { newMultiBar } = await import(
'../../../src/progress/cli-progress-factory.js'
);
const { BaseProgressTracker } = await import(
'../../../src/progress/base-progress-tracker.js'
);
describe('BaseProgressTracker', () => {
let tracker;
let mockMultiBar;
let mockProgressBar;
let mockTimeTokensBar;
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
// Setup mocks
mockProgressBar = { update: jest.fn() };
mockTimeTokensBar = { update: jest.fn() };
mockMultiBar = {
create: jest
.fn()
.mockReturnValueOnce(mockTimeTokensBar)
.mockReturnValueOnce(mockProgressBar),
stop: jest.fn()
};
newMultiBar.mockReturnValue(mockMultiBar);
tracker = new BaseProgressTracker({ numUnits: 10, unitName: 'task' });
});
afterEach(() => {
jest.useRealTimers();
});
describe('cleanup', () => {
it('should stop and clear timer interval', () => {
tracker.start();
expect(tracker._timerInterval).toBeTruthy();
tracker.cleanup();
expect(tracker._timerInterval).toBeNull();
});
it('should stop and null multibar reference', () => {
tracker.start();
expect(tracker.multibar).toBeTruthy();
tracker.cleanup();
expect(mockMultiBar.stop).toHaveBeenCalled();
expect(tracker.multibar).toBeNull();
});
it('should null progress bar references', () => {
tracker.start();
expect(tracker.timeTokensBar).toBeTruthy();
expect(tracker.progressBar).toBeTruthy();
tracker.cleanup();
expect(tracker.timeTokensBar).toBeNull();
expect(tracker.progressBar).toBeNull();
});
it('should set finished state', () => {
tracker.start();
expect(tracker.isStarted).toBe(true);
expect(tracker.isFinished).toBe(false);
tracker.cleanup();
expect(tracker.isStarted).toBe(false);
expect(tracker.isFinished).toBe(true);
});
it('should handle cleanup when multibar.stop throws error', () => {
tracker.start();
mockMultiBar.stop.mockImplementation(() => {
throw new Error('Stop failed');
});
expect(() => tracker.cleanup()).not.toThrow();
expect(tracker.multibar).toBeNull();
});
it('should be safe to call multiple times', () => {
tracker.start();
tracker.cleanup();
tracker.cleanup();
tracker.cleanup();
expect(mockMultiBar.stop).toHaveBeenCalledTimes(1);
});
it('should be safe to call without starting', () => {
expect(() => tracker.cleanup()).not.toThrow();
expect(tracker.multibar).toBeNull();
});
});
describe('stop vs cleanup', () => {
it('stop should call cleanup and null multibar reference', () => {
tracker.start();
tracker.stop();
// stop() now calls cleanup() which nulls the multibar
expect(tracker.multibar).toBeNull();
expect(tracker.isFinished).toBe(true);
});
it('cleanup should null multibar preventing getSummary', () => {
tracker.start();
tracker.cleanup();
expect(tracker.multibar).toBeNull();
expect(tracker.isFinished).toBe(true);
});
});
});
```
--------------------------------------------------------------------------------
/.taskmaster/reports/task-complexity-report_test-prd-tag.json:
--------------------------------------------------------------------------------
```json
{
"meta": {
"generatedAt": "2025-06-13T06:52:00.611Z",
"tasksAnalyzed": 5,
"totalTasks": 5,
"analysisCount": 5,
"thresholdScore": 5,
"projectName": "Taskmaster",
"usedResearch": true
},
"complexityAnalysis": [
{
"taskId": 1,
"taskTitle": "Setup Project Repository and Node.js Environment",
"complexityScore": 4,
"recommendedSubtasks": 6,
"expansionPrompt": "Break down the setup process into subtasks such as initializing npm, creating directory structure, installing dependencies, configuring package.json, adding configuration files, and setting up the main entry point.",
"reasoning": "This task involves several standard setup steps that are well-defined and sequential, with low algorithmic complexity but moderate procedural detail. Each step is independent and can be assigned as a subtask, making the overall complexity moderate."
},
{
"taskId": 2,
"taskTitle": "Implement Core Functionality and CLI Interface",
"complexityScore": 7,
"recommendedSubtasks": 7,
"expansionPrompt": "Expand into subtasks for implementing main logic, designing CLI commands, creating the CLI entry point, integrating business logic, adding error handling, formatting output, and ensuring CLI executability.",
"reasoning": "This task requires both application logic and user interface (CLI) development, including error handling and integration. The need to coordinate between core logic and CLI, plus ensuring usability, increases complexity and warrants detailed subtasking."
},
{
"taskId": 3,
"taskTitle": "Implement Testing Suite and Validation",
"complexityScore": 6,
"recommendedSubtasks": 6,
"expansionPrompt": "Divide into subtasks for configuring Jest, writing unit tests, writing integration tests, testing CLI commands, setting up coverage reporting, and preparing test fixtures/mocks.",
"reasoning": "Comprehensive testing involves multiple types of tests and configuration steps. While each is straightforward, the breadth of coverage and need for automation and validation increases the overall complexity."
},
{
"taskId": 4,
"taskTitle": "Setup Node.js Project with CLI Interface",
"complexityScore": 5,
"recommendedSubtasks": 7,
"expansionPrompt": "Break down into subtasks for npm initialization, package.json setup, directory structure creation, dependency installation, CLI entry point creation, package.json bin configuration, and CLI executability.",
"reasoning": "This task combines project setup with initial CLI implementation. While each step is standard, the integration of CLI elements adds a layer of complexity beyond a basic setup."
},
{
"taskId": 5,
"taskTitle": "Implement Core Functionality with Testing",
"complexityScore": 8,
"recommendedSubtasks": 8,
"expansionPrompt": "Expand into subtasks for implementing each feature (A, B, C), setting up the testing framework, writing tests for each feature, integrating CLI with core logic, and adding coverage reporting.",
"reasoning": "This task requires simultaneous development of multiple features, integration with CLI, and comprehensive testing. The coordination and depth required for both implementation and validation make it the most complex among the listed tasks."
}
]
}
```
--------------------------------------------------------------------------------
/src/ai-providers/zai.js:
--------------------------------------------------------------------------------
```javascript
/**
* zai.js
* AI provider implementation for Z.ai (GLM) models.
* Uses the OpenAI-compatible API endpoint.
*/
import { OpenAICompatibleProvider } from './openai-compatible.js';
/**
* Z.ai provider supporting GLM models through OpenAI-compatible API.
*/
export class ZAIProvider extends OpenAICompatibleProvider {
constructor() {
super({
name: 'Z.ai',
apiKeyEnvVar: 'ZAI_API_KEY',
requiresApiKey: true,
defaultBaseURL: 'https://api.z.ai/api/paas/v4/',
supportsStructuredOutputs: true
});
}
/**
* Override token parameter preparation for ZAI
* ZAI API doesn't support max_tokens parameter
* @returns {object} Empty object for ZAI (doesn't support maxOutputTokens)
*/
prepareTokenParam() {
// ZAI API rejects max_tokens parameter with error code 1210
return {};
}
/**
* Introspects a Zod schema to find the property that expects an array
* @param {import('zod').ZodType} schema - The Zod schema to introspect
* @returns {string|null} The property name that expects an array, or null if not found
*/
findArrayPropertyInSchema(schema) {
try {
// Get the def object from Zod v4 API
const def = schema._zod.def;
// Check if schema is a ZodObject
const isObject = def?.type === 'object' || def?.typeName === 'ZodObject';
if (!isObject) {
return null;
}
// Get the shape - it can be a function, property, or getter
let shape = def.shape;
if (typeof shape === 'function') {
shape = shape();
}
if (!shape || typeof shape !== 'object') {
return null;
}
// Find the first property that is an array
for (const [key, value] of Object.entries(shape)) {
// Get the def object for the property using Zod v4 API
const valueDef = value._zod.def;
// Check if the property is a ZodArray
const isArray =
valueDef?.type === 'array' || valueDef?.typeName === 'ZodArray';
if (isArray) {
return key;
}
}
return null;
} catch (error) {
// If introspection fails, log and return null
console.warn('Failed to introspect Zod schema:', error.message);
return null;
}
}
/**
* Override generateObject to normalize GLM's response format
* GLM sometimes returns bare arrays instead of objects with properties,
* even when the schema has multiple properties.
* @param {object} params - Parameters for object generation
* @returns {Promise<object>} Normalized response
*/
async generateObject(params) {
const result = await super.generateObject(params);
// If result.object is an array, wrap it based on schema introspection
if (Array.isArray(result.object)) {
// Try to find the array property from the schema
const wrapperKey = this.findArrayPropertyInSchema(params.schema);
if (wrapperKey) {
return {
...result,
object: {
[wrapperKey]: result.object
}
};
}
// Fallback: if we can't introspect the schema, use the object name
// This handles edge cases where schema introspection might fail
console.warn(
`GLM returned a bare array for '${params.objectName}' but could not determine wrapper property from schema. Using objectName as fallback.`
);
return {
...result,
object: {
[params.objectName]: result.object
}
};
}
return result;
}
}
```
--------------------------------------------------------------------------------
/.github/scripts/check-pre-release-mode.mjs:
--------------------------------------------------------------------------------
```
#!/usr/bin/env node
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Get context from command line argument or environment
const context = process.argv[2] || process.env.GITHUB_WORKFLOW || 'manual';
function findRootDir(startDir) {
let currentDir = resolve(startDir);
while (currentDir !== '/') {
if (existsSync(join(currentDir, 'package.json'))) {
try {
const pkg = JSON.parse(
readFileSync(join(currentDir, 'package.json'), 'utf8')
);
if (pkg.name === 'task-master-ai' || pkg.repository) {
return currentDir;
}
} catch {}
}
currentDir = dirname(currentDir);
}
throw new Error('Could not find root directory');
}
function checkPreReleaseMode() {
console.log('🔍 Checking if branch is in pre-release mode...');
const rootDir = findRootDir(__dirname);
const preJsonPath = join(rootDir, '.changeset', 'pre.json');
// Check if pre.json exists
if (!existsSync(preJsonPath)) {
console.log('✅ Not in active pre-release mode - safe to proceed');
process.exit(0);
}
try {
// Read and parse pre.json
const preJsonContent = readFileSync(preJsonPath, 'utf8');
const preJson = JSON.parse(preJsonContent);
// Check if we're in active pre-release mode
if (preJson.mode === 'pre') {
console.error('❌ ERROR: This branch is in active pre-release mode!');
console.error('');
// Provide context-specific error messages
if (context === 'Release Check' || context === 'pull_request') {
console.error(
'Pre-release mode must be exited before merging to main.'
);
console.error('');
console.error(
'To fix this, run the following commands in your branch:'
);
console.error(' npx changeset pre exit');
console.error(' git add -u');
console.error(' git commit -m "chore: exit pre-release mode"');
console.error(' git push');
console.error('');
console.error('Then update this pull request.');
} else if (context === 'Release' || context === 'main') {
console.error(
'Pre-release mode should only be used on feature branches, not main.'
);
console.error('');
console.error('To fix this, run the following commands locally:');
console.error(' npx changeset pre exit');
console.error(' git add -u');
console.error(' git commit -m "chore: exit pre-release mode"');
console.error(' git push origin main');
console.error('');
console.error('Then re-run this workflow.');
} else {
console.error('Pre-release mode must be exited before proceeding.');
console.error('');
console.error('To fix this, run the following commands:');
console.error(' npx changeset pre exit');
console.error(' git add -u');
console.error(' git commit -m "chore: exit pre-release mode"');
console.error(' git push');
}
process.exit(1);
}
console.log('✅ Not in active pre-release mode - safe to proceed');
process.exit(0);
} catch (error) {
console.error(`❌ ERROR: Unable to parse .changeset/pre.json – aborting.`);
console.error(`Error details: ${error.message}`);
process.exit(1);
}
}
// Run the check
checkPreReleaseMode();
```
--------------------------------------------------------------------------------
/apps/extension/src/services/config-service.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Config Service
* Manages Task Master config.json file operations
*/
import * as path from 'path';
import * as fs from 'fs/promises';
import * as vscode from 'vscode';
import type { ExtensionLogger } from '../utils/logger';
export interface TaskMasterConfigJson {
anthropicApiKey?: string;
perplexityApiKey?: string;
openaiApiKey?: string;
googleApiKey?: string;
xaiApiKey?: string;
openrouterApiKey?: string;
mistralApiKey?: string;
debug?: boolean;
models?: {
main?: string;
research?: string;
fallback?: string;
};
}
export class ConfigService {
private configCache: TaskMasterConfigJson | null = null;
private lastReadTime = 0;
private readonly CACHE_DURATION = 5000; // 5 seconds
constructor(private logger: ExtensionLogger) {}
/**
* Read Task Master config.json from the workspace
*/
async readConfig(): Promise<TaskMasterConfigJson | null> {
// Check cache first
if (
this.configCache &&
Date.now() - this.lastReadTime < this.CACHE_DURATION
) {
return this.configCache;
}
try {
const workspaceRoot = this.getWorkspaceRoot();
if (!workspaceRoot) {
this.logger.warn('No workspace folder found');
return null;
}
const configPath = path.join(workspaceRoot, '.taskmaster', 'config.json');
try {
const configContent = await fs.readFile(configPath, 'utf-8');
const config = JSON.parse(configContent) as TaskMasterConfigJson;
// Cache the result
this.configCache = config;
this.lastReadTime = Date.now();
this.logger.debug('Successfully read Task Master config', {
hasModels: !!config.models,
debug: config.debug
});
return config;
} catch (error) {
if ((error as any).code === 'ENOENT') {
this.logger.debug('Task Master config.json not found');
} else {
this.logger.error('Failed to read Task Master config', error);
}
return null;
}
} catch (error) {
this.logger.error('Error accessing Task Master config', error);
return null;
}
}
/**
* Get safe config for display (with sensitive data masked)
*/
async getSafeConfig(): Promise<Record<string, any> | null> {
const config = await this.readConfig();
if (!config) {
return null;
}
// Create a safe copy with masked API keys
const safeConfig: Record<string, any> = {
...config
};
// Mask all API keys
const apiKeyFields = [
'anthropicApiKey',
'perplexityApiKey',
'openaiApiKey',
'googleApiKey',
'xaiApiKey',
'openrouterApiKey',
'mistralApiKey'
];
for (const field of apiKeyFields) {
if (safeConfig[field]) {
safeConfig[field] = this.maskApiKey(safeConfig[field]);
}
}
return safeConfig;
}
/**
* Mask API key for display
* Shows only the last 4 characters for better security
*/
private maskApiKey(key: string): string {
if (key.length <= 4) {
return '****';
}
const visibleChars = 4;
const maskedLength = key.length - visibleChars;
return (
'*'.repeat(Math.min(maskedLength, 12)) +
key.substring(key.length - visibleChars)
);
}
/**
* Clear cache
*/
clearCache(): void {
this.configCache = null;
this.lastReadTime = 0;
}
/**
* Get workspace root path
*/
private getWorkspaceRoot(): string | undefined {
return vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
}
}
```
--------------------------------------------------------------------------------
/src/progress/cli-progress-factory.js:
--------------------------------------------------------------------------------
```javascript
import cliProgress from 'cli-progress';
/**
* Default configuration for progress bars
* Extracted to avoid duplication and provide single source of truth
*/
const DEFAULT_CONFIG = {
clearOnComplete: false,
stopOnComplete: true,
hideCursor: true,
barsize: 40 // Standard terminal width for progress bar
};
/**
* Available presets for progress bar styling
* Makes it easy to see what options are available
*/
const PRESETS = {
shades_classic: cliProgress.Presets.shades_classic,
shades_grey: cliProgress.Presets.shades_grey,
rect: cliProgress.Presets.rect,
legacy: cliProgress.Presets.legacy
};
/**
* Factory class for creating CLI progress bars
* Provides a consistent interface for creating both single and multi-bar instances
*/
export class ProgressBarFactory {
constructor(defaultOptions = {}, defaultPreset = PRESETS.shades_classic) {
this.defaultOptions = { ...DEFAULT_CONFIG, ...defaultOptions };
this.defaultPreset = defaultPreset;
}
/**
* Creates a new single progress bar
* @param {Object} opts - Custom options to override defaults
* @param {Object} preset - Progress bar preset for styling
* @returns {cliProgress.SingleBar} Configured single progress bar instance
*/
createSingleBar(opts = {}, preset = null) {
const config = this._mergeConfig(opts);
const barPreset = preset || this.defaultPreset;
return new cliProgress.SingleBar(config, barPreset);
}
/**
* Creates a new multi-bar container
* @param {Object} opts - Custom options to override defaults
* @param {Object} preset - Progress bar preset for styling
* @returns {cliProgress.MultiBar} Configured multi-bar instance
*/
createMultiBar(opts = {}, preset = null) {
const config = this._mergeConfig(opts);
const barPreset = preset || this.defaultPreset;
return new cliProgress.MultiBar(config, barPreset);
}
/**
* Merges custom options with defaults
* @private
* @param {Object} customOpts - Custom options to merge
* @returns {Object} Merged configuration
*/
_mergeConfig(customOpts) {
return { ...this.defaultOptions, ...customOpts };
}
/**
* Updates the default configuration
* @param {Object} options - New default options
*/
setDefaultOptions(options) {
this.defaultOptions = { ...this.defaultOptions, ...options };
}
/**
* Updates the default preset
* @param {Object} preset - New default preset
*/
setDefaultPreset(preset) {
this.defaultPreset = preset;
}
}
// Create a default factory instance for backward compatibility
const defaultFactory = new ProgressBarFactory();
/**
* Legacy function for creating a single progress bar
* @deprecated Use ProgressBarFactory.createSingleBar() instead
* @param {Object} opts - Progress bar options
* @returns {cliProgress.SingleBar} Single progress bar instance
*/
export function newSingle(opts = {}) {
return defaultFactory.createSingleBar(opts);
}
/**
* Legacy function for creating a multi-bar
* @deprecated Use ProgressBarFactory.createMultiBar() instead
* @param {Object} opts - Progress bar options
* @returns {cliProgress.MultiBar} Multi-bar instance
*/
export function newMultiBar(opts = {}) {
return defaultFactory.createMultiBar(opts);
}
// Export presets for easy access
export { PRESETS };
// Export the factory class as default
export default ProgressBarFactory;
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/add-subtask.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/add-subtask.js
* Tool for adding subtasks to existing tasks
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { addSubtaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the addSubtask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerAddSubtaskTool(server) {
server.addTool({
name: 'add_subtask',
description: 'Add a subtask to an existing task',
parameters: z.object({
id: z.string().describe('Parent task ID (required)'),
taskId: z
.string()
.optional()
.describe('Existing task ID to convert to subtask'),
title: z
.string()
.optional()
.describe('Title for the new subtask (when creating a new subtask)'),
description: z
.string()
.optional()
.describe('Description for the new subtask'),
details: z
.string()
.optional()
.describe('Implementation details for the new subtask'),
status: z
.string()
.optional()
.describe("Status for the new subtask (default: 'pending')"),
dependencies: z
.string()
.optional()
.describe('Comma-separated list of dependency IDs for the new subtask'),
file: z
.string()
.optional()
.describe(
'Absolute path to the tasks file (default: tasks/tasks.json)'
),
skipGenerate: z
.boolean()
.optional()
.describe('Skip regenerating task files'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await addSubtaskDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id,
taskId: args.taskId,
title: args.title,
description: args.description,
details: args.details,
status: args.status,
dependencies: args.dependencies,
skipGenerate: args.skipGenerate,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
if (result.success) {
log.info(`Subtask added successfully: ${result.data.message}`);
} else {
log.error(`Failed to add subtask: ${result.error.message}`);
}
return handleApiResult(
result,
log,
'Error adding subtask',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in addSubtask tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/src/provider-registry/index.js:
--------------------------------------------------------------------------------
```javascript
/**
* Provider Registry - Singleton for managing AI providers
*
* This module implements a singleton registry that allows dynamic registration
* of AI providers at runtime, while maintaining compatibility with the existing
* static PROVIDERS object in ai-services-unified.js.
*/
// Singleton instance
let instance = null;
/**
* Provider Registry class - Manages dynamic provider registration
*/
class ProviderRegistry {
constructor() {
// Private provider map
this._providers = new Map();
// Flag to track initialization
this._initialized = false;
}
/**
* Get the singleton instance
* @returns {ProviderRegistry} The singleton instance
*/
static getInstance() {
if (!instance) {
instance = new ProviderRegistry();
}
return instance;
}
/**
* Initialize the registry
* @returns {ProviderRegistry} The singleton instance
*/
initialize() {
if (this._initialized) {
return this;
}
this._initialized = true;
return this;
}
/**
* Register a provider with the registry
* @param {string} providerName - The name of the provider
* @param {object} provider - The provider instance
* @param {object} options - Additional options for registration
* @returns {ProviderRegistry} The singleton instance for chaining
*/
registerProvider(providerName, provider, options = {}) {
if (!providerName || typeof providerName !== 'string') {
throw new Error('Provider name must be a non-empty string');
}
if (!provider) {
throw new Error('Provider instance is required');
}
// Validate that provider implements the required interface
if (
typeof provider.generateText !== 'function' ||
typeof provider.streamText !== 'function' ||
typeof provider.generateObject !== 'function'
) {
throw new Error('Provider must implement BaseAIProvider interface');
}
// Add provider to the registry
this._providers.set(providerName, {
instance: provider,
options,
registeredAt: new Date()
});
return this;
}
/**
* Check if a provider exists in the registry
* @param {string} providerName - The name of the provider
* @returns {boolean} True if the provider exists
*/
hasProvider(providerName) {
return this._providers.has(providerName);
}
/**
* Get a provider from the registry
* @param {string} providerName - The name of the provider
* @returns {object|null} The provider instance or null if not found
*/
getProvider(providerName) {
const providerEntry = this._providers.get(providerName);
return providerEntry ? providerEntry.instance : null;
}
/**
* Get all registered providers
* @returns {Map} Map of all registered providers
*/
getAllProviders() {
return new Map(this._providers);
}
/**
* Remove a provider from the registry
* @param {string} providerName - The name of the provider
* @returns {boolean} True if the provider was removed
*/
unregisterProvider(providerName) {
if (this._providers.has(providerName)) {
this._providers.delete(providerName);
return true;
}
return false;
}
/**
* Reset the registry (primarily for testing)
*/
reset() {
this._providers.clear();
this._initialized = false;
}
}
ProviderRegistry.getInstance().initialize(); // Ensure singleton is initialized on import
// Export singleton getter
export default ProviderRegistry;
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/copy-tag.js:
--------------------------------------------------------------------------------
```javascript
/**
* copy-tag.js
* Direct function implementation for copying a tag
*/
import { copyTag } from '../../../../scripts/modules/task-manager/tag-management.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for copying a tag with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.sourceName - Name of the source tag to copy from
* @param {string} args.targetName - Name of the new tag to create
* @param {string} [args.description] - Optional description for the new tag
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
* @param {string} [args.projectRoot] - Project root path
* @param {Object} log - Logger object
* @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function copyTagDirect(args, log, context = {}) {
// Destructure expected args
const { tasksJsonPath, sourceName, targetName, description, projectRoot } =
args;
const { session } = context;
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
try {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('copyTagDirect called without tasksJsonPath');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Check required parameters
if (!sourceName || typeof sourceName !== 'string') {
log.error('Missing required parameter: sourceName');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'Source tag name is required and must be a string'
}
};
}
if (!targetName || typeof targetName !== 'string') {
log.error('Missing required parameter: targetName');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'Target tag name is required and must be a string'
}
};
}
log.info(`Copying tag from "${sourceName}" to "${targetName}"`);
// Prepare options
const options = {
description
};
// Call the copyTag function
const result = await copyTag(
tasksJsonPath,
sourceName,
targetName,
options,
{
session,
mcpLog,
projectRoot
},
'json' // outputFormat - use 'json' to suppress CLI UI
);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
sourceName: result.sourceName,
targetName: result.targetName,
copied: result.copied,
tasksCopied: result.tasksCopied,
description: result.description,
message: `Successfully copied tag from "${result.sourceName}" to "${result.targetName}"`
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in copyTagDirect: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'COPY_TAG_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/integration/services/task-retrieval.service.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Task Retrieval Service
* Core service for retrieving tasks with enriched document content
* Uses repository for task structure and API for document content
*/
import {
ERROR_CODES,
TaskMasterError
} from '../../../common/errors/task-master-error.js';
import { getLogger } from '../../../common/logger/factory.js';
import type { Task } from '../../../common/types/index.js';
import { AuthManager } from '../../auth/managers/auth-manager.js';
import { ApiClient } from '../../storage/utils/api-client.js';
import type { TaskRepository } from '../../tasks/repositories/task-repository.interface.js';
/**
* Response from the get task API endpoint
*/
interface GetTaskResponse {
task: Task;
document?: {
id: string;
title: string;
content: string;
createdAt: string;
updatedAt: string;
};
}
/**
* TaskRetrievalService handles fetching tasks with enriched document content
* Uses repository for task structure and API endpoint for document content
*/
export class TaskRetrievalService {
private readonly repository: TaskRepository;
private readonly projectId: string;
private readonly apiClient: ApiClient;
private readonly authManager: AuthManager;
private readonly logger = getLogger('TaskRetrievalService');
constructor(
repository: TaskRepository,
projectId: string,
apiClient: ApiClient,
authManager: AuthManager
) {
this.repository = repository;
this.projectId = projectId;
this.apiClient = apiClient;
this.authManager = authManager;
}
/**
* Get task by ID (UUID or display ID like HAM-123)
* Uses repository for task structure and API for enriched document content
* @returns Task with subtasks, dependencies, and document content in details field
*/
async getTask(taskId: string): Promise<Task | null> {
try {
this.authManager.ensureBriefSelected('getTask');
const task = await this.repository.getTask(this.projectId, taskId);
if (!task) {
throw new TaskMasterError(
`Task ${taskId} not found`,
ERROR_CODES.TASK_NOT_FOUND,
{
operation: 'getTask',
taskId,
userMessage: `Task ${taskId} isn't available in the current project.`
}
);
}
// Fetch document content from API and merge into task.details
try {
const url = `/ai/api/v1/tasks/${task.id}`;
const apiResult = await this.apiClient.get<GetTaskResponse>(url);
if (apiResult.document?.content) {
task.details = apiResult.document.content;
}
} catch (error) {
// Document fetch failed - log but don't fail the whole operation
this.logger.debug(
`Could not fetch document content for task ${taskId}: ${error}`
);
}
this.logger.info(`✓ Retrieved task ${taskId}`);
if (task.details) {
this.logger.debug(
` Document content available (${task.details.length} chars)`
);
}
return task;
} catch (error) {
// If it's already a TaskMasterError, just add context and re-throw
if (error instanceof TaskMasterError) {
throw error.withContext({
operation: 'getTask',
taskId
});
}
// For other errors, wrap them
const errorMessage =
error instanceof Error ? error.message : String(error);
throw new TaskMasterError(
errorMessage,
ERROR_CODES.STORAGE_ERROR,
{
operation: 'getTask',
taskId
},
error as Error
);
}
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/remove-subtask.js:
--------------------------------------------------------------------------------
```javascript
/**
* Direct function wrapper for removeSubtask
*/
import { removeSubtask } from '../../../../scripts/modules/task-manager.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
/**
* Remove a subtask from its parent task
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - Subtask ID in format "parentId.subtaskId" (required)
* @param {boolean} [args.convert] - Whether to convert the subtask to a standalone task
* @param {boolean} [args.skipGenerate] - Skip regenerating task files
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function removeSubtaskDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, convert, skipGenerate, projectRoot, tag } = args;
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('removeSubtaskDirect called without tasksJsonPath');
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
if (!id) {
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message:
'Subtask ID is required and must be in format "parentId.subtaskId"'
}
};
}
// Validate subtask ID format
if (!id.includes('.')) {
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: `Invalid subtask ID format: ${id}. Expected format: "parentId.subtaskId"`
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Convert convertToTask to a boolean
const convertToTask = convert === true;
// Determine if we should generate files
const generateFiles = !skipGenerate;
log.info(
`Removing subtask ${id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})`
);
// Use the provided tasksPath
const result = await removeSubtask(
tasksPath,
id,
convertToTask,
generateFiles,
{
projectRoot,
tag
}
);
// Restore normal logging
disableSilentMode();
if (convertToTask && result) {
// Return info about the converted task
return {
success: true,
data: {
message: `Subtask ${id} successfully converted to task #${result.id}`,
task: result
}
};
} else {
// Return simple success message for deletion
return {
success: true,
data: {
message: `Subtask ${id} successfully removed`
}
};
}
} catch (error) {
// Ensure silent mode is disabled even if an outer error occurs
disableSilentMode();
log.error(`Error in removeSubtaskDirect: ${error.message}`);
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/src/ui/confirm.js:
--------------------------------------------------------------------------------
```javascript
import chalk from 'chalk';
import boxen from 'boxen';
/**
* Confirm removing profile rules (destructive operation)
* @param {string[]} profiles - Array of profile names to remove
* @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise
*/
async function confirmProfilesRemove(profiles) {
const profileList = profiles
.map((b) => b.charAt(0).toUpperCase() + b.slice(1))
.join(', ');
console.log(
boxen(
chalk.yellow(
`WARNING: This will selectively remove Task Master components for: ${profileList}.
What will be removed:
• Task Master specific rule files (e.g., cursor_rules.mdc, taskmaster.mdc, etc.)
• Task Master MCP server configuration (if no other MCP servers exist)
What will be preserved:
• Your existing custom rule files
• Other MCP server configurations
• The profile directory itself (unless completely empty after removal)
The .[profile] directory will only be removed if ALL of the following are true:
• All rules in the directory were Task Master rules (no custom rules)
• No other files or folders exist in the profile directory
• The MCP configuration was completely removed (no other servers)
Are you sure you want to proceed?`
),
{ padding: 1, borderColor: 'yellow', borderStyle: 'round' }
)
);
const inquirer = await import('inquirer');
const { confirm } = await inquirer.default.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Type y to confirm selective removal, or n to abort:',
default: false
}
]);
return confirm;
}
/**
* Confirm removing ALL remaining profile rules (extremely critical operation)
* @param {string[]} profiles - Array of profile names to remove
* @param {string[]} remainingProfiles - Array of profiles that would be left after removal
* @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise
*/
async function confirmRemoveAllRemainingProfiles(profiles, remainingProfiles) {
const profileList = profiles
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
.join(', ');
console.log(
boxen(
chalk.red.bold(
`⚠️ CRITICAL WARNING: REMOVING ALL TASK MASTER RULE PROFILES ⚠️\n\n` +
`You are about to remove Task Master components for: ${profileList}\n` +
`This will leave your project with NO Task Master rule profiles remaining!\n\n` +
`What will be removed:\n` +
`• All Task Master specific rule files\n` +
`• Task Master MCP server configurations\n` +
`• Profile directories (only if completely empty after removal)\n\n` +
`What will be preserved:\n` +
`• Your existing custom rule files\n` +
`• Other MCP server configurations\n` +
`• Profile directories with custom content\n\n` +
`This could impact Task Master functionality but will preserve your custom configurations.\n\n` +
`Are you absolutely sure you want to proceed?`
),
{
padding: 1,
borderColor: 'red',
borderStyle: 'double',
title: '🚨 CRITICAL OPERATION',
titleAlignment: 'center'
}
)
);
const inquirer = await import('inquirer');
const { confirm } = await inquirer.default.prompt([
{
type: 'confirm',
name: 'confirm',
message:
'Type y to confirm removing ALL Task Master rule profiles, or n to abort:',
default: false
}
]);
return confirm;
}
export { confirmProfilesRemove, confirmRemoveAllRemainingProfiles };
```
--------------------------------------------------------------------------------
/scripts/modules/task-manager/update-single-task-status.js:
--------------------------------------------------------------------------------
```javascript
import chalk from 'chalk';
import { log } from '../utils.js';
import { isValidTaskStatus } from '../../../src/constants/task-status.js';
/**
* Update the status of a single task
* @param {string} tasksPath - Path to the tasks.json file
* @param {string} taskIdInput - Task ID to update
* @param {string} newStatus - New status
* @param {Object} data - Tasks data
* @param {boolean} showUi - Whether to show UI elements
*/
async function updateSingleTaskStatus(
tasksPath,
taskIdInput,
newStatus,
data,
showUi = true
) {
if (!isValidTaskStatus(newStatus)) {
throw new Error(
`Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}`
);
}
// Check if it's a subtask (e.g., "1.2")
if (taskIdInput.includes('.')) {
const [parentId, subtaskId] = taskIdInput
.split('.')
.map((id) => parseInt(id, 10));
// Find the parent task
const parentTask = data.tasks.find((t) => t.id === parentId);
if (!parentTask) {
throw new Error(`Parent task ${parentId} not found`);
}
// Find the subtask
if (!parentTask.subtasks) {
throw new Error(`Parent task ${parentId} has no subtasks`);
}
const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
if (!subtask) {
throw new Error(
`Subtask ${subtaskId} not found in parent task ${parentId}`
);
}
// Update the subtask status
const oldStatus = subtask.status || 'pending';
subtask.status = newStatus;
log(
'info',
`Updated subtask ${parentId}.${subtaskId} status from '${oldStatus}' to '${newStatus}'`
);
// Check if all subtasks are done (if setting to 'done')
if (
newStatus.toLowerCase() === 'done' ||
newStatus.toLowerCase() === 'completed'
) {
const allSubtasksDone = parentTask.subtasks.every(
(st) => st.status === 'done' || st.status === 'completed'
);
// Suggest updating parent task if all subtasks are done
if (
allSubtasksDone &&
parentTask.status !== 'done' &&
parentTask.status !== 'completed'
) {
// Only show suggestion in CLI mode
if (showUi) {
console.log(
chalk.yellow(
`All subtasks of parent task ${parentId} are now marked as done.`
)
);
console.log(
chalk.yellow(
`Consider updating the parent task status with: task-master set-status --id=${parentId} --status=done`
)
);
}
}
}
} else {
// Handle regular task
const taskId = parseInt(taskIdInput, 10);
const task = data.tasks.find((t) => t.id === taskId);
if (!task) {
throw new Error(`Task ${taskId} not found`);
}
// Update the task status
const oldStatus = task.status || 'pending';
task.status = newStatus;
log(
'info',
`Updated task ${taskId} status from '${oldStatus}' to '${newStatus}'`
);
// If marking as done, also mark all subtasks as done
if (
(newStatus.toLowerCase() === 'done' ||
newStatus.toLowerCase() === 'completed') &&
task.subtasks &&
task.subtasks.length > 0
) {
const pendingSubtasks = task.subtasks.filter(
(st) => st.status !== 'done' && st.status !== 'completed'
);
if (pendingSubtasks.length > 0) {
log(
'info',
`Also marking ${pendingSubtasks.length} subtasks as '${newStatus}'`
);
pendingSubtasks.forEach((subtask) => {
subtask.status = newStatus;
});
}
}
}
}
export default updateSingleTaskStatus;
```
--------------------------------------------------------------------------------
/apps/extension/src/components/TaskDetails/useTaskDetails.ts:
--------------------------------------------------------------------------------
```typescript
import { useMemo } from 'react';
import { useTaskDetails as useTaskDetailsQuery } from '../../webview/hooks/useTaskQueries';
import type { TaskMasterTask } from '../../webview/types';
interface TaskFileData {
details?: string;
testStrategy?: string;
}
interface UseTaskDetailsProps {
taskId: string;
sendMessage: (message: any) => Promise<any>;
tasks: TaskMasterTask[];
}
export const useTaskDetails = ({
taskId,
sendMessage,
tasks
}: UseTaskDetailsProps) => {
// Parse task ID to determine if it's a subtask (e.g., "13.2")
const { isSubtask, parentId, subtaskIndex, taskIdForFetch } = useMemo(() => {
// Ensure taskId is a string
const taskIdStr = String(taskId);
const parts = taskIdStr.split('.');
if (parts.length === 2) {
return {
isSubtask: true,
parentId: parts[0],
subtaskIndex: parseInt(parts[1]) - 1, // Convert to 0-based index
taskIdForFetch: parts[0] // Always fetch parent task for subtasks
};
}
return {
isSubtask: false,
parentId: taskIdStr,
subtaskIndex: -1,
taskIdForFetch: taskIdStr
};
}, [taskId]);
// Use React Query to fetch full task details
const { data: fullTaskData, error: taskDetailsError } =
useTaskDetailsQuery(taskIdForFetch);
// Find current task from local state for immediate display
const { currentTask, parentTask } = useMemo(() => {
if (isSubtask) {
const parent = tasks.find((t) => t.id === parentId);
if (parent && parent.subtasks && parent.subtasks[subtaskIndex]) {
const subtask = parent.subtasks[subtaskIndex];
return { currentTask: subtask, parentTask: parent };
}
} else {
const task = tasks.find((t) => t.id === String(taskId));
if (task) {
return { currentTask: task, parentTask: null };
}
}
return { currentTask: null, parentTask: null };
}, [taskId, tasks, isSubtask, parentId, subtaskIndex]);
// Merge full task data from React Query with local state
const mergedCurrentTask = useMemo(() => {
if (!currentTask || !fullTaskData) return currentTask;
if (isSubtask && fullTaskData.subtasks) {
// Find the specific subtask in the full data
const subtaskData = fullTaskData.subtasks.find(
(st: any) =>
st.id === currentTask.id || st.id === parseInt(currentTask.id as any)
);
if (subtaskData) {
return { ...currentTask, ...subtaskData };
}
} else if (!isSubtask) {
// Merge parent task data
return { ...currentTask, ...fullTaskData };
}
return currentTask;
}, [currentTask, fullTaskData, isSubtask]);
// Extract task file data
const taskFileData: TaskFileData = useMemo(() => {
if (!mergedCurrentTask) return {};
return {
details: mergedCurrentTask.details || '',
testStrategy: mergedCurrentTask.testStrategy || ''
};
}, [mergedCurrentTask]);
// Get complexity score
const complexity = useMemo(() => {
if (mergedCurrentTask?.complexityScore !== undefined) {
return { score: mergedCurrentTask.complexityScore };
}
return null;
}, [mergedCurrentTask]);
// Function to refresh data after AI operations
const refreshComplexityAfterAI = () => {
// React Query will automatically refetch when mutations invalidate the query
// No need for manual refresh
};
return {
currentTask: mergedCurrentTask,
parentTask,
isSubtask,
taskFileData,
taskFileDataError: taskDetailsError ? 'Failed to load task details' : null,
complexity,
refreshComplexityAfterAI
};
};
```
--------------------------------------------------------------------------------
/src/prompts/analyze-complexity.json:
--------------------------------------------------------------------------------
```json
{
"id": "analyze-complexity",
"version": "1.0.0",
"description": "Analyze task complexity and generate expansion recommendations",
"metadata": {
"author": "system",
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z",
"tags": ["analysis", "complexity", "expansion", "recommendations"]
},
"parameters": {
"tasks": {
"type": "array",
"required": true,
"description": "Array of tasks to analyze"
},
"gatheredContext": {
"type": "string",
"default": "",
"description": "Additional project context"
},
"threshold": {
"type": "number",
"default": 5,
"min": 1,
"max": 10,
"description": "Complexity threshold for expansion recommendation"
},
"useResearch": {
"type": "boolean",
"default": false,
"description": "Use research mode for deeper analysis"
},
"hasCodebaseAnalysis": {
"type": "boolean",
"default": false,
"description": "Whether codebase analysis is available"
},
"projectRoot": {
"type": "string",
"default": "",
"description": "Project root path for context"
}
},
"prompts": {
"default": {
"system": "You are an expert software architect and project manager analyzing task complexity. Your analysis should consider implementation effort, technical challenges, dependencies, and testing requirements.\n\nIMPORTANT: For each task, provide an analysis object with ALL of the following fields:\n- taskId: The ID of the task being analyzed (positive integer)\n- taskTitle: The title of the task\n- complexityScore: A score from 1-10 indicating complexity\n- recommendedSubtasks: Number of subtasks recommended (non-negative integer; 0 if no expansion needed)\n- expansionPrompt: A prompt to guide subtask generation\n- reasoning: Your reasoning for the complexity score\n\nYour response MUST be a JSON object with a single \"complexityAnalysis\" property containing an array of these analysis objects. You may optionally include a \"metadata\" object, but no other top-level properties.",
"user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before analyzing task complexity:\n\n1. Use the Glob tool to explore the project structure and understand the codebase size\n2. Use the Grep tool to search for existing implementations related to each task\n3. Use the Read tool to examine key files that would be affected by these tasks\n4. Understand the current implementation state, patterns used, and technical debt\n\nBased on your codebase analysis:\n- Assess complexity based on ACTUAL code that needs to be modified/created\n- Consider existing abstractions and patterns that could simplify implementation\n- Identify tasks that require refactoring vs. greenfield development\n- Factor in dependencies between existing code and new features\n- Provide more accurate subtask recommendations based on real code structure\n\nProject Root: {{projectRoot}}\n\n{{/if}}Analyze the following tasks to determine their complexity (1-10 scale) and recommend the number of subtasks for expansion. Provide a brief reasoning and an initial expansion prompt for each.{{#if useResearch}} Consider current best practices, common implementation patterns, and industry standards in your analysis.{{/if}}\n\nTasks:\n{{{json tasks}}}\n{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}\n{{/if}}\n"
}
}
}
```
--------------------------------------------------------------------------------
/apps/cli/src/lib/model-management.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview TypeScript bridge for model management functions
* Wraps the JavaScript functions with proper TypeScript types
* Will remove once we move models.js and config-manager to new structure
*/
// @ts-ignore - JavaScript module without types
import * as configManagerJs from '../../../../scripts/modules/config-manager.js';
// @ts-ignore - JavaScript module without types
import * as modelsJs from '../../../../scripts/modules/task-manager/models.js';
// ========== Types ==========
export interface ModelCost {
input: number;
output: number;
}
export interface ModelData {
id: string;
provider?: string;
swe_score?: number | null;
cost_per_1m_tokens?: ModelCost | null;
allowed_roles?: string[];
max_tokens?: number;
supported?: boolean;
}
export interface ModelConfiguration {
provider: string;
modelId: string;
baseURL?: string;
sweScore: number | null;
cost: ModelCost | null;
keyStatus: {
cli: boolean;
mcp: boolean;
};
}
export interface ModelConfigurationResponse {
success: boolean;
data?: {
activeModels: {
main: ModelConfiguration;
research: ModelConfiguration;
fallback: ModelConfiguration | null;
};
message: string;
};
error?: {
code: string;
message: string;
};
}
export interface AvailableModel {
provider: string;
modelId: string;
sweScore: number | null;
cost: ModelCost | null;
allowedRoles: string[];
}
export interface AvailableModelsResponse {
success: boolean;
data?: {
models: AvailableModel[];
message: string;
};
error?: {
code: string;
message: string;
};
}
export interface SetModelResponse {
success: boolean;
data?: {
role: string;
provider: string;
modelId: string;
message: string;
warning?: string | null;
};
error?: {
code: string;
message: string;
};
}
export interface SetModelOptions {
providerHint?: string;
baseURL?: string;
session?: Record<string, string | undefined>;
mcpLog?: {
info: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
};
projectRoot: string;
}
// ========== Wrapped Functions ==========
/**
* Get the current model configuration
*/
export async function getModelConfiguration(
options: SetModelOptions
): Promise<ModelConfigurationResponse> {
return modelsJs.getModelConfiguration(
options as any
) as Promise<ModelConfigurationResponse>;
}
/**
* Get all available models
*/
export async function getAvailableModelsList(
options: SetModelOptions
): Promise<AvailableModelsResponse> {
return modelsJs.getAvailableModelsList(
options as any
) as Promise<AvailableModelsResponse>;
}
/**
* Set a model for a specific role
*/
export async function setModel(
role: 'main' | 'research' | 'fallback',
modelId: string,
options: SetModelOptions
): Promise<SetModelResponse> {
return modelsJs.setModel(
role,
modelId,
options as any
) as Promise<SetModelResponse>;
}
/**
* Get config from config manager
*/
export function getConfig(projectRoot: string): any {
return configManagerJs.getConfig(projectRoot);
}
/**
* Write config using config manager
*/
export function writeConfig(config: any, projectRoot: string): boolean {
return configManagerJs.writeConfig(config, projectRoot);
}
/**
* Get available models from config manager
*/
export function getAvailableModels(): ModelData[] {
return configManagerJs.getAvailableModels() as ModelData[];
}
```
--------------------------------------------------------------------------------
/tests/unit/kebab-case-validation.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Kebab case validation tests
*/
import { jest } from '@jest/globals';
import { toKebabCase } from '../../scripts/modules/utils.js';
// Create a test implementation of detectCamelCaseFlags
function testDetectCamelCaseFlags(args) {
const camelCaseFlags = [];
for (const arg of args) {
if (arg.startsWith('--')) {
const flagName = arg.split('=')[0].slice(2); // Remove -- and anything after =
// Skip single-word flags - they can't be camelCase
if (!flagName.includes('-') && !/[A-Z]/.test(flagName)) {
continue;
}
// Check for camelCase pattern (lowercase followed by uppercase)
if (/[a-z][A-Z]/.test(flagName)) {
const kebabVersion = toKebabCase(flagName);
if (kebabVersion !== flagName) {
camelCaseFlags.push({
original: flagName,
kebabCase: kebabVersion
});
}
}
}
}
return camelCaseFlags;
}
describe('Kebab Case Validation', () => {
describe('toKebabCase', () => {
test('should convert camelCase to kebab-case', () => {
expect(toKebabCase('promptText')).toBe('prompt-text');
expect(toKebabCase('userID')).toBe('user-id');
expect(toKebabCase('numTasks')).toBe('num-tasks');
});
test('should handle already kebab-case strings', () => {
expect(toKebabCase('already-kebab-case')).toBe('already-kebab-case');
expect(toKebabCase('kebab-case')).toBe('kebab-case');
});
test('should handle single words', () => {
expect(toKebabCase('single')).toBe('single');
expect(toKebabCase('file')).toBe('file');
});
});
describe('detectCamelCaseFlags', () => {
test('should properly detect camelCase flags', () => {
const args = [
'node',
'task-master',
'add-task',
'--promptText=test',
'--userID=123'
];
const flags = testDetectCamelCaseFlags(args);
expect(flags).toHaveLength(2);
expect(flags).toContainEqual({
original: 'promptText',
kebabCase: 'prompt-text'
});
expect(flags).toContainEqual({
original: 'userID',
kebabCase: 'user-id'
});
});
test('should not flag kebab-case or lowercase flags', () => {
const args = [
'node',
'task-master',
'add-task',
'--prompt=test',
'--user-id=123'
];
const flags = testDetectCamelCaseFlags(args);
expect(flags).toHaveLength(0);
});
test('should not flag any single-word flags regardless of case', () => {
const args = [
'node',
'task-master',
'add-task',
'--prompt=test', // lowercase
'--PROMPT=test', // uppercase
'--Prompt=test', // mixed case
'--file=test', // lowercase
'--FILE=test', // uppercase
'--File=test' // mixed case
];
const flags = testDetectCamelCaseFlags(args);
expect(flags).toHaveLength(0);
});
test('should handle mixed case flags correctly', () => {
const args = [
'node',
'task-master',
'add-task',
'--prompt=test', // single word, should pass
'--promptText=test', // camelCase, should flag
'--prompt-text=test', // kebab-case, should pass
'--ID=123', // single word, should pass
'--userId=123', // camelCase, should flag
'--user-id=123' // kebab-case, should pass
];
const flags = testDetectCamelCaseFlags(args);
expect(flags).toHaveLength(2);
expect(flags).toContainEqual({
original: 'promptText',
kebabCase: 'prompt-text'
});
expect(flags).toContainEqual({
original: 'userId',
kebabCase: 'user-id'
});
});
});
});
```
--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/find-next-task.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Tests for the find-next-task.js module
*/
import { jest } from '@jest/globals';
import findNextTask from '../../../../../scripts/modules/task-manager/find-next-task.js';
describe('findNextTask', () => {
test('should return the highest priority task with all dependencies satisfied', () => {
const tasks = [
{
id: 1,
title: 'Setup Project',
status: 'done',
dependencies: [],
priority: 'high'
},
{
id: 2,
title: 'Implement Core Features',
status: 'pending',
dependencies: [1],
priority: 'high'
},
{
id: 3,
title: 'Create Documentation',
status: 'pending',
dependencies: [1],
priority: 'medium'
},
{
id: 4,
title: 'Deploy Application',
status: 'pending',
dependencies: [2, 3],
priority: 'high'
}
];
const nextTask = findNextTask(tasks);
expect(nextTask).toBeDefined();
expect(nextTask.id).toBe(2);
expect(nextTask.title).toBe('Implement Core Features');
});
test('should prioritize by priority level when dependencies are equal', () => {
const tasks = [
{
id: 1,
title: 'Setup Project',
status: 'done',
dependencies: [],
priority: 'high'
},
{
id: 2,
title: 'Low Priority Task',
status: 'pending',
dependencies: [1],
priority: 'low'
},
{
id: 3,
title: 'Medium Priority Task',
status: 'pending',
dependencies: [1],
priority: 'medium'
},
{
id: 4,
title: 'High Priority Task',
status: 'pending',
dependencies: [1],
priority: 'high'
}
];
const nextTask = findNextTask(tasks);
expect(nextTask.id).toBe(4);
expect(nextTask.priority).toBe('high');
});
test('should return null when all tasks are completed', () => {
const tasks = [
{
id: 1,
title: 'Setup Project',
status: 'done',
dependencies: [],
priority: 'high'
},
{
id: 2,
title: 'Implement Features',
status: 'done',
dependencies: [1],
priority: 'high'
}
];
const nextTask = findNextTask(tasks);
expect(nextTask).toBeNull();
});
test('should return null when all pending tasks have unsatisfied dependencies', () => {
const tasks = [
{
id: 1,
title: 'Setup Project',
status: 'pending',
dependencies: [2],
priority: 'high'
},
{
id: 2,
title: 'Implement Features',
status: 'pending',
dependencies: [1],
priority: 'high'
}
];
const nextTask = findNextTask(tasks);
expect(nextTask).toBeNull();
});
test('should handle empty tasks array', () => {
const nextTask = findNextTask([]);
expect(nextTask).toBeNull();
});
test('should consider subtask dependencies when finding next task', () => {
const tasks = [
{
id: 1,
title: 'Parent Task',
status: 'in-progress',
dependencies: [],
priority: 'high',
subtasks: [
{
id: 1,
title: 'Subtask 1',
status: 'done',
dependencies: []
},
{
id: 2,
title: 'Subtask 2',
status: 'pending',
dependencies: []
}
]
},
{
id: 2,
title: 'Dependent Task',
status: 'pending',
dependencies: [1],
priority: 'high'
}
];
const nextTask = findNextTask(tasks);
// Task 2 should not be returned because Task 1 is not completely done
// (it has a pending subtask)
expect(nextTask).not.toEqual(expect.objectContaining({ id: 2 }));
});
});
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/scope-up.js:
--------------------------------------------------------------------------------
```javascript
/**
* scope-up.js
* Direct function implementation for scoping up task complexity
*/
import { scopeUpTask } from '../../../../scripts/modules/task-manager.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for scoping up task complexity with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.id - Comma-separated list of task IDs to scope up
* @param {string} [args.strength='regular'] - Strength level (light, regular, heavy)
* @param {string} [args.prompt] - Custom prompt for scoping adjustments
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
* @param {boolean} [args.research=false] - Whether to use research capabilities for scoping
* @param {string} args.projectRoot - Project root path
* @param {string} [args.tag] - Tag for the task context (optional)
* @param {Object} log - Logger object
* @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function scopeUpDirect(args, log, context = {}) {
// Destructure expected args
const {
tasksJsonPath,
id,
strength = 'regular',
prompt: customPrompt,
research = false,
projectRoot,
tag
} = args;
const { session } = context; // Destructure session from context
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
try {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('scopeUpDirect called without tasksJsonPath');
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Check required parameters
if (!id) {
log.error('Missing required parameter: id');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'The id parameter is required for scoping up tasks'
}
};
}
// Parse task IDs - convert to numbers as expected by scopeUpTask
const taskIds = id.split(',').map((taskId) => parseInt(taskId.trim(), 10));
log.info(
`Scoping up tasks: ${taskIds.join(', ')}, strength: ${strength}, research: ${research}`
);
// Call the scopeUpTask function
const result = await scopeUpTask(
tasksJsonPath,
taskIds,
strength,
customPrompt,
{
session,
mcpLog,
projectRoot,
commandName: 'scope-up',
outputType: 'mcp',
tag,
research
},
'json' // outputFormat
);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
updatedTasks: result.updatedTasks,
tasksUpdated: result.updatedTasks.length,
message: `Successfully scoped up ${result.updatedTasks.length} task(s)`,
telemetryData: result.telemetryData
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in scopeUpDirect: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'SCOPE_UP_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/expand-all.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/expand-all.js
* Tool for expanding all pending tasks with subtasks
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { expandAllTasksDirect } from '../core/task-master-core.js';
import {
findTasksPath,
resolveComplexityReportOutputPath
} from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the expandAll tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerExpandAllTool(server) {
server.addTool({
name: 'expand_all',
description:
'Expand all pending tasks into subtasks based on complexity or defaults',
parameters: z.object({
num: z
.string()
.optional()
.describe(
'Target number of subtasks per task (uses complexity/defaults otherwise)'
),
research: z
.boolean()
.optional()
.describe(
'Enable research-backed subtask generation (e.g., using Perplexity)'
),
prompt: z
.string()
.optional()
.describe(
'Additional context to guide subtask generation for all tasks'
),
force: z
.boolean()
.optional()
.describe(
'Force regeneration of subtasks for tasks that already have them'
),
file: z
.string()
.optional()
.describe(
'Absolute path to the tasks file in the /tasks folder inside the project root (default: tasks/tasks.json)'
),
projectRoot: z
.string()
.optional()
.describe(
'Absolute path to the project root directory (derived from session if possible)'
),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(
`Tool expand_all execution started with args: ${JSON.stringify(args)}`
);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
log.info(`Resolved tasks.json path: ${tasksJsonPath}`);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Resolve complexity report path to use recommendations from analyze-complexity
const complexityReportPath = resolveComplexityReportOutputPath(
null,
{ projectRoot: args.projectRoot, tag: resolvedTag },
log
);
log.info(`Using complexity report path: ${complexityReportPath}`);
const result = await expandAllTasksDirect(
{
tasksJsonPath: tasksJsonPath,
num: args.num,
research: args.research,
prompt: args.prompt,
force: args.force,
projectRoot: args.projectRoot,
tag: resolvedTag,
complexityReportPath
},
log,
{ session }
);
return handleApiResult(
result,
log,
'Error expanding all tasks',
undefined,
args.projectRoot
);
} catch (error) {
log.error(
`Unexpected error in expand_all tool execute: ${error.message}`
);
if (error.stack) {
log.error(error.stack);
}
return createErrorResponse(
`An unexpected error occurred: ${error.message}`
);
}
})
});
}
```
--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/json-extractor.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Extract JSON from AI's response using a tolerant parser.
*
* The function removes common wrappers such as markdown fences or variable
* declarations and then attempts to parse the remaining text with
* `jsonc-parser`. If valid JSON (or JSONC) can be parsed, it is returned as a
* string via `JSON.stringify`. Otherwise the original text is returned.
*
* @param text - Raw text which may contain JSON
* @returns A valid JSON string if extraction succeeds, otherwise the original text
*/
import { parse, type ParseError } from 'jsonc-parser';
export function extractJson(text: string): string {
let content = text.trim();
// Strip ```json or ``` fences
const fenceMatch = /```(?:json)?\s*([\s\S]*?)\s*```/i.exec(content);
if (fenceMatch) {
content = fenceMatch[1];
}
// Strip variable declarations like `const foo =` or `let foo =`
const varMatch = /^\s*(?:const|let|var)\s+\w+\s*=\s*([\s\S]*)/i.exec(content);
if (varMatch) {
content = varMatch[1];
// Remove trailing semicolon if present
if (content.trim().endsWith(';')) {
content = content.trim().slice(0, -1);
}
}
// Find the first opening bracket
const firstObj = content.indexOf('{');
const firstArr = content.indexOf('[');
if (firstObj === -1 && firstArr === -1) {
return text;
}
const start =
firstArr === -1
? firstObj
: firstObj === -1
? firstArr
: Math.min(firstObj, firstArr);
content = content.slice(start);
// Try to parse the entire string with jsonc-parser
const tryParse = (value: string): string | undefined => {
const errors: ParseError[] = [];
try {
const result = parse(value, errors, { allowTrailingComma: true });
if (errors.length === 0) {
return JSON.stringify(result, null, 2);
}
} catch {
// ignore
}
return undefined;
};
const parsed = tryParse(content);
if (parsed !== undefined) {
return parsed;
}
// If parsing the full string failed, use a more efficient approach
// to find valid JSON boundaries
const openChar = content[0];
const closeChar = openChar === '{' ? '}' : ']';
// Find all potential closing positions by tracking nesting depth
const closingPositions: number[] = [];
let depth = 0;
let inString = false;
let escapeNext = false;
for (let i = 0; i < content.length; i++) {
const char = content[i];
if (escapeNext) {
escapeNext = false;
continue;
}
if (char === '\\') {
escapeNext = true;
continue;
}
if (char === '"' && !inString) {
inString = true;
continue;
}
if (char === '"' && inString) {
inString = false;
continue;
}
// Skip content inside strings
if (inString) continue;
if (char === openChar) {
depth++;
} else if (char === closeChar) {
depth--;
if (depth === 0) {
closingPositions.push(i + 1);
}
}
}
// Try parsing at each valid closing position, starting from the end
for (let i = closingPositions.length - 1; i >= 0; i--) {
const attempt = tryParse(content.slice(0, closingPositions[i]));
if (attempt !== undefined) {
return attempt;
}
}
// As a final fallback, try the original character-by-character approach
// but only for the last 1000 characters to limit performance impact
const searchStart = Math.max(0, content.length - 1000);
for (let end = content.length - 1; end > searchStart; end--) {
const attempt = tryParse(content.slice(0, end));
if (attempt !== undefined) {
return attempt;
}
}
return text;
}
```