This is page 12 of 50. Use http://codebase.md/eyaltoledano/claude-task-master?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── commands
│ │ └── dedupe.md
│ └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│ └── marketplace.json
├── .coderabbit.yaml
├── .cursor
│ ├── mcp.json
│ └── rules
│ ├── ai_providers.mdc
│ ├── ai_services.mdc
│ ├── architecture.mdc
│ ├── changeset.mdc
│ ├── commands.mdc
│ ├── context_gathering.mdc
│ ├── cursor_rules.mdc
│ ├── dependencies.mdc
│ ├── dev_workflow.mdc
│ ├── git_workflow.mdc
│ ├── glossary.mdc
│ ├── mcp.mdc
│ ├── new_features.mdc
│ ├── self_improve.mdc
│ ├── tags.mdc
│ ├── taskmaster.mdc
│ ├── tasks.mdc
│ ├── telemetry.mdc
│ ├── test_workflow.mdc
│ ├── tests.mdc
│ ├── ui.mdc
│ └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── enhancements---feature-requests.md
│ │ └── feedback.md
│ ├── PULL_REQUEST_TEMPLATE
│ │ ├── bugfix.md
│ │ ├── config.yml
│ │ ├── feature.md
│ │ └── integration.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── scripts
│ │ ├── auto-close-duplicates.mjs
│ │ ├── backfill-duplicate-comments.mjs
│ │ ├── check-pre-release-mode.mjs
│ │ ├── parse-metrics.mjs
│ │ ├── release.mjs
│ │ ├── tag-extension.mjs
│ │ ├── utils.mjs
│ │ └── validate-changesets.mjs
│ └── workflows
│ ├── auto-close-duplicates.yml
│ ├── backfill-duplicate-comments.yml
│ ├── ci.yml
│ ├── claude-dedupe-issues.yml
│ ├── claude-docs-trigger.yml
│ ├── claude-docs-updater.yml
│ ├── claude-issue-triage.yml
│ ├── claude.yml
│ ├── extension-ci.yml
│ ├── extension-release.yml
│ ├── log-issue-events.yml
│ ├── pre-release.yml
│ ├── release-check.yml
│ ├── release.yml
│ ├── update-models-md.yml
│ └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│ ├── hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── settings
│ │ └── mcp.json
│ └── steering
│ ├── dev_workflow.md
│ ├── kiro_rules.md
│ ├── self_improve.md
│ ├── taskmaster_hooks_workflow.md
│ └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│ ├── CLAUDE.md
│ ├── config.json
│ ├── docs
│ │ ├── autonomous-tdd-git-workflow.md
│ │ ├── MIGRATION-ROADMAP.md
│ │ ├── prd-tm-start.txt
│ │ ├── prd.txt
│ │ ├── README.md
│ │ ├── research
│ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│ │ │ ├── 2025-06-14_test-save-functionality.md
│ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│ │ ├── task-template-importing-prd.txt
│ │ ├── tdd-workflow-phase-0-spike.md
│ │ ├── tdd-workflow-phase-1-core-rails.md
│ │ ├── tdd-workflow-phase-1-orchestrator.md
│ │ ├── tdd-workflow-phase-2-pr-resumability.md
│ │ ├── tdd-workflow-phase-3-extensibility-guardrails.md
│ │ ├── test-prd.txt
│ │ └── tm-core-phase-1.txt
│ ├── reports
│ │ ├── task-complexity-report_autonomous-tdd-git-workflow.json
│ │ ├── task-complexity-report_cc-kiro-hooks.json
│ │ ├── task-complexity-report_tdd-phase-1-core-rails.json
│ │ ├── task-complexity-report_tdd-workflow-phase-0.json
│ │ ├── task-complexity-report_test-prd-tag.json
│ │ ├── task-complexity-report_tm-core-phase-1.json
│ │ ├── task-complexity-report.json
│ │ └── tm-core-complexity.json
│ ├── state.json
│ ├── tasks
│ │ ├── task_001_tm-start.txt
│ │ ├── task_002_tm-start.txt
│ │ ├── task_003_tm-start.txt
│ │ ├── task_004_tm-start.txt
│ │ ├── task_007_tm-start.txt
│ │ └── tasks.json
│ └── templates
│ ├── example_prd_rpg.md
│ └── example_prd.md
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── apps
│ ├── cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── command-registry.ts
│ │ │ ├── commands
│ │ │ │ ├── auth.command.ts
│ │ │ │ ├── autopilot
│ │ │ │ │ ├── abort.command.ts
│ │ │ │ │ ├── commit.command.ts
│ │ │ │ │ ├── complete.command.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next.command.ts
│ │ │ │ │ ├── resume.command.ts
│ │ │ │ │ ├── shared.ts
│ │ │ │ │ ├── start.command.ts
│ │ │ │ │ └── status.command.ts
│ │ │ │ ├── briefs.command.ts
│ │ │ │ ├── context.command.ts
│ │ │ │ ├── export.command.ts
│ │ │ │ ├── list.command.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── custom-providers.ts
│ │ │ │ │ ├── fetchers.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompts.ts
│ │ │ │ │ ├── setup.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── next.command.ts
│ │ │ │ ├── set-status.command.ts
│ │ │ │ ├── show.command.ts
│ │ │ │ ├── start.command.ts
│ │ │ │ └── tags.command.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── model-management.ts
│ │ │ ├── types
│ │ │ │ └── tag-management.d.ts
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── cardBox.component.ts
│ │ │ │ │ ├── dashboard.component.ts
│ │ │ │ │ ├── header.component.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next-task.component.ts
│ │ │ │ │ ├── suggested-steps.component.ts
│ │ │ │ │ └── task-detail.component.ts
│ │ │ │ ├── display
│ │ │ │ │ ├── messages.ts
│ │ │ │ │ └── tables.ts
│ │ │ │ ├── formatters
│ │ │ │ │ ├── complexity-formatters.ts
│ │ │ │ │ ├── dependency-formatters.ts
│ │ │ │ │ ├── priority-formatters.ts
│ │ │ │ │ ├── status-formatters.spec.ts
│ │ │ │ │ └── status-formatters.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── layout
│ │ │ │ ├── helpers.spec.ts
│ │ │ │ └── helpers.ts
│ │ │ └── utils
│ │ │ ├── auth-helpers.ts
│ │ │ ├── auto-update.ts
│ │ │ ├── brief-selection.ts
│ │ │ ├── display-helpers.ts
│ │ │ ├── error-handler.ts
│ │ │ ├── index.ts
│ │ │ ├── project-root.ts
│ │ │ ├── task-status.ts
│ │ │ ├── ui.spec.ts
│ │ │ └── ui.ts
│ │ ├── tests
│ │ │ ├── integration
│ │ │ │ └── commands
│ │ │ │ └── autopilot
│ │ │ │ └── workflow.test.ts
│ │ │ └── unit
│ │ │ ├── commands
│ │ │ │ ├── autopilot
│ │ │ │ │ └── shared.test.ts
│ │ │ │ ├── list.command.spec.ts
│ │ │ │ └── show.command.spec.ts
│ │ │ └── ui
│ │ │ └── dashboard.component.spec.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── docs
│ │ ├── archive
│ │ │ ├── ai-client-utils-example.mdx
│ │ │ ├── ai-development-workflow.mdx
│ │ │ ├── command-reference.mdx
│ │ │ ├── configuration.mdx
│ │ │ ├── cursor-setup.mdx
│ │ │ ├── examples.mdx
│ │ │ └── Installation.mdx
│ │ ├── best-practices
│ │ │ ├── advanced-tasks.mdx
│ │ │ ├── configuration-advanced.mdx
│ │ │ └── index.mdx
│ │ ├── capabilities
│ │ │ ├── cli-root-commands.mdx
│ │ │ ├── index.mdx
│ │ │ ├── mcp.mdx
│ │ │ ├── rpg-method.mdx
│ │ │ └── task-structure.mdx
│ │ ├── CHANGELOG.md
│ │ ├── command-reference.mdx
│ │ ├── configuration.mdx
│ │ ├── docs.json
│ │ ├── favicon.svg
│ │ ├── getting-started
│ │ │ ├── api-keys.mdx
│ │ │ ├── contribute.mdx
│ │ │ ├── faq.mdx
│ │ │ └── quick-start
│ │ │ ├── configuration-quick.mdx
│ │ │ ├── execute-quick.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── moving-forward.mdx
│ │ │ ├── prd-quick.mdx
│ │ │ ├── quick-start.mdx
│ │ │ ├── requirements.mdx
│ │ │ ├── rules-quick.mdx
│ │ │ └── tasks-quick.mdx
│ │ ├── introduction.mdx
│ │ ├── licensing.md
│ │ ├── logo
│ │ │ ├── dark.svg
│ │ │ ├── light.svg
│ │ │ └── task-master-logo.png
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── style.css
│ │ ├── tdd-workflow
│ │ │ ├── ai-agent-integration.mdx
│ │ │ └── quickstart.mdx
│ │ ├── vercel.json
│ │ └── whats-new.mdx
│ ├── extension
│ │ ├── .vscodeignore
│ │ ├── assets
│ │ │ ├── banner.png
│ │ │ ├── icon-dark.svg
│ │ │ ├── icon-light.svg
│ │ │ ├── icon.png
│ │ │ ├── screenshots
│ │ │ │ ├── kanban-board.png
│ │ │ │ └── task-details.png
│ │ │ └── sidebar-icon.svg
│ │ ├── CHANGELOG.md
│ │ ├── components.json
│ │ ├── docs
│ │ │ ├── extension-CI-setup.md
│ │ │ └── extension-development-guide.md
│ │ ├── esbuild.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── package.mjs
│ │ ├── package.publish.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── components
│ │ │ │ ├── ConfigView.tsx
│ │ │ │ ├── constants.ts
│ │ │ │ ├── TaskDetails
│ │ │ │ │ ├── AIActionsSection.tsx
│ │ │ │ │ ├── DetailsSection.tsx
│ │ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ │ ├── SubtasksSection.tsx
│ │ │ │ │ ├── TaskMetadataSidebar.tsx
│ │ │ │ │ └── useTaskDetails.ts
│ │ │ │ ├── TaskDetailsView.tsx
│ │ │ │ ├── TaskMasterLogo.tsx
│ │ │ │ └── ui
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── breadcrumb.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── collapsible.tsx
│ │ │ │ ├── CollapsibleSection.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── scroll-area.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── shadcn-io
│ │ │ │ │ └── kanban
│ │ │ │ │ └── index.tsx
│ │ │ │ └── textarea.tsx
│ │ │ ├── extension.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── utils.ts
│ │ │ ├── services
│ │ │ │ ├── config-service.ts
│ │ │ │ ├── error-handler.ts
│ │ │ │ ├── notification-preferences.ts
│ │ │ │ ├── polling-service.ts
│ │ │ │ ├── polling-strategies.ts
│ │ │ │ ├── sidebar-webview-manager.ts
│ │ │ │ ├── task-repository.ts
│ │ │ │ ├── terminal-manager.ts
│ │ │ │ └── webview-manager.ts
│ │ │ ├── test
│ │ │ │ └── extension.test.ts
│ │ │ ├── utils
│ │ │ │ ├── configManager.ts
│ │ │ │ ├── connectionManager.ts
│ │ │ │ ├── errorHandler.ts
│ │ │ │ ├── event-emitter.ts
│ │ │ │ ├── logger.ts
│ │ │ │ ├── mcpClient.ts
│ │ │ │ ├── notificationPreferences.ts
│ │ │ │ └── task-master-api
│ │ │ │ ├── cache
│ │ │ │ │ └── cache-manager.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mcp-client.ts
│ │ │ │ ├── transformers
│ │ │ │ │ └── task-transformer.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ └── webview
│ │ │ ├── App.tsx
│ │ │ ├── components
│ │ │ │ ├── AppContent.tsx
│ │ │ │ ├── EmptyState.tsx
│ │ │ │ ├── ErrorBoundary.tsx
│ │ │ │ ├── PollingStatus.tsx
│ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ ├── SidebarView.tsx
│ │ │ │ ├── TagDropdown.tsx
│ │ │ │ ├── TaskCard.tsx
│ │ │ │ ├── TaskEditModal.tsx
│ │ │ │ ├── TaskMasterKanban.tsx
│ │ │ │ ├── ToastContainer.tsx
│ │ │ │ └── ToastNotification.tsx
│ │ │ ├── constants
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ └── VSCodeContext.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useTaskQueries.ts
│ │ │ │ ├── useVSCodeMessages.ts
│ │ │ │ └── useWebviewHeight.ts
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── providers
│ │ │ │ └── QueryProvider.tsx
│ │ │ ├── reducers
│ │ │ │ └── appReducer.ts
│ │ │ ├── sidebar.tsx
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ └── utils
│ │ │ ├── logger.ts
│ │ │ └── toast.ts
│ │ └── tsconfig.json
│ └── mcp
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── tools
│ │ ├── autopilot
│ │ │ ├── abort.tool.ts
│ │ │ ├── commit.tool.ts
│ │ │ ├── complete.tool.ts
│ │ │ ├── finalize.tool.ts
│ │ │ ├── index.ts
│ │ │ ├── next.tool.ts
│ │ │ ├── resume.tool.ts
│ │ │ ├── start.tool.ts
│ │ │ └── status.tool.ts
│ │ ├── README-ZOD-V3.md
│ │ └── tasks
│ │ ├── get-task.tool.ts
│ │ ├── get-tasks.tool.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── assets
│ ├── .windsurfrules
│ ├── AGENTS.md
│ ├── claude
│ │ └── TM_COMMANDS_GUIDE.md
│ ├── config.json
│ ├── env.example
│ ├── example_prd_rpg.txt
│ ├── example_prd.txt
│ ├── GEMINI.md
│ ├── gitignore
│ ├── kiro-hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── roocode
│ │ ├── .roo
│ │ │ ├── rules-architect
│ │ │ │ └── architect-rules
│ │ │ ├── rules-ask
│ │ │ │ └── ask-rules
│ │ │ ├── rules-code
│ │ │ │ └── code-rules
│ │ │ ├── rules-debug
│ │ │ │ └── debug-rules
│ │ │ ├── rules-orchestrator
│ │ │ │ └── orchestrator-rules
│ │ │ └── rules-test
│ │ │ └── test-rules
│ │ └── .roomodes
│ ├── rules
│ │ ├── cursor_rules.mdc
│ │ ├── dev_workflow.mdc
│ │ ├── self_improve.mdc
│ │ ├── taskmaster_hooks_workflow.mdc
│ │ └── taskmaster.mdc
│ └── scripts_README.md
├── bin
│ └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE_CODE_PLUGIN.md
├── CLAUDE.md
├── context
│ ├── chats
│ │ ├── add-task-dependencies-1.md
│ │ └── max-min-tokens.txt.md
│ ├── fastmcp-core.txt
│ ├── fastmcp-docs.txt
│ ├── MCP_INTEGRATION.md
│ ├── mcp-js-sdk-docs.txt
│ ├── mcp-protocol-repo.txt
│ ├── mcp-protocol-schema-03262025.json
│ └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│ ├── claude-code-integration.md
│ ├── CLI-COMMANDER-PATTERN.md
│ ├── command-reference.md
│ ├── configuration.md
│ ├── contributor-docs
│ │ ├── testing-roo-integration.md
│ │ └── worktree-setup.md
│ ├── cross-tag-task-movement.md
│ ├── examples
│ │ ├── claude-code-usage.md
│ │ └── codex-cli-usage.md
│ ├── examples.md
│ ├── licensing.md
│ ├── mcp-provider-guide.md
│ ├── mcp-provider.md
│ ├── migration-guide.md
│ ├── models.md
│ ├── providers
│ │ ├── codex-cli.md
│ │ └── gemini-cli.md
│ ├── README.md
│ ├── scripts
│ │ └── models-json-to-markdown.js
│ ├── task-structure.md
│ └── tutorial.md
├── images
│ ├── hamster-hiring.png
│ └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│ ├── server.js
│ └── src
│ ├── core
│ │ ├── __tests__
│ │ │ └── context-manager.test.js
│ │ ├── context-manager.js
│ │ ├── direct-functions
│ │ │ ├── add-dependency.js
│ │ │ ├── add-subtask.js
│ │ │ ├── add-tag.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── cache-stats.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── complexity-report.js
│ │ │ ├── copy-tag.js
│ │ │ ├── create-tag-from-branch.js
│ │ │ ├── delete-tag.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── fix-dependencies.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── initialize-project.js
│ │ │ ├── list-tags.js
│ │ │ ├── models.js
│ │ │ ├── move-task-cross-tag.js
│ │ │ ├── move-task.js
│ │ │ ├── next-task.js
│ │ │ ├── parse-prd.js
│ │ │ ├── remove-dependency.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── rename-tag.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── rules.js
│ │ │ ├── scope-down.js
│ │ │ ├── scope-up.js
│ │ │ ├── set-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ ├── update-tasks.js
│ │ │ ├── use-tag.js
│ │ │ └── validate-dependencies.js
│ │ ├── task-master-core.js
│ │ └── utils
│ │ ├── env-utils.js
│ │ └── path-utils.js
│ ├── custom-sdk
│ │ ├── errors.js
│ │ ├── index.js
│ │ ├── json-extractor.js
│ │ ├── language-model.js
│ │ ├── message-converter.js
│ │ └── schema-converter.js
│ ├── index.js
│ ├── logger.js
│ ├── providers
│ │ └── mcp-provider.js
│ └── tools
│ ├── add-dependency.js
│ ├── add-subtask.js
│ ├── add-tag.js
│ ├── add-task.js
│ ├── analyze.js
│ ├── clear-subtasks.js
│ ├── complexity-report.js
│ ├── copy-tag.js
│ ├── delete-tag.js
│ ├── expand-all.js
│ ├── expand-task.js
│ ├── fix-dependencies.js
│ ├── generate.js
│ ├── get-operation-status.js
│ ├── index.js
│ ├── initialize-project.js
│ ├── list-tags.js
│ ├── models.js
│ ├── move-task.js
│ ├── next-task.js
│ ├── parse-prd.js
│ ├── README-ZOD-V3.md
│ ├── remove-dependency.js
│ ├── remove-subtask.js
│ ├── remove-task.js
│ ├── rename-tag.js
│ ├── research.js
│ ├── response-language.js
│ ├── rules.js
│ ├── scope-down.js
│ ├── scope-up.js
│ ├── set-task-status.js
│ ├── tool-registry.js
│ ├── update-subtask.js
│ ├── update-task.js
│ ├── update.js
│ ├── use-tag.js
│ ├── utils.js
│ └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│ ├── ai-sdk-provider-grok-cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── errors.test.ts
│ │ │ ├── errors.ts
│ │ │ ├── grok-cli-language-model.ts
│ │ │ ├── grok-cli-provider.test.ts
│ │ │ ├── grok-cli-provider.ts
│ │ │ ├── index.ts
│ │ │ ├── json-extractor.test.ts
│ │ │ ├── json-extractor.ts
│ │ │ ├── message-converter.test.ts
│ │ │ ├── message-converter.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── build-config
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ └── tsdown.base.ts
│ │ └── tsconfig.json
│ ├── claude-code-plugin
│ │ ├── .claude-plugin
│ │ │ └── plugin.json
│ │ ├── .gitignore
│ │ ├── agents
│ │ │ ├── task-checker.md
│ │ │ ├── task-executor.md
│ │ │ └── task-orchestrator.md
│ │ ├── CHANGELOG.md
│ │ ├── commands
│ │ │ ├── add-dependency.md
│ │ │ ├── add-subtask.md
│ │ │ ├── add-task.md
│ │ │ ├── analyze-complexity.md
│ │ │ ├── analyze-project.md
│ │ │ ├── auto-implement-tasks.md
│ │ │ ├── command-pipeline.md
│ │ │ ├── complexity-report.md
│ │ │ ├── convert-task-to-subtask.md
│ │ │ ├── expand-all-tasks.md
│ │ │ ├── expand-task.md
│ │ │ ├── fix-dependencies.md
│ │ │ ├── generate-tasks.md
│ │ │ ├── help.md
│ │ │ ├── init-project-quick.md
│ │ │ ├── init-project.md
│ │ │ ├── install-taskmaster.md
│ │ │ ├── learn.md
│ │ │ ├── list-tasks-by-status.md
│ │ │ ├── list-tasks-with-subtasks.md
│ │ │ ├── list-tasks.md
│ │ │ ├── next-task.md
│ │ │ ├── parse-prd-with-research.md
│ │ │ ├── parse-prd.md
│ │ │ ├── project-status.md
│ │ │ ├── quick-install-taskmaster.md
│ │ │ ├── remove-all-subtasks.md
│ │ │ ├── remove-dependency.md
│ │ │ ├── remove-subtask.md
│ │ │ ├── remove-subtasks.md
│ │ │ ├── remove-task.md
│ │ │ ├── setup-models.md
│ │ │ ├── show-task.md
│ │ │ ├── smart-workflow.md
│ │ │ ├── sync-readme.md
│ │ │ ├── tm-main.md
│ │ │ ├── to-cancelled.md
│ │ │ ├── to-deferred.md
│ │ │ ├── to-done.md
│ │ │ ├── to-in-progress.md
│ │ │ ├── to-pending.md
│ │ │ ├── to-review.md
│ │ │ ├── update-single-task.md
│ │ │ ├── update-task.md
│ │ │ ├── update-tasks-from-id.md
│ │ │ ├── validate-dependencies.md
│ │ │ └── view-models.md
│ │ ├── mcp.json
│ │ └── package.json
│ ├── tm-bridge
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── add-tag-bridge.ts
│ │ │ ├── bridge-types.ts
│ │ │ ├── bridge-utils.ts
│ │ │ ├── expand-bridge.ts
│ │ │ ├── index.ts
│ │ │ ├── tags-bridge.ts
│ │ │ ├── update-bridge.ts
│ │ │ └── use-tag-bridge.ts
│ │ └── tsconfig.json
│ └── tm-core
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docs
│ │ └── listTasks-architecture.md
│ ├── package.json
│ ├── POC-STATUS.md
│ ├── README.md
│ ├── src
│ │ ├── common
│ │ │ ├── constants
│ │ │ │ ├── index.ts
│ │ │ │ ├── paths.ts
│ │ │ │ └── providers.ts
│ │ │ ├── errors
│ │ │ │ ├── index.ts
│ │ │ │ └── task-master-error.ts
│ │ │ ├── interfaces
│ │ │ │ ├── configuration.interface.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── storage.interface.ts
│ │ │ ├── logger
│ │ │ │ ├── factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── logger.spec.ts
│ │ │ │ └── logger.ts
│ │ │ ├── mappers
│ │ │ │ ├── TaskMapper.test.ts
│ │ │ │ └── TaskMapper.ts
│ │ │ ├── types
│ │ │ │ ├── database.types.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── legacy.ts
│ │ │ │ └── repository-types.ts
│ │ │ └── utils
│ │ │ ├── git-utils.ts
│ │ │ ├── id-generator.ts
│ │ │ ├── index.ts
│ │ │ ├── path-helpers.ts
│ │ │ ├── path-normalizer.spec.ts
│ │ │ ├── path-normalizer.ts
│ │ │ ├── project-root-finder.spec.ts
│ │ │ ├── project-root-finder.ts
│ │ │ ├── run-id-generator.spec.ts
│ │ │ └── run-id-generator.ts
│ │ ├── index.ts
│ │ ├── modules
│ │ │ ├── ai
│ │ │ │ ├── index.ts
│ │ │ │ ├── interfaces
│ │ │ │ │ └── ai-provider.interface.ts
│ │ │ │ └── providers
│ │ │ │ ├── base-provider.ts
│ │ │ │ └── index.ts
│ │ │ ├── auth
│ │ │ │ ├── auth-domain.spec.ts
│ │ │ │ ├── auth-domain.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── auth-manager.spec.ts
│ │ │ │ │ └── auth-manager.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── context-store.ts
│ │ │ │ │ ├── oauth-service.ts
│ │ │ │ │ ├── organization.service.ts
│ │ │ │ │ ├── supabase-session-storage.spec.ts
│ │ │ │ │ └── supabase-session-storage.ts
│ │ │ │ └── types.ts
│ │ │ ├── briefs
│ │ │ │ ├── briefs-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── brief-service.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils
│ │ │ │ └── url-parser.ts
│ │ │ ├── commands
│ │ │ │ └── index.ts
│ │ │ ├── config
│ │ │ │ ├── config-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── config-manager.spec.ts
│ │ │ │ │ └── config-manager.ts
│ │ │ │ └── services
│ │ │ │ ├── config-loader.service.spec.ts
│ │ │ │ ├── config-loader.service.ts
│ │ │ │ ├── config-merger.service.spec.ts
│ │ │ │ ├── config-merger.service.ts
│ │ │ │ ├── config-persistence.service.spec.ts
│ │ │ │ ├── config-persistence.service.ts
│ │ │ │ ├── environment-config-provider.service.spec.ts
│ │ │ │ ├── environment-config-provider.service.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── runtime-state-manager.service.spec.ts
│ │ │ │ └── runtime-state-manager.service.ts
│ │ │ ├── dependencies
│ │ │ │ └── index.ts
│ │ │ ├── execution
│ │ │ │ ├── executors
│ │ │ │ │ ├── base-executor.ts
│ │ │ │ │ ├── claude-executor.ts
│ │ │ │ │ └── executor-factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── executor-service.ts
│ │ │ │ └── types.ts
│ │ │ ├── git
│ │ │ │ ├── adapters
│ │ │ │ │ ├── git-adapter.test.ts
│ │ │ │ │ └── git-adapter.ts
│ │ │ │ ├── git-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── services
│ │ │ │ ├── branch-name-generator.spec.ts
│ │ │ │ ├── branch-name-generator.ts
│ │ │ │ ├── commit-message-generator.test.ts
│ │ │ │ ├── commit-message-generator.ts
│ │ │ │ ├── scope-detector.test.ts
│ │ │ │ ├── scope-detector.ts
│ │ │ │ ├── template-engine.test.ts
│ │ │ │ └── template-engine.ts
│ │ │ ├── integration
│ │ │ │ ├── clients
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── supabase-client.ts
│ │ │ │ ├── integration-domain.ts
│ │ │ │ └── services
│ │ │ │ ├── export.service.ts
│ │ │ │ ├── task-expansion.service.ts
│ │ │ │ └── task-retrieval.service.ts
│ │ │ ├── reports
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ └── complexity-report-manager.ts
│ │ │ │ └── types.ts
│ │ │ ├── storage
│ │ │ │ ├── adapters
│ │ │ │ │ ├── activity-logger.ts
│ │ │ │ │ ├── api-storage.ts
│ │ │ │ │ └── file-storage
│ │ │ │ │ ├── file-operations.ts
│ │ │ │ │ ├── file-storage.ts
│ │ │ │ │ ├── format-handler.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── path-resolver.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── storage-factory.ts
│ │ │ │ └── utils
│ │ │ │ └── api-client.ts
│ │ │ ├── tasks
│ │ │ │ ├── entities
│ │ │ │ │ └── task.entity.ts
│ │ │ │ ├── parser
│ │ │ │ │ └── index.ts
│ │ │ │ ├── repositories
│ │ │ │ │ ├── supabase
│ │ │ │ │ │ ├── dependency-fetcher.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── supabase-repository.ts
│ │ │ │ │ └── task-repository.interface.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── preflight-checker.service.ts
│ │ │ │ │ ├── tag.service.ts
│ │ │ │ │ ├── task-execution-service.ts
│ │ │ │ │ ├── task-loader.service.ts
│ │ │ │ │ └── task-service.ts
│ │ │ │ └── tasks-domain.ts
│ │ │ ├── ui
│ │ │ │ └── index.ts
│ │ │ └── workflow
│ │ │ ├── managers
│ │ │ │ ├── workflow-state-manager.spec.ts
│ │ │ │ └── workflow-state-manager.ts
│ │ │ ├── orchestrators
│ │ │ │ ├── workflow-orchestrator.test.ts
│ │ │ │ └── workflow-orchestrator.ts
│ │ │ ├── services
│ │ │ │ ├── test-result-validator.test.ts
│ │ │ │ ├── test-result-validator.ts
│ │ │ │ ├── test-result-validator.types.ts
│ │ │ │ ├── workflow-activity-logger.ts
│ │ │ │ └── workflow.service.ts
│ │ │ ├── types.ts
│ │ │ └── workflow-domain.ts
│ │ ├── subpath-exports.test.ts
│ │ ├── tm-core.ts
│ │ └── utils
│ │ └── time.utils.ts
│ ├── tests
│ │ ├── auth
│ │ │ └── auth-refresh.test.ts
│ │ ├── integration
│ │ │ ├── auth-token-refresh.test.ts
│ │ │ ├── list-tasks.test.ts
│ │ │ └── storage
│ │ │ └── activity-logger.test.ts
│ │ ├── mocks
│ │ │ └── mock-provider.ts
│ │ ├── setup.ts
│ │ └── unit
│ │ ├── base-provider.test.ts
│ │ ├── executor.test.ts
│ │ └── smoke.test.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│ ├── create-worktree.sh
│ ├── dev.js
│ ├── init.js
│ ├── list-worktrees.sh
│ ├── modules
│ │ ├── ai-services-unified.js
│ │ ├── bridge-utils.js
│ │ ├── commands.js
│ │ ├── config-manager.js
│ │ ├── dependency-manager.js
│ │ ├── index.js
│ │ ├── prompt-manager.js
│ │ ├── supported-models.json
│ │ ├── sync-readme.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── find-next-task.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── is-task-dependent.js
│ │ │ ├── list-tasks.js
│ │ │ ├── migrate.js
│ │ │ ├── models.js
│ │ │ ├── move-task.js
│ │ │ ├── parse-prd
│ │ │ │ ├── index.js
│ │ │ │ ├── parse-prd-config.js
│ │ │ │ ├── parse-prd-helpers.js
│ │ │ │ ├── parse-prd-non-streaming.js
│ │ │ │ ├── parse-prd-streaming.js
│ │ │ │ └── parse-prd.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── scope-adjustment.js
│ │ │ ├── set-task-status.js
│ │ │ ├── tag-management.js
│ │ │ ├── task-exists.js
│ │ │ ├── update-single-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ └── update-tasks.js
│ │ ├── task-manager.js
│ │ ├── ui.js
│ │ ├── update-config-tokens.js
│ │ ├── utils
│ │ │ ├── contextGatherer.js
│ │ │ ├── fuzzyTaskSearch.js
│ │ │ └── git-utils.js
│ │ └── utils.js
│ ├── task-complexity-report.json
│ ├── test-claude-errors.js
│ └── test-claude.js
├── sonar-project.properties
├── src
│ ├── ai-providers
│ │ ├── anthropic.js
│ │ ├── azure.js
│ │ ├── base-provider.js
│ │ ├── bedrock.js
│ │ ├── claude-code.js
│ │ ├── codex-cli.js
│ │ ├── gemini-cli.js
│ │ ├── google-vertex.js
│ │ ├── google.js
│ │ ├── grok-cli.js
│ │ ├── groq.js
│ │ ├── index.js
│ │ ├── lmstudio.js
│ │ ├── ollama.js
│ │ ├── openai-compatible.js
│ │ ├── openai.js
│ │ ├── openrouter.js
│ │ ├── perplexity.js
│ │ ├── xai.js
│ │ ├── zai-coding.js
│ │ └── zai.js
│ ├── constants
│ │ ├── commands.js
│ │ ├── paths.js
│ │ ├── profiles.js
│ │ ├── rules-actions.js
│ │ ├── task-priority.js
│ │ └── task-status.js
│ ├── profiles
│ │ ├── amp.js
│ │ ├── base-profile.js
│ │ ├── claude.js
│ │ ├── cline.js
│ │ ├── codex.js
│ │ ├── cursor.js
│ │ ├── gemini.js
│ │ ├── index.js
│ │ ├── kilo.js
│ │ ├── kiro.js
│ │ ├── opencode.js
│ │ ├── roo.js
│ │ ├── trae.js
│ │ ├── vscode.js
│ │ ├── windsurf.js
│ │ └── zed.js
│ ├── progress
│ │ ├── base-progress-tracker.js
│ │ ├── cli-progress-factory.js
│ │ ├── parse-prd-tracker.js
│ │ ├── progress-tracker-builder.js
│ │ └── tracker-ui.js
│ ├── prompts
│ │ ├── add-task.json
│ │ ├── analyze-complexity.json
│ │ ├── expand-task.json
│ │ ├── parse-prd.json
│ │ ├── README.md
│ │ ├── research.json
│ │ ├── schemas
│ │ │ ├── parameter.schema.json
│ │ │ ├── prompt-template.schema.json
│ │ │ ├── README.md
│ │ │ └── variant.schema.json
│ │ ├── update-subtask.json
│ │ ├── update-task.json
│ │ └── update-tasks.json
│ ├── provider-registry
│ │ └── index.js
│ ├── schemas
│ │ ├── add-task.js
│ │ ├── analyze-complexity.js
│ │ ├── base-schemas.js
│ │ ├── expand-task.js
│ │ ├── parse-prd.js
│ │ ├── registry.js
│ │ ├── update-subtask.js
│ │ ├── update-task.js
│ │ └── update-tasks.js
│ ├── task-master.js
│ ├── ui
│ │ ├── confirm.js
│ │ ├── indicators.js
│ │ └── parse-prd.js
│ └── utils
│ ├── asset-resolver.js
│ ├── create-mcp-config.js
│ ├── format.js
│ ├── getVersion.js
│ ├── logger-utils.js
│ ├── manage-gitignore.js
│ ├── path-utils.js
│ ├── profiles.js
│ ├── rule-transformer.js
│ ├── stream-parser.js
│ └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│ ├── e2e
│ │ ├── e2e_helpers.sh
│ │ ├── parse_llm_output.cjs
│ │ ├── run_e2e.sh
│ │ ├── run_fallback_verification.sh
│ │ └── test_llm_analysis.sh
│ ├── fixtures
│ │ ├── .taskmasterconfig
│ │ ├── sample-claude-response.js
│ │ ├── sample-prd.txt
│ │ └── sample-tasks.js
│ ├── helpers
│ │ └── tool-counts.js
│ ├── integration
│ │ ├── claude-code-error-handling.test.js
│ │ ├── claude-code-optional.test.js
│ │ ├── cli
│ │ │ ├── commands.test.js
│ │ │ ├── complex-cross-tag-scenarios.test.js
│ │ │ └── move-cross-tag.test.js
│ │ ├── manage-gitignore.test.js
│ │ ├── mcp-server
│ │ │ └── direct-functions.test.js
│ │ ├── move-task-cross-tag.integration.test.js
│ │ ├── move-task-simple.integration.test.js
│ │ ├── profiles
│ │ │ ├── amp-init-functionality.test.js
│ │ │ ├── claude-init-functionality.test.js
│ │ │ ├── cline-init-functionality.test.js
│ │ │ ├── codex-init-functionality.test.js
│ │ │ ├── cursor-init-functionality.test.js
│ │ │ ├── gemini-init-functionality.test.js
│ │ │ ├── opencode-init-functionality.test.js
│ │ │ ├── roo-files-inclusion.test.js
│ │ │ ├── roo-init-functionality.test.js
│ │ │ ├── rules-files-inclusion.test.js
│ │ │ ├── trae-init-functionality.test.js
│ │ │ ├── vscode-init-functionality.test.js
│ │ │ └── windsurf-init-functionality.test.js
│ │ └── providers
│ │ └── temperature-support.test.js
│ ├── manual
│ │ ├── progress
│ │ │ ├── parse-prd-analysis.js
│ │ │ ├── test-parse-prd.js
│ │ │ └── TESTING_GUIDE.md
│ │ └── prompts
│ │ ├── prompt-test.js
│ │ └── README.md
│ ├── README.md
│ ├── setup.js
│ └── unit
│ ├── ai-providers
│ │ ├── base-provider.test.js
│ │ ├── claude-code.test.js
│ │ ├── codex-cli.test.js
│ │ ├── gemini-cli.test.js
│ │ ├── lmstudio.test.js
│ │ ├── mcp-components.test.js
│ │ ├── openai-compatible.test.js
│ │ ├── openai.test.js
│ │ ├── provider-registry.test.js
│ │ ├── zai-coding.test.js
│ │ ├── zai-provider.test.js
│ │ ├── zai-schema-introspection.test.js
│ │ └── zai.test.js
│ ├── ai-services-unified.test.js
│ ├── commands.test.js
│ ├── config-manager.test.js
│ ├── config-manager.test.mjs
│ ├── dependency-manager.test.js
│ ├── init.test.js
│ ├── initialize-project.test.js
│ ├── kebab-case-validation.test.js
│ ├── manage-gitignore.test.js
│ ├── mcp
│ │ └── tools
│ │ ├── __mocks__
│ │ │ └── move-task.js
│ │ ├── add-task.test.js
│ │ ├── analyze-complexity.test.js
│ │ ├── expand-all.test.js
│ │ ├── get-tasks.test.js
│ │ ├── initialize-project.test.js
│ │ ├── move-task-cross-tag-options.test.js
│ │ ├── move-task-cross-tag.test.js
│ │ ├── remove-task.test.js
│ │ └── tool-registration.test.js
│ ├── mcp-providers
│ │ ├── mcp-components.test.js
│ │ └── mcp-provider.test.js
│ ├── parse-prd.test.js
│ ├── profiles
│ │ ├── amp-integration.test.js
│ │ ├── claude-integration.test.js
│ │ ├── cline-integration.test.js
│ │ ├── codex-integration.test.js
│ │ ├── cursor-integration.test.js
│ │ ├── gemini-integration.test.js
│ │ ├── kilo-integration.test.js
│ │ ├── kiro-integration.test.js
│ │ ├── mcp-config-validation.test.js
│ │ ├── opencode-integration.test.js
│ │ ├── profile-safety-check.test.js
│ │ ├── roo-integration.test.js
│ │ ├── rule-transformer-cline.test.js
│ │ ├── rule-transformer-cursor.test.js
│ │ ├── rule-transformer-gemini.test.js
│ │ ├── rule-transformer-kilo.test.js
│ │ ├── rule-transformer-kiro.test.js
│ │ ├── rule-transformer-opencode.test.js
│ │ ├── rule-transformer-roo.test.js
│ │ ├── rule-transformer-trae.test.js
│ │ ├── rule-transformer-vscode.test.js
│ │ ├── rule-transformer-windsurf.test.js
│ │ ├── rule-transformer-zed.test.js
│ │ ├── rule-transformer.test.js
│ │ ├── selective-profile-removal.test.js
│ │ ├── subdirectory-support.test.js
│ │ ├── trae-integration.test.js
│ │ ├── vscode-integration.test.js
│ │ ├── windsurf-integration.test.js
│ │ └── zed-integration.test.js
│ ├── progress
│ │ └── base-progress-tracker.test.js
│ ├── prompt-manager.test.js
│ ├── prompts
│ │ ├── expand-task-prompt.test.js
│ │ └── prompt-migration.test.js
│ ├── scripts
│ │ └── modules
│ │ ├── commands
│ │ │ ├── move-cross-tag.test.js
│ │ │ └── README.md
│ │ ├── dependency-manager
│ │ │ ├── circular-dependencies.test.js
│ │ │ ├── cross-tag-dependencies.test.js
│ │ │ └── fix-dependencies-command.test.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.test.js
│ │ │ ├── add-task.test.js
│ │ │ ├── analyze-task-complexity.test.js
│ │ │ ├── clear-subtasks.test.js
│ │ │ ├── complexity-report-tag-isolation.test.js
│ │ │ ├── expand-all-tasks.test.js
│ │ │ ├── expand-task.test.js
│ │ │ ├── find-next-task.test.js
│ │ │ ├── generate-task-files.test.js
│ │ │ ├── list-tasks.test.js
│ │ │ ├── models-baseurl.test.js
│ │ │ ├── move-task-cross-tag.test.js
│ │ │ ├── move-task.test.js
│ │ │ ├── parse-prd-schema.test.js
│ │ │ ├── parse-prd.test.js
│ │ │ ├── remove-subtask.test.js
│ │ │ ├── remove-task.test.js
│ │ │ ├── research.test.js
│ │ │ ├── scope-adjustment.test.js
│ │ │ ├── set-task-status.test.js
│ │ │ ├── setup.js
│ │ │ ├── update-single-task-status.test.js
│ │ │ ├── update-subtask-by-id.test.js
│ │ │ ├── update-task-by-id.test.js
│ │ │ └── update-tasks.test.js
│ │ ├── ui
│ │ │ └── cross-tag-error-display.test.js
│ │ └── utils-tag-aware-paths.test.js
│ ├── task-finder.test.js
│ ├── task-manager
│ │ ├── clear-subtasks.test.js
│ │ ├── move-task.test.js
│ │ ├── tag-boundary.test.js
│ │ └── tag-management.test.js
│ ├── task-master.test.js
│ ├── ui
│ │ └── indicators.test.js
│ ├── ui.test.js
│ ├── utils-strip-ansi.test.js
│ └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
├── turbo.json
└── update-task-migration-plan.md
```
# Files
--------------------------------------------------------------------------------
/apps/extension/src/components/TaskDetails/DetailsSection.tsx:
--------------------------------------------------------------------------------
```typescript
import type React from 'react';
import { CollapsibleSection } from '@/components/ui/CollapsibleSection';
interface MarkdownRendererProps {
content: string;
className?: string;
}
const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
content,
className = ''
}) => {
const parseMarkdown = (text: string) => {
const parts = [];
const lines = text.split('\n');
let currentBlock = [];
let inCodeBlock = false;
let codeLanguage = '';
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('```')) {
if (inCodeBlock) {
if (currentBlock.length > 0) {
parts.push({
type: 'code',
content: currentBlock.join('\n'),
language: codeLanguage
});
currentBlock = [];
}
inCodeBlock = false;
codeLanguage = '';
} else {
if (currentBlock.length > 0) {
parts.push({
type: 'text',
content: currentBlock.join('\n')
});
currentBlock = [];
}
inCodeBlock = true;
codeLanguage = line.substring(3).trim();
}
} else {
currentBlock.push(line);
}
}
if (currentBlock.length > 0) {
parts.push({
type: inCodeBlock ? 'code' : 'text',
content: currentBlock.join('\n'),
language: codeLanguage
});
}
return parts;
};
const parts = parseMarkdown(content);
return (
<div className={className}>
{parts.map((part, index) => {
if (part.type === 'code') {
return (
<pre
key={index}
className="bg-vscode-editor-background rounded-md p-4 overflow-x-auto mb-4 border border-vscode-editor-lineHighlightBorder"
>
<code className="text-sm text-vscode-editor-foreground font-mono">
{part.content}
</code>
</pre>
);
}
return (
<div key={index} className="whitespace-pre-wrap mb-4 last:mb-0">
{part.content.split('\n').map((line, lineIndex) => {
const bulletMatch = line.match(/^(\s*)([-*])\s(.+)$/);
if (bulletMatch) {
const indent = bulletMatch[1].length;
return (
<div
key={lineIndex}
className="flex gap-2 mb-1"
style={{ paddingLeft: `${indent * 16}px` }}
>
<span className="text-vscode-foreground/60">•</span>
<span className="flex-1">{bulletMatch[3]}</span>
</div>
);
}
const numberedMatch = line.match(/^(\s*)(\d+\.)\s(.+)$/);
if (numberedMatch) {
const indent = numberedMatch[1].length;
return (
<div
key={lineIndex}
className="flex gap-2 mb-1"
style={{ paddingLeft: `${indent * 16}px` }}
>
<span className="text-vscode-foreground/60 font-mono">
{numberedMatch[2]}
</span>
<span className="flex-1">{numberedMatch[3]}</span>
</div>
);
}
const headingMatch = line.match(/^(#{1,6})\s(.+)$/);
if (headingMatch) {
const level = headingMatch[1].length;
const headingLevel = Math.min(level + 2, 6);
const headingClassName =
'font-semibold text-vscode-foreground mb-2 mt-4 first:mt-0';
switch (headingLevel) {
case 3:
return (
<h3 key={lineIndex} className={headingClassName}>
{headingMatch[2]}
</h3>
);
case 4:
return (
<h4 key={lineIndex} className={headingClassName}>
{headingMatch[2]}
</h4>
);
case 5:
return (
<h5 key={lineIndex} className={headingClassName}>
{headingMatch[2]}
</h5>
);
case 6:
return (
<h6 key={lineIndex} className={headingClassName}>
{headingMatch[2]}
</h6>
);
default:
return (
<h3 key={lineIndex} className={headingClassName}>
{headingMatch[2]}
</h3>
);
}
}
if (line.trim() === '') {
return <div key={lineIndex} className="h-2" />;
}
return (
<div key={lineIndex} className="mb-2 last:mb-0">
{line}
</div>
);
})}
</div>
);
})}
</div>
);
};
interface DetailsSectionProps {
title: string;
content?: string;
error?: string | null;
emptyMessage?: string;
defaultExpanded?: boolean;
}
export const DetailsSection: React.FC<DetailsSectionProps> = ({
title,
content,
error,
emptyMessage = 'No details available',
defaultExpanded = false
}) => {
return (
<CollapsibleSection title={title} defaultExpanded={defaultExpanded}>
<div className={title.toLowerCase().replace(/\s+/g, '-') + '-content'}>
{error ? (
<div className="text-sm text-red-400 py-2">
Error loading {title.toLowerCase()}: {error}
</div>
) : content !== undefined && content !== '' ? (
<MarkdownRenderer content={content} />
) : (
<div className="text-sm text-vscode-foreground/50 py-2">
{emptyMessage}
</div>
)}
</div>
</CollapsibleSection>
);
};
```
--------------------------------------------------------------------------------
/src/utils/timeout-manager.js:
--------------------------------------------------------------------------------
```javascript
import { StreamingError, STREAMING_ERROR_CODES } from './stream-parser.js';
/**
* Utility class for managing timeouts in async operations
* Reduces code duplication for timeout handling patterns
*/
export class TimeoutManager {
/**
* Wraps a promise with a timeout that will reject if not resolved in time
*
* @param {Promise} promise - The promise to wrap with timeout
* @param {number} timeoutMs - Timeout duration in milliseconds
* @param {string} operationName - Name of the operation for error messages
* @returns {Promise} The result of the promise or throws timeout error
*
* @example
* const result = await TimeoutManager.withTimeout(
* fetchData(),
* 5000,
* 'Data fetch operation'
* );
*/
static async withTimeout(promise, timeoutMs, operationName = 'Operation') {
let timeoutHandle;
const timeoutPromise = new Promise((_, reject) => {
timeoutHandle = setTimeout(() => {
reject(
new StreamingError(
`${operationName} timed out after ${timeoutMs / 1000} seconds`,
STREAMING_ERROR_CODES.STREAM_PROCESSING_FAILED
)
);
}, timeoutMs);
});
try {
// Race between the actual promise and the timeout
const result = await Promise.race([promise, timeoutPromise]);
// Clear timeout if promise resolved first
clearTimeout(timeoutHandle);
return result;
} catch (error) {
// Always clear timeout on error
clearTimeout(timeoutHandle);
throw error;
}
}
/**
* Wraps a promise with a timeout, but returns undefined instead of throwing on timeout
* Useful for optional operations that shouldn't fail the main flow
*
* @param {Promise} promise - The promise to wrap with timeout
* @param {number} timeoutMs - Timeout duration in milliseconds
* @param {*} defaultValue - Value to return on timeout (default: undefined)
* @returns {Promise} The result of the promise or defaultValue on timeout
*
* @example
* const usage = await TimeoutManager.withSoftTimeout(
* getUsageStats(),
* 1000,
* { tokens: 0 }
* );
*/
static async withSoftTimeout(promise, timeoutMs, defaultValue = undefined) {
let timeoutHandle;
const timeoutPromise = new Promise((resolve) => {
timeoutHandle = setTimeout(() => {
resolve(defaultValue);
}, timeoutMs);
});
try {
const result = await Promise.race([promise, timeoutPromise]);
clearTimeout(timeoutHandle);
return result;
} catch (error) {
// On error, clear timeout and return default value
clearTimeout(timeoutHandle);
return defaultValue;
}
}
/**
* Creates a reusable timeout controller for multiple operations
* Useful when you need to apply the same timeout to multiple promises
*
* @param {number} timeoutMs - Timeout duration in milliseconds
* @param {string} operationName - Base name for operations
* @returns {Object} Controller with wrap method
*
* @example
* const controller = TimeoutManager.createController(60000, 'AI Service');
* const result1 = await controller.wrap(service.call1(), 'call 1');
* const result2 = await controller.wrap(service.call2(), 'call 2');
*/
static createController(timeoutMs, operationName = 'Operation') {
return {
timeoutMs,
operationName,
async wrap(promise, specificName = null) {
const fullName = specificName
? `${operationName} - ${specificName}`
: operationName;
return TimeoutManager.withTimeout(promise, timeoutMs, fullName);
},
async wrapSoft(promise, defaultValue = undefined) {
return TimeoutManager.withSoftTimeout(promise, timeoutMs, defaultValue);
}
};
}
/**
* Checks if an error is a timeout error from this manager
*
* @param {Error} error - The error to check
* @returns {boolean} True if this is a timeout error
*/
static isTimeoutError(error) {
return (
error instanceof StreamingError &&
error.code === STREAMING_ERROR_CODES.STREAM_PROCESSING_FAILED &&
error.message.includes('timed out')
);
}
}
/**
* Duration helper class for more readable timeout specifications
*/
export class Duration {
constructor(value, unit = 'ms') {
this.milliseconds = this._toMilliseconds(value, unit);
}
static milliseconds(value) {
return new Duration(value, 'ms');
}
static seconds(value) {
return new Duration(value, 's');
}
static minutes(value) {
return new Duration(value, 'm');
}
static hours(value) {
return new Duration(value, 'h');
}
get seconds() {
return this.milliseconds / 1000;
}
get minutes() {
return this.milliseconds / 60000;
}
get hours() {
return this.milliseconds / 3600000;
}
toString() {
if (this.milliseconds < 1000) {
return `${this.milliseconds}ms`;
} else if (this.milliseconds < 60000) {
return `${this.seconds}s`;
} else if (this.milliseconds < 3600000) {
return `${Math.floor(this.minutes)}m ${Math.floor(this.seconds % 60)}s`;
} else {
return `${Math.floor(this.hours)}h ${Math.floor(this.minutes % 60)}m`;
}
}
_toMilliseconds(value, unit) {
const conversions = {
ms: 1,
s: 1000,
m: 60000,
h: 3600000
};
return value * (conversions[unit] || 1);
}
}
```
--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/errors.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for error handling utilities
*/
import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';
import { describe, expect, it } from 'vitest';
import {
createAPICallError,
createAuthenticationError,
createInstallationError,
createTimeoutError,
getErrorMetadata,
isAuthenticationError,
isInstallationError,
isTimeoutError
} from './errors.js';
describe('createAPICallError', () => {
it('should create APICallError with metadata', () => {
const error = createAPICallError({
message: 'Test error',
code: 'TEST_ERROR',
exitCode: 1,
stderr: 'Error output',
stdout: 'Success output',
promptExcerpt: 'Test prompt',
isRetryable: true
});
expect(error).toBeInstanceOf(APICallError);
expect(error.message).toBe('Test error');
expect(error.isRetryable).toBe(true);
expect(error.url).toBe('grok-cli://command');
expect(error.data).toEqual({
code: 'TEST_ERROR',
exitCode: 1,
stderr: 'Error output',
stdout: 'Success output',
promptExcerpt: 'Test prompt'
});
});
it('should create APICallError with minimal parameters', () => {
const error = createAPICallError({
message: 'Simple error'
});
expect(error).toBeInstanceOf(APICallError);
expect(error.message).toBe('Simple error');
expect(error.isRetryable).toBe(false);
});
});
describe('createAuthenticationError', () => {
it('should create LoadAPIKeyError with custom message', () => {
const error = createAuthenticationError({
message: 'Custom auth error'
});
expect(error).toBeInstanceOf(LoadAPIKeyError);
expect(error.message).toBe('Custom auth error');
});
it('should create LoadAPIKeyError with default message', () => {
const error = createAuthenticationError({});
expect(error).toBeInstanceOf(LoadAPIKeyError);
expect(error.message).toContain('Authentication failed');
});
});
describe('createTimeoutError', () => {
it('should create APICallError for timeout', () => {
const error = createTimeoutError({
message: 'Operation timed out',
timeoutMs: 5000,
promptExcerpt: 'Test prompt'
});
expect(error).toBeInstanceOf(APICallError);
expect(error.message).toBe('Operation timed out');
expect(error.isRetryable).toBe(true);
expect(error.data).toEqual({
code: 'TIMEOUT',
promptExcerpt: 'Test prompt',
timeoutMs: 5000
});
});
});
describe('createInstallationError', () => {
it('should create APICallError for installation issues', () => {
const error = createInstallationError({
message: 'CLI not found'
});
expect(error).toBeInstanceOf(APICallError);
expect(error.message).toBe('CLI not found');
expect(error.isRetryable).toBe(false);
expect(error.url).toBe('grok-cli://installation');
});
it('should create APICallError with default message', () => {
const error = createInstallationError({});
expect(error).toBeInstanceOf(APICallError);
expect(error.message).toContain('Grok CLI is not installed');
});
});
describe('isAuthenticationError', () => {
it('should return true for LoadAPIKeyError', () => {
const error = new LoadAPIKeyError({ message: 'Auth failed' });
expect(isAuthenticationError(error)).toBe(true);
});
it('should return true for APICallError with 401 exit code', () => {
const error = new APICallError({
message: 'Unauthorized',
data: { exitCode: 401 }
});
expect(isAuthenticationError(error)).toBe(true);
});
it('should return false for other errors', () => {
const error = new Error('Generic error');
expect(isAuthenticationError(error)).toBe(false);
});
});
describe('isTimeoutError', () => {
it('should return true for timeout APICallError', () => {
const error = new APICallError({
message: 'Timeout',
data: { code: 'TIMEOUT' }
});
expect(isTimeoutError(error)).toBe(true);
});
it('should return false for other errors', () => {
const error = new APICallError({ message: 'Other error' });
expect(isTimeoutError(error)).toBe(false);
});
});
describe('isInstallationError', () => {
it('should return true for installation APICallError', () => {
const error = new APICallError({
message: 'Not installed',
url: 'grok-cli://installation'
});
expect(isInstallationError(error)).toBe(true);
});
it('should return false for other errors', () => {
const error = new APICallError({ message: 'Other error' });
expect(isInstallationError(error)).toBe(false);
});
});
describe('getErrorMetadata', () => {
it('should return metadata from APICallError', () => {
const metadata = {
code: 'TEST_ERROR',
exitCode: 1,
stderr: 'Error output'
};
const error = new APICallError({
message: 'Test error',
data: metadata
});
const result = getErrorMetadata(error);
expect(result).toEqual(metadata);
});
it('should return undefined for errors without metadata', () => {
const error = new Error('Generic error');
const result = getErrorMetadata(error);
expect(result).toBeUndefined();
});
it('should return undefined for APICallError without data', () => {
const error = new APICallError({ message: 'Test error' });
const result = getErrorMetadata(error);
expect(result).toBeUndefined();
});
});
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/services/scope-detector.ts:
--------------------------------------------------------------------------------
```typescript
/**
* ScopeDetector - Intelligent scope detection from changed files
*
* Automatically determines conventional commit scopes based on file paths
* using configurable pattern matching and priority-based resolution.
* // TODO: remove this
*/
export interface ScopeMapping {
[pattern: string]: string;
}
export interface ScopePriority {
[scope: string]: number;
}
// Ordered from most specific to least specific
const DEFAULT_SCOPE_MAPPINGS: Array<[string, string]> = [
// Special file types (check first - most specific)
['**/*.test.*', 'test'],
['**/*.spec.*', 'test'],
['**/test/**', 'test'],
['**/tests/**', 'test'],
['**/__tests__/**', 'test'],
// Dependencies (specific files)
['**/package-lock.json', 'deps'],
['package-lock.json', 'deps'],
['**/pnpm-lock.yaml', 'deps'],
['pnpm-lock.yaml', 'deps'],
['**/yarn.lock', 'deps'],
['yarn.lock', 'deps'],
// Configuration files (before packages so root configs don't match package patterns)
['**/package.json', 'config'],
['package.json', 'config'],
['**/tsconfig*.json', 'config'],
['tsconfig*.json', 'config'],
['**/.eslintrc*', 'config'],
['.eslintrc*', 'config'],
['**/vite.config.*', 'config'],
['vite.config.*', 'config'],
['**/vitest.config.*', 'config'],
['vitest.config.*', 'config'],
// Package-level scopes (more specific than feature-level)
['packages/cli/**', 'cli'],
['packages/tm-core/**', 'core'],
['packages/mcp-server/**', 'mcp'],
// Feature-level scopes (within any package)
['**/workflow/**', 'workflow'],
['**/git/**', 'git'],
['**/storage/**', 'storage'],
['**/auth/**', 'auth'],
['**/config/**', 'config'],
// Documentation (least specific)
['**/*.md', 'docs'],
['**/docs/**', 'docs'],
['README*', 'docs'],
['CHANGELOG*', 'docs']
];
const DEFAULT_SCOPE_PRIORITIES: ScopePriority = {
core: 100,
cli: 90,
mcp: 85,
workflow: 80,
git: 75,
storage: 70,
auth: 65,
config: 60,
test: 50,
docs: 30,
deps: 20,
repo: 10
};
export class ScopeDetector {
private scopeMappings: Array<[string, string]>;
private scopePriorities: ScopePriority;
constructor(customMappings?: ScopeMapping, customPriorities?: ScopePriority) {
// Start with default mappings
this.scopeMappings = [...DEFAULT_SCOPE_MAPPINGS];
// Add custom mappings at the start (highest priority)
if (customMappings) {
const customEntries = Object.entries(customMappings);
this.scopeMappings = [...customEntries, ...this.scopeMappings];
}
this.scopePriorities = {
...DEFAULT_SCOPE_PRIORITIES,
...customPriorities
};
}
/**
* Detect the most relevant scope from a list of changed files
* Returns the scope with the highest priority
*/
detectScope(files: string[]): string {
if (files.length === 0) {
return 'repo';
}
const scopeCounts = new Map<string, number>();
// Count occurrences of each scope
for (const file of files) {
const scope = this.getMatchingScope(file);
if (scope) {
scopeCounts.set(scope, (scopeCounts.get(scope) || 0) + 1);
}
}
// If no scopes matched, default to 'repo'
if (scopeCounts.size === 0) {
return 'repo';
}
// Find scope with highest priority (considering both priority and count)
let bestScope = 'repo';
let bestScore = 0;
for (const [scope, count] of scopeCounts) {
const priority = this.getScopePriority(scope);
// Score = priority * count (files in that scope)
const score = priority * count;
if (score > bestScore) {
bestScore = score;
bestScope = scope;
}
}
return bestScope;
}
/**
* Get all matching scopes for the given files
*/
getAllMatchingScopes(files: string[]): string[] {
const scopes = new Set<string>();
for (const file of files) {
const scope = this.getMatchingScope(file);
if (scope) {
scopes.add(scope);
}
}
return Array.from(scopes);
}
/**
* Get the matching scope for a single file
* Returns the first matching scope (order matters!)
*/
getMatchingScope(file: string): string | null {
// Normalize path separators
const normalizedFile = file.replace(/\\/g, '/');
for (const [pattern, scope] of this.scopeMappings) {
if (this.matchesPattern(normalizedFile, pattern)) {
return scope;
}
}
return null;
}
/**
* Get the priority of a scope
*/
getScopePriority(scope: string): number {
return this.scopePriorities[scope] || 0;
}
/**
* Match a file path against a glob-like pattern
* Supports:
* - ** for multi-level directory matching
* - * for single-level matching
*/
private matchesPattern(filePath: string, pattern: string): boolean {
// Replace ** first with a unique placeholder
let regexPattern = pattern.replace(/\*\*/g, '§GLOBSTAR§');
// Escape special regex characters (but not our placeholder or *)
regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
// Replace single * with [^/]* (matches anything except /)
regexPattern = regexPattern.replace(/\*/g, '[^/]*');
// Replace placeholder with .* (matches anything including /)
regexPattern = regexPattern.replace(/§GLOBSTAR§/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(filePath);
}
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "task-master-ai",
"version": "0.33.0",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js",
"type": "module",
"bin": {
"task-master": "dist/task-master.js",
"task-master-mcp": "dist/mcp-server.js",
"task-master-ai": "dist/mcp-server.js"
},
"workspaces": ["apps/*", "packages/*", "."],
"scripts": {
"build": "npm run build:build-config && cross-env NODE_ENV=production tsdown",
"dev": "tsdown --watch",
"turbo:dev": "turbo dev",
"turbo:build": "turbo build",
"turbo:typecheck": "turbo typecheck",
"build:build-config": "npm run build -w @tm/build-config",
"test": "cross-env NODE_ENV=test node --experimental-vm-modules node_modules/.bin/jest",
"test:unit": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=unit",
"test:integration": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=integration",
"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",
"test:watch": "node --experimental-vm-modules node_modules/.bin/jest --watch",
"test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage",
"test:ci": "node --experimental-vm-modules node_modules/.bin/jest --coverage --ci",
"test:e2e": "./tests/e2e/run_e2e.sh",
"test:e2e-report": "./tests/e2e/run_e2e.sh --analyze-log",
"postpack": "chmod +x dist/task-master.js dist/mcp-server.js",
"changeset": "changeset",
"changeset:validate": "node .github/scripts/validate-changesets.mjs",
"release": "changeset publish",
"publish-packages": "turbo run build lint test && changeset version && changeset publish",
"inspector": "npx @modelcontextprotocol/inspector node dist/mcp-server.js",
"mcp-server": "node dist/mcp-server.js",
"format-check": "biome format .",
"format": "biome format . --write",
"deps:check": "manypkg check || echo 'Note: Workspace package version warnings are expected for internal @tm/* packages'",
"deps:fix": "manypkg fix"
},
"keywords": [
"claude",
"task",
"management",
"ai",
"development",
"cursor",
"anthropic",
"llm",
"mcp",
"context"
],
"author": "Eyal Toledano",
"license": "MIT WITH Commons-Clause",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^3.0.23",
"@ai-sdk/anthropic": "^2.0.18",
"@ai-sdk/azure": "^2.0.34",
"@ai-sdk/google": "^2.0.16",
"@ai-sdk/google-vertex": "^3.0.29",
"@ai-sdk/groq": "^2.0.21",
"@ai-sdk/mistral": "^2.0.16",
"@ai-sdk/openai": "^2.0.34",
"@ai-sdk/openai-compatible": "^1.0.25",
"@ai-sdk/perplexity": "^2.0.10",
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/provider-utils": "^3.0.10",
"@ai-sdk/xai": "^2.0.22",
"@aws-sdk/credential-providers": "^3.895.0",
"@inquirer/search": "^3.0.15",
"@openrouter/ai-sdk-provider": "^1.2.0",
"@streamparser/json": "^0.0.22",
"@supabase/supabase-js": "^2.57.4",
"ai": "^5.0.51",
"ai-sdk-provider-claude-code": "^2.1.0",
"ai-sdk-provider-codex-cli": "^0.3.0",
"ai-sdk-provider-gemini-cli": "^1.1.1",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"boxen": "^8.0.1",
"chalk": "5.6.2",
"cli-highlight": "^2.1.11",
"cli-progress": "^3.12.0",
"cli-table3": "^0.6.5",
"commander": "^12.1.0",
"cors": "^2.8.5",
"date-fns": "^4.1.0",
"dotenv": "^16.6.1",
"express": "^4.21.2",
"fastmcp": "^3.23.1",
"figlet": "^1.8.0",
"fs-extra": "^11.3.0",
"fuse.js": "^7.1.0",
"gpt-tokens": "^1.3.14",
"gradient-string": "^3.0.0",
"helmet": "^8.1.0",
"inquirer": "^12.5.0",
"jsonc-parser": "^3.3.1",
"jsonrepair": "^3.13.0",
"jsonwebtoken": "^9.0.2",
"lru-cache": "^10.2.0",
"marked": "^15.0.12",
"marked-terminal": "^7.3.0",
"ollama-ai-provider-v2": "^1.3.1",
"open": "^10.2.0",
"ora": "^8.2.0",
"simple-git": "^3.28.0",
"steno": "^4.0.2",
"undici": "^7.16.0",
"uuid": "^11.1.0",
"zod": "^4.1.12"
},
"optionalDependencies": {
"@anthropic-ai/claude-code": "^1.0.88",
"@biomejs/cli-linux-x64": "^1.9.4"
},
"engines": {
"node": ">=18.0.0"
},
"packageManager": "[email protected]",
"repository": {
"type": "git",
"url": "git+https://github.com/eyaltoledano/claude-task-master.git"
},
"homepage": "https://github.com/eyaltoledano/claude-task-master#readme",
"bugs": {
"url": "https://github.com/eyaltoledano/claude-task-master/issues"
},
"files": [
"dist/**",
"README-task-master.md",
"README.md",
"LICENSE",
"CHANGELOG.md"
],
"overrides": {
"node-fetch": "^2.6.12",
"whatwg-url": "^11.0.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.28.1",
"@manypkg/cli": "^0.25.1",
"@tm/ai-sdk-provider-grok-cli": "*",
"@tm/cli": "*",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.14",
"@types/marked-terminal": "^6.1.1",
"concurrently": "^9.2.1",
"cross-env": "^10.0.0",
"execa": "^8.0.1",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"mock-fs": "^5.5.0",
"prettier": "^3.5.3",
"supertest": "^7.1.0",
"ts-jest": "^29.4.2",
"tsdown": "^0.15.2",
"tsx": "^4.20.4",
"turbo": "2.5.6",
"typescript": "^5.9.2"
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/task-master-core.js:
--------------------------------------------------------------------------------
```javascript
/**
* task-master-core.js
* Central module that imports and re-exports all direct function implementations
* for improved organization and maintainability.
*/
// Import direct function implementations
import { getCacheStatsDirect } from './direct-functions/cache-stats.js';
import { parsePRDDirect } from './direct-functions/parse-prd.js';
import { updateTasksDirect } from './direct-functions/update-tasks.js';
import { updateTaskByIdDirect } from './direct-functions/update-task-by-id.js';
import { updateSubtaskByIdDirect } from './direct-functions/update-subtask-by-id.js';
import { generateTaskFilesDirect } from './direct-functions/generate-task-files.js';
import { setTaskStatusDirect } from './direct-functions/set-task-status.js';
import { nextTaskDirect } from './direct-functions/next-task.js';
import { expandTaskDirect } from './direct-functions/expand-task.js';
import { addTaskDirect } from './direct-functions/add-task.js';
import { addSubtaskDirect } from './direct-functions/add-subtask.js';
import { removeSubtaskDirect } from './direct-functions/remove-subtask.js';
import { analyzeTaskComplexityDirect } from './direct-functions/analyze-task-complexity.js';
import { clearSubtasksDirect } from './direct-functions/clear-subtasks.js';
import { expandAllTasksDirect } from './direct-functions/expand-all-tasks.js';
import { removeDependencyDirect } from './direct-functions/remove-dependency.js';
import { validateDependenciesDirect } from './direct-functions/validate-dependencies.js';
import { fixDependenciesDirect } from './direct-functions/fix-dependencies.js';
import { complexityReportDirect } from './direct-functions/complexity-report.js';
import { addDependencyDirect } from './direct-functions/add-dependency.js';
import { removeTaskDirect } from './direct-functions/remove-task.js';
import { initializeProjectDirect } from './direct-functions/initialize-project.js';
import { modelsDirect } from './direct-functions/models.js';
import { moveTaskDirect } from './direct-functions/move-task.js';
import { moveTaskCrossTagDirect } from './direct-functions/move-task-cross-tag.js';
import { researchDirect } from './direct-functions/research.js';
import { addTagDirect } from './direct-functions/add-tag.js';
import { deleteTagDirect } from './direct-functions/delete-tag.js';
import { listTagsDirect } from './direct-functions/list-tags.js';
import { useTagDirect } from './direct-functions/use-tag.js';
import { renameTagDirect } from './direct-functions/rename-tag.js';
import { copyTagDirect } from './direct-functions/copy-tag.js';
import { scopeUpDirect } from './direct-functions/scope-up.js';
import { scopeDownDirect } from './direct-functions/scope-down.js';
// Re-export utility functions
export { findTasksPath } from './utils/path-utils.js';
// Use Map for potential future enhancements like introspection or dynamic dispatch
export const directFunctions = new Map([
['getCacheStatsDirect', getCacheStatsDirect],
['parsePRDDirect', parsePRDDirect],
['updateTasksDirect', updateTasksDirect],
['updateTaskByIdDirect', updateTaskByIdDirect],
['updateSubtaskByIdDirect', updateSubtaskByIdDirect],
['generateTaskFilesDirect', generateTaskFilesDirect],
['setTaskStatusDirect', setTaskStatusDirect],
['nextTaskDirect', nextTaskDirect],
['expandTaskDirect', expandTaskDirect],
['addTaskDirect', addTaskDirect],
['addSubtaskDirect', addSubtaskDirect],
['removeSubtaskDirect', removeSubtaskDirect],
['analyzeTaskComplexityDirect', analyzeTaskComplexityDirect],
['clearSubtasksDirect', clearSubtasksDirect],
['expandAllTasksDirect', expandAllTasksDirect],
['removeDependencyDirect', removeDependencyDirect],
['validateDependenciesDirect', validateDependenciesDirect],
['fixDependenciesDirect', fixDependenciesDirect],
['complexityReportDirect', complexityReportDirect],
['addDependencyDirect', addDependencyDirect],
['removeTaskDirect', removeTaskDirect],
['initializeProjectDirect', initializeProjectDirect],
['modelsDirect', modelsDirect],
['moveTaskDirect', moveTaskDirect],
['moveTaskCrossTagDirect', moveTaskCrossTagDirect],
['researchDirect', researchDirect],
['addTagDirect', addTagDirect],
['deleteTagDirect', deleteTagDirect],
['listTagsDirect', listTagsDirect],
['useTagDirect', useTagDirect],
['renameTagDirect', renameTagDirect],
['copyTagDirect', copyTagDirect],
['scopeUpDirect', scopeUpDirect],
['scopeDownDirect', scopeDownDirect]
]);
// Re-export all direct function implementations
export {
getCacheStatsDirect,
parsePRDDirect,
updateTasksDirect,
updateTaskByIdDirect,
updateSubtaskByIdDirect,
generateTaskFilesDirect,
setTaskStatusDirect,
nextTaskDirect,
expandTaskDirect,
addTaskDirect,
addSubtaskDirect,
removeSubtaskDirect,
analyzeTaskComplexityDirect,
clearSubtasksDirect,
expandAllTasksDirect,
removeDependencyDirect,
validateDependenciesDirect,
fixDependenciesDirect,
complexityReportDirect,
addDependencyDirect,
removeTaskDirect,
initializeProjectDirect,
modelsDirect,
moveTaskDirect,
moveTaskCrossTagDirect,
researchDirect,
addTagDirect,
deleteTagDirect,
listTagsDirect,
useTagDirect,
renameTagDirect,
copyTagDirect,
scopeUpDirect,
scopeDownDirect
};
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/git-domain.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Git Domain Facade
* Public API for Git operations
*/
import type { StatusResult } from 'simple-git';
import { GitAdapter } from './adapters/git-adapter.js';
import { CommitMessageGenerator } from './services/commit-message-generator.js';
import type { CommitMessageOptions } from './services/commit-message-generator.js';
/**
* Git Domain - Unified API for Git operations
*/
export class GitDomain {
private gitAdapter: GitAdapter;
private commitGenerator: CommitMessageGenerator;
constructor(projectPath: string) {
this.gitAdapter = new GitAdapter(projectPath);
this.commitGenerator = new CommitMessageGenerator();
}
// ========== Repository Validation ==========
/**
* Check if directory is a git repository
*/
async isGitRepository(): Promise<boolean> {
return this.gitAdapter.isGitRepository();
}
/**
* Ensure we're in a valid git repository
*/
async ensureGitRepository(): Promise<void> {
return this.gitAdapter.ensureGitRepository();
}
/**
* Get repository root path
*/
async getRepositoryRoot(): Promise<string> {
return this.gitAdapter.getRepositoryRoot();
}
// ========== Working Tree Status ==========
/**
* Check if working tree is clean
*/
async isWorkingTreeClean(): Promise<boolean> {
return this.gitAdapter.isWorkingTreeClean();
}
/**
* Get git status
*/
async getStatus(): Promise<StatusResult> {
return this.gitAdapter.getStatus();
}
/**
* Get status summary
*/
async getStatusSummary(): Promise<{
isClean: boolean;
staged: number;
modified: number;
deleted: number;
untracked: number;
totalChanges: number;
}> {
return this.gitAdapter.getStatusSummary();
}
/**
* Check if there are uncommitted changes
*/
async hasUncommittedChanges(): Promise<boolean> {
return this.gitAdapter.hasUncommittedChanges();
}
/**
* Check if there are staged changes
*/
async hasStagedChanges(): Promise<boolean> {
return this.gitAdapter.hasStagedChanges();
}
// ========== Branch Operations ==========
/**
* Get current branch name
*/
async getCurrentBranch(): Promise<string> {
return this.gitAdapter.getCurrentBranch();
}
/**
* List all local branches
*/
async listBranches(): Promise<string[]> {
return this.gitAdapter.listBranches();
}
/**
* Check if a branch exists
*/
async branchExists(branchName: string): Promise<boolean> {
return this.gitAdapter.branchExists(branchName);
}
/**
* Create a new branch
*/
async createBranch(
branchName: string,
options?: { checkout?: boolean }
): Promise<void> {
return this.gitAdapter.createBranch(branchName, options);
}
/**
* Checkout an existing branch
*/
async checkoutBranch(
branchName: string,
options?: { force?: boolean }
): Promise<void> {
return this.gitAdapter.checkoutBranch(branchName, options);
}
/**
* Create and checkout a new branch
*/
async createAndCheckoutBranch(branchName: string): Promise<void> {
return this.gitAdapter.createAndCheckoutBranch(branchName);
}
/**
* Delete a branch
*/
async deleteBranch(
branchName: string,
options?: { force?: boolean }
): Promise<void> {
return this.gitAdapter.deleteBranch(branchName, options);
}
/**
* Get default branch name
*/
async getDefaultBranch(): Promise<string> {
return this.gitAdapter.getDefaultBranch();
}
/**
* Check if on default branch
*/
async isOnDefaultBranch(): Promise<boolean> {
return this.gitAdapter.isOnDefaultBranch();
}
// ========== Commit Operations ==========
/**
* Stage files for commit
*/
async stageFiles(files: string[]): Promise<void> {
return this.gitAdapter.stageFiles(files);
}
/**
* Unstage files
*/
async unstageFiles(files: string[]): Promise<void> {
return this.gitAdapter.unstageFiles(files);
}
/**
* Create a commit
*/
async createCommit(
message: string,
options?: {
metadata?: Record<string, string>;
allowEmpty?: boolean;
enforceNonDefaultBranch?: boolean;
force?: boolean;
}
): Promise<void> {
return this.gitAdapter.createCommit(message, options);
}
/**
* Get commit log
*/
async getCommitLog(options?: { maxCount?: number }): Promise<any[]> {
return this.gitAdapter.getCommitLog(options);
}
/**
* Get last commit
*/
async getLastCommit(): Promise<any> {
return this.gitAdapter.getLastCommit();
}
// ========== Remote Operations ==========
/**
* Check if repository has remotes
*/
async hasRemote(): Promise<boolean> {
return this.gitAdapter.hasRemote();
}
/**
* Get all configured remotes
*/
async getRemotes(): Promise<any[]> {
return this.gitAdapter.getRemotes();
}
// ========== Commit Message Generation ==========
/**
* Generate a conventional commit message
*/
generateCommitMessage(options: CommitMessageOptions): string {
return this.commitGenerator.generateMessage(options);
}
/**
* Validate a conventional commit message
*/
validateCommitMessage(message: string) {
return this.commitGenerator.validateConventionalCommit(message);
}
/**
* Parse a commit message
*/
parseCommitMessage(message: string) {
return this.commitGenerator.parseCommitMessage(message);
}
}
```
--------------------------------------------------------------------------------
/tests/unit/profiles/kilo-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('Kilo 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('.kilocodemodes')) {
return 'Existing kilocodemodes content';
}
if (filePath.toString().includes('-rules')) {
return 'Existing mode rules content';
}
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 createProjectStructure behavior for Kilo files
function mockCreateKiloStructure() {
// Create main .kilo directory
fs.mkdirSync(path.join(tempDir, '.kilo'), { recursive: true });
// Create rules directory
fs.mkdirSync(path.join(tempDir, '.kilo', 'rules'), { recursive: true });
// Create mode-specific rule directories
const kiloModes = [
'architect',
'ask',
'orchestrator',
'code',
'debug',
'test'
];
for (const mode of kiloModes) {
fs.mkdirSync(path.join(tempDir, '.kilo', `rules-${mode}`), {
recursive: true
});
fs.writeFileSync(
path.join(tempDir, '.kilo', `rules-${mode}`, `${mode}-rules`),
`Content for ${mode} rules`
);
}
// Create additional directories
fs.mkdirSync(path.join(tempDir, '.kilo', 'config'), { recursive: true });
fs.mkdirSync(path.join(tempDir, '.kilo', 'templates'), { recursive: true });
fs.mkdirSync(path.join(tempDir, '.kilo', 'logs'), { recursive: true });
// Copy .kilocodemodes file
fs.writeFileSync(
path.join(tempDir, '.kilocodemodes'),
'Kilocodemodes file content'
);
}
test('creates all required .kilo directories', () => {
// Act
mockCreateKiloStructure();
// Assert
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.kilo'), {
recursive: true
});
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules'),
{ recursive: true }
);
// Verify all mode directories are created
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-architect'),
{ recursive: true }
);
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-ask'),
{ recursive: true }
);
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-orchestrator'),
{ recursive: true }
);
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-code'),
{ recursive: true }
);
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-debug'),
{ recursive: true }
);
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-test'),
{ recursive: true }
);
});
test('creates rule files for all modes', () => {
// Act
mockCreateKiloStructure();
// Assert - check all rule files are created
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-architect', 'architect-rules'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-ask', 'ask-rules'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-orchestrator', 'orchestrator-rules'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-code', 'code-rules'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-debug', 'debug-rules'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'rules-test', 'test-rules'),
expect.any(String)
);
});
test('creates .kilocodemodes file in project root', () => {
// Act
mockCreateKiloStructure();
// Assert
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilocodemodes'),
expect.any(String)
);
});
test('creates additional required Kilo directories', () => {
// Act
mockCreateKiloStructure();
// Assert
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'config'),
{ recursive: true }
);
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'templates'),
{ recursive: true }
);
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kilo', 'logs'),
{ recursive: true }
);
});
});
```
--------------------------------------------------------------------------------
/apps/cli/tests/unit/commands/list.command.spec.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Unit tests for ListTasksCommand
*/
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import type { TmCore } from '@tm/core';
// Mock dependencies
vi.mock('@tm/core', () => ({
createTmCore: vi.fn(),
OUTPUT_FORMATS: ['text', 'json', 'compact'],
TASK_STATUSES: [
'pending',
'in-progress',
'done',
'review',
'deferred',
'cancelled'
],
STATUS_ICONS: {
pending: '⏳',
'in-progress': '🔄',
done: '✅',
review: '👀',
deferred: '⏸️',
cancelled: '❌'
}
}));
vi.mock('../../../src/utils/project-root.js', () => ({
getProjectRoot: vi.fn((path?: string) => path || '/test/project')
}));
vi.mock('../../../src/utils/error-handler.js', () => ({
displayError: vi.fn()
}));
vi.mock('../../../src/utils/display-helpers.js', () => ({
displayCommandHeader: vi.fn()
}));
vi.mock('../../../src/ui/index.js', () => ({
calculateDependencyStatistics: vi.fn(() => ({ total: 0, blocked: 0 })),
calculateSubtaskStatistics: vi.fn(() => ({ total: 0, completed: 0 })),
calculateTaskStatistics: vi.fn(() => ({ total: 0, completed: 0 })),
displayDashboards: vi.fn(),
displayRecommendedNextTask: vi.fn(),
displaySuggestedNextSteps: vi.fn(),
getPriorityBreakdown: vi.fn(() => ({})),
getTaskDescription: vi.fn(() => 'Test description')
}));
vi.mock('../../../src/utils/ui.js', () => ({
createTaskTable: vi.fn(() => 'Table output'),
displayWarning: vi.fn()
}));
import { ListTasksCommand } from '../../../src/commands/list.command.js';
describe('ListTasksCommand', () => {
let consoleLogSpy: any;
let mockTmCore: Partial<TmCore>;
beforeEach(() => {
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
mockTmCore = {
tasks: {
list: vi.fn().mockResolvedValue({
tasks: [{ id: '1', title: 'Test Task', status: 'pending' }],
total: 1,
filtered: 1,
storageType: 'json'
}),
getStorageType: vi.fn().mockReturnValue('json')
} as any,
config: {
getActiveTag: vi.fn().mockReturnValue('master')
} as any
};
});
afterEach(() => {
vi.clearAllMocks();
consoleLogSpy.mockRestore();
});
describe('JSON output format', () => {
it('should use JSON format when --json flag is set', async () => {
const command = new ListTasksCommand();
// Mock the tmCore initialization
(command as any).tmCore = mockTmCore;
// Execute with --json flag
await (command as any).executeCommand({
json: true,
format: 'text' // Should be overridden by --json
});
// Verify JSON output was called
expect(consoleLogSpy).toHaveBeenCalled();
const output = consoleLogSpy.mock.calls[0][0];
// Should be valid JSON
expect(() => JSON.parse(output)).not.toThrow();
const parsed = JSON.parse(output);
expect(parsed).toHaveProperty('tasks');
expect(parsed).toHaveProperty('metadata');
});
it('should override --format when --json is set', async () => {
const command = new ListTasksCommand();
(command as any).tmCore = mockTmCore;
await (command as any).executeCommand({
json: true,
format: 'compact' // Should be overridden
});
// Should output JSON, not compact format
const output = consoleLogSpy.mock.calls[0][0];
expect(() => JSON.parse(output)).not.toThrow();
});
it('should use specified format when --json is not set', async () => {
const command = new ListTasksCommand();
(command as any).tmCore = mockTmCore;
await (command as any).executeCommand({
format: 'compact'
});
// Should use compact format (not JSON)
const output = consoleLogSpy.mock.calls;
// In compact mode, output is not JSON
expect(output.length).toBeGreaterThan(0);
});
it('should default to text format when neither flag is set', async () => {
const command = new ListTasksCommand();
(command as any).tmCore = mockTmCore;
await (command as any).executeCommand({});
// Should use text format (not JSON)
// If any console.log was called, verify it's not JSON
if (consoleLogSpy.mock.calls.length > 0) {
const output = consoleLogSpy.mock.calls[0][0];
// Text format output should not be parseable JSON
// or should be the table string we mocked
expect(
output === 'Table output' ||
(() => {
try {
JSON.parse(output);
return false;
} catch {
return true;
}
})()
).toBe(true);
}
});
});
describe('format validation', () => {
it('should accept valid formats', () => {
const command = new ListTasksCommand();
expect((command as any).validateOptions({ format: 'text' })).toBe(true);
expect((command as any).validateOptions({ format: 'json' })).toBe(true);
expect((command as any).validateOptions({ format: 'compact' })).toBe(
true
);
});
it('should reject invalid formats', () => {
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
const command = new ListTasksCommand();
expect((command as any).validateOptions({ format: 'invalid' })).toBe(
false
);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining('Invalid format: invalid')
);
consoleErrorSpy.mockRestore();
});
});
});
```
--------------------------------------------------------------------------------
/apps/extension/package.mjs:
--------------------------------------------------------------------------------
```
import { execSync } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs-extra';
// --- Configuration ---
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const packageDir = path.resolve(__dirname, 'vsix-build');
// --- End Configuration ---
try {
console.log('🚀 Starting packaging process...');
// 1. Build Project
console.log('\nBuilding JavaScript...');
execSync('npm run build:js', { stdio: 'inherit' });
console.log('\nBuilding CSS...');
execSync('npm run build:css', { stdio: 'inherit' });
// 2. Prepare Clean Directory
console.log(`\nPreparing clean directory at: ${packageDir}`);
fs.emptyDirSync(packageDir);
// 3. Copy Build Artifacts (excluding source maps)
console.log('Copying build artifacts...');
const distDir = path.resolve(__dirname, 'dist');
const targetDistDir = path.resolve(packageDir, 'dist');
fs.ensureDirSync(targetDistDir);
// Only copy the files we need (exclude .map files)
const filesToCopy = ['extension.js', 'index.js', 'index.css', 'sidebar.js'];
for (const file of filesToCopy) {
const srcFile = path.resolve(distDir, file);
const destFile = path.resolve(targetDistDir, file);
if (fs.existsSync(srcFile)) {
fs.copySync(srcFile, destFile);
console.log(` - Copied dist/${file}`);
}
}
// 4. Copy additional files
const additionalFiles = ['README.md', 'CHANGELOG.md', 'AGENTS.md'];
for (const file of additionalFiles) {
if (fs.existsSync(path.resolve(__dirname, file))) {
fs.copySync(
path.resolve(__dirname, file),
path.resolve(packageDir, file)
);
console.log(` - Copied ${file}`);
}
}
// 5. Sync versions and prepare the final package.json
console.log('Syncing versions and preparing the final package.json...');
// Read current versions
const devPackagePath = path.resolve(__dirname, 'package.json');
const publishPackagePath = path.resolve(__dirname, 'package.publish.json');
const devPackage = JSON.parse(fs.readFileSync(devPackagePath, 'utf8'));
const publishPackage = JSON.parse(
fs.readFileSync(publishPackagePath, 'utf8')
);
// Handle RC versions for VS Code Marketplace
let finalVersion = devPackage.version;
if (finalVersion.includes('-rc.')) {
console.log(
' - Detected RC version, transforming for VS Code Marketplace...'
);
// Extract base version and RC number
const baseVersion = finalVersion.replace(/-rc\.\d+$/, '');
const rcMatch = finalVersion.match(/rc\.(\d+)/);
const rcNumber = rcMatch ? parseInt(rcMatch[1]) : 0;
// For each RC iteration, increment the patch version
// This ensures unique versions in VS Code Marketplace
if (rcNumber > 0) {
const [major, minor, patch] = baseVersion.split('.').map(Number);
finalVersion = `${major}.${minor}.${patch + rcNumber}`;
console.log(
` - RC version mapping: ${devPackage.version} → ${finalVersion}`
);
} else {
finalVersion = baseVersion;
console.log(
` - RC version mapping: ${devPackage.version} → ${finalVersion}`
);
}
}
// Check if versions need updating
if (publishPackage.version !== finalVersion) {
console.log(
` - Version sync needed: ${publishPackage.version} → ${finalVersion}`
);
publishPackage.version = finalVersion;
// Update the source package.publish.json file with the final version
fs.writeFileSync(
publishPackagePath,
JSON.stringify(publishPackage, null, '\t') + '\n'
);
console.log(` - Updated package.publish.json version to ${finalVersion}`);
} else {
console.log(` - Versions already in sync: ${finalVersion}`);
}
// Copy the (now synced) package.publish.json as package.json
fs.copySync(publishPackagePath, path.resolve(packageDir, 'package.json'));
console.log(' - Copied package.publish.json as package.json');
// 6. Copy .vscodeignore if it exists
if (fs.existsSync(path.resolve(__dirname, '.vscodeignore'))) {
fs.copySync(
path.resolve(__dirname, '.vscodeignore'),
path.resolve(packageDir, '.vscodeignore')
);
console.log(' - Copied .vscodeignore');
}
// 7. Copy LICENSE if it exists
if (fs.existsSync(path.resolve(__dirname, 'LICENSE'))) {
fs.copySync(
path.resolve(__dirname, 'LICENSE'),
path.resolve(packageDir, 'LICENSE')
);
console.log(' - Copied LICENSE');
}
// 7a. Copy assets directory if it exists
const assetsDir = path.resolve(__dirname, 'assets');
if (fs.existsSync(assetsDir)) {
const targetAssetsDir = path.resolve(packageDir, 'assets');
fs.copySync(assetsDir, targetAssetsDir);
console.log(' - Copied assets directory');
}
// Small delay to ensure file system operations complete
await new Promise((resolve) => setTimeout(resolve, 100));
// 8. Final step - manual packaging
console.log('\n✅ Build preparation complete!');
console.log('\nTo create the VSIX package, run:');
console.log(
'\x1b[36m%s\x1b[0m',
`cd vsix-build && npx vsce package --no-dependencies`
);
// Use the transformed version for output
console.log(
`\nYour extension will be packaged to: vsix-build/task-master-${finalVersion}.vsix`
);
} catch (error) {
console.error('\n❌ Packaging failed!');
console.error(error.message);
process.exit(1);
}
```
--------------------------------------------------------------------------------
/src/profiles/opencode.js:
--------------------------------------------------------------------------------
```javascript
// Opencode profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { log } from '../../scripts/modules/utils.js';
import { createProfile } from './base-profile.js';
/**
* Transform standard MCP config format to OpenCode format
* @param {Object} mcpConfig - Standard MCP configuration object
* @returns {Object} - Transformed OpenCode configuration object
*/
function transformToOpenCodeFormat(mcpConfig) {
const openCodeConfig = {
$schema: 'https://opencode.ai/config.json'
};
// Transform mcpServers to mcp
if (mcpConfig.mcpServers) {
openCodeConfig.mcp = {};
for (const [serverName, serverConfig] of Object.entries(
mcpConfig.mcpServers
)) {
// Transform server configuration
const transformedServer = {
type: 'local'
};
// Combine command and args into single command array
if (serverConfig.command && serverConfig.args) {
transformedServer.command = [
serverConfig.command,
...serverConfig.args
];
} else if (serverConfig.command) {
transformedServer.command = [serverConfig.command];
}
// Add enabled flag
transformedServer.enabled = true;
// Transform env to environment
if (serverConfig.env) {
transformedServer.environment = serverConfig.env;
}
// update with transformed config
openCodeConfig.mcp[serverName] = transformedServer;
}
}
return openCodeConfig;
}
/**
* Lifecycle function called after MCP config generation to transform to OpenCode format
* @param {string} targetDir - Target project directory
* @param {string} assetsDir - Assets directory (unused for OpenCode)
*/
function onPostConvertRulesProfile(targetDir, assetsDir) {
const openCodeConfigPath = path.join(targetDir, 'opencode.json');
if (!fs.existsSync(openCodeConfigPath)) {
log('debug', '[OpenCode] No opencode.json found to transform');
return;
}
try {
// Read the generated standard MCP config
const mcpConfigContent = fs.readFileSync(openCodeConfigPath, 'utf8');
const mcpConfig = JSON.parse(mcpConfigContent);
// Check if it's already in OpenCode format (has $schema)
if (mcpConfig.$schema) {
log(
'info',
'[OpenCode] opencode.json already in OpenCode format, skipping transformation'
);
return;
}
// Transform to OpenCode format
const openCodeConfig = transformToOpenCodeFormat(mcpConfig);
// Write back the transformed config with proper formatting
fs.writeFileSync(
openCodeConfigPath,
JSON.stringify(openCodeConfig, null, 2) + '\n'
);
log('info', '[OpenCode] Transformed opencode.json to OpenCode format');
log(
'debug',
`[OpenCode] Added schema, renamed mcpServers->mcp, combined command+args, added type/enabled, renamed env->environment`
);
} catch (error) {
log(
'error',
`[OpenCode] Failed to transform opencode.json: ${error.message}`
);
}
}
/**
* Lifecycle function called when removing OpenCode profile
* @param {string} targetDir - Target project directory
*/
function onRemoveRulesProfile(targetDir) {
const openCodeConfigPath = path.join(targetDir, 'opencode.json');
if (!fs.existsSync(openCodeConfigPath)) {
log('debug', '[OpenCode] No opencode.json found to clean up');
return;
}
try {
// Read the current config
const configContent = fs.readFileSync(openCodeConfigPath, 'utf8');
const config = JSON.parse(configContent);
// Check if it has the mcp section and taskmaster-ai server
if (config.mcp && config.mcp['taskmaster-ai']) {
// Remove taskmaster-ai server
delete config.mcp['taskmaster-ai'];
// Check if there are other MCP servers
const remainingServers = Object.keys(config.mcp);
if (remainingServers.length === 0) {
// No other servers, remove entire mcp section
delete config.mcp;
}
// Check if config is now empty (only has $schema)
const remainingKeys = Object.keys(config).filter(
(key) => key !== '$schema'
);
if (remainingKeys.length === 0) {
// Config only has schema left, remove entire file
fs.rmSync(openCodeConfigPath, { force: true });
log('info', '[OpenCode] Removed empty opencode.json file');
} else {
// Write back the modified config
fs.writeFileSync(
openCodeConfigPath,
JSON.stringify(config, null, 2) + '\n'
);
log(
'info',
'[OpenCode] Removed TaskMaster from opencode.json, preserved other configurations'
);
}
} else {
log('debug', '[OpenCode] TaskMaster not found in opencode.json');
}
} catch (error) {
log(
'error',
`[OpenCode] Failed to clean up opencode.json: ${error.message}`
);
}
}
// Create and export opencode profile using the base factory
export const opencodeProfile = createProfile({
name: 'opencode',
displayName: 'OpenCode',
url: 'opencode.ai',
docsUrl: 'opencode.ai/docs/',
profileDir: '.', // Root directory
rulesDir: '.', // Root directory for AGENTS.md
mcpConfigName: 'opencode.json', // Override default 'mcp.json'
includeDefaultRules: false,
fileMap: {
'AGENTS.md': 'AGENTS.md'
},
onPostConvert: onPostConvertRulesProfile,
onRemove: onRemoveRulesProfile
});
// Export lifecycle functions separately to avoid naming conflicts
export { onPostConvertRulesProfile, onRemoveRulesProfile };
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/update-subtask-by-id.js:
--------------------------------------------------------------------------------
```javascript
/**
* update-subtask-by-id.js
* Direct function implementation for appending information to a specific subtask
*/
import { updateSubtaskById } from '../../../../scripts/modules/task-manager.js';
import {
enableSilentMode,
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for updateSubtaskById with error handling.
*
* @param {Object} args - Command arguments containing id, prompt, useResearch, tasksJsonPath, and projectRoot.
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - Subtask ID in format "parent.sub".
* @param {string} args.prompt - Information to append to the subtask.
* @param {boolean} [args.research] - Whether to use research role.
* @param {string} [args.projectRoot] - Project root path.
* @param {string} [args.tag] - Tag for the task (optional)
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function updateSubtaskByIdDirect(args, log, context = {}) {
const { session } = context;
// Destructure expected args, including projectRoot
const { tasksJsonPath, id, prompt, research, projectRoot, tag } = args;
const logWrapper = createLogWrapper(log);
try {
logWrapper.info(
`Updating subtask by ID via direct function. ID: ${id}, ProjectRoot: ${projectRoot}`
);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
logWrapper.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage }
};
}
// Basic validation for ID format (e.g., '5.2')
if (!id || typeof id !== 'string' || !id.includes('.')) {
const errorMessage =
'Invalid subtask ID format. Must be in format "parentId.subtaskId" (e.g., "5.2").';
logWrapper.error(errorMessage);
return {
success: false,
error: { code: 'INVALID_SUBTASK_ID', message: errorMessage }
};
}
if (!prompt) {
const errorMessage =
'No prompt specified. Please provide the information to append.';
logWrapper.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_PROMPT', message: errorMessage }
};
}
// Validate subtask ID format
const subtaskId = id;
if (typeof subtaskId !== 'string' && typeof subtaskId !== 'number') {
const errorMessage = `Invalid subtask ID type: ${typeof subtaskId}. Subtask ID must be a string or number.`;
log.error(errorMessage);
return {
success: false,
error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage }
};
}
const subtaskIdStr = String(subtaskId);
if (!subtaskIdStr.includes('.')) {
const errorMessage = `Invalid subtask ID format: ${subtaskIdStr}. Subtask ID must be in format "parentId.subtaskId" (e.g., "5.2").`;
log.error(errorMessage);
return {
success: false,
error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage }
};
}
// Use the provided path
const tasksPath = tasksJsonPath;
const useResearch = research === true;
log.info(
`Updating subtask with ID ${subtaskIdStr} with prompt "${prompt}" and research: ${useResearch}`
);
const wasSilent = isSilentMode();
if (!wasSilent) {
enableSilentMode();
}
try {
// Call legacy script which handles both API and file storage via bridge
const coreResult = await updateSubtaskById(
tasksPath,
subtaskIdStr,
prompt,
useResearch,
{
mcpLog: logWrapper,
session,
projectRoot,
tag,
commandName: 'update-subtask',
outputType: 'mcp'
},
'json'
);
if (!coreResult || coreResult.updatedSubtask === null) {
const message = `Subtask ${id} or its parent task not found.`;
logWrapper.error(message);
return {
success: false,
error: { code: 'SUBTASK_NOT_FOUND', message: message }
};
}
const parentId = subtaskIdStr.split('.')[0];
const successMessage = `Successfully updated subtask with ID ${subtaskIdStr}`;
logWrapper.success(successMessage);
return {
success: true,
data: {
message: `Successfully updated subtask with ID ${subtaskIdStr}`,
subtaskId: subtaskIdStr,
parentId: parentId,
subtask: coreResult.updatedSubtask,
tasksPath,
useResearch,
telemetryData: coreResult.telemetryData,
tagInfo: coreResult.tagInfo
}
};
} catch (error) {
logWrapper.error(`Error updating subtask by ID: ${error.message}`);
return {
success: false,
error: {
code: 'UPDATE_SUBTASK_CORE_ERROR',
message: error.message || 'Unknown error updating subtask'
}
};
} finally {
if (!wasSilent && isSilentMode()) {
disableSilentMode();
}
}
} catch (error) {
logWrapper.error(
`Setup error in updateSubtaskByIdDirect: ${error.message}`
);
if (isSilentMode()) disableSilentMode();
return {
success: false,
error: {
code: 'DIRECT_FUNCTION_SETUP_ERROR',
message: error.message || 'Unknown setup error'
}
};
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/custom-sdk/schema-converter.js:
--------------------------------------------------------------------------------
```javascript
/**
* @fileoverview Schema conversion utilities for MCP AI SDK provider
*/
/**
* Convert Zod schema to human-readable JSON instructions
* @param {import('zod').ZodSchema} schema - Zod schema object
* @param {string} [objectName='result'] - Name of the object being generated
* @returns {string} Instructions for JSON generation
*/
export function convertSchemaToInstructions(schema, objectName = 'result') {
try {
// Generate example structure from schema
const exampleStructure = generateExampleFromSchema(schema);
return `
CRITICAL JSON GENERATION INSTRUCTIONS:
You must respond with ONLY valid JSON that matches this exact structure for "${objectName}":
${JSON.stringify(exampleStructure, null, 2)}
STRICT REQUIREMENTS:
1. Response must start with { and end with }
2. Use double quotes for all strings and property names
3. Do not include any text before or after the JSON
4. Do not wrap in markdown code blocks
5. Do not include explanations or comments
6. Follow the exact property names and types shown above
7. All required fields must be present
Begin your response immediately with the opening brace {`;
} catch (error) {
// Fallback to basic JSON instructions if schema parsing fails
return `
CRITICAL JSON GENERATION INSTRUCTIONS:
You must respond with ONLY valid JSON for "${objectName}".
STRICT REQUIREMENTS:
1. Response must start with { and end with }
2. Use double quotes for all strings and property names
3. Do not include any text before or after the JSON
4. Do not wrap in markdown code blocks
5. Do not include explanations or comments
Begin your response immediately with the opening brace {`;
}
}
/**
* Generate example structure from Zod schema
* @param {import('zod').ZodSchema} schema - Zod schema
* @returns {any} Example object matching the schema
*/
function generateExampleFromSchema(schema) {
// This is a simplified schema-to-example converter
// For production, you might want to use a more sophisticated library
if (!schema || typeof schema._def === 'undefined') {
return {};
}
const def = schema._def;
switch (def.typeName) {
case 'ZodObject':
const result = {};
const shape = def.shape();
for (const [key, fieldSchema] of Object.entries(shape)) {
result[key] = generateExampleFromSchema(fieldSchema);
}
return result;
case 'ZodString':
// Check for min/max length constraints
if (def.checks) {
const minCheck = def.checks.find((c) => c.kind === 'min');
const maxCheck = def.checks.find((c) => c.kind === 'max');
if (minCheck && maxCheck) {
return (
'<string between ' +
minCheck.value +
'-' +
maxCheck.value +
' characters>'
);
} else if (minCheck) {
return '<string with at least ' + minCheck.value + ' characters>';
} else if (maxCheck) {
return '<string up to ' + maxCheck.value + ' characters>';
}
}
return '<string>';
case 'ZodNumber':
// Check for int, positive, min/max constraints
if (def.checks) {
const intCheck = def.checks.find((c) => c.kind === 'int');
const minCheck = def.checks.find((c) => c.kind === 'min');
const maxCheck = def.checks.find((c) => c.kind === 'max');
if (intCheck && minCheck && minCheck.value > 0) {
return '<positive integer>';
} else if (intCheck) {
return '<integer>';
} else if (minCheck || maxCheck) {
return (
'<number' +
(minCheck ? ' >= ' + minCheck.value : '') +
(maxCheck ? ' <= ' + maxCheck.value : '') +
'>'
);
}
}
return '<number>';
case 'ZodBoolean':
return '<boolean>';
case 'ZodArray':
const elementExample = generateExampleFromSchema(def.type);
return [elementExample];
case 'ZodOptional':
return generateExampleFromSchema(def.innerType);
case 'ZodNullable':
return generateExampleFromSchema(def.innerType);
case 'ZodEnum':
return def.values[0] || 'enum_value';
case 'ZodLiteral':
return def.value;
case 'ZodUnion':
// Use the first option from the union
if (def.options && def.options.length > 0) {
return generateExampleFromSchema(def.options[0]);
}
return 'union_value';
case 'ZodRecord':
return {
key: generateExampleFromSchema(def.valueType)
};
default:
// For unknown types, return a placeholder
return `<${def.typeName || 'unknown'}>`;
}
}
/**
* Enhance prompt with JSON generation instructions
* @param {Array} prompt - AI SDK prompt array
* @param {string} jsonInstructions - JSON generation instructions
* @returns {Array} Enhanced prompt array
*/
export function enhancePromptForJSON(prompt, jsonInstructions) {
const enhancedPrompt = [...prompt];
// Find system message or create one
let systemMessageIndex = enhancedPrompt.findIndex(
(msg) => msg.role === 'system'
);
if (systemMessageIndex >= 0) {
// Append to existing system message
const currentContent = enhancedPrompt[systemMessageIndex].content;
enhancedPrompt[systemMessageIndex] = {
...enhancedPrompt[systemMessageIndex],
content: currentContent + '\n\n' + jsonInstructions
};
} else {
// Add new system message at the beginning
enhancedPrompt.unshift({
role: 'system',
content: jsonInstructions
});
}
return enhancedPrompt;
}
```
--------------------------------------------------------------------------------
/tests/fixtures/sample-tasks.js:
--------------------------------------------------------------------------------
```javascript
/**
* Sample task data for testing
*/
export const sampleTasks = {
meta: {
projectName: 'Test Project',
projectVersion: '1.0.0',
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
},
tasks: [
{
id: 1,
title: 'Initialize Project',
description: 'Set up the project structure and dependencies',
status: 'done',
dependencies: [],
priority: 'high',
details:
'Create directory structure, initialize package.json, and install dependencies',
testStrategy: 'Verify all directories and files are created correctly'
},
{
id: 2,
title: 'Create Core Functionality',
description: 'Implement the main features of the application',
status: 'in-progress',
dependencies: [1],
priority: 'high',
details:
'Implement user authentication, data processing, and API endpoints',
testStrategy: 'Write unit tests for all core functions',
subtasks: [
{
id: 1,
title: 'Implement Authentication',
description: 'Create user authentication system',
status: 'done',
dependencies: []
},
{
id: 2,
title: 'Set Up Database',
description: 'Configure database connection and models',
status: 'pending',
dependencies: [1]
}
]
},
{
id: 3,
title: 'Implement UI Components',
description: 'Create the user interface components',
status: 'pending',
dependencies: [2],
priority: 'medium',
details: 'Design and implement React components for the user interface',
testStrategy: 'Test components with React Testing Library',
subtasks: [
{
id: 1,
title: 'Create Header Component',
description: 'Implement the header component',
status: 'pending',
dependencies: [],
details: 'Create a responsive header with navigation links'
},
{
id: 2,
title: 'Create Footer Component',
description: 'Implement the footer component',
status: 'pending',
dependencies: [],
details: 'Create a footer with copyright information and links'
}
]
}
]
};
export const emptySampleTasks = {
meta: {
projectName: 'Empty Project',
projectVersion: '1.0.0',
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
},
tasks: []
};
export const crossLevelDependencyTasks = {
tasks: [
{
id: 2,
title: 'Task 2 with subtasks',
description: 'Parent task',
status: 'pending',
dependencies: [],
subtasks: [
{
id: 1,
title: 'Subtask 2.1',
description: 'First subtask',
status: 'pending',
dependencies: []
},
{
id: 2,
title: 'Subtask 2.2',
description: 'Second subtask that should depend on Task 11',
status: 'pending',
dependencies: []
}
]
},
{
id: 11,
title: 'Task 11',
description: 'Top-level task that 2.2 should depend on',
status: 'done',
dependencies: []
}
]
};
// ============================================================================
// Tagged Format Fixtures (for tag-aware system tests)
// ============================================================================
/**
* Single task in master tag - minimal fixture
* Use: Basic happy path tests
*/
export const taggedOneTask = {
tag: 'master',
tasks: [
{
id: 1,
title: 'Task 1',
description: 'First task',
status: 'pending',
dependencies: [],
priority: 'medium'
}
]
};
/**
* Task with subtasks in master tag
* Use: Testing subtask operations (expand, update-subtask)
*/
export const taggedTaskWithSubtasks = {
tag: 'master',
tasks: [
{
id: 1,
title: 'Parent Task',
description: 'Task with subtasks',
status: 'in-progress',
dependencies: [],
priority: 'high',
subtasks: [
{
id: 1,
title: 'Subtask 1.1',
description: 'First subtask',
status: 'done',
dependencies: []
},
{
id: 2,
title: 'Subtask 1.2',
description: 'Second subtask',
status: 'pending',
dependencies: [1]
}
]
}
]
};
/**
* Multiple tasks with dependencies in master tag
* Use: Testing dependency operations, task ordering
*/
export const taggedTasksWithDependencies = {
tag: 'master',
tasks: [
{
id: 1,
title: 'Setup',
description: 'Initial setup task',
status: 'done',
dependencies: [],
priority: 'high'
},
{
id: 2,
title: 'Core Feature',
description: 'Main feature implementation',
status: 'in-progress',
dependencies: [1],
priority: 'high'
},
{
id: 3,
title: 'Polish',
description: 'Final touches',
status: 'pending',
dependencies: [2],
priority: 'low'
}
]
};
/**
* Empty tag - no tasks
* Use: Testing edge cases, "add first task" scenarios
*/
export const taggedEmptyTasks = {
tag: 'master',
tasks: []
};
/**
* Helper function to create custom tagged fixture
* @param {string} tagName - Tag name (default: 'master')
* @param {Array} tasks - Array of task objects
* @returns {Object} Tagged task data
*
* @example
* const customData = createTaggedFixture('feature-branch', [
* { id: 1, title: 'Custom Task', status: 'pending', dependencies: [] }
* ]);
*/
export function createTaggedFixture(tagName = 'master', tasks = []) {
return {
tag: tagName,
tasks
};
}
```
--------------------------------------------------------------------------------
/tests/unit/ai-providers/openai-compatible.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Tests for OpenAICompatibleProvider base class
*/
import { OpenAICompatibleProvider } from '../../../src/ai-providers/openai-compatible.js';
describe('OpenAICompatibleProvider', () => {
describe('constructor', () => {
it('should initialize with required config', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY'
});
expect(provider.name).toBe('Test Provider');
expect(provider.apiKeyEnvVar).toBe('TEST_API_KEY');
expect(provider.requiresApiKey).toBe(true);
});
it('should initialize with requiresApiKey set to false', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
requiresApiKey: false
});
expect(provider.requiresApiKey).toBe(false);
});
it('should throw error if name is missing', () => {
expect(() => {
new OpenAICompatibleProvider({
apiKeyEnvVar: 'TEST_API_KEY'
});
}).toThrow('Provider name is required');
});
it('should throw error if apiKeyEnvVar is missing', () => {
expect(() => {
new OpenAICompatibleProvider({
name: 'Test Provider'
});
}).toThrow('API key environment variable name is required');
});
});
describe('getRequiredApiKeyName', () => {
it('should return correct environment variable name', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY'
});
expect(provider.getRequiredApiKeyName()).toBe('TEST_API_KEY');
});
});
describe('isRequiredApiKey', () => {
it('should return true by default', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY'
});
expect(provider.isRequiredApiKey()).toBe(true);
});
it('should return false when explicitly set', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
requiresApiKey: false
});
expect(provider.isRequiredApiKey()).toBe(false);
});
});
describe('validateAuth', () => {
it('should validate API key is present when required', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
requiresApiKey: true
});
expect(() => {
provider.validateAuth({});
}).toThrow('Test Provider API key is required');
});
it('should not validate API key when not required', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
requiresApiKey: false
});
expect(() => {
provider.validateAuth({});
}).not.toThrow();
});
it('should pass with valid API key', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY'
});
expect(() => {
provider.validateAuth({ apiKey: 'test-key' });
}).not.toThrow();
});
});
describe('getBaseURL', () => {
it('should return custom baseURL from params', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
defaultBaseURL: 'https://default.api.com'
});
const baseURL = provider.getBaseURL({
baseURL: 'https://custom.api.com'
});
expect(baseURL).toBe('https://custom.api.com');
});
it('should return default baseURL if no custom provided', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
defaultBaseURL: 'https://default.api.com'
});
const baseURL = provider.getBaseURL({});
expect(baseURL).toBe('https://default.api.com');
});
it('should use custom getBaseURL function', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
getBaseURL: (params) => `https://api.example.com/${params.route}`
});
const baseURL = provider.getBaseURL({ route: 'v2' });
expect(baseURL).toBe('https://api.example.com/v2');
});
});
describe('getClient', () => {
it('should create client with API key when required', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
requiresApiKey: true,
defaultBaseURL: 'https://api.example.com'
});
const client = provider.getClient({ apiKey: 'test-key' });
expect(client).toBeDefined();
});
it('should create client without API key when not required', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
requiresApiKey: false,
defaultBaseURL: 'https://api.example.com'
});
const client = provider.getClient({});
expect(client).toBeDefined();
});
it('should create client even when API key is required but missing (validation deferred to SDK)', () => {
const provider = new OpenAICompatibleProvider({
name: 'Test Provider',
apiKeyEnvVar: 'TEST_API_KEY',
requiresApiKey: true
});
// getClient() no longer validates API key - validation is deferred to SDK initialization
const client = provider.getClient({});
expect(typeof client).toBe('function');
});
});
});
```
--------------------------------------------------------------------------------
/src/progress/parse-prd-tracker.js:
--------------------------------------------------------------------------------
```javascript
import chalk from 'chalk';
import { newMultiBar } from './cli-progress-factory.js';
import { BaseProgressTracker } from './base-progress-tracker.js';
import {
createProgressHeader,
createProgressRow,
createBorder
} from './tracker-ui.js';
import {
getCliPriorityIndicators,
getPriorityIndicator,
getStatusBarPriorityIndicators,
getPriorityColors
} from '../ui/indicators.js';
// Get centralized priority indicators
const PRIORITY_INDICATORS = getCliPriorityIndicators();
const PRIORITY_DOTS = getStatusBarPriorityIndicators();
const PRIORITY_COLORS = getPriorityColors();
// Constants
const CONSTANTS = {
DEBOUNCE_DELAY: 100,
MAX_TITLE_LENGTH: 57,
TRUNCATED_LENGTH: 54,
TASK_ID_PAD_START: 3,
TASK_ID_PAD_END: 4,
PRIORITY_PAD_END: 3,
VALID_PRIORITIES: ['high', 'medium', 'low'],
DEFAULT_PRIORITY: 'medium'
};
/**
* Helper class to manage update debouncing
*/
class UpdateDebouncer {
constructor(delay = CONSTANTS.DEBOUNCE_DELAY) {
this.delay = delay;
this.pendingTimeout = null;
}
debounce(callback) {
this.clear();
this.pendingTimeout = setTimeout(() => {
callback();
this.pendingTimeout = null;
}, this.delay);
}
clear() {
if (this.pendingTimeout) {
clearTimeout(this.pendingTimeout);
this.pendingTimeout = null;
}
}
hasPending() {
return this.pendingTimeout !== null;
}
}
/**
* Helper class to manage priority counts
*/
class PriorityManager {
constructor() {
this.priorities = { high: 0, medium: 0, low: 0 };
}
increment(priority) {
const normalized = this.normalize(priority);
this.priorities[normalized]++;
return normalized;
}
normalize(priority) {
const lowercased = priority
? priority.toLowerCase()
: CONSTANTS.DEFAULT_PRIORITY;
return CONSTANTS.VALID_PRIORITIES.includes(lowercased)
? lowercased
: CONSTANTS.DEFAULT_PRIORITY;
}
getCounts() {
return { ...this.priorities };
}
}
/**
* Helper class for formatting task display elements
*/
class TaskFormatter {
static formatTitle(title, taskNumber) {
if (!title) return `Task ${taskNumber}`;
return title.length > CONSTANTS.MAX_TITLE_LENGTH
? title.substring(0, CONSTANTS.TRUNCATED_LENGTH) + '...'
: title;
}
static formatPriority(priority) {
return getPriorityIndicator(priority, false).padEnd(
CONSTANTS.PRIORITY_PAD_END,
' '
);
}
static formatTaskId(taskNumber) {
return taskNumber
.toString()
.padStart(CONSTANTS.TASK_ID_PAD_START, ' ')
.padEnd(CONSTANTS.TASK_ID_PAD_END, ' ');
}
}
/**
* Tracks progress for PRD parsing operations with multibar display
*/
class ParsePrdTracker extends BaseProgressTracker {
_initializeCustomProperties(options) {
this.append = options.append;
this.priorityManager = new PriorityManager();
this.debouncer = new UpdateDebouncer();
this.headerShown = false;
}
_getTimeTokensBarFormat() {
return `{clock} {elapsed} | ${PRIORITY_DOTS.high} {high} ${PRIORITY_DOTS.medium} {medium} ${PRIORITY_DOTS.low} {low} | Tokens (I/O): {in}/{out} | Est: {remaining}`;
}
_getProgressBarFormat() {
return 'Tasks {tasks} |{bar}| {percentage}%';
}
_getCustomTimeTokensPayload() {
return this.priorityManager.getCounts();
}
addTaskLine(taskNumber, title, priority = 'medium') {
if (!this.multibar || this.isFinished) return;
this._ensureHeaderShown();
const normalizedPriority = this._updateTaskCounters(taskNumber, priority);
// Immediately update the time/tokens bar to show the new priority count
this._updateTimeTokensBar();
this.debouncer.debounce(() => {
this._updateProgressDisplay(taskNumber, title, normalizedPriority);
});
}
_ensureHeaderShown() {
if (!this.headerShown) {
this.headerShown = true;
createProgressHeader(
this.multibar,
' TASK | PRI | TITLE',
'------+-----+----------------------------------------------------------------'
);
}
}
_updateTaskCounters(taskNumber, priority) {
const normalizedPriority = this.priorityManager.increment(priority);
this.completedUnits = taskNumber;
return normalizedPriority;
}
_updateProgressDisplay(taskNumber, title, normalizedPriority) {
this.progressBar.update(this.completedUnits, {
tasks: `${this.completedUnits}/${this.numUnits}`
});
const displayTitle = TaskFormatter.formatTitle(title, taskNumber);
const priorityDisplay = TaskFormatter.formatPriority(normalizedPriority);
const taskIdCentered = TaskFormatter.formatTaskId(taskNumber);
createProgressRow(
this.multibar,
` ${taskIdCentered} | ${priorityDisplay} | {title}`,
{ title: displayTitle }
);
createBorder(
this.multibar,
'------+-----+----------------------------------------------------------------'
);
this._updateTimeTokensBar();
}
finish() {
// Flush any pending updates before finishing
if (this.debouncer.hasPending()) {
this.debouncer.clear();
this._updateTimeTokensBar();
}
this.cleanup();
super.finish();
}
/**
* Override cleanup to handle pending updates
*/
_performCustomCleanup() {
this.debouncer.clear();
}
getSummary() {
return {
...super.getSummary(),
taskPriorities: this.priorityManager.getCounts(),
actionVerb: this.append ? 'appended' : 'generated'
};
}
}
export function createParsePrdTracker(options = {}) {
return new ParsePrdTracker(options);
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/initialize-project.js:
--------------------------------------------------------------------------------
```javascript
import { initializeProject } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
import {
enableSilentMode,
disableSilentMode
// isSilentMode // Not used directly here
} from '../../../../scripts/modules/utils.js';
import os from 'os'; // Import os module for home directory check
import { RULE_PROFILES } from '../../../../src/constants/profiles.js';
import { convertAllRulesToProfileRules } from '../../../../src/utils/rule-transformer.js';
/**
* Direct function wrapper for initializing a project.
* Derives target directory from session, sets CWD, and calls core init logic.
* @param {object} args - Arguments containing initialization options (addAliases, initGit, storeTasksInGit, skipInstall, yes, projectRoot, rules)
* @param {object} log - The FastMCP logger instance.
* @param {object} context - The context object, must contain { session }.
* @returns {Promise<{success: boolean, data?: any, error?: {code: string, message: string}}>} - Standard result object.
*/
export async function initializeProjectDirect(args, log, context = {}) {
const { session } = context; // Keep session if core logic needs it
const homeDir = os.homedir();
log.info(`Args received in direct function: ${JSON.stringify(args)}`);
// --- Determine Target Directory ---
// TRUST the projectRoot passed from the tool layer via args
// The HOF in the tool layer already normalized and validated it came from a reliable source (args or session)
const targetDirectory = args.projectRoot;
// --- Validate the targetDirectory (basic sanity checks) ---
if (
!targetDirectory ||
typeof targetDirectory !== 'string' || // Ensure it's a string
targetDirectory === '/' ||
targetDirectory === homeDir
) {
log.error(
`Invalid target directory received from tool layer: '${targetDirectory}'`
);
return {
success: false,
error: {
code: 'INVALID_TARGET_DIRECTORY',
message: `Cannot initialize project: Invalid target directory '${targetDirectory}' received. Please ensure a valid workspace/folder is open or specified.`,
details: `Received args.projectRoot: ${args.projectRoot}` // Show what was received
}
};
}
// --- Proceed with validated targetDirectory ---
log.info(`Validated target directory for initialization: ${targetDirectory}`);
const originalCwd = process.cwd();
let resultData;
let success = false;
let errorResult = null;
log.info(
`Temporarily changing CWD to ${targetDirectory} for initialization.`
);
process.chdir(targetDirectory); // Change CWD to the HOF-provided root
enableSilentMode();
try {
// Construct options ONLY from the relevant flags in args
// The core initializeProject operates in the current CWD, which we just set
const options = {
addAliases: args.addAliases,
initGit: args.initGit,
storeTasksInGit: args.storeTasksInGit,
skipInstall: args.skipInstall,
yes: true // Force yes mode
};
// Handle rules option with MCP-specific defaults
if (Array.isArray(args.rules) && args.rules.length > 0) {
options.rules = args.rules;
options.rulesExplicitlyProvided = true;
log.info(`Including rules: ${args.rules.join(', ')}`);
} else {
// For MCP initialization, default to Cursor profile only
options.rules = ['cursor'];
options.rulesExplicitlyProvided = true;
log.info(`No rule profiles specified, defaulting to: Cursor`);
}
log.info(`Initializing project with options: ${JSON.stringify(options)}`);
const result = await initializeProject(options); // Call core logic
resultData = {
message: 'Project initialized successfully.',
next_step:
'Now that the project is initialized, the next step is to create the tasks by parsing a PRD. This will create the tasks folder and the initial task files (tasks folder will be created when parse-prd is run). The parse-prd tool will require a prd.txt file as input (typically found in .taskmaster/docs/ directory). You can create a prd.txt file by asking the user about their idea, and then using the .taskmaster/templates/example_prd.txt file as a template to generate a prd.txt file in .taskmaster/docs/. You may skip all of this if the user already has a prd.txt file. You can THEN use the parse-prd tool to create the tasks. So: step 1 after initialization is to create a prd.txt file in .taskmaster/docs/prd.txt or confirm the user already has one. Step 2 is to use the parse-prd tool to create the tasks. Do not bother looking for tasks after initialization, just use the parse-prd tool to create the tasks after creating a prd.txt from which to parse the tasks. You do NOT need to reinitialize the project to parse-prd.',
...result
};
success = true;
log.info(
`Project initialization completed successfully in ${targetDirectory}.`
);
} catch (error) {
log.error(`Core initializeProject failed: ${error.message}`);
errorResult = {
code: 'INITIALIZATION_FAILED',
message: `Core project initialization failed: ${error.message}`,
details: error.stack
};
success = false;
} finally {
disableSilentMode();
log.info(`Restoring original CWD: ${originalCwd}`);
process.chdir(originalCwd);
}
if (success) {
return { success: true, data: resultData };
} else {
return { success: false, error: errorResult };
}
}
```
--------------------------------------------------------------------------------
/src/ai-providers/claude-code.js:
--------------------------------------------------------------------------------
```javascript
/**
* src/ai-providers/claude-code.js
*
* Claude Code provider implementation using the ai-sdk-provider-claude-code package.
* This provider uses the local Claude Agent SDK (via Claude Code CLI) with OAuth token authentication.
*
* Authentication:
* - Uses CLAUDE_CODE_OAUTH_TOKEN managed by Claude Code CLI
* - Token is set up via: claude setup-token
* - No manual API key configuration required
*
*/
import { createClaudeCode } from 'ai-sdk-provider-claude-code';
import { BaseAIProvider } from './base-provider.js';
import {
getClaudeCodeSettingsForCommand,
getSupportedModelsForProvider
} from '../../scripts/modules/config-manager.js';
import { execSync } from 'child_process';
import { log } from '../../scripts/modules/utils.js';
let _claudeCliChecked = false;
let _claudeCliAvailable = null;
/**
* Provider for Claude Code CLI integration via AI SDK
*
* Features:
* - No API key required (uses local Claude Code CLI)
* - Supported models loaded from supported-models.json
* - Command-specific configuration support
*/
export class ClaudeCodeProvider extends BaseAIProvider {
constructor() {
super();
this.name = 'Claude Code';
// Load supported models from supported-models.json
this.supportedModels = getSupportedModelsForProvider('claude-code');
// Validate that models were loaded successfully
if (this.supportedModels.length === 0) {
log(
'warn',
'No supported models found for claude-code provider. Check supported-models.json configuration.'
);
}
// Claude Code requires explicit JSON schema mode
this.needsExplicitJsonSchema = true;
// Claude Code does not support temperature parameter
this.supportsTemperature = false;
}
/**
* @returns {string} The environment variable name for API key (not used)
*/
getRequiredApiKeyName() {
return 'CLAUDE_CODE_API_KEY';
}
/**
* @returns {boolean} False - Claude Code doesn't require API keys
*/
isRequiredApiKey() {
return false;
}
/**
* Optional CLI availability check for Claude Code
* @param {object} params - Parameters (ignored)
*/
validateAuth(params) {
// Claude Code uses local CLI - perform lightweight availability check
// This is optional validation that fails fast with actionable guidance
if (
process.env.NODE_ENV !== 'test' &&
!_claudeCliChecked &&
!process.env.CLAUDE_CODE_OAUTH_TOKEN
) {
try {
execSync('claude --version', { stdio: 'pipe', timeout: 1000 });
_claudeCliAvailable = true;
} catch (error) {
_claudeCliAvailable = false;
log(
'warn',
'Claude Code CLI not detected. Install it with: npm install -g @anthropic-ai/claude-code'
);
} finally {
_claudeCliChecked = true;
}
}
}
/**
* Creates a Claude Code client instance
* @param {object} params - Client parameters
* @param {string} [params.commandName] - Command name for settings lookup
* @returns {Function} Claude Code provider function
* @throws {Error} If Claude Code CLI is not available or client creation fails
*/
getClient(params = {}) {
try {
const settings =
getClaudeCodeSettingsForCommand(params.commandName) || {};
// Environment variable isolation to prevent API key conflicts
// The ai-sdk-provider-claude-code SDK automatically picks up ANTHROPIC_API_KEY,
// which can cause conflicts if that key is intended for the Anthropic provider.
const originalAnthropicKey = process.env.ANTHROPIC_API_KEY;
const claudeCodeKey = process.env.CLAUDE_CODE_API_KEY;
try {
// If CLAUDE_CODE_API_KEY is set, use it exclusively
if (claudeCodeKey) {
process.env.ANTHROPIC_API_KEY = claudeCodeKey;
} else if (originalAnthropicKey) {
// If only ANTHROPIC_API_KEY exists, temporarily unset it to force OAuth mode
delete process.env.ANTHROPIC_API_KEY;
}
return createClaudeCode({
defaultSettings: {
// Restore previous default behavior from pre-2.0 versions
// These must be inside defaultSettings to be applied by the provider
systemPrompt: {
type: 'preset',
preset: 'claude_code'
},
// Enable loading of CLAUDE.md and settings.json files
settingSources: ['user', 'project', 'local'],
...settings
}
});
} finally {
// Restore original environment state
if (originalAnthropicKey) {
process.env.ANTHROPIC_API_KEY = originalAnthropicKey;
} else {
delete process.env.ANTHROPIC_API_KEY;
}
}
} catch (error) {
// Provide more helpful error message
const msg = String(error?.message || '');
const code = error?.code;
if (code === 'ENOENT' || /claude/i.test(msg)) {
const enhancedError = new Error(
`Claude Code CLI not available. Please install Claude Code CLI first. Original error: ${error.message}`
);
enhancedError.cause = error;
this.handleError('Claude Code CLI initialization', enhancedError);
} else {
this.handleError('client initialization', error);
}
}
}
/**
* @returns {string[]} List of supported model IDs
*/
getSupportedModels() {
return this.supportedModels;
}
/**
* Check if a model is supported
* @param {string} modelId - Model ID to check
* @returns {boolean} True if supported
*/
isModelSupported(modelId) {
if (!modelId) return false;
return this.supportedModels.includes(String(modelId).toLowerCase());
}
}
```
--------------------------------------------------------------------------------
/src/prompts/add-task.json:
--------------------------------------------------------------------------------
```json
{
"id": "add-task",
"version": "1.0.0",
"description": "Generate a new task based on description",
"metadata": {
"author": "system",
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z",
"tags": ["task-creation", "generation"]
},
"parameters": {
"prompt": {
"type": "string",
"required": true,
"description": "User's task description"
},
"newTaskId": {
"type": "number",
"required": true,
"description": "ID for the new task"
},
"existingTasks": {
"type": "array",
"description": "List of existing tasks for context"
},
"gatheredContext": {
"type": "string",
"description": "Context gathered from codebase analysis"
},
"contextFromArgs": {
"type": "string",
"description": "Additional context from manual args"
},
"priority": {
"type": "string",
"default": "medium",
"enum": ["high", "medium", "low"],
"description": "Task priority"
},
"dependencies": {
"type": "array",
"description": "Task dependency IDs"
},
"useResearch": {
"type": "boolean",
"default": false,
"description": "Use research mode"
},
"hasCodebaseAnalysis": {
"type": "boolean",
"required": false,
"default": false,
"description": "Whether codebase analysis is available"
},
"projectRoot": {
"type": "string",
"required": false,
"default": "",
"description": "Project root path for context"
}
},
"prompts": {
"default": {
"system": "You are a helpful assistant that creates well-structured tasks for a software development project. Generate a single new task based on the user's description, adhering strictly to the provided JSON schema.\n\nIMPORTANT: Your response MUST be a JSON object with the following structure (no wrapper property, just these fields directly):\n{\n \"title\": \"string\",\n \"description\": \"string\",\n \"details\": \"string\",\n \"testStrategy\": \"string\",\n \"dependencies\": [array of numbers]\n}\n\nDo not include any other top-level properties. Do NOT wrap this in any additional object.\n\nPay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\nWhen determining dependencies for a new task, follow these principles:\n1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n2. Prioritize task dependencies that are semantically related to the functionality being built.\n3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n6. Pay special attention to foundation tasks (1-5) but don't automatically include them without reason.\n7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\nThe dependencies array should contain task IDs (numbers) of prerequisite tasks.{{#if useResearch}}\n\nResearch current best practices and technologies relevant to this task.{{/if}}",
"user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before generating the task:\n\n1. Use the Glob tool to explore the project structure (e.g., \"**/*.js\", \"**/*.json\", \"**/README.md\")\n2. Use the Grep tool to search for existing implementations, patterns, and technologies\n3. Use the Read tool to examine key files like package.json, main entry points, and relevant source files\n4. Analyze the current implementation to understand what already exists\n\nBased on your analysis:\n- Identify existing components/features that relate to this new task\n- Understand the technology stack, frameworks, and patterns in use\n- Generate implementation details that align with the project's current architecture\n- Reference specific files, functions, or patterns from the codebase in your details\n\nProject Root: {{projectRoot}}\n\n{{/if}}You are generating the details for Task #{{newTaskId}}. Based on the user's request: \"{{prompt}}\", create a comprehensive new task for a software development project.\n \n {{gatheredContext}}\n \n {{#if useResearch}}Research current best practices, technologies, and implementation patterns relevant to this task. {{/if}}Based on the information about existing tasks provided above, include appropriate dependencies in the \"dependencies\" array. Only include task IDs that this new task directly depends on.\n \n Return your answer as a single JSON object matching the schema precisely:\n \n {\n \"title\": \"Task title goes here\",\n \"description\": \"A concise one or two sentence description of what the task involves\",\n \"details\": \"Detailed implementation steps, considerations, code examples, or technical approach\",\n \"testStrategy\": \"Specific steps to verify correct implementation and functionality\",\n \"dependencies\": [1, 3] // Example: IDs of tasks that must be completed before this task\n }\n \n Make sure the details and test strategy are comprehensive and specific{{#if useResearch}}, incorporating current best practices from your research{{/if}}. DO NOT include the task ID in the title.\n {{#if contextFromArgs}}{{contextFromArgs}}{{/if}}"
}
}
}
```
--------------------------------------------------------------------------------
/docs/providers/gemini-cli.md:
--------------------------------------------------------------------------------
```markdown
# Gemini CLI Provider
The Gemini CLI provider allows you to use Google's Gemini models through the Gemini CLI tool, leveraging your existing Gemini subscription and OAuth authentication.
## Why Use Gemini CLI?
The primary benefit of using the `gemini-cli` provider is to leverage your existing Personal Gemini Code Assist license/usage Google offers for free, or Gemini Code Assist Standard/Enterprise subscription you may already have, via OAuth configured through the Gemini CLI. This is ideal for users who:
- Have an active Gemini Code Assist license (including those using the free tier offere by Google)
- Want to use OAuth authentication instead of managing API keys
- Have already configured authentication via `gemini` OAuth login
## Installation
The provider is already included in Task Master. However, you need to install the Gemini CLI tool:
```bash
# Install gemini CLI globally
npm install -g @google/gemini-cli
```
## Authentication
### Primary Method: CLI Authentication (Recommended)
The Gemini CLI provider is designed to use your pre-configured OAuth authentication:
```bash
# Launch Gemini CLI and go through the authentication procedure
gemini
```
For OAuth use, select `Login with Google` - This will open a browser window for OAuth authentication. Once authenticated, Task Master will automatically use these credentials when you select the `gemini-cli` provider and models.
### Alternative Method: API Key
While the primary use case is OAuth authentication, you can also use an API key if needed:
```bash
export GEMINI_API_KEY="your-gemini-api-key"
```
**Note:** If you want to use API keys, consider using the standard `google` provider instead, as `gemini-cli` is specifically designed for OAuth/subscription users.
More details on authentication steps and options can be found in the [gemini-cli GitHub README](https://github.com/google-gemini/gemini-cli).
## Configuration
Use the `task-master init` command to run through the guided initialization:
```bash
task-master init
```
**OR**
Configure `gemini-cli` as a provider using the Task Master models command:
```bash
# Set gemini-cli as your main provider with gemini-2.5-pro
task-master models --set-main gemini-2.5-pro --gemini-cli
# Or use the faster gemini-2.5-flash model
task-master models --set-main gemini-2.5-flash --gemini-cli
```
You can also manually edit your `.taskmaster/config.json`:
```json
{
"models": {
"main": {
"provider": "gemini-cli",
"modelId": "gemini-2.5-pro",
"maxTokens": 65536,
"temperature": 0.2
},
"research": {
"provider": "gemini-cli",
"modelId": "gemini-2.5-pro",
"maxTokens": 65536,
"temperature": 0.1
},
"fallback": {
"provider": "gemini-cli",
"modelId": "gemini-2.5-flash",
"maxTokens": 65536,
"temperature": 0.2
}
},
"global": {
"logLevel": "info",
"debug": false,
"defaultNumTasks": 10,
"defaultSubtasks": 5,
"defaultPriority": "medium",
"projectName": "Taskmaster",
"ollamaBaseURL": "http://localhost:11434/api",
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
"responseLanguage": "English",
"defaultTag": "master",
"azureOpenaiBaseURL": "https://your-endpoint.openai.azure.com/"
},
"claudeCode": {}
}
```
### Available Models
The gemini-cli provider supports only two models:
- `gemini-2.5-pro` - High performance model (1M token context window, 65,536 max output tokens)
- `gemini-2.5-flash` - Fast, efficient model (1M token context window, 65,536 max output tokens)
## Usage Examples
### Basic Usage
Once gemini-cli is installed and authenticated, and Task Master simply use Task Master as normal:
```bash
# The provider will automatically use your OAuth credentials
task-master parse-prd my-prd.txt
```
## Troubleshooting
### "Authentication failed" Error
If you get an authentication error:
1. **Primary solution**: Run `gemini` to authenticate with your Google account - use `/auth` slash command in **gemini-cli** to change authentication method if desired.
2. **Check authentication status**: Run `gemini` and use `/about` to verify your Auth Method and GCP Project if applicable.
3. **If using API key** (not recommended): Ensure `GEMINI_API_KEY` env variable is set correctly, see the gemini-cli README.md for more info.
### "Model not found" Error
The gemini-cli provider only supports two models:
- `gemini-2.5-pro`
- `gemini-2.5-flash`
If you need other Gemini models, use the standard `google` provider with an API key instead.
### Gemini CLI Not Found
If you get a "gemini: command not found" error:
```bash
# Install the Gemini CLI globally
npm install -g @google/gemini-cli
# Verify installation
gemini --version
```
## Important Notes
- **OAuth vs API Key**: This provider is specifically designed for users who want to use OAuth authentication via gemini-cli. If you prefer using API keys, consider using the standard `google` provider instead.
- **Limited Model Support**: Only `gemini-2.5-pro` and `gemini-2.5-flash` are available through gemini-cli.
- **Subscription Benefits**: Using OAuth authentication allows you to leverage any subscription benefits associated with your Google account.
- The provider uses the `ai-sdk-provider-gemini-cli` npm package internally.
- Supports all standard Task Master features: text generation, streaming, and structured object generation.
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/update-task-by-id.js:
--------------------------------------------------------------------------------
```javascript
/**
* update-task-by-id.js
* Direct function implementation for updating a single task by ID with new information
*/
import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
import {
enableSilentMode,
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
import { findTasksPath } from '../utils/path-utils.js';
/**
* Direct function wrapper for updateTaskById with error handling.
*
* @param {Object} args - Command arguments containing id, prompt, useResearch, tasksJsonPath, and projectRoot.
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - Task ID (or subtask ID like "1.2").
* @param {string} args.prompt - New information/context prompt.
* @param {boolean} [args.research] - Whether to use research role.
* @param {boolean} [args.append] - Whether to append timestamped information instead of full update.
* @param {string} [args.projectRoot] - Project root path.
* @param {string} [args.tag] - Tag for the task (optional)
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function updateTaskByIdDirect(args, log, context = {}) {
const { session } = context;
// Destructure expected args, including projectRoot
const { tasksJsonPath, id, prompt, research, append, projectRoot, tag } =
args;
const logWrapper = createLogWrapper(log);
try {
logWrapper.info(
`Updating task by ID via direct function. ID: ${id}, ProjectRoot: ${projectRoot}`
);
// Check required parameters (id and prompt)
if (!id) {
const errorMessage =
'No task ID specified. Please provide a task ID to update.';
logWrapper.error(errorMessage);
return {
success: false,
error: { code: 'INPUT_VALIDATION_ERROR', message: errorMessage }
};
}
if (!prompt) {
const errorMessage =
'No prompt specified. Please provide a prompt with new information for the task update.';
logWrapper.error(errorMessage);
return {
success: false,
error: { code: 'INPUT_VALIDATION_ERROR', message: errorMessage }
};
}
// Parse taskId - handle numeric, alphanumeric, and subtask IDs
let taskId;
if (typeof id === 'string') {
// Keep ID as string - supports numeric (1, 2), alphanumeric (TAS-49, JIRA-123), and subtask IDs (1.2, TAS-49.1)
taskId = id;
} else if (typeof id === 'number') {
// Convert number to string for consistency
taskId = String(id);
} else {
const errorMessage = `Invalid task ID type: ${typeof id}. Task ID must be a string or number.`;
logWrapper.error(errorMessage);
return {
success: false,
error: { code: 'INPUT_VALIDATION_ERROR', message: errorMessage }
};
}
// Resolve tasks.json path - use provided or find it
const tasksPath =
tasksJsonPath ||
findTasksPath({ projectRoot, file: args.file }, logWrapper);
if (!tasksPath) {
const errorMessage = 'tasks.json path could not be resolved.';
logWrapper.error(errorMessage);
return {
success: false,
error: { code: 'INPUT_VALIDATION_ERROR', message: errorMessage }
};
}
// Get research flag
const useResearch = research === true;
logWrapper.info(
`Updating task with ID ${taskId} with prompt "${prompt}" and research: ${useResearch}`
);
const wasSilent = isSilentMode();
if (!wasSilent) {
enableSilentMode();
}
try {
// Call legacy script which handles both API and file storage via bridge
const coreResult = await updateTaskById(
tasksPath,
taskId,
prompt,
useResearch,
{
mcpLog: logWrapper,
session,
projectRoot,
tag,
commandName: 'update-task',
outputType: 'mcp'
},
'json',
append || false
);
// Check if the core function returned null or an object without success
if (!coreResult || coreResult.updatedTask === null) {
const message = `Task ${taskId} was not updated (likely already completed).`;
logWrapper.info(message);
return {
success: true,
data: {
message: message,
taskId: taskId,
updated: false,
telemetryData: coreResult?.telemetryData,
tagInfo: coreResult?.tagInfo
}
};
}
const successMessage = `Successfully updated task with ID ${taskId} based on the prompt`;
logWrapper.info(successMessage);
return {
success: true,
data: {
message: successMessage,
taskId: taskId,
tasksPath: tasksPath,
useResearch: useResearch,
updated: true,
updatedTask: coreResult.updatedTask,
telemetryData: coreResult.telemetryData,
tagInfo: coreResult.tagInfo
}
};
} catch (error) {
logWrapper.error(`Error updating task by ID: ${error.message}`);
return {
success: false,
error: {
code: 'UPDATE_TASK_CORE_ERROR',
message: error.message || 'Unknown error updating task'
}
};
} finally {
if (!wasSilent && isSilentMode()) {
disableSilentMode();
}
}
} catch (error) {
logWrapper.error(`Setup error in updateTaskByIdDirect: ${error.message}`);
if (isSilentMode()) disableSilentMode();
return {
success: false,
error: {
code: 'DIRECT_FUNCTION_SETUP_ERROR',
message: error.message || 'Unknown setup error'
}
};
}
}
```
--------------------------------------------------------------------------------
/apps/cli/tests/unit/commands/autopilot/shared.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Unit tests for autopilot shared utilities
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
OutputFormatter,
parseSubtasks,
validateTaskId
} from '../../../../src/commands/autopilot/shared.js';
// Mock fs-extra
vi.mock('fs-extra', () => ({
default: {
pathExists: vi.fn(),
readJSON: vi.fn(),
writeJSON: vi.fn(),
ensureDir: vi.fn(),
remove: vi.fn()
},
pathExists: vi.fn(),
readJSON: vi.fn(),
writeJSON: vi.fn(),
ensureDir: vi.fn(),
remove: vi.fn()
}));
describe('Autopilot Shared Utilities', () => {
const projectRoot = '/test/project';
const statePath = `${projectRoot}/.taskmaster/workflow-state.json`;
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('validateTaskId', () => {
it('should validate simple task IDs', () => {
expect(validateTaskId('1')).toBe(true);
expect(validateTaskId('10')).toBe(true);
expect(validateTaskId('999')).toBe(true);
});
it('should validate subtask IDs', () => {
expect(validateTaskId('1.1')).toBe(true);
expect(validateTaskId('1.2')).toBe(true);
expect(validateTaskId('10.5')).toBe(true);
});
it('should validate nested subtask IDs', () => {
expect(validateTaskId('1.1.1')).toBe(true);
expect(validateTaskId('1.2.3')).toBe(true);
});
it('should reject invalid formats', () => {
expect(validateTaskId('')).toBe(false);
expect(validateTaskId('abc')).toBe(false);
expect(validateTaskId('1.')).toBe(false);
expect(validateTaskId('.1')).toBe(false);
expect(validateTaskId('1..2')).toBe(false);
expect(validateTaskId('1.2.3.')).toBe(false);
});
});
describe('parseSubtasks', () => {
it('should parse subtasks from task data', () => {
const task = {
id: '1',
title: 'Test Task',
subtasks: [
{ id: '1', title: 'Subtask 1', status: 'pending' },
{ id: '2', title: 'Subtask 2', status: 'done' },
{ id: '3', title: 'Subtask 3', status: 'in-progress' }
]
};
const result = parseSubtasks(task, 5);
expect(result).toHaveLength(3);
expect(result[0]).toEqual({
id: '1',
title: 'Subtask 1',
status: 'pending',
attempts: 0,
maxAttempts: 5
});
expect(result[1]).toEqual({
id: '2',
title: 'Subtask 2',
status: 'completed',
attempts: 0,
maxAttempts: 5
});
});
it('should return empty array for missing subtasks', () => {
const task = { id: '1', title: 'Test Task' };
expect(parseSubtasks(task)).toEqual([]);
});
it('should use default maxAttempts', () => {
const task = {
subtasks: [{ id: '1', title: 'Subtask 1', status: 'pending' }]
};
const result = parseSubtasks(task);
expect(result[0].maxAttempts).toBe(3);
});
});
// State persistence tests omitted - covered in integration tests
describe('OutputFormatter', () => {
let consoleLogSpy: any;
let consoleErrorSpy: any;
beforeEach(() => {
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
consoleLogSpy.mockRestore();
consoleErrorSpy.mockRestore();
});
describe('JSON mode', () => {
it('should output JSON for success', () => {
const formatter = new OutputFormatter(true);
formatter.success('Test message', { key: 'value' });
expect(consoleLogSpy).toHaveBeenCalled();
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
expect(output.success).toBe(true);
expect(output.message).toBe('Test message');
expect(output.key).toBe('value');
});
it('should output JSON for error', () => {
const formatter = new OutputFormatter(true);
formatter.error('Error message', { code: 'ERR001' });
expect(consoleErrorSpy).toHaveBeenCalled();
const output = JSON.parse(consoleErrorSpy.mock.calls[0][0]);
expect(output.error).toBe('Error message');
expect(output.code).toBe('ERR001');
});
it('should output JSON for data', () => {
const formatter = new OutputFormatter(true);
formatter.output({ test: 'data' });
expect(consoleLogSpy).toHaveBeenCalled();
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
expect(output.test).toBe('data');
});
});
describe('Text mode', () => {
it('should output formatted text for success', () => {
const formatter = new OutputFormatter(false);
formatter.success('Test message');
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('✓ Test message')
);
});
it('should output formatted text for error', () => {
const formatter = new OutputFormatter(false);
formatter.error('Error message');
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining('Error: Error message')
);
});
it('should output formatted text for warning', () => {
const consoleWarnSpy = vi
.spyOn(console, 'warn')
.mockImplementation(() => {});
const formatter = new OutputFormatter(false);
formatter.warning('Warning message');
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('⚠ Warning message')
);
consoleWarnSpy.mockRestore();
});
it('should not output info in JSON mode', () => {
const formatter = new OutputFormatter(true);
formatter.info('Info message');
expect(consoleLogSpy).not.toHaveBeenCalled();
});
});
});
});
```
--------------------------------------------------------------------------------
/.github/workflows/claude-docs-updater.yml:
--------------------------------------------------------------------------------
```yaml
name: Claude Documentation Updater
on:
workflow_dispatch:
inputs:
commit_sha:
description: 'The commit SHA that triggered this update'
required: true
type: string
commit_message:
description: 'The commit message'
required: true
type: string
changed_files:
description: 'List of changed files'
required: true
type: string
commit_diff:
description: 'Diff summary of changes'
required: true
type: string
jobs:
update-docs:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: next
fetch-depth: 0 # Need full history to checkout specific commit
- name: Create docs update branch
id: create-branch
run: |
BRANCH_NAME="docs/auto-update-$(date +%Y%m%d-%H%M%S)"
git checkout -b $BRANCH_NAME
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Run Claude Code to Update Documentation
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "30"
mode: "agent"
github_token: ${{ secrets.GITHUB_TOKEN }}
experimental_allowed_domains: |
.anthropic.com
.github.com
api.github.com
.githubusercontent.com
registry.npmjs.org
.task-master.dev
base_branch: "next"
direct_prompt: |
You are a documentation specialist. Analyze the recent changes pushed to the 'next' branch and update the documentation accordingly.
Recent changes:
- Commit: ${{ inputs.commit_message }}
- Changed files:
${{ inputs.changed_files }}
- Changes summary:
${{ inputs.commit_diff }}
Your task:
1. Analyze the changes to understand what functionality was added, modified, or removed
2. Check if these changes require documentation updates in apps/docs/
3. If documentation updates are needed:
- Update relevant documentation files in apps/docs/
- Ensure examples are updated if APIs changed
- Update any configuration documentation if config options changed
- Add new documentation pages if new features were added
- Update the changelog or release notes if applicable
4. If no documentation updates are needed, skip creating changes
Guidelines:
- Focus only on user-facing changes that need documentation
- Keep documentation clear, concise, and helpful
- Include code examples where appropriate
- Maintain consistent documentation style with existing docs
- Don't document internal implementation details unless they affect users
- Update navigation/menu files if new pages are added
Only make changes if the documentation truly needs updating based on the code changes.
- name: Check if changes were made
id: check-changes
run: |
if git diff --quiet; then
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
git add -A
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "docs: auto-update documentation based on changes in next branch
This PR was automatically generated to update documentation based on recent changes.
Original commit: ${{ inputs.commit_message }}
Co-authored-by: Claude <[email protected]>"
fi
- name: Push changes and create PR
if: steps.check-changes.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git push origin ${{ steps.create-branch.outputs.branch_name }}
# Create PR using GitHub CLI
gh pr create \
--title "docs: update documentation for recent changes" \
--body "## 📚 Documentation Update
This PR automatically updates documentation based on recent changes merged to the \`next\` branch.
### Original Changes
**Commit:** ${{ inputs.commit_sha }}
**Message:** ${{ inputs.commit_message }}
### Changed Files in Original Commit
\`\`\`
${{ inputs.changed_files }}
\`\`\`
### Documentation Updates
This PR includes documentation updates to reflect the changes above. Please review to ensure:
- [ ] Documentation accurately reflects the changes
- [ ] Examples are correct and working
- [ ] No important details are missing
- [ ] Style is consistent with existing documentation
---
*This PR was automatically generated by Claude Code GitHub Action*" \
--base next \
--head ${{ steps.create-branch.outputs.branch_name }} \
--label "documentation" \
--label "automated"
```
--------------------------------------------------------------------------------
/scripts/modules/task-manager/set-task-status.js:
--------------------------------------------------------------------------------
```javascript
import path from 'path';
import chalk from 'chalk';
import boxen from 'boxen';
import {
log,
readJSON,
writeJSON,
findTaskById,
ensureTagMetadata
} from '../utils.js';
import { displayBanner } from '../ui.js';
import { validateTaskDependencies } from '../dependency-manager.js';
import { getDebugFlag } from '../config-manager.js';
import updateSingleTaskStatus from './update-single-task-status.js';
import generateTaskFiles from './generate-task-files.js';
import {
isValidTaskStatus,
TASK_STATUS_OPTIONS
} from '../../../src/constants/task-status.js';
/**
* Set the status of a task
* @param {string} tasksPath - Path to the tasks.json file
* @param {string} taskIdInput - Task ID(s) to update
* @param {string} newStatus - New status
* @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot for tag resolution)
* @param {string} [options.projectRoot] - Project root path
* @param {string} [options.tag] - Optional tag to override current tag resolution
* @param {string} [options.mcpLog] - MCP logger object
* @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
*/
async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
const { projectRoot, tag } = options;
try {
if (!isValidTaskStatus(newStatus)) {
throw new Error(
`Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}`
);
}
// Determine if we're in MCP mode by checking for mcpLog
const isMcpMode = !!options?.mcpLog;
// Only display UI elements if not in MCP mode
if (!isMcpMode) {
console.log(
boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), {
padding: 1,
borderColor: 'blue',
borderStyle: 'round'
})
);
}
log('info', `Reading tasks from ${tasksPath}...`);
// Read the raw data without tag resolution to preserve tagged structure
let rawData = readJSON(tasksPath, projectRoot, tag); // No tag parameter
// Handle the case where readJSON returns resolved data with _rawTaggedData
if (rawData && rawData._rawTaggedData) {
// Use the raw tagged data and discard the resolved view
rawData = rawData._rawTaggedData;
}
// Ensure the tag exists in the raw data
if (!rawData || !rawData[tag] || !Array.isArray(rawData[tag].tasks)) {
throw new Error(
`Invalid tasks file or tag "${tag}" not found at ${tasksPath}`
);
}
// Get the tasks for the current tag
const data = {
tasks: rawData[tag].tasks,
tag,
_rawTaggedData: rawData
};
if (!data || !data.tasks) {
throw new Error(`No valid tasks found in ${tasksPath}`);
}
// Handle multiple task IDs (comma-separated)
const taskIds = taskIdInput.split(',').map((id) => id.trim());
const updatedTasks = [];
// Update each task and capture old status for display
for (const id of taskIds) {
// Capture old status before updating
let oldStatus = 'unknown';
if (id.includes('.')) {
// Handle subtask
const [parentId, subtaskId] = id
.split('.')
.map((id) => parseInt(id, 10));
const parentTask = data.tasks.find((t) => t.id === parentId);
if (parentTask?.subtasks) {
const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
oldStatus = subtask?.status || 'pending';
}
} else {
// Handle regular task
const taskId = parseInt(id, 10);
const task = data.tasks.find((t) => t.id === taskId);
oldStatus = task?.status || 'pending';
}
await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode);
updatedTasks.push({ id, oldStatus, newStatus });
}
// Update the raw data structure with the modified tasks
rawData[tag].tasks = data.tasks;
// Ensure the tag has proper metadata
ensureTagMetadata(rawData[tag], {
description: `Tasks for ${tag} context`
});
// Write the updated raw data back to the file
// The writeJSON function will automatically filter out _rawTaggedData
writeJSON(tasksPath, rawData, projectRoot, tag);
// Validate dependencies after status update
log('info', 'Validating dependencies after status update...');
validateTaskDependencies(data.tasks);
// Generate individual task files
// log('info', 'Regenerating task files...');
// await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
// mcpLog: options.mcpLog
// });
// Display success message - only in CLI mode
if (!isMcpMode) {
for (const updateInfo of updatedTasks) {
const { id, oldStatus, newStatus: updatedStatus } = updateInfo;
console.log(
boxen(
chalk.white.bold(`Successfully updated task ${id} status:`) +
'\n' +
`From: ${chalk.yellow(oldStatus)}\n` +
`To: ${chalk.green(updatedStatus)}`,
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
)
);
}
}
// Return success value for programmatic use
return {
success: true,
updatedTasks: updatedTasks.map(({ id, oldStatus, newStatus }) => ({
id,
oldStatus,
newStatus
}))
};
} catch (error) {
log('error', `Error setting task status: ${error.message}`);
// Only show error UI in CLI mode
if (!options?.mcpLog) {
console.error(chalk.red(`Error: ${error.message}`));
// Pass session to getDebugFlag
if (getDebugFlag(options?.session)) {
// Use getter
console.error(error);
}
process.exit(1);
} else {
// In MCP mode, throw the error for the caller to handle
throw error;
}
}
}
export default setTaskStatus;
```
--------------------------------------------------------------------------------
/tests/unit/scripts/modules/dependency-manager/fix-dependencies-command.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Unit test to ensure fixDependenciesCommand writes JSON with the correct
* projectRoot and tag arguments so that tag data is preserved.
*/
import { jest } from '@jest/globals';
// Mock process.exit to prevent test termination
const mockProcessExit = jest.fn();
const originalExit = process.exit;
process.exit = mockProcessExit;
// Mock utils.js BEFORE importing the module under test
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
readJSON: jest.fn(),
writeJSON: jest.fn(),
log: jest.fn(),
findProjectRoot: jest.fn(() => '/mock/project/root'),
getCurrentTag: jest.fn(() => 'master'),
taskExists: jest.fn(() => true),
formatTaskId: jest.fn((id) => id),
findCycles: jest.fn(() => []),
traverseDependencies: jest.fn((sourceTasks, allTasks, options = {}) => []),
isSilentMode: jest.fn(() => true),
resolveTag: jest.fn(() => 'master'),
getTasksForTag: jest.fn(() => []),
setTasksForTag: jest.fn(),
enableSilentMode: jest.fn(),
disableSilentMode: jest.fn(),
isEmpty: jest.fn((value) => {
if (value === null || value === undefined) return true;
if (Array.isArray(value)) return value.length === 0;
if (typeof value === 'object' && value !== null)
return Object.keys(value).length === 0;
return false; // Not an array or object
}),
resolveEnvVariable: jest.fn()
}));
// Mock ui.js
jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
displayBanner: jest.fn(),
formatDependenciesWithStatus: jest.fn()
}));
// Mock task-manager.js
jest.unstable_mockModule(
'../../../../../scripts/modules/task-manager.js',
() => ({
generateTaskFiles: jest.fn()
})
);
// Mock external libraries
jest.unstable_mockModule('chalk', () => ({
default: {
green: jest.fn((text) => text),
cyan: jest.fn((text) => text),
bold: jest.fn((text) => text)
}
}));
jest.unstable_mockModule('boxen', () => ({
default: jest.fn((text) => text)
}));
// Import the mocked modules
const { readJSON, writeJSON, log, taskExists } = await import(
'../../../../../scripts/modules/utils.js'
);
// Import the module under test
const { fixDependenciesCommand } = await import(
'../../../../../scripts/modules/dependency-manager.js'
);
describe('fixDependenciesCommand tag preservation', () => {
beforeEach(() => {
jest.clearAllMocks();
mockProcessExit.mockClear();
});
afterAll(() => {
// Restore original process.exit
process.exit = originalExit;
});
it('calls writeJSON with projectRoot and tag parameters when changes are made', async () => {
const tasksPath = '/mock/tasks.json';
const projectRoot = '/mock/project/root';
const tag = 'master';
// Mock data WITH dependency issues to trigger writeJSON
const tasksDataWithIssues = {
tasks: [
{
id: 1,
title: 'Task 1',
dependencies: [999] // Non-existent dependency to trigger fix
},
{
id: 2,
title: 'Task 2',
dependencies: []
}
],
tag: 'master',
_rawTaggedData: {
master: {
tasks: [
{
id: 1,
title: 'Task 1',
dependencies: [999]
}
]
}
}
};
readJSON.mockReturnValue(tasksDataWithIssues);
taskExists.mockReturnValue(false); // Make dependency invalid to trigger fix
await fixDependenciesCommand(tasksPath, {
context: { projectRoot, tag }
});
// Verify readJSON was called with correct parameters
expect(readJSON).toHaveBeenCalledWith(tasksPath, projectRoot, tag);
// Verify writeJSON was called (should be triggered by removing invalid dependency)
expect(writeJSON).toHaveBeenCalled();
// Check the writeJSON call parameters
const writeJSONCalls = writeJSON.mock.calls;
const lastWriteCall = writeJSONCalls[writeJSONCalls.length - 1];
const [calledPath, _data, calledProjectRoot, calledTag] = lastWriteCall;
expect(calledPath).toBe(tasksPath);
expect(calledProjectRoot).toBe(projectRoot);
expect(calledTag).toBe(tag);
// Verify process.exit was NOT called (meaning the function succeeded)
expect(mockProcessExit).not.toHaveBeenCalled();
});
it('does not call writeJSON when no changes are needed', async () => {
const tasksPath = '/mock/tasks.json';
const projectRoot = '/mock/project/root';
const tag = 'master';
// Mock data WITHOUT dependency issues (no changes needed)
const cleanTasksData = {
tasks: [
{
id: 1,
title: 'Task 1',
dependencies: [] // Clean, no issues
}
],
tag: 'master'
};
readJSON.mockReturnValue(cleanTasksData);
taskExists.mockReturnValue(true); // All dependencies exist
await fixDependenciesCommand(tasksPath, {
context: { projectRoot, tag }
});
// Verify readJSON was called
expect(readJSON).toHaveBeenCalledWith(tasksPath, projectRoot, tag);
// Verify writeJSON was NOT called (no changes needed)
expect(writeJSON).not.toHaveBeenCalled();
// Verify process.exit was NOT called
expect(mockProcessExit).not.toHaveBeenCalled();
});
it('handles early exit when no valid tasks found', async () => {
const tasksPath = '/mock/tasks.json';
// Mock invalid data to trigger early exit
readJSON.mockReturnValue(null);
await fixDependenciesCommand(tasksPath, {
context: { projectRoot: '/mock', tag: 'master' }
});
// Verify readJSON was called
expect(readJSON).toHaveBeenCalled();
// Verify writeJSON was NOT called (early exit)
expect(writeJSON).not.toHaveBeenCalled();
// Verify process.exit WAS called due to invalid data
expect(mockProcessExit).toHaveBeenCalledWith(1);
});
});
```
--------------------------------------------------------------------------------
/src/profiles/zed.js:
--------------------------------------------------------------------------------
```javascript
// Zed profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { isSilentMode, log } from '../../scripts/modules/utils.js';
import { createProfile } from './base-profile.js';
/**
* Transform standard MCP config format to Zed format
* @param {Object} mcpConfig - Standard MCP configuration object
* @returns {Object} - Transformed Zed configuration object
*/
function transformToZedFormat(mcpConfig) {
const zedConfig = {};
// Transform mcpServers to context_servers
if (mcpConfig.mcpServers) {
zedConfig['context_servers'] = mcpConfig.mcpServers;
}
// Preserve any other existing settings
for (const [key, value] of Object.entries(mcpConfig)) {
if (key !== 'mcpServers') {
zedConfig[key] = value;
}
}
return zedConfig;
}
// Lifecycle functions for Zed profile
function onAddRulesProfile(targetDir, assetsDir) {
// MCP transformation will be handled in onPostConvertRulesProfile
// File copying is handled by the base profile via fileMap
}
function onRemoveRulesProfile(targetDir) {
// Clean up .rules (Zed uses .rules directly in root)
const userRulesFile = path.join(targetDir, '.rules');
try {
// Remove Task Master .rules
if (fs.existsSync(userRulesFile)) {
fs.rmSync(userRulesFile, { force: true });
log('debug', `[Zed] Removed ${userRulesFile}`);
}
} catch (err) {
log('error', `[Zed] Failed to remove Zed instructions: ${err.message}`);
}
// MCP Removal: Remove context_servers section
const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
if (!fs.existsSync(mcpConfigPath)) {
log('debug', '[Zed] No .zed/settings.json found to clean up');
return;
}
try {
// Read the current config
const configContent = fs.readFileSync(mcpConfigPath, 'utf8');
const config = JSON.parse(configContent);
// Check if it has the context_servers section and task-master-ai server
if (
config['context_servers'] &&
config['context_servers']['task-master-ai']
) {
// Remove task-master-ai server
delete config['context_servers']['task-master-ai'];
// Check if there are other MCP servers in context_servers
const remainingServers = Object.keys(config['context_servers']);
if (remainingServers.length === 0) {
// No other servers, remove entire context_servers section
delete config['context_servers'];
log('debug', '[Zed] Removed empty context_servers section');
}
// Check if config is now empty
const remainingKeys = Object.keys(config);
if (remainingKeys.length === 0) {
// Config is empty, remove entire file
fs.rmSync(mcpConfigPath, { force: true });
log('info', '[Zed] Removed empty settings.json file');
// Check if .zed directory is empty
const zedDirPath = path.join(targetDir, '.zed');
if (fs.existsSync(zedDirPath)) {
const remainingContents = fs.readdirSync(zedDirPath);
if (remainingContents.length === 0) {
fs.rmSync(zedDirPath, { recursive: true, force: true });
log('debug', '[Zed] Removed empty .zed directory');
}
}
} else {
// Write back the modified config
fs.writeFileSync(
mcpConfigPath,
JSON.stringify(config, null, '\t') + '\n'
);
log(
'info',
'[Zed] Removed TaskMaster from settings.json, preserved other configurations'
);
}
} else {
log('debug', '[Zed] TaskMaster not found in context_servers');
}
} catch (error) {
log('error', `[Zed] Failed to clean up settings.json: ${error.message}`);
}
}
function onPostConvertRulesProfile(targetDir, assetsDir) {
// Handle .rules setup (same as onAddRulesProfile)
onAddRulesProfile(targetDir, assetsDir);
// Transform MCP config to Zed format
const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
if (!fs.existsSync(mcpConfigPath)) {
log('debug', '[Zed] No .zed/settings.json found to transform');
return;
}
try {
// Read the generated standard MCP config
const mcpConfigContent = fs.readFileSync(mcpConfigPath, 'utf8');
const mcpConfig = JSON.parse(mcpConfigContent);
// Check if it's already in Zed format (has context_servers)
if (mcpConfig['context_servers']) {
log(
'info',
'[Zed] settings.json already in Zed format, skipping transformation'
);
return;
}
// Transform to Zed format
const zedConfig = transformToZedFormat(mcpConfig);
// Add "source": "custom" to task-master-ai server for Zed
if (
zedConfig['context_servers'] &&
zedConfig['context_servers']['task-master-ai']
) {
zedConfig['context_servers']['task-master-ai'].source = 'custom';
}
// Write back the transformed config with proper formatting
fs.writeFileSync(
mcpConfigPath,
JSON.stringify(zedConfig, null, '\t') + '\n'
);
log('info', '[Zed] Transformed settings.json to Zed format');
log('debug', '[Zed] Renamed mcpServers to context_servers');
} catch (error) {
log('error', `[Zed] Failed to transform settings.json: ${error.message}`);
}
}
// Create and export zed profile using the base factory
export const zedProfile = createProfile({
name: 'zed',
displayName: 'Zed',
url: 'zed.dev',
docsUrl: 'zed.dev/docs',
profileDir: '.zed',
rulesDir: '.',
mcpConfig: true,
mcpConfigName: 'settings.json',
includeDefaultRules: false,
fileMap: {
'AGENTS.md': '.rules'
},
onAdd: onAddRulesProfile,
onRemove: onRemoveRulesProfile,
onPostConvert: onPostConvertRulesProfile
});
// Export lifecycle functions separately to avoid naming conflicts
export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };
```