This is page 17 of 69. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── commands
│ │ └── dedupe.md
│ └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│ └── marketplace.json
├── .coderabbit.yaml
├── .cursor
│ ├── mcp.json
│ └── rules
│ ├── ai_providers.mdc
│ ├── ai_services.mdc
│ ├── architecture.mdc
│ ├── changeset.mdc
│ ├── commands.mdc
│ ├── context_gathering.mdc
│ ├── cursor_rules.mdc
│ ├── dependencies.mdc
│ ├── dev_workflow.mdc
│ ├── git_workflow.mdc
│ ├── glossary.mdc
│ ├── mcp.mdc
│ ├── new_features.mdc
│ ├── self_improve.mdc
│ ├── tags.mdc
│ ├── taskmaster.mdc
│ ├── tasks.mdc
│ ├── telemetry.mdc
│ ├── test_workflow.mdc
│ ├── tests.mdc
│ ├── ui.mdc
│ └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── enhancements---feature-requests.md
│ │ └── feedback.md
│ ├── PULL_REQUEST_TEMPLATE
│ │ ├── bugfix.md
│ │ ├── config.yml
│ │ ├── feature.md
│ │ └── integration.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── scripts
│ │ ├── auto-close-duplicates.mjs
│ │ ├── backfill-duplicate-comments.mjs
│ │ ├── check-pre-release-mode.mjs
│ │ ├── parse-metrics.mjs
│ │ ├── release.mjs
│ │ ├── tag-extension.mjs
│ │ ├── utils.mjs
│ │ └── validate-changesets.mjs
│ └── workflows
│ ├── auto-close-duplicates.yml
│ ├── backfill-duplicate-comments.yml
│ ├── ci.yml
│ ├── claude-dedupe-issues.yml
│ ├── claude-docs-trigger.yml
│ ├── claude-docs-updater.yml
│ ├── claude-issue-triage.yml
│ ├── claude.yml
│ ├── extension-ci.yml
│ ├── extension-release.yml
│ ├── log-issue-events.yml
│ ├── pre-release.yml
│ ├── release-check.yml
│ ├── release.yml
│ ├── update-models-md.yml
│ └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│ ├── hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── settings
│ │ └── mcp.json
│ └── steering
│ ├── dev_workflow.md
│ ├── kiro_rules.md
│ ├── self_improve.md
│ ├── taskmaster_hooks_workflow.md
│ └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│ ├── CLAUDE.md
│ ├── config.json
│ ├── docs
│ │ ├── autonomous-tdd-git-workflow.md
│ │ ├── MIGRATION-ROADMAP.md
│ │ ├── prd-tm-start.txt
│ │ ├── prd.txt
│ │ ├── README.md
│ │ ├── research
│ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│ │ │ ├── 2025-06-14_test-save-functionality.md
│ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│ │ ├── task-template-importing-prd.txt
│ │ ├── tdd-workflow-phase-0-spike.md
│ │ ├── tdd-workflow-phase-1-core-rails.md
│ │ ├── tdd-workflow-phase-1-orchestrator.md
│ │ ├── tdd-workflow-phase-2-pr-resumability.md
│ │ ├── tdd-workflow-phase-3-extensibility-guardrails.md
│ │ ├── test-prd.txt
│ │ └── tm-core-phase-1.txt
│ ├── reports
│ │ ├── task-complexity-report_autonomous-tdd-git-workflow.json
│ │ ├── task-complexity-report_cc-kiro-hooks.json
│ │ ├── task-complexity-report_tdd-phase-1-core-rails.json
│ │ ├── task-complexity-report_tdd-workflow-phase-0.json
│ │ ├── task-complexity-report_test-prd-tag.json
│ │ ├── task-complexity-report_tm-core-phase-1.json
│ │ ├── task-complexity-report.json
│ │ └── tm-core-complexity.json
│ ├── state.json
│ ├── tasks
│ │ ├── task_001_tm-start.txt
│ │ ├── task_002_tm-start.txt
│ │ ├── task_003_tm-start.txt
│ │ ├── task_004_tm-start.txt
│ │ ├── task_007_tm-start.txt
│ │ └── tasks.json
│ └── templates
│ ├── example_prd_rpg.md
│ └── example_prd.md
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── apps
│ ├── cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── command-registry.ts
│ │ │ ├── commands
│ │ │ │ ├── auth.command.ts
│ │ │ │ ├── autopilot
│ │ │ │ │ ├── abort.command.ts
│ │ │ │ │ ├── commit.command.ts
│ │ │ │ │ ├── complete.command.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next.command.ts
│ │ │ │ │ ├── resume.command.ts
│ │ │ │ │ ├── shared.ts
│ │ │ │ │ ├── start.command.ts
│ │ │ │ │ └── status.command.ts
│ │ │ │ ├── briefs.command.ts
│ │ │ │ ├── context.command.ts
│ │ │ │ ├── export.command.ts
│ │ │ │ ├── list.command.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── custom-providers.ts
│ │ │ │ │ ├── fetchers.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompts.ts
│ │ │ │ │ ├── setup.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── next.command.ts
│ │ │ │ ├── set-status.command.ts
│ │ │ │ ├── show.command.ts
│ │ │ │ ├── start.command.ts
│ │ │ │ └── tags.command.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── model-management.ts
│ │ │ ├── types
│ │ │ │ └── tag-management.d.ts
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── cardBox.component.ts
│ │ │ │ │ ├── dashboard.component.ts
│ │ │ │ │ ├── header.component.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next-task.component.ts
│ │ │ │ │ ├── suggested-steps.component.ts
│ │ │ │ │ └── task-detail.component.ts
│ │ │ │ ├── display
│ │ │ │ │ ├── messages.ts
│ │ │ │ │ └── tables.ts
│ │ │ │ ├── formatters
│ │ │ │ │ ├── complexity-formatters.ts
│ │ │ │ │ ├── dependency-formatters.ts
│ │ │ │ │ ├── priority-formatters.ts
│ │ │ │ │ ├── status-formatters.spec.ts
│ │ │ │ │ └── status-formatters.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── layout
│ │ │ │ ├── helpers.spec.ts
│ │ │ │ └── helpers.ts
│ │ │ └── utils
│ │ │ ├── auth-helpers.ts
│ │ │ ├── auto-update.ts
│ │ │ ├── brief-selection.ts
│ │ │ ├── display-helpers.ts
│ │ │ ├── error-handler.ts
│ │ │ ├── index.ts
│ │ │ ├── project-root.ts
│ │ │ ├── task-status.ts
│ │ │ ├── ui.spec.ts
│ │ │ └── ui.ts
│ │ ├── tests
│ │ │ ├── integration
│ │ │ │ └── commands
│ │ │ │ └── autopilot
│ │ │ │ └── workflow.test.ts
│ │ │ └── unit
│ │ │ ├── commands
│ │ │ │ ├── autopilot
│ │ │ │ │ └── shared.test.ts
│ │ │ │ ├── list.command.spec.ts
│ │ │ │ └── show.command.spec.ts
│ │ │ └── ui
│ │ │ └── dashboard.component.spec.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── docs
│ │ ├── archive
│ │ │ ├── ai-client-utils-example.mdx
│ │ │ ├── ai-development-workflow.mdx
│ │ │ ├── command-reference.mdx
│ │ │ ├── configuration.mdx
│ │ │ ├── cursor-setup.mdx
│ │ │ ├── examples.mdx
│ │ │ └── Installation.mdx
│ │ ├── best-practices
│ │ │ ├── advanced-tasks.mdx
│ │ │ ├── configuration-advanced.mdx
│ │ │ └── index.mdx
│ │ ├── capabilities
│ │ │ ├── cli-root-commands.mdx
│ │ │ ├── index.mdx
│ │ │ ├── mcp.mdx
│ │ │ ├── rpg-method.mdx
│ │ │ └── task-structure.mdx
│ │ ├── CHANGELOG.md
│ │ ├── command-reference.mdx
│ │ ├── configuration.mdx
│ │ ├── docs.json
│ │ ├── favicon.svg
│ │ ├── getting-started
│ │ │ ├── api-keys.mdx
│ │ │ ├── contribute.mdx
│ │ │ ├── faq.mdx
│ │ │ └── quick-start
│ │ │ ├── configuration-quick.mdx
│ │ │ ├── execute-quick.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── moving-forward.mdx
│ │ │ ├── prd-quick.mdx
│ │ │ ├── quick-start.mdx
│ │ │ ├── requirements.mdx
│ │ │ ├── rules-quick.mdx
│ │ │ └── tasks-quick.mdx
│ │ ├── introduction.mdx
│ │ ├── licensing.md
│ │ ├── logo
│ │ │ ├── dark.svg
│ │ │ ├── light.svg
│ │ │ └── task-master-logo.png
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── style.css
│ │ ├── tdd-workflow
│ │ │ ├── ai-agent-integration.mdx
│ │ │ └── quickstart.mdx
│ │ ├── vercel.json
│ │ └── whats-new.mdx
│ ├── extension
│ │ ├── .vscodeignore
│ │ ├── assets
│ │ │ ├── banner.png
│ │ │ ├── icon-dark.svg
│ │ │ ├── icon-light.svg
│ │ │ ├── icon.png
│ │ │ ├── screenshots
│ │ │ │ ├── kanban-board.png
│ │ │ │ └── task-details.png
│ │ │ └── sidebar-icon.svg
│ │ ├── CHANGELOG.md
│ │ ├── components.json
│ │ ├── docs
│ │ │ ├── extension-CI-setup.md
│ │ │ └── extension-development-guide.md
│ │ ├── esbuild.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── package.mjs
│ │ ├── package.publish.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── components
│ │ │ │ ├── ConfigView.tsx
│ │ │ │ ├── constants.ts
│ │ │ │ ├── TaskDetails
│ │ │ │ │ ├── AIActionsSection.tsx
│ │ │ │ │ ├── DetailsSection.tsx
│ │ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ │ ├── SubtasksSection.tsx
│ │ │ │ │ ├── TaskMetadataSidebar.tsx
│ │ │ │ │ └── useTaskDetails.ts
│ │ │ │ ├── TaskDetailsView.tsx
│ │ │ │ ├── TaskMasterLogo.tsx
│ │ │ │ └── ui
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── breadcrumb.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── collapsible.tsx
│ │ │ │ ├── CollapsibleSection.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── scroll-area.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── shadcn-io
│ │ │ │ │ └── kanban
│ │ │ │ │ └── index.tsx
│ │ │ │ └── textarea.tsx
│ │ │ ├── extension.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── utils.ts
│ │ │ ├── services
│ │ │ │ ├── config-service.ts
│ │ │ │ ├── error-handler.ts
│ │ │ │ ├── notification-preferences.ts
│ │ │ │ ├── polling-service.ts
│ │ │ │ ├── polling-strategies.ts
│ │ │ │ ├── sidebar-webview-manager.ts
│ │ │ │ ├── task-repository.ts
│ │ │ │ ├── terminal-manager.ts
│ │ │ │ └── webview-manager.ts
│ │ │ ├── test
│ │ │ │ └── extension.test.ts
│ │ │ ├── utils
│ │ │ │ ├── configManager.ts
│ │ │ │ ├── connectionManager.ts
│ │ │ │ ├── errorHandler.ts
│ │ │ │ ├── event-emitter.ts
│ │ │ │ ├── logger.ts
│ │ │ │ ├── mcpClient.ts
│ │ │ │ ├── notificationPreferences.ts
│ │ │ │ └── task-master-api
│ │ │ │ ├── cache
│ │ │ │ │ └── cache-manager.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mcp-client.ts
│ │ │ │ ├── transformers
│ │ │ │ │ └── task-transformer.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ └── webview
│ │ │ ├── App.tsx
│ │ │ ├── components
│ │ │ │ ├── AppContent.tsx
│ │ │ │ ├── EmptyState.tsx
│ │ │ │ ├── ErrorBoundary.tsx
│ │ │ │ ├── PollingStatus.tsx
│ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ ├── SidebarView.tsx
│ │ │ │ ├── TagDropdown.tsx
│ │ │ │ ├── TaskCard.tsx
│ │ │ │ ├── TaskEditModal.tsx
│ │ │ │ ├── TaskMasterKanban.tsx
│ │ │ │ ├── ToastContainer.tsx
│ │ │ │ └── ToastNotification.tsx
│ │ │ ├── constants
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ └── VSCodeContext.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useTaskQueries.ts
│ │ │ │ ├── useVSCodeMessages.ts
│ │ │ │ └── useWebviewHeight.ts
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── providers
│ │ │ │ └── QueryProvider.tsx
│ │ │ ├── reducers
│ │ │ │ └── appReducer.ts
│ │ │ ├── sidebar.tsx
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ └── utils
│ │ │ ├── logger.ts
│ │ │ └── toast.ts
│ │ └── tsconfig.json
│ └── mcp
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── tools
│ │ ├── autopilot
│ │ │ ├── abort.tool.ts
│ │ │ ├── commit.tool.ts
│ │ │ ├── complete.tool.ts
│ │ │ ├── finalize.tool.ts
│ │ │ ├── index.ts
│ │ │ ├── next.tool.ts
│ │ │ ├── resume.tool.ts
│ │ │ ├── start.tool.ts
│ │ │ └── status.tool.ts
│ │ ├── README-ZOD-V3.md
│ │ └── tasks
│ │ ├── get-task.tool.ts
│ │ ├── get-tasks.tool.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── assets
│ ├── .windsurfrules
│ ├── AGENTS.md
│ ├── claude
│ │ └── TM_COMMANDS_GUIDE.md
│ ├── config.json
│ ├── env.example
│ ├── example_prd_rpg.txt
│ ├── example_prd.txt
│ ├── GEMINI.md
│ ├── gitignore
│ ├── kiro-hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── roocode
│ │ ├── .roo
│ │ │ ├── rules-architect
│ │ │ │ └── architect-rules
│ │ │ ├── rules-ask
│ │ │ │ └── ask-rules
│ │ │ ├── rules-code
│ │ │ │ └── code-rules
│ │ │ ├── rules-debug
│ │ │ │ └── debug-rules
│ │ │ ├── rules-orchestrator
│ │ │ │ └── orchestrator-rules
│ │ │ └── rules-test
│ │ │ └── test-rules
│ │ └── .roomodes
│ ├── rules
│ │ ├── cursor_rules.mdc
│ │ ├── dev_workflow.mdc
│ │ ├── self_improve.mdc
│ │ ├── taskmaster_hooks_workflow.mdc
│ │ └── taskmaster.mdc
│ └── scripts_README.md
├── bin
│ └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE_CODE_PLUGIN.md
├── CLAUDE.md
├── context
│ ├── chats
│ │ ├── add-task-dependencies-1.md
│ │ └── max-min-tokens.txt.md
│ ├── fastmcp-core.txt
│ ├── fastmcp-docs.txt
│ ├── MCP_INTEGRATION.md
│ ├── mcp-js-sdk-docs.txt
│ ├── mcp-protocol-repo.txt
│ ├── mcp-protocol-schema-03262025.json
│ └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│ ├── claude-code-integration.md
│ ├── CLI-COMMANDER-PATTERN.md
│ ├── command-reference.md
│ ├── configuration.md
│ ├── contributor-docs
│ │ ├── testing-roo-integration.md
│ │ └── worktree-setup.md
│ ├── cross-tag-task-movement.md
│ ├── examples
│ │ ├── claude-code-usage.md
│ │ └── codex-cli-usage.md
│ ├── examples.md
│ ├── licensing.md
│ ├── mcp-provider-guide.md
│ ├── mcp-provider.md
│ ├── migration-guide.md
│ ├── models.md
│ ├── providers
│ │ ├── codex-cli.md
│ │ └── gemini-cli.md
│ ├── README.md
│ ├── scripts
│ │ └── models-json-to-markdown.js
│ ├── task-structure.md
│ └── tutorial.md
├── images
│ ├── hamster-hiring.png
│ └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│ ├── server.js
│ └── src
│ ├── core
│ │ ├── __tests__
│ │ │ └── context-manager.test.js
│ │ ├── context-manager.js
│ │ ├── direct-functions
│ │ │ ├── add-dependency.js
│ │ │ ├── add-subtask.js
│ │ │ ├── add-tag.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── cache-stats.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── complexity-report.js
│ │ │ ├── copy-tag.js
│ │ │ ├── create-tag-from-branch.js
│ │ │ ├── delete-tag.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── fix-dependencies.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── initialize-project.js
│ │ │ ├── list-tags.js
│ │ │ ├── models.js
│ │ │ ├── move-task-cross-tag.js
│ │ │ ├── move-task.js
│ │ │ ├── next-task.js
│ │ │ ├── parse-prd.js
│ │ │ ├── remove-dependency.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── rename-tag.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── rules.js
│ │ │ ├── scope-down.js
│ │ │ ├── scope-up.js
│ │ │ ├── set-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ ├── update-tasks.js
│ │ │ ├── use-tag.js
│ │ │ └── validate-dependencies.js
│ │ ├── task-master-core.js
│ │ └── utils
│ │ ├── env-utils.js
│ │ └── path-utils.js
│ ├── custom-sdk
│ │ ├── errors.js
│ │ ├── index.js
│ │ ├── json-extractor.js
│ │ ├── language-model.js
│ │ ├── message-converter.js
│ │ └── schema-converter.js
│ ├── index.js
│ ├── logger.js
│ ├── providers
│ │ └── mcp-provider.js
│ └── tools
│ ├── add-dependency.js
│ ├── add-subtask.js
│ ├── add-tag.js
│ ├── add-task.js
│ ├── analyze.js
│ ├── clear-subtasks.js
│ ├── complexity-report.js
│ ├── copy-tag.js
│ ├── delete-tag.js
│ ├── expand-all.js
│ ├── expand-task.js
│ ├── fix-dependencies.js
│ ├── generate.js
│ ├── get-operation-status.js
│ ├── index.js
│ ├── initialize-project.js
│ ├── list-tags.js
│ ├── models.js
│ ├── move-task.js
│ ├── next-task.js
│ ├── parse-prd.js
│ ├── README-ZOD-V3.md
│ ├── remove-dependency.js
│ ├── remove-subtask.js
│ ├── remove-task.js
│ ├── rename-tag.js
│ ├── research.js
│ ├── response-language.js
│ ├── rules.js
│ ├── scope-down.js
│ ├── scope-up.js
│ ├── set-task-status.js
│ ├── tool-registry.js
│ ├── update-subtask.js
│ ├── update-task.js
│ ├── update.js
│ ├── use-tag.js
│ ├── utils.js
│ └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│ ├── ai-sdk-provider-grok-cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── errors.test.ts
│ │ │ ├── errors.ts
│ │ │ ├── grok-cli-language-model.ts
│ │ │ ├── grok-cli-provider.test.ts
│ │ │ ├── grok-cli-provider.ts
│ │ │ ├── index.ts
│ │ │ ├── json-extractor.test.ts
│ │ │ ├── json-extractor.ts
│ │ │ ├── message-converter.test.ts
│ │ │ ├── message-converter.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── build-config
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ └── tsdown.base.ts
│ │ └── tsconfig.json
│ ├── claude-code-plugin
│ │ ├── .claude-plugin
│ │ │ └── plugin.json
│ │ ├── .gitignore
│ │ ├── agents
│ │ │ ├── task-checker.md
│ │ │ ├── task-executor.md
│ │ │ └── task-orchestrator.md
│ │ ├── CHANGELOG.md
│ │ ├── commands
│ │ │ ├── add-dependency.md
│ │ │ ├── add-subtask.md
│ │ │ ├── add-task.md
│ │ │ ├── analyze-complexity.md
│ │ │ ├── analyze-project.md
│ │ │ ├── auto-implement-tasks.md
│ │ │ ├── command-pipeline.md
│ │ │ ├── complexity-report.md
│ │ │ ├── convert-task-to-subtask.md
│ │ │ ├── expand-all-tasks.md
│ │ │ ├── expand-task.md
│ │ │ ├── fix-dependencies.md
│ │ │ ├── generate-tasks.md
│ │ │ ├── help.md
│ │ │ ├── init-project-quick.md
│ │ │ ├── init-project.md
│ │ │ ├── install-taskmaster.md
│ │ │ ├── learn.md
│ │ │ ├── list-tasks-by-status.md
│ │ │ ├── list-tasks-with-subtasks.md
│ │ │ ├── list-tasks.md
│ │ │ ├── next-task.md
│ │ │ ├── parse-prd-with-research.md
│ │ │ ├── parse-prd.md
│ │ │ ├── project-status.md
│ │ │ ├── quick-install-taskmaster.md
│ │ │ ├── remove-all-subtasks.md
│ │ │ ├── remove-dependency.md
│ │ │ ├── remove-subtask.md
│ │ │ ├── remove-subtasks.md
│ │ │ ├── remove-task.md
│ │ │ ├── setup-models.md
│ │ │ ├── show-task.md
│ │ │ ├── smart-workflow.md
│ │ │ ├── sync-readme.md
│ │ │ ├── tm-main.md
│ │ │ ├── to-cancelled.md
│ │ │ ├── to-deferred.md
│ │ │ ├── to-done.md
│ │ │ ├── to-in-progress.md
│ │ │ ├── to-pending.md
│ │ │ ├── to-review.md
│ │ │ ├── update-single-task.md
│ │ │ ├── update-task.md
│ │ │ ├── update-tasks-from-id.md
│ │ │ ├── validate-dependencies.md
│ │ │ └── view-models.md
│ │ ├── mcp.json
│ │ └── package.json
│ ├── tm-bridge
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── add-tag-bridge.ts
│ │ │ ├── bridge-types.ts
│ │ │ ├── bridge-utils.ts
│ │ │ ├── expand-bridge.ts
│ │ │ ├── index.ts
│ │ │ ├── tags-bridge.ts
│ │ │ ├── update-bridge.ts
│ │ │ └── use-tag-bridge.ts
│ │ └── tsconfig.json
│ └── tm-core
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docs
│ │ └── listTasks-architecture.md
│ ├── package.json
│ ├── POC-STATUS.md
│ ├── README.md
│ ├── src
│ │ ├── common
│ │ │ ├── constants
│ │ │ │ ├── index.ts
│ │ │ │ ├── paths.ts
│ │ │ │ └── providers.ts
│ │ │ ├── errors
│ │ │ │ ├── index.ts
│ │ │ │ └── task-master-error.ts
│ │ │ ├── interfaces
│ │ │ │ ├── configuration.interface.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── storage.interface.ts
│ │ │ ├── logger
│ │ │ │ ├── factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── logger.spec.ts
│ │ │ │ └── logger.ts
│ │ │ ├── mappers
│ │ │ │ ├── TaskMapper.test.ts
│ │ │ │ └── TaskMapper.ts
│ │ │ ├── types
│ │ │ │ ├── database.types.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── legacy.ts
│ │ │ │ └── repository-types.ts
│ │ │ └── utils
│ │ │ ├── git-utils.ts
│ │ │ ├── id-generator.ts
│ │ │ ├── index.ts
│ │ │ ├── path-helpers.ts
│ │ │ ├── path-normalizer.spec.ts
│ │ │ ├── path-normalizer.ts
│ │ │ ├── project-root-finder.spec.ts
│ │ │ ├── project-root-finder.ts
│ │ │ ├── run-id-generator.spec.ts
│ │ │ └── run-id-generator.ts
│ │ ├── index.ts
│ │ ├── modules
│ │ │ ├── ai
│ │ │ │ ├── index.ts
│ │ │ │ ├── interfaces
│ │ │ │ │ └── ai-provider.interface.ts
│ │ │ │ └── providers
│ │ │ │ ├── base-provider.ts
│ │ │ │ └── index.ts
│ │ │ ├── auth
│ │ │ │ ├── auth-domain.spec.ts
│ │ │ │ ├── auth-domain.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── auth-manager.spec.ts
│ │ │ │ │ └── auth-manager.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── context-store.ts
│ │ │ │ │ ├── oauth-service.ts
│ │ │ │ │ ├── organization.service.ts
│ │ │ │ │ ├── supabase-session-storage.spec.ts
│ │ │ │ │ └── supabase-session-storage.ts
│ │ │ │ └── types.ts
│ │ │ ├── briefs
│ │ │ │ ├── briefs-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── brief-service.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils
│ │ │ │ └── url-parser.ts
│ │ │ ├── commands
│ │ │ │ └── index.ts
│ │ │ ├── config
│ │ │ │ ├── config-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── config-manager.spec.ts
│ │ │ │ │ └── config-manager.ts
│ │ │ │ └── services
│ │ │ │ ├── config-loader.service.spec.ts
│ │ │ │ ├── config-loader.service.ts
│ │ │ │ ├── config-merger.service.spec.ts
│ │ │ │ ├── config-merger.service.ts
│ │ │ │ ├── config-persistence.service.spec.ts
│ │ │ │ ├── config-persistence.service.ts
│ │ │ │ ├── environment-config-provider.service.spec.ts
│ │ │ │ ├── environment-config-provider.service.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── runtime-state-manager.service.spec.ts
│ │ │ │ └── runtime-state-manager.service.ts
│ │ │ ├── dependencies
│ │ │ │ └── index.ts
│ │ │ ├── execution
│ │ │ │ ├── executors
│ │ │ │ │ ├── base-executor.ts
│ │ │ │ │ ├── claude-executor.ts
│ │ │ │ │ └── executor-factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── executor-service.ts
│ │ │ │ └── types.ts
│ │ │ ├── git
│ │ │ │ ├── adapters
│ │ │ │ │ ├── git-adapter.test.ts
│ │ │ │ │ └── git-adapter.ts
│ │ │ │ ├── git-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── services
│ │ │ │ ├── branch-name-generator.spec.ts
│ │ │ │ ├── branch-name-generator.ts
│ │ │ │ ├── commit-message-generator.test.ts
│ │ │ │ ├── commit-message-generator.ts
│ │ │ │ ├── scope-detector.test.ts
│ │ │ │ ├── scope-detector.ts
│ │ │ │ ├── template-engine.test.ts
│ │ │ │ └── template-engine.ts
│ │ │ ├── integration
│ │ │ │ ├── clients
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── supabase-client.ts
│ │ │ │ ├── integration-domain.ts
│ │ │ │ └── services
│ │ │ │ ├── export.service.ts
│ │ │ │ ├── task-expansion.service.ts
│ │ │ │ └── task-retrieval.service.ts
│ │ │ ├── reports
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ └── complexity-report-manager.ts
│ │ │ │ └── types.ts
│ │ │ ├── storage
│ │ │ │ ├── adapters
│ │ │ │ │ ├── activity-logger.ts
│ │ │ │ │ ├── api-storage.ts
│ │ │ │ │ └── file-storage
│ │ │ │ │ ├── file-operations.ts
│ │ │ │ │ ├── file-storage.ts
│ │ │ │ │ ├── format-handler.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── path-resolver.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── storage-factory.ts
│ │ │ │ └── utils
│ │ │ │ └── api-client.ts
│ │ │ ├── tasks
│ │ │ │ ├── entities
│ │ │ │ │ └── task.entity.ts
│ │ │ │ ├── parser
│ │ │ │ │ └── index.ts
│ │ │ │ ├── repositories
│ │ │ │ │ ├── supabase
│ │ │ │ │ │ ├── dependency-fetcher.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── supabase-repository.ts
│ │ │ │ │ └── task-repository.interface.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── preflight-checker.service.ts
│ │ │ │ │ ├── tag.service.ts
│ │ │ │ │ ├── task-execution-service.ts
│ │ │ │ │ ├── task-loader.service.ts
│ │ │ │ │ └── task-service.ts
│ │ │ │ └── tasks-domain.ts
│ │ │ ├── ui
│ │ │ │ └── index.ts
│ │ │ └── workflow
│ │ │ ├── managers
│ │ │ │ ├── workflow-state-manager.spec.ts
│ │ │ │ └── workflow-state-manager.ts
│ │ │ ├── orchestrators
│ │ │ │ ├── workflow-orchestrator.test.ts
│ │ │ │ └── workflow-orchestrator.ts
│ │ │ ├── services
│ │ │ │ ├── test-result-validator.test.ts
│ │ │ │ ├── test-result-validator.ts
│ │ │ │ ├── test-result-validator.types.ts
│ │ │ │ ├── workflow-activity-logger.ts
│ │ │ │ └── workflow.service.ts
│ │ │ ├── types.ts
│ │ │ └── workflow-domain.ts
│ │ ├── subpath-exports.test.ts
│ │ ├── tm-core.ts
│ │ └── utils
│ │ └── time.utils.ts
│ ├── tests
│ │ ├── auth
│ │ │ └── auth-refresh.test.ts
│ │ ├── integration
│ │ │ ├── auth-token-refresh.test.ts
│ │ │ ├── list-tasks.test.ts
│ │ │ └── storage
│ │ │ └── activity-logger.test.ts
│ │ ├── mocks
│ │ │ └── mock-provider.ts
│ │ ├── setup.ts
│ │ └── unit
│ │ ├── base-provider.test.ts
│ │ ├── executor.test.ts
│ │ └── smoke.test.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│ ├── create-worktree.sh
│ ├── dev.js
│ ├── init.js
│ ├── list-worktrees.sh
│ ├── modules
│ │ ├── ai-services-unified.js
│ │ ├── bridge-utils.js
│ │ ├── commands.js
│ │ ├── config-manager.js
│ │ ├── dependency-manager.js
│ │ ├── index.js
│ │ ├── prompt-manager.js
│ │ ├── supported-models.json
│ │ ├── sync-readme.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── find-next-task.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── is-task-dependent.js
│ │ │ ├── list-tasks.js
│ │ │ ├── migrate.js
│ │ │ ├── models.js
│ │ │ ├── move-task.js
│ │ │ ├── parse-prd
│ │ │ │ ├── index.js
│ │ │ │ ├── parse-prd-config.js
│ │ │ │ ├── parse-prd-helpers.js
│ │ │ │ ├── parse-prd-non-streaming.js
│ │ │ │ ├── parse-prd-streaming.js
│ │ │ │ └── parse-prd.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── scope-adjustment.js
│ │ │ ├── set-task-status.js
│ │ │ ├── tag-management.js
│ │ │ ├── task-exists.js
│ │ │ ├── update-single-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ └── update-tasks.js
│ │ ├── task-manager.js
│ │ ├── ui.js
│ │ ├── update-config-tokens.js
│ │ ├── utils
│ │ │ ├── contextGatherer.js
│ │ │ ├── fuzzyTaskSearch.js
│ │ │ └── git-utils.js
│ │ └── utils.js
│ ├── task-complexity-report.json
│ ├── test-claude-errors.js
│ └── test-claude.js
├── sonar-project.properties
├── src
│ ├── ai-providers
│ │ ├── anthropic.js
│ │ ├── azure.js
│ │ ├── base-provider.js
│ │ ├── bedrock.js
│ │ ├── claude-code.js
│ │ ├── codex-cli.js
│ │ ├── gemini-cli.js
│ │ ├── google-vertex.js
│ │ ├── google.js
│ │ ├── grok-cli.js
│ │ ├── groq.js
│ │ ├── index.js
│ │ ├── lmstudio.js
│ │ ├── ollama.js
│ │ ├── openai-compatible.js
│ │ ├── openai.js
│ │ ├── openrouter.js
│ │ ├── perplexity.js
│ │ ├── xai.js
│ │ ├── zai-coding.js
│ │ └── zai.js
│ ├── constants
│ │ ├── commands.js
│ │ ├── paths.js
│ │ ├── profiles.js
│ │ ├── rules-actions.js
│ │ ├── task-priority.js
│ │ └── task-status.js
│ ├── profiles
│ │ ├── amp.js
│ │ ├── base-profile.js
│ │ ├── claude.js
│ │ ├── cline.js
│ │ ├── codex.js
│ │ ├── cursor.js
│ │ ├── gemini.js
│ │ ├── index.js
│ │ ├── kilo.js
│ │ ├── kiro.js
│ │ ├── opencode.js
│ │ ├── roo.js
│ │ ├── trae.js
│ │ ├── vscode.js
│ │ ├── windsurf.js
│ │ └── zed.js
│ ├── progress
│ │ ├── base-progress-tracker.js
│ │ ├── cli-progress-factory.js
│ │ ├── parse-prd-tracker.js
│ │ ├── progress-tracker-builder.js
│ │ └── tracker-ui.js
│ ├── prompts
│ │ ├── add-task.json
│ │ ├── analyze-complexity.json
│ │ ├── expand-task.json
│ │ ├── parse-prd.json
│ │ ├── README.md
│ │ ├── research.json
│ │ ├── schemas
│ │ │ ├── parameter.schema.json
│ │ │ ├── prompt-template.schema.json
│ │ │ ├── README.md
│ │ │ └── variant.schema.json
│ │ ├── update-subtask.json
│ │ ├── update-task.json
│ │ └── update-tasks.json
│ ├── provider-registry
│ │ └── index.js
│ ├── schemas
│ │ ├── add-task.js
│ │ ├── analyze-complexity.js
│ │ ├── base-schemas.js
│ │ ├── expand-task.js
│ │ ├── parse-prd.js
│ │ ├── registry.js
│ │ ├── update-subtask.js
│ │ ├── update-task.js
│ │ └── update-tasks.js
│ ├── task-master.js
│ ├── ui
│ │ ├── confirm.js
│ │ ├── indicators.js
│ │ └── parse-prd.js
│ └── utils
│ ├── asset-resolver.js
│ ├── create-mcp-config.js
│ ├── format.js
│ ├── getVersion.js
│ ├── logger-utils.js
│ ├── manage-gitignore.js
│ ├── path-utils.js
│ ├── profiles.js
│ ├── rule-transformer.js
│ ├── stream-parser.js
│ └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│ ├── e2e
│ │ ├── e2e_helpers.sh
│ │ ├── parse_llm_output.cjs
│ │ ├── run_e2e.sh
│ │ ├── run_fallback_verification.sh
│ │ └── test_llm_analysis.sh
│ ├── fixtures
│ │ ├── .taskmasterconfig
│ │ ├── sample-claude-response.js
│ │ ├── sample-prd.txt
│ │ └── sample-tasks.js
│ ├── helpers
│ │ └── tool-counts.js
│ ├── integration
│ │ ├── claude-code-error-handling.test.js
│ │ ├── claude-code-optional.test.js
│ │ ├── cli
│ │ │ ├── commands.test.js
│ │ │ ├── complex-cross-tag-scenarios.test.js
│ │ │ └── move-cross-tag.test.js
│ │ ├── manage-gitignore.test.js
│ │ ├── mcp-server
│ │ │ └── direct-functions.test.js
│ │ ├── move-task-cross-tag.integration.test.js
│ │ ├── move-task-simple.integration.test.js
│ │ ├── profiles
│ │ │ ├── amp-init-functionality.test.js
│ │ │ ├── claude-init-functionality.test.js
│ │ │ ├── cline-init-functionality.test.js
│ │ │ ├── codex-init-functionality.test.js
│ │ │ ├── cursor-init-functionality.test.js
│ │ │ ├── gemini-init-functionality.test.js
│ │ │ ├── opencode-init-functionality.test.js
│ │ │ ├── roo-files-inclusion.test.js
│ │ │ ├── roo-init-functionality.test.js
│ │ │ ├── rules-files-inclusion.test.js
│ │ │ ├── trae-init-functionality.test.js
│ │ │ ├── vscode-init-functionality.test.js
│ │ │ └── windsurf-init-functionality.test.js
│ │ └── providers
│ │ └── temperature-support.test.js
│ ├── manual
│ │ ├── progress
│ │ │ ├── parse-prd-analysis.js
│ │ │ ├── test-parse-prd.js
│ │ │ └── TESTING_GUIDE.md
│ │ └── prompts
│ │ ├── prompt-test.js
│ │ └── README.md
│ ├── README.md
│ ├── setup.js
│ └── unit
│ ├── ai-providers
│ │ ├── base-provider.test.js
│ │ ├── claude-code.test.js
│ │ ├── codex-cli.test.js
│ │ ├── gemini-cli.test.js
│ │ ├── lmstudio.test.js
│ │ ├── mcp-components.test.js
│ │ ├── openai-compatible.test.js
│ │ ├── openai.test.js
│ │ ├── provider-registry.test.js
│ │ ├── zai-coding.test.js
│ │ ├── zai-provider.test.js
│ │ ├── zai-schema-introspection.test.js
│ │ └── zai.test.js
│ ├── ai-services-unified.test.js
│ ├── commands.test.js
│ ├── config-manager.test.js
│ ├── config-manager.test.mjs
│ ├── dependency-manager.test.js
│ ├── init.test.js
│ ├── initialize-project.test.js
│ ├── kebab-case-validation.test.js
│ ├── manage-gitignore.test.js
│ ├── mcp
│ │ └── tools
│ │ ├── __mocks__
│ │ │ └── move-task.js
│ │ ├── add-task.test.js
│ │ ├── analyze-complexity.test.js
│ │ ├── expand-all.test.js
│ │ ├── get-tasks.test.js
│ │ ├── initialize-project.test.js
│ │ ├── move-task-cross-tag-options.test.js
│ │ ├── move-task-cross-tag.test.js
│ │ ├── remove-task.test.js
│ │ └── tool-registration.test.js
│ ├── mcp-providers
│ │ ├── mcp-components.test.js
│ │ └── mcp-provider.test.js
│ ├── parse-prd.test.js
│ ├── profiles
│ │ ├── amp-integration.test.js
│ │ ├── claude-integration.test.js
│ │ ├── cline-integration.test.js
│ │ ├── codex-integration.test.js
│ │ ├── cursor-integration.test.js
│ │ ├── gemini-integration.test.js
│ │ ├── kilo-integration.test.js
│ │ ├── kiro-integration.test.js
│ │ ├── mcp-config-validation.test.js
│ │ ├── opencode-integration.test.js
│ │ ├── profile-safety-check.test.js
│ │ ├── roo-integration.test.js
│ │ ├── rule-transformer-cline.test.js
│ │ ├── rule-transformer-cursor.test.js
│ │ ├── rule-transformer-gemini.test.js
│ │ ├── rule-transformer-kilo.test.js
│ │ ├── rule-transformer-kiro.test.js
│ │ ├── rule-transformer-opencode.test.js
│ │ ├── rule-transformer-roo.test.js
│ │ ├── rule-transformer-trae.test.js
│ │ ├── rule-transformer-vscode.test.js
│ │ ├── rule-transformer-windsurf.test.js
│ │ ├── rule-transformer-zed.test.js
│ │ ├── rule-transformer.test.js
│ │ ├── selective-profile-removal.test.js
│ │ ├── subdirectory-support.test.js
│ │ ├── trae-integration.test.js
│ │ ├── vscode-integration.test.js
│ │ ├── windsurf-integration.test.js
│ │ └── zed-integration.test.js
│ ├── progress
│ │ └── base-progress-tracker.test.js
│ ├── prompt-manager.test.js
│ ├── prompts
│ │ ├── expand-task-prompt.test.js
│ │ └── prompt-migration.test.js
│ ├── scripts
│ │ └── modules
│ │ ├── commands
│ │ │ ├── move-cross-tag.test.js
│ │ │ └── README.md
│ │ ├── dependency-manager
│ │ │ ├── circular-dependencies.test.js
│ │ │ ├── cross-tag-dependencies.test.js
│ │ │ └── fix-dependencies-command.test.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.test.js
│ │ │ ├── add-task.test.js
│ │ │ ├── analyze-task-complexity.test.js
│ │ │ ├── clear-subtasks.test.js
│ │ │ ├── complexity-report-tag-isolation.test.js
│ │ │ ├── expand-all-tasks.test.js
│ │ │ ├── expand-task.test.js
│ │ │ ├── find-next-task.test.js
│ │ │ ├── generate-task-files.test.js
│ │ │ ├── list-tasks.test.js
│ │ │ ├── models-baseurl.test.js
│ │ │ ├── move-task-cross-tag.test.js
│ │ │ ├── move-task.test.js
│ │ │ ├── parse-prd-schema.test.js
│ │ │ ├── parse-prd.test.js
│ │ │ ├── remove-subtask.test.js
│ │ │ ├── remove-task.test.js
│ │ │ ├── research.test.js
│ │ │ ├── scope-adjustment.test.js
│ │ │ ├── set-task-status.test.js
│ │ │ ├── setup.js
│ │ │ ├── update-single-task-status.test.js
│ │ │ ├── update-subtask-by-id.test.js
│ │ │ ├── update-task-by-id.test.js
│ │ │ └── update-tasks.test.js
│ │ ├── ui
│ │ │ └── cross-tag-error-display.test.js
│ │ └── utils-tag-aware-paths.test.js
│ ├── task-finder.test.js
│ ├── task-manager
│ │ ├── clear-subtasks.test.js
│ │ ├── move-task.test.js
│ │ ├── tag-boundary.test.js
│ │ └── tag-management.test.js
│ ├── task-master.test.js
│ ├── ui
│ │ └── indicators.test.js
│ ├── ui.test.js
│ ├── utils-strip-ansi.test.js
│ └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
├── turbo.json
└── update-task-migration-plan.md
```
# Files
--------------------------------------------------------------------------------
/src/profiles/opencode.js:
--------------------------------------------------------------------------------
```javascript
1 | // Opencode profile for rule-transformer
2 | import path from 'path';
3 | import fs from 'fs';
4 | import { log } from '../../scripts/modules/utils.js';
5 | import { createProfile } from './base-profile.js';
6 |
7 | /**
8 | * Transform standard MCP config format to OpenCode format
9 | * @param {Object} mcpConfig - Standard MCP configuration object
10 | * @returns {Object} - Transformed OpenCode configuration object
11 | */
12 | function transformToOpenCodeFormat(mcpConfig) {
13 | const openCodeConfig = {
14 | $schema: 'https://opencode.ai/config.json'
15 | };
16 |
17 | // Transform mcpServers to mcp
18 | if (mcpConfig.mcpServers) {
19 | openCodeConfig.mcp = {};
20 |
21 | for (const [serverName, serverConfig] of Object.entries(
22 | mcpConfig.mcpServers
23 | )) {
24 | // Transform server configuration
25 | const transformedServer = {
26 | type: 'local'
27 | };
28 |
29 | // Combine command and args into single command array
30 | if (serverConfig.command && serverConfig.args) {
31 | transformedServer.command = [
32 | serverConfig.command,
33 | ...serverConfig.args
34 | ];
35 | } else if (serverConfig.command) {
36 | transformedServer.command = [serverConfig.command];
37 | }
38 |
39 | // Add enabled flag
40 | transformedServer.enabled = true;
41 |
42 | // Transform env to environment
43 | if (serverConfig.env) {
44 | transformedServer.environment = serverConfig.env;
45 | }
46 |
47 | // update with transformed config
48 | openCodeConfig.mcp[serverName] = transformedServer;
49 | }
50 | }
51 |
52 | return openCodeConfig;
53 | }
54 |
55 | /**
56 | * Lifecycle function called after MCP config generation to transform to OpenCode format
57 | * @param {string} targetDir - Target project directory
58 | * @param {string} assetsDir - Assets directory (unused for OpenCode)
59 | */
60 | function onPostConvertRulesProfile(targetDir, assetsDir) {
61 | const openCodeConfigPath = path.join(targetDir, 'opencode.json');
62 |
63 | if (!fs.existsSync(openCodeConfigPath)) {
64 | log('debug', '[OpenCode] No opencode.json found to transform');
65 | return;
66 | }
67 |
68 | try {
69 | // Read the generated standard MCP config
70 | const mcpConfigContent = fs.readFileSync(openCodeConfigPath, 'utf8');
71 | const mcpConfig = JSON.parse(mcpConfigContent);
72 |
73 | // Check if it's already in OpenCode format (has $schema)
74 | if (mcpConfig.$schema) {
75 | log(
76 | 'info',
77 | '[OpenCode] opencode.json already in OpenCode format, skipping transformation'
78 | );
79 | return;
80 | }
81 |
82 | // Transform to OpenCode format
83 | const openCodeConfig = transformToOpenCodeFormat(mcpConfig);
84 |
85 | // Write back the transformed config with proper formatting
86 | fs.writeFileSync(
87 | openCodeConfigPath,
88 | JSON.stringify(openCodeConfig, null, 2) + '\n'
89 | );
90 |
91 | log('info', '[OpenCode] Transformed opencode.json to OpenCode format');
92 | log(
93 | 'debug',
94 | `[OpenCode] Added schema, renamed mcpServers->mcp, combined command+args, added type/enabled, renamed env->environment`
95 | );
96 | } catch (error) {
97 | log(
98 | 'error',
99 | `[OpenCode] Failed to transform opencode.json: ${error.message}`
100 | );
101 | }
102 | }
103 |
104 | /**
105 | * Lifecycle function called when removing OpenCode profile
106 | * @param {string} targetDir - Target project directory
107 | */
108 | function onRemoveRulesProfile(targetDir) {
109 | const openCodeConfigPath = path.join(targetDir, 'opencode.json');
110 |
111 | if (!fs.existsSync(openCodeConfigPath)) {
112 | log('debug', '[OpenCode] No opencode.json found to clean up');
113 | return;
114 | }
115 |
116 | try {
117 | // Read the current config
118 | const configContent = fs.readFileSync(openCodeConfigPath, 'utf8');
119 | const config = JSON.parse(configContent);
120 |
121 | // Check if it has the mcp section and taskmaster-ai server
122 | if (config.mcp && config.mcp['taskmaster-ai']) {
123 | // Remove taskmaster-ai server
124 | delete config.mcp['taskmaster-ai'];
125 |
126 | // Check if there are other MCP servers
127 | const remainingServers = Object.keys(config.mcp);
128 |
129 | if (remainingServers.length === 0) {
130 | // No other servers, remove entire mcp section
131 | delete config.mcp;
132 | }
133 |
134 | // Check if config is now empty (only has $schema)
135 | const remainingKeys = Object.keys(config).filter(
136 | (key) => key !== '$schema'
137 | );
138 |
139 | if (remainingKeys.length === 0) {
140 | // Config only has schema left, remove entire file
141 | fs.rmSync(openCodeConfigPath, { force: true });
142 | log('info', '[OpenCode] Removed empty opencode.json file');
143 | } else {
144 | // Write back the modified config
145 | fs.writeFileSync(
146 | openCodeConfigPath,
147 | JSON.stringify(config, null, 2) + '\n'
148 | );
149 | log(
150 | 'info',
151 | '[OpenCode] Removed TaskMaster from opencode.json, preserved other configurations'
152 | );
153 | }
154 | } else {
155 | log('debug', '[OpenCode] TaskMaster not found in opencode.json');
156 | }
157 | } catch (error) {
158 | log(
159 | 'error',
160 | `[OpenCode] Failed to clean up opencode.json: ${error.message}`
161 | );
162 | }
163 | }
164 |
165 | // Create and export opencode profile using the base factory
166 | export const opencodeProfile = createProfile({
167 | name: 'opencode',
168 | displayName: 'OpenCode',
169 | url: 'opencode.ai',
170 | docsUrl: 'opencode.ai/docs/',
171 | profileDir: '.', // Root directory
172 | rulesDir: '.', // Root directory for AGENTS.md
173 | mcpConfigName: 'opencode.json', // Override default 'mcp.json'
174 | includeDefaultRules: false,
175 | fileMap: {
176 | 'AGENTS.md': 'AGENTS.md'
177 | },
178 | onPostConvert: onPostConvertRulesProfile,
179 | onRemove: onRemoveRulesProfile
180 | });
181 |
182 | // Export lifecycle functions separately to avoid naming conflicts
183 | export { onPostConvertRulesProfile, onRemoveRulesProfile };
184 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/update-subtask-by-id.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * update-subtask-by-id.js
3 | * Direct function implementation for appending information to a specific subtask
4 | */
5 |
6 | import { updateSubtaskById } from '../../../../scripts/modules/task-manager.js';
7 | import {
8 | enableSilentMode,
9 | disableSilentMode,
10 | isSilentMode
11 | } from '../../../../scripts/modules/utils.js';
12 | import { createLogWrapper } from '../../tools/utils.js';
13 |
14 | /**
15 | * Direct function wrapper for updateSubtaskById with error handling.
16 | *
17 | * @param {Object} args - Command arguments containing id, prompt, useResearch, tasksJsonPath, and projectRoot.
18 | * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
19 | * @param {string} args.id - Subtask ID in format "parent.sub".
20 | * @param {string} args.prompt - Information to append to the subtask.
21 | * @param {boolean} [args.research] - Whether to use research role.
22 | * @param {string} [args.projectRoot] - Project root path.
23 | * @param {string} [args.tag] - Tag for the task (optional)
24 | * @param {Object} log - Logger object.
25 | * @param {Object} context - Context object containing session data.
26 | * @returns {Promise<Object>} - Result object with success status and data/error information.
27 | */
28 | export async function updateSubtaskByIdDirect(args, log, context = {}) {
29 | const { session } = context;
30 | // Destructure expected args, including projectRoot
31 | const { tasksJsonPath, id, prompt, research, projectRoot, tag } = args;
32 |
33 | const logWrapper = createLogWrapper(log);
34 |
35 | try {
36 | logWrapper.info(
37 | `Updating subtask by ID via direct function. ID: ${id}, ProjectRoot: ${projectRoot}`
38 | );
39 |
40 | // Check if tasksJsonPath was provided
41 | if (!tasksJsonPath) {
42 | const errorMessage = 'tasksJsonPath is required but was not provided.';
43 | logWrapper.error(errorMessage);
44 | return {
45 | success: false,
46 | error: { code: 'MISSING_ARGUMENT', message: errorMessage }
47 | };
48 | }
49 |
50 | // Basic validation for ID format (e.g., '5.2')
51 | if (!id || typeof id !== 'string' || !id.includes('.')) {
52 | const errorMessage =
53 | 'Invalid subtask ID format. Must be in format "parentId.subtaskId" (e.g., "5.2").';
54 | logWrapper.error(errorMessage);
55 | return {
56 | success: false,
57 | error: { code: 'INVALID_SUBTASK_ID', message: errorMessage }
58 | };
59 | }
60 |
61 | if (!prompt) {
62 | const errorMessage =
63 | 'No prompt specified. Please provide the information to append.';
64 | logWrapper.error(errorMessage);
65 | return {
66 | success: false,
67 | error: { code: 'MISSING_PROMPT', message: errorMessage }
68 | };
69 | }
70 |
71 | // Validate subtask ID format
72 | const subtaskId = id;
73 | if (typeof subtaskId !== 'string' && typeof subtaskId !== 'number') {
74 | const errorMessage = `Invalid subtask ID type: ${typeof subtaskId}. Subtask ID must be a string or number.`;
75 | log.error(errorMessage);
76 | return {
77 | success: false,
78 | error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage }
79 | };
80 | }
81 |
82 | const subtaskIdStr = String(subtaskId);
83 | if (!subtaskIdStr.includes('.')) {
84 | const errorMessage = `Invalid subtask ID format: ${subtaskIdStr}. Subtask ID must be in format "parentId.subtaskId" (e.g., "5.2").`;
85 | log.error(errorMessage);
86 | return {
87 | success: false,
88 | error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage }
89 | };
90 | }
91 |
92 | // Use the provided path
93 | const tasksPath = tasksJsonPath;
94 | const useResearch = research === true;
95 |
96 | log.info(
97 | `Updating subtask with ID ${subtaskIdStr} with prompt "${prompt}" and research: ${useResearch}`
98 | );
99 |
100 | const wasSilent = isSilentMode();
101 | if (!wasSilent) {
102 | enableSilentMode();
103 | }
104 |
105 | try {
106 | // Call legacy script which handles both API and file storage via bridge
107 | const coreResult = await updateSubtaskById(
108 | tasksPath,
109 | subtaskIdStr,
110 | prompt,
111 | useResearch,
112 | {
113 | mcpLog: logWrapper,
114 | session,
115 | projectRoot,
116 | tag,
117 | commandName: 'update-subtask',
118 | outputType: 'mcp'
119 | },
120 | 'json'
121 | );
122 |
123 | if (!coreResult || coreResult.updatedSubtask === null) {
124 | const message = `Subtask ${id} or its parent task not found.`;
125 | logWrapper.error(message);
126 | return {
127 | success: false,
128 | error: { code: 'SUBTASK_NOT_FOUND', message: message }
129 | };
130 | }
131 |
132 | const parentId = subtaskIdStr.split('.')[0];
133 | const successMessage = `Successfully updated subtask with ID ${subtaskIdStr}`;
134 | logWrapper.success(successMessage);
135 | return {
136 | success: true,
137 | data: {
138 | message: `Successfully updated subtask with ID ${subtaskIdStr}`,
139 | subtaskId: subtaskIdStr,
140 | parentId: parentId,
141 | subtask: coreResult.updatedSubtask,
142 | tasksPath,
143 | useResearch,
144 | telemetryData: coreResult.telemetryData,
145 | tagInfo: coreResult.tagInfo
146 | }
147 | };
148 | } catch (error) {
149 | logWrapper.error(`Error updating subtask by ID: ${error.message}`);
150 | return {
151 | success: false,
152 | error: {
153 | code: 'UPDATE_SUBTASK_CORE_ERROR',
154 | message: error.message || 'Unknown error updating subtask'
155 | }
156 | };
157 | } finally {
158 | if (!wasSilent && isSilentMode()) {
159 | disableSilentMode();
160 | }
161 | }
162 | } catch (error) {
163 | logWrapper.error(
164 | `Setup error in updateSubtaskByIdDirect: ${error.message}`
165 | );
166 | if (isSilentMode()) disableSilentMode();
167 | return {
168 | success: false,
169 | error: {
170 | code: 'DIRECT_FUNCTION_SETUP_ERROR',
171 | message: error.message || 'Unknown setup error'
172 | }
173 | };
174 | }
175 | }
176 |
```
--------------------------------------------------------------------------------
/mcp-server/src/custom-sdk/schema-converter.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * @fileoverview Schema conversion utilities for MCP AI SDK provider
3 | */
4 |
5 | /**
6 | * Convert Zod schema to human-readable JSON instructions
7 | * @param {import('zod').ZodSchema} schema - Zod schema object
8 | * @param {string} [objectName='result'] - Name of the object being generated
9 | * @returns {string} Instructions for JSON generation
10 | */
11 | export function convertSchemaToInstructions(schema, objectName = 'result') {
12 | try {
13 | // Generate example structure from schema
14 | const exampleStructure = generateExampleFromSchema(schema);
15 |
16 | return `
17 | CRITICAL JSON GENERATION INSTRUCTIONS:
18 |
19 | You must respond with ONLY valid JSON that matches this exact structure for "${objectName}":
20 |
21 | ${JSON.stringify(exampleStructure, null, 2)}
22 |
23 | STRICT REQUIREMENTS:
24 | 1. Response must start with { and end with }
25 | 2. Use double quotes for all strings and property names
26 | 3. Do not include any text before or after the JSON
27 | 4. Do not wrap in markdown code blocks
28 | 5. Do not include explanations or comments
29 | 6. Follow the exact property names and types shown above
30 | 7. All required fields must be present
31 |
32 | Begin your response immediately with the opening brace {`;
33 | } catch (error) {
34 | // Fallback to basic JSON instructions if schema parsing fails
35 | return `
36 | CRITICAL JSON GENERATION INSTRUCTIONS:
37 |
38 | You must respond with ONLY valid JSON for "${objectName}".
39 |
40 | STRICT REQUIREMENTS:
41 | 1. Response must start with { and end with }
42 | 2. Use double quotes for all strings and property names
43 | 3. Do not include any text before or after the JSON
44 | 4. Do not wrap in markdown code blocks
45 | 5. Do not include explanations or comments
46 |
47 | Begin your response immediately with the opening brace {`;
48 | }
49 | }
50 |
51 | /**
52 | * Generate example structure from Zod schema
53 | * @param {import('zod').ZodSchema} schema - Zod schema
54 | * @returns {any} Example object matching the schema
55 | */
56 | function generateExampleFromSchema(schema) {
57 | // This is a simplified schema-to-example converter
58 | // For production, you might want to use a more sophisticated library
59 |
60 | if (!schema || typeof schema._def === 'undefined') {
61 | return {};
62 | }
63 |
64 | const def = schema._def;
65 |
66 | switch (def.typeName) {
67 | case 'ZodObject':
68 | const result = {};
69 | const shape = def.shape();
70 |
71 | for (const [key, fieldSchema] of Object.entries(shape)) {
72 | result[key] = generateExampleFromSchema(fieldSchema);
73 | }
74 |
75 | return result;
76 |
77 | case 'ZodString':
78 | // Check for min/max length constraints
79 | if (def.checks) {
80 | const minCheck = def.checks.find((c) => c.kind === 'min');
81 | const maxCheck = def.checks.find((c) => c.kind === 'max');
82 | if (minCheck && maxCheck) {
83 | return (
84 | '<string between ' +
85 | minCheck.value +
86 | '-' +
87 | maxCheck.value +
88 | ' characters>'
89 | );
90 | } else if (minCheck) {
91 | return '<string with at least ' + minCheck.value + ' characters>';
92 | } else if (maxCheck) {
93 | return '<string up to ' + maxCheck.value + ' characters>';
94 | }
95 | }
96 | return '<string>';
97 |
98 | case 'ZodNumber':
99 | // Check for int, positive, min/max constraints
100 | if (def.checks) {
101 | const intCheck = def.checks.find((c) => c.kind === 'int');
102 | const minCheck = def.checks.find((c) => c.kind === 'min');
103 | const maxCheck = def.checks.find((c) => c.kind === 'max');
104 |
105 | if (intCheck && minCheck && minCheck.value > 0) {
106 | return '<positive integer>';
107 | } else if (intCheck) {
108 | return '<integer>';
109 | } else if (minCheck || maxCheck) {
110 | return (
111 | '<number' +
112 | (minCheck ? ' >= ' + minCheck.value : '') +
113 | (maxCheck ? ' <= ' + maxCheck.value : '') +
114 | '>'
115 | );
116 | }
117 | }
118 | return '<number>';
119 |
120 | case 'ZodBoolean':
121 | return '<boolean>';
122 |
123 | case 'ZodArray':
124 | const elementExample = generateExampleFromSchema(def.type);
125 | return [elementExample];
126 |
127 | case 'ZodOptional':
128 | return generateExampleFromSchema(def.innerType);
129 |
130 | case 'ZodNullable':
131 | return generateExampleFromSchema(def.innerType);
132 |
133 | case 'ZodEnum':
134 | return def.values[0] || 'enum_value';
135 |
136 | case 'ZodLiteral':
137 | return def.value;
138 |
139 | case 'ZodUnion':
140 | // Use the first option from the union
141 | if (def.options && def.options.length > 0) {
142 | return generateExampleFromSchema(def.options[0]);
143 | }
144 | return 'union_value';
145 |
146 | case 'ZodRecord':
147 | return {
148 | key: generateExampleFromSchema(def.valueType)
149 | };
150 |
151 | default:
152 | // For unknown types, return a placeholder
153 | return `<${def.typeName || 'unknown'}>`;
154 | }
155 | }
156 |
157 | /**
158 | * Enhance prompt with JSON generation instructions
159 | * @param {Array} prompt - AI SDK prompt array
160 | * @param {string} jsonInstructions - JSON generation instructions
161 | * @returns {Array} Enhanced prompt array
162 | */
163 | export function enhancePromptForJSON(prompt, jsonInstructions) {
164 | const enhancedPrompt = [...prompt];
165 |
166 | // Find system message or create one
167 | let systemMessageIndex = enhancedPrompt.findIndex(
168 | (msg) => msg.role === 'system'
169 | );
170 |
171 | if (systemMessageIndex >= 0) {
172 | // Append to existing system message
173 | const currentContent = enhancedPrompt[systemMessageIndex].content;
174 | enhancedPrompt[systemMessageIndex] = {
175 | ...enhancedPrompt[systemMessageIndex],
176 | content: currentContent + '\n\n' + jsonInstructions
177 | };
178 | } else {
179 | // Add new system message at the beginning
180 | enhancedPrompt.unshift({
181 | role: 'system',
182 | content: jsonInstructions
183 | });
184 | }
185 |
186 | return enhancedPrompt;
187 | }
188 |
```
--------------------------------------------------------------------------------
/tests/fixtures/sample-tasks.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Sample task data for testing
3 | */
4 |
5 | export const sampleTasks = {
6 | meta: {
7 | projectName: 'Test Project',
8 | projectVersion: '1.0.0',
9 | createdAt: '2023-01-01T00:00:00.000Z',
10 | updatedAt: '2023-01-01T00:00:00.000Z'
11 | },
12 | tasks: [
13 | {
14 | id: 1,
15 | title: 'Initialize Project',
16 | description: 'Set up the project structure and dependencies',
17 | status: 'done',
18 | dependencies: [],
19 | priority: 'high',
20 | details:
21 | 'Create directory structure, initialize package.json, and install dependencies',
22 | testStrategy: 'Verify all directories and files are created correctly'
23 | },
24 | {
25 | id: 2,
26 | title: 'Create Core Functionality',
27 | description: 'Implement the main features of the application',
28 | status: 'in-progress',
29 | dependencies: [1],
30 | priority: 'high',
31 | details:
32 | 'Implement user authentication, data processing, and API endpoints',
33 | testStrategy: 'Write unit tests for all core functions',
34 | subtasks: [
35 | {
36 | id: 1,
37 | title: 'Implement Authentication',
38 | description: 'Create user authentication system',
39 | status: 'done',
40 | dependencies: []
41 | },
42 | {
43 | id: 2,
44 | title: 'Set Up Database',
45 | description: 'Configure database connection and models',
46 | status: 'pending',
47 | dependencies: [1]
48 | }
49 | ]
50 | },
51 | {
52 | id: 3,
53 | title: 'Implement UI Components',
54 | description: 'Create the user interface components',
55 | status: 'pending',
56 | dependencies: [2],
57 | priority: 'medium',
58 | details: 'Design and implement React components for the user interface',
59 | testStrategy: 'Test components with React Testing Library',
60 | subtasks: [
61 | {
62 | id: 1,
63 | title: 'Create Header Component',
64 | description: 'Implement the header component',
65 | status: 'pending',
66 | dependencies: [],
67 | details: 'Create a responsive header with navigation links'
68 | },
69 | {
70 | id: 2,
71 | title: 'Create Footer Component',
72 | description: 'Implement the footer component',
73 | status: 'pending',
74 | dependencies: [],
75 | details: 'Create a footer with copyright information and links'
76 | }
77 | ]
78 | }
79 | ]
80 | };
81 |
82 | export const emptySampleTasks = {
83 | meta: {
84 | projectName: 'Empty Project',
85 | projectVersion: '1.0.0',
86 | createdAt: '2023-01-01T00:00:00.000Z',
87 | updatedAt: '2023-01-01T00:00:00.000Z'
88 | },
89 | tasks: []
90 | };
91 |
92 | export const crossLevelDependencyTasks = {
93 | tasks: [
94 | {
95 | id: 2,
96 | title: 'Task 2 with subtasks',
97 | description: 'Parent task',
98 | status: 'pending',
99 | dependencies: [],
100 | subtasks: [
101 | {
102 | id: 1,
103 | title: 'Subtask 2.1',
104 | description: 'First subtask',
105 | status: 'pending',
106 | dependencies: []
107 | },
108 | {
109 | id: 2,
110 | title: 'Subtask 2.2',
111 | description: 'Second subtask that should depend on Task 11',
112 | status: 'pending',
113 | dependencies: []
114 | }
115 | ]
116 | },
117 | {
118 | id: 11,
119 | title: 'Task 11',
120 | description: 'Top-level task that 2.2 should depend on',
121 | status: 'done',
122 | dependencies: []
123 | }
124 | ]
125 | };
126 |
127 | // ============================================================================
128 | // Tagged Format Fixtures (for tag-aware system tests)
129 | // ============================================================================
130 |
131 | /**
132 | * Single task in master tag - minimal fixture
133 | * Use: Basic happy path tests
134 | */
135 | export const taggedOneTask = {
136 | tag: 'master',
137 | tasks: [
138 | {
139 | id: 1,
140 | title: 'Task 1',
141 | description: 'First task',
142 | status: 'pending',
143 | dependencies: [],
144 | priority: 'medium'
145 | }
146 | ]
147 | };
148 |
149 | /**
150 | * Task with subtasks in master tag
151 | * Use: Testing subtask operations (expand, update-subtask)
152 | */
153 | export const taggedTaskWithSubtasks = {
154 | tag: 'master',
155 | tasks: [
156 | {
157 | id: 1,
158 | title: 'Parent Task',
159 | description: 'Task with subtasks',
160 | status: 'in-progress',
161 | dependencies: [],
162 | priority: 'high',
163 | subtasks: [
164 | {
165 | id: 1,
166 | title: 'Subtask 1.1',
167 | description: 'First subtask',
168 | status: 'done',
169 | dependencies: []
170 | },
171 | {
172 | id: 2,
173 | title: 'Subtask 1.2',
174 | description: 'Second subtask',
175 | status: 'pending',
176 | dependencies: [1]
177 | }
178 | ]
179 | }
180 | ]
181 | };
182 |
183 | /**
184 | * Multiple tasks with dependencies in master tag
185 | * Use: Testing dependency operations, task ordering
186 | */
187 | export const taggedTasksWithDependencies = {
188 | tag: 'master',
189 | tasks: [
190 | {
191 | id: 1,
192 | title: 'Setup',
193 | description: 'Initial setup task',
194 | status: 'done',
195 | dependencies: [],
196 | priority: 'high'
197 | },
198 | {
199 | id: 2,
200 | title: 'Core Feature',
201 | description: 'Main feature implementation',
202 | status: 'in-progress',
203 | dependencies: [1],
204 | priority: 'high'
205 | },
206 | {
207 | id: 3,
208 | title: 'Polish',
209 | description: 'Final touches',
210 | status: 'pending',
211 | dependencies: [2],
212 | priority: 'low'
213 | }
214 | ]
215 | };
216 |
217 | /**
218 | * Empty tag - no tasks
219 | * Use: Testing edge cases, "add first task" scenarios
220 | */
221 | export const taggedEmptyTasks = {
222 | tag: 'master',
223 | tasks: []
224 | };
225 |
226 | /**
227 | * Helper function to create custom tagged fixture
228 | * @param {string} tagName - Tag name (default: 'master')
229 | * @param {Array} tasks - Array of task objects
230 | * @returns {Object} Tagged task data
231 | *
232 | * @example
233 | * const customData = createTaggedFixture('feature-branch', [
234 | * { id: 1, title: 'Custom Task', status: 'pending', dependencies: [] }
235 | * ]);
236 | */
237 | export function createTaggedFixture(tagName = 'master', tasks = []) {
238 | return {
239 | tag: tagName,
240 | tasks
241 | };
242 | }
243 |
```
--------------------------------------------------------------------------------
/tests/unit/ai-providers/openai-compatible.test.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Tests for OpenAICompatibleProvider base class
3 | */
4 |
5 | import { OpenAICompatibleProvider } from '../../../src/ai-providers/openai-compatible.js';
6 |
7 | describe('OpenAICompatibleProvider', () => {
8 | describe('constructor', () => {
9 | it('should initialize with required config', () => {
10 | const provider = new OpenAICompatibleProvider({
11 | name: 'Test Provider',
12 | apiKeyEnvVar: 'TEST_API_KEY'
13 | });
14 |
15 | expect(provider.name).toBe('Test Provider');
16 | expect(provider.apiKeyEnvVar).toBe('TEST_API_KEY');
17 | expect(provider.requiresApiKey).toBe(true);
18 | });
19 |
20 | it('should initialize with requiresApiKey set to false', () => {
21 | const provider = new OpenAICompatibleProvider({
22 | name: 'Test Provider',
23 | apiKeyEnvVar: 'TEST_API_KEY',
24 | requiresApiKey: false
25 | });
26 |
27 | expect(provider.requiresApiKey).toBe(false);
28 | });
29 |
30 | it('should throw error if name is missing', () => {
31 | expect(() => {
32 | new OpenAICompatibleProvider({
33 | apiKeyEnvVar: 'TEST_API_KEY'
34 | });
35 | }).toThrow('Provider name is required');
36 | });
37 |
38 | it('should throw error if apiKeyEnvVar is missing', () => {
39 | expect(() => {
40 | new OpenAICompatibleProvider({
41 | name: 'Test Provider'
42 | });
43 | }).toThrow('API key environment variable name is required');
44 | });
45 | });
46 |
47 | describe('getRequiredApiKeyName', () => {
48 | it('should return correct environment variable name', () => {
49 | const provider = new OpenAICompatibleProvider({
50 | name: 'Test Provider',
51 | apiKeyEnvVar: 'TEST_API_KEY'
52 | });
53 |
54 | expect(provider.getRequiredApiKeyName()).toBe('TEST_API_KEY');
55 | });
56 | });
57 |
58 | describe('isRequiredApiKey', () => {
59 | it('should return true by default', () => {
60 | const provider = new OpenAICompatibleProvider({
61 | name: 'Test Provider',
62 | apiKeyEnvVar: 'TEST_API_KEY'
63 | });
64 |
65 | expect(provider.isRequiredApiKey()).toBe(true);
66 | });
67 |
68 | it('should return false when explicitly set', () => {
69 | const provider = new OpenAICompatibleProvider({
70 | name: 'Test Provider',
71 | apiKeyEnvVar: 'TEST_API_KEY',
72 | requiresApiKey: false
73 | });
74 |
75 | expect(provider.isRequiredApiKey()).toBe(false);
76 | });
77 | });
78 |
79 | describe('validateAuth', () => {
80 | it('should validate API key is present when required', () => {
81 | const provider = new OpenAICompatibleProvider({
82 | name: 'Test Provider',
83 | apiKeyEnvVar: 'TEST_API_KEY',
84 | requiresApiKey: true
85 | });
86 |
87 | expect(() => {
88 | provider.validateAuth({});
89 | }).toThrow('Test Provider API key is required');
90 | });
91 |
92 | it('should not validate API key when not required', () => {
93 | const provider = new OpenAICompatibleProvider({
94 | name: 'Test Provider',
95 | apiKeyEnvVar: 'TEST_API_KEY',
96 | requiresApiKey: false
97 | });
98 |
99 | expect(() => {
100 | provider.validateAuth({});
101 | }).not.toThrow();
102 | });
103 |
104 | it('should pass with valid API key', () => {
105 | const provider = new OpenAICompatibleProvider({
106 | name: 'Test Provider',
107 | apiKeyEnvVar: 'TEST_API_KEY'
108 | });
109 |
110 | expect(() => {
111 | provider.validateAuth({ apiKey: 'test-key' });
112 | }).not.toThrow();
113 | });
114 | });
115 |
116 | describe('getBaseURL', () => {
117 | it('should return custom baseURL from params', () => {
118 | const provider = new OpenAICompatibleProvider({
119 | name: 'Test Provider',
120 | apiKeyEnvVar: 'TEST_API_KEY',
121 | defaultBaseURL: 'https://default.api.com'
122 | });
123 |
124 | const baseURL = provider.getBaseURL({
125 | baseURL: 'https://custom.api.com'
126 | });
127 | expect(baseURL).toBe('https://custom.api.com');
128 | });
129 |
130 | it('should return default baseURL if no custom provided', () => {
131 | const provider = new OpenAICompatibleProvider({
132 | name: 'Test Provider',
133 | apiKeyEnvVar: 'TEST_API_KEY',
134 | defaultBaseURL: 'https://default.api.com'
135 | });
136 |
137 | const baseURL = provider.getBaseURL({});
138 | expect(baseURL).toBe('https://default.api.com');
139 | });
140 |
141 | it('should use custom getBaseURL function', () => {
142 | const provider = new OpenAICompatibleProvider({
143 | name: 'Test Provider',
144 | apiKeyEnvVar: 'TEST_API_KEY',
145 | getBaseURL: (params) => `https://api.example.com/${params.route}`
146 | });
147 |
148 | const baseURL = provider.getBaseURL({ route: 'v2' });
149 | expect(baseURL).toBe('https://api.example.com/v2');
150 | });
151 | });
152 |
153 | describe('getClient', () => {
154 | it('should create client with API key when required', () => {
155 | const provider = new OpenAICompatibleProvider({
156 | name: 'Test Provider',
157 | apiKeyEnvVar: 'TEST_API_KEY',
158 | requiresApiKey: true,
159 | defaultBaseURL: 'https://api.example.com'
160 | });
161 |
162 | const client = provider.getClient({ apiKey: 'test-key' });
163 | expect(client).toBeDefined();
164 | });
165 |
166 | it('should create client without API key when not required', () => {
167 | const provider = new OpenAICompatibleProvider({
168 | name: 'Test Provider',
169 | apiKeyEnvVar: 'TEST_API_KEY',
170 | requiresApiKey: false,
171 | defaultBaseURL: 'https://api.example.com'
172 | });
173 |
174 | const client = provider.getClient({});
175 | expect(client).toBeDefined();
176 | });
177 |
178 | it('should create client even when API key is required but missing (validation deferred to SDK)', () => {
179 | const provider = new OpenAICompatibleProvider({
180 | name: 'Test Provider',
181 | apiKeyEnvVar: 'TEST_API_KEY',
182 | requiresApiKey: true
183 | });
184 |
185 | // getClient() no longer validates API key - validation is deferred to SDK initialization
186 | const client = provider.getClient({});
187 | expect(typeof client).toBe('function');
188 | });
189 | });
190 | });
191 |
```
--------------------------------------------------------------------------------
/src/progress/parse-prd-tracker.js:
--------------------------------------------------------------------------------
```javascript
1 | import chalk from 'chalk';
2 | import { newMultiBar } from './cli-progress-factory.js';
3 | import { BaseProgressTracker } from './base-progress-tracker.js';
4 | import {
5 | createProgressHeader,
6 | createProgressRow,
7 | createBorder
8 | } from './tracker-ui.js';
9 | import {
10 | getCliPriorityIndicators,
11 | getPriorityIndicator,
12 | getStatusBarPriorityIndicators,
13 | getPriorityColors
14 | } from '../ui/indicators.js';
15 |
16 | // Get centralized priority indicators
17 | const PRIORITY_INDICATORS = getCliPriorityIndicators();
18 | const PRIORITY_DOTS = getStatusBarPriorityIndicators();
19 | const PRIORITY_COLORS = getPriorityColors();
20 |
21 | // Constants
22 | const CONSTANTS = {
23 | DEBOUNCE_DELAY: 100,
24 | MAX_TITLE_LENGTH: 57,
25 | TRUNCATED_LENGTH: 54,
26 | TASK_ID_PAD_START: 3,
27 | TASK_ID_PAD_END: 4,
28 | PRIORITY_PAD_END: 3,
29 | VALID_PRIORITIES: ['high', 'medium', 'low'],
30 | DEFAULT_PRIORITY: 'medium'
31 | };
32 |
33 | /**
34 | * Helper class to manage update debouncing
35 | */
36 | class UpdateDebouncer {
37 | constructor(delay = CONSTANTS.DEBOUNCE_DELAY) {
38 | this.delay = delay;
39 | this.pendingTimeout = null;
40 | }
41 |
42 | debounce(callback) {
43 | this.clear();
44 | this.pendingTimeout = setTimeout(() => {
45 | callback();
46 | this.pendingTimeout = null;
47 | }, this.delay);
48 | }
49 |
50 | clear() {
51 | if (this.pendingTimeout) {
52 | clearTimeout(this.pendingTimeout);
53 | this.pendingTimeout = null;
54 | }
55 | }
56 |
57 | hasPending() {
58 | return this.pendingTimeout !== null;
59 | }
60 | }
61 |
62 | /**
63 | * Helper class to manage priority counts
64 | */
65 | class PriorityManager {
66 | constructor() {
67 | this.priorities = { high: 0, medium: 0, low: 0 };
68 | }
69 |
70 | increment(priority) {
71 | const normalized = this.normalize(priority);
72 | this.priorities[normalized]++;
73 | return normalized;
74 | }
75 |
76 | normalize(priority) {
77 | const lowercased = priority
78 | ? priority.toLowerCase()
79 | : CONSTANTS.DEFAULT_PRIORITY;
80 | return CONSTANTS.VALID_PRIORITIES.includes(lowercased)
81 | ? lowercased
82 | : CONSTANTS.DEFAULT_PRIORITY;
83 | }
84 |
85 | getCounts() {
86 | return { ...this.priorities };
87 | }
88 | }
89 |
90 | /**
91 | * Helper class for formatting task display elements
92 | */
93 | class TaskFormatter {
94 | static formatTitle(title, taskNumber) {
95 | if (!title) return `Task ${taskNumber}`;
96 | return title.length > CONSTANTS.MAX_TITLE_LENGTH
97 | ? title.substring(0, CONSTANTS.TRUNCATED_LENGTH) + '...'
98 | : title;
99 | }
100 |
101 | static formatPriority(priority) {
102 | return getPriorityIndicator(priority, false).padEnd(
103 | CONSTANTS.PRIORITY_PAD_END,
104 | ' '
105 | );
106 | }
107 |
108 | static formatTaskId(taskNumber) {
109 | return taskNumber
110 | .toString()
111 | .padStart(CONSTANTS.TASK_ID_PAD_START, ' ')
112 | .padEnd(CONSTANTS.TASK_ID_PAD_END, ' ');
113 | }
114 | }
115 |
116 | /**
117 | * Tracks progress for PRD parsing operations with multibar display
118 | */
119 | class ParsePrdTracker extends BaseProgressTracker {
120 | _initializeCustomProperties(options) {
121 | this.append = options.append;
122 | this.priorityManager = new PriorityManager();
123 | this.debouncer = new UpdateDebouncer();
124 | this.headerShown = false;
125 | }
126 |
127 | _getTimeTokensBarFormat() {
128 | return `{clock} {elapsed} | ${PRIORITY_DOTS.high} {high} ${PRIORITY_DOTS.medium} {medium} ${PRIORITY_DOTS.low} {low} | Tokens (I/O): {in}/{out} | Est: {remaining}`;
129 | }
130 |
131 | _getProgressBarFormat() {
132 | return 'Tasks {tasks} |{bar}| {percentage}%';
133 | }
134 |
135 | _getCustomTimeTokensPayload() {
136 | return this.priorityManager.getCounts();
137 | }
138 |
139 | addTaskLine(taskNumber, title, priority = 'medium') {
140 | if (!this.multibar || this.isFinished) return;
141 |
142 | this._ensureHeaderShown();
143 | const normalizedPriority = this._updateTaskCounters(taskNumber, priority);
144 |
145 | // Immediately update the time/tokens bar to show the new priority count
146 | this._updateTimeTokensBar();
147 |
148 | this.debouncer.debounce(() => {
149 | this._updateProgressDisplay(taskNumber, title, normalizedPriority);
150 | });
151 | }
152 |
153 | _ensureHeaderShown() {
154 | if (!this.headerShown) {
155 | this.headerShown = true;
156 | createProgressHeader(
157 | this.multibar,
158 | ' TASK | PRI | TITLE',
159 | '------+-----+----------------------------------------------------------------'
160 | );
161 | }
162 | }
163 |
164 | _updateTaskCounters(taskNumber, priority) {
165 | const normalizedPriority = this.priorityManager.increment(priority);
166 | this.completedUnits = taskNumber;
167 | return normalizedPriority;
168 | }
169 |
170 | _updateProgressDisplay(taskNumber, title, normalizedPriority) {
171 | this.progressBar.update(this.completedUnits, {
172 | tasks: `${this.completedUnits}/${this.numUnits}`
173 | });
174 |
175 | const displayTitle = TaskFormatter.formatTitle(title, taskNumber);
176 | const priorityDisplay = TaskFormatter.formatPriority(normalizedPriority);
177 | const taskIdCentered = TaskFormatter.formatTaskId(taskNumber);
178 |
179 | createProgressRow(
180 | this.multibar,
181 | ` ${taskIdCentered} | ${priorityDisplay} | {title}`,
182 | { title: displayTitle }
183 | );
184 |
185 | createBorder(
186 | this.multibar,
187 | '------+-----+----------------------------------------------------------------'
188 | );
189 |
190 | this._updateTimeTokensBar();
191 | }
192 |
193 | finish() {
194 | // Flush any pending updates before finishing
195 | if (this.debouncer.hasPending()) {
196 | this.debouncer.clear();
197 | this._updateTimeTokensBar();
198 | }
199 | this.cleanup();
200 | super.finish();
201 | }
202 |
203 | /**
204 | * Override cleanup to handle pending updates
205 | */
206 | _performCustomCleanup() {
207 | this.debouncer.clear();
208 | }
209 |
210 | getSummary() {
211 | return {
212 | ...super.getSummary(),
213 | taskPriorities: this.priorityManager.getCounts(),
214 | actionVerb: this.append ? 'appended' : 'generated'
215 | };
216 | }
217 | }
218 |
219 | export function createParsePrdTracker(options = {}) {
220 | return new ParsePrdTracker(options);
221 | }
222 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/initialize-project.js:
--------------------------------------------------------------------------------
```javascript
1 | import { initializeProject } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
2 | import {
3 | enableSilentMode,
4 | disableSilentMode
5 | // isSilentMode // Not used directly here
6 | } from '../../../../scripts/modules/utils.js';
7 | import os from 'os'; // Import os module for home directory check
8 | import { RULE_PROFILES } from '../../../../src/constants/profiles.js';
9 | import { convertAllRulesToProfileRules } from '../../../../src/utils/rule-transformer.js';
10 |
11 | /**
12 | * Direct function wrapper for initializing a project.
13 | * Derives target directory from session, sets CWD, and calls core init logic.
14 | * @param {object} args - Arguments containing initialization options (addAliases, initGit, storeTasksInGit, skipInstall, yes, projectRoot, rules)
15 | * @param {object} log - The FastMCP logger instance.
16 | * @param {object} context - The context object, must contain { session }.
17 | * @returns {Promise<{success: boolean, data?: any, error?: {code: string, message: string}}>} - Standard result object.
18 | */
19 | export async function initializeProjectDirect(args, log, context = {}) {
20 | const { session } = context; // Keep session if core logic needs it
21 | const homeDir = os.homedir();
22 |
23 | log.info(`Args received in direct function: ${JSON.stringify(args)}`);
24 |
25 | // --- Determine Target Directory ---
26 | // TRUST the projectRoot passed from the tool layer via args
27 | // The HOF in the tool layer already normalized and validated it came from a reliable source (args or session)
28 | const targetDirectory = args.projectRoot;
29 |
30 | // --- Validate the targetDirectory (basic sanity checks) ---
31 | if (
32 | !targetDirectory ||
33 | typeof targetDirectory !== 'string' || // Ensure it's a string
34 | targetDirectory === '/' ||
35 | targetDirectory === homeDir
36 | ) {
37 | log.error(
38 | `Invalid target directory received from tool layer: '${targetDirectory}'`
39 | );
40 | return {
41 | success: false,
42 | error: {
43 | code: 'INVALID_TARGET_DIRECTORY',
44 | message: `Cannot initialize project: Invalid target directory '${targetDirectory}' received. Please ensure a valid workspace/folder is open or specified.`,
45 | details: `Received args.projectRoot: ${args.projectRoot}` // Show what was received
46 | }
47 | };
48 | }
49 |
50 | // --- Proceed with validated targetDirectory ---
51 | log.info(`Validated target directory for initialization: ${targetDirectory}`);
52 |
53 | const originalCwd = process.cwd();
54 | let resultData;
55 | let success = false;
56 | let errorResult = null;
57 |
58 | log.info(
59 | `Temporarily changing CWD to ${targetDirectory} for initialization.`
60 | );
61 | process.chdir(targetDirectory); // Change CWD to the HOF-provided root
62 |
63 | enableSilentMode();
64 | try {
65 | // Construct options ONLY from the relevant flags in args
66 | // The core initializeProject operates in the current CWD, which we just set
67 | const options = {
68 | addAliases: args.addAliases,
69 | initGit: args.initGit,
70 | storeTasksInGit: args.storeTasksInGit,
71 | skipInstall: args.skipInstall,
72 | yes: true // Force yes mode
73 | };
74 |
75 | // Handle rules option with MCP-specific defaults
76 | if (Array.isArray(args.rules) && args.rules.length > 0) {
77 | options.rules = args.rules;
78 | options.rulesExplicitlyProvided = true;
79 | log.info(`Including rules: ${args.rules.join(', ')}`);
80 | } else {
81 | // For MCP initialization, default to Cursor profile only
82 | options.rules = ['cursor'];
83 | options.rulesExplicitlyProvided = true;
84 | log.info(`No rule profiles specified, defaulting to: Cursor`);
85 | }
86 |
87 | log.info(`Initializing project with options: ${JSON.stringify(options)}`);
88 | const result = await initializeProject(options); // Call core logic
89 |
90 | resultData = {
91 | message: 'Project initialized successfully.',
92 | next_step:
93 | '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.',
94 | ...result
95 | };
96 | success = true;
97 | log.info(
98 | `Project initialization completed successfully in ${targetDirectory}.`
99 | );
100 | } catch (error) {
101 | log.error(`Core initializeProject failed: ${error.message}`);
102 | errorResult = {
103 | code: 'INITIALIZATION_FAILED',
104 | message: `Core project initialization failed: ${error.message}`,
105 | details: error.stack
106 | };
107 | success = false;
108 | } finally {
109 | disableSilentMode();
110 | log.info(`Restoring original CWD: ${originalCwd}`);
111 | process.chdir(originalCwd);
112 | }
113 |
114 | if (success) {
115 | return { success: true, data: resultData };
116 | } else {
117 | return { success: false, error: errorResult };
118 | }
119 | }
120 |
```
--------------------------------------------------------------------------------
/src/ai-providers/claude-code.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * src/ai-providers/claude-code.js
3 | *
4 | * Claude Code provider implementation using the ai-sdk-provider-claude-code package.
5 | * This provider uses the local Claude Agent SDK (via Claude Code CLI) with OAuth token authentication.
6 | *
7 | * Authentication:
8 | * - Uses CLAUDE_CODE_OAUTH_TOKEN managed by Claude Code CLI
9 | * - Token is set up via: claude setup-token
10 | * - No manual API key configuration required
11 | *
12 | */
13 |
14 | import { createClaudeCode } from 'ai-sdk-provider-claude-code';
15 | import { BaseAIProvider } from './base-provider.js';
16 | import {
17 | getClaudeCodeSettingsForCommand,
18 | getSupportedModelsForProvider
19 | } from '../../scripts/modules/config-manager.js';
20 | import { execSync } from 'child_process';
21 | import { log } from '../../scripts/modules/utils.js';
22 |
23 | let _claudeCliChecked = false;
24 | let _claudeCliAvailable = null;
25 |
26 | /**
27 | * Provider for Claude Code CLI integration via AI SDK
28 | *
29 | * Features:
30 | * - No API key required (uses local Claude Code CLI)
31 | * - Supported models loaded from supported-models.json
32 | * - Command-specific configuration support
33 | */
34 | export class ClaudeCodeProvider extends BaseAIProvider {
35 | constructor() {
36 | super();
37 | this.name = 'Claude Code';
38 | // Load supported models from supported-models.json
39 | this.supportedModels = getSupportedModelsForProvider('claude-code');
40 |
41 | // Validate that models were loaded successfully
42 | if (this.supportedModels.length === 0) {
43 | log(
44 | 'warn',
45 | 'No supported models found for claude-code provider. Check supported-models.json configuration.'
46 | );
47 | }
48 |
49 | // Claude Code requires explicit JSON schema mode
50 | this.needsExplicitJsonSchema = true;
51 | // Claude Code does not support temperature parameter
52 | this.supportsTemperature = false;
53 | }
54 |
55 | /**
56 | * @returns {string} The environment variable name for API key (not used)
57 | */
58 | getRequiredApiKeyName() {
59 | return 'CLAUDE_CODE_API_KEY';
60 | }
61 |
62 | /**
63 | * @returns {boolean} False - Claude Code doesn't require API keys
64 | */
65 | isRequiredApiKey() {
66 | return false;
67 | }
68 |
69 | /**
70 | * Optional CLI availability check for Claude Code
71 | * @param {object} params - Parameters (ignored)
72 | */
73 | validateAuth(params) {
74 | // Claude Code uses local CLI - perform lightweight availability check
75 | // This is optional validation that fails fast with actionable guidance
76 | if (
77 | process.env.NODE_ENV !== 'test' &&
78 | !_claudeCliChecked &&
79 | !process.env.CLAUDE_CODE_OAUTH_TOKEN
80 | ) {
81 | try {
82 | execSync('claude --version', { stdio: 'pipe', timeout: 1000 });
83 | _claudeCliAvailable = true;
84 | } catch (error) {
85 | _claudeCliAvailable = false;
86 | log(
87 | 'warn',
88 | 'Claude Code CLI not detected. Install it with: npm install -g @anthropic-ai/claude-code'
89 | );
90 | } finally {
91 | _claudeCliChecked = true;
92 | }
93 | }
94 | }
95 |
96 | /**
97 | * Creates a Claude Code client instance
98 | * @param {object} params - Client parameters
99 | * @param {string} [params.commandName] - Command name for settings lookup
100 | * @returns {Function} Claude Code provider function
101 | * @throws {Error} If Claude Code CLI is not available or client creation fails
102 | */
103 | getClient(params = {}) {
104 | try {
105 | const settings =
106 | getClaudeCodeSettingsForCommand(params.commandName) || {};
107 |
108 | // Environment variable isolation to prevent API key conflicts
109 | // The ai-sdk-provider-claude-code SDK automatically picks up ANTHROPIC_API_KEY,
110 | // which can cause conflicts if that key is intended for the Anthropic provider.
111 | const originalAnthropicKey = process.env.ANTHROPIC_API_KEY;
112 | const claudeCodeKey = process.env.CLAUDE_CODE_API_KEY;
113 |
114 | try {
115 | // If CLAUDE_CODE_API_KEY is set, use it exclusively
116 | if (claudeCodeKey) {
117 | process.env.ANTHROPIC_API_KEY = claudeCodeKey;
118 | } else if (originalAnthropicKey) {
119 | // If only ANTHROPIC_API_KEY exists, temporarily unset it to force OAuth mode
120 | delete process.env.ANTHROPIC_API_KEY;
121 | }
122 |
123 | return createClaudeCode({
124 | defaultSettings: {
125 | // Restore previous default behavior from pre-2.0 versions
126 | // These must be inside defaultSettings to be applied by the provider
127 | systemPrompt: {
128 | type: 'preset',
129 | preset: 'claude_code'
130 | },
131 | // Enable loading of CLAUDE.md and settings.json files
132 | settingSources: ['user', 'project', 'local'],
133 | ...settings
134 | }
135 | });
136 | } finally {
137 | // Restore original environment state
138 | if (originalAnthropicKey) {
139 | process.env.ANTHROPIC_API_KEY = originalAnthropicKey;
140 | } else {
141 | delete process.env.ANTHROPIC_API_KEY;
142 | }
143 | }
144 | } catch (error) {
145 | // Provide more helpful error message
146 | const msg = String(error?.message || '');
147 | const code = error?.code;
148 | if (code === 'ENOENT' || /claude/i.test(msg)) {
149 | const enhancedError = new Error(
150 | `Claude Code CLI not available. Please install Claude Code CLI first. Original error: ${error.message}`
151 | );
152 | enhancedError.cause = error;
153 | this.handleError('Claude Code CLI initialization', enhancedError);
154 | } else {
155 | this.handleError('client initialization', error);
156 | }
157 | }
158 | }
159 |
160 | /**
161 | * @returns {string[]} List of supported model IDs
162 | */
163 | getSupportedModels() {
164 | return this.supportedModels;
165 | }
166 |
167 | /**
168 | * Check if a model is supported
169 | * @param {string} modelId - Model ID to check
170 | * @returns {boolean} True if supported
171 | */
172 | isModelSupported(modelId) {
173 | if (!modelId) return false;
174 | return this.supportedModels.includes(String(modelId).toLowerCase());
175 | }
176 | }
177 |
```
--------------------------------------------------------------------------------
/src/prompts/add-task.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "id": "add-task",
3 | "version": "1.0.0",
4 | "description": "Generate a new task based on description",
5 | "metadata": {
6 | "author": "system",
7 | "created": "2024-01-01T00:00:00Z",
8 | "updated": "2024-01-01T00:00:00Z",
9 | "tags": ["task-creation", "generation"]
10 | },
11 | "parameters": {
12 | "prompt": {
13 | "type": "string",
14 | "required": true,
15 | "description": "User's task description"
16 | },
17 | "newTaskId": {
18 | "type": "number",
19 | "required": true,
20 | "description": "ID for the new task"
21 | },
22 | "existingTasks": {
23 | "type": "array",
24 | "description": "List of existing tasks for context"
25 | },
26 | "gatheredContext": {
27 | "type": "string",
28 | "description": "Context gathered from codebase analysis"
29 | },
30 | "contextFromArgs": {
31 | "type": "string",
32 | "description": "Additional context from manual args"
33 | },
34 | "priority": {
35 | "type": "string",
36 | "default": "medium",
37 | "enum": ["high", "medium", "low"],
38 | "description": "Task priority"
39 | },
40 | "dependencies": {
41 | "type": "array",
42 | "description": "Task dependency IDs"
43 | },
44 | "useResearch": {
45 | "type": "boolean",
46 | "default": false,
47 | "description": "Use research mode"
48 | },
49 | "hasCodebaseAnalysis": {
50 | "type": "boolean",
51 | "required": false,
52 | "default": false,
53 | "description": "Whether codebase analysis is available"
54 | },
55 | "projectRoot": {
56 | "type": "string",
57 | "required": false,
58 | "default": "",
59 | "description": "Project root path for context"
60 | }
61 | },
62 | "prompts": {
63 | "default": {
64 | "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}}",
65 | "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}}"
66 | }
67 | }
68 | }
69 |
```
--------------------------------------------------------------------------------
/docs/providers/gemini-cli.md:
--------------------------------------------------------------------------------
```markdown
1 | # Gemini CLI Provider
2 |
3 | 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.
4 |
5 | ## Why Use Gemini CLI?
6 |
7 | 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:
8 |
9 | - Have an active Gemini Code Assist license (including those using the free tier offere by Google)
10 | - Want to use OAuth authentication instead of managing API keys
11 | - Have already configured authentication via `gemini` OAuth login
12 |
13 | ## Installation
14 |
15 | The provider is already included in Task Master. However, you need to install the Gemini CLI tool:
16 |
17 | ```bash
18 | # Install gemini CLI globally
19 | npm install -g @google/gemini-cli
20 | ```
21 |
22 | ## Authentication
23 |
24 | ### Primary Method: CLI Authentication (Recommended)
25 |
26 | The Gemini CLI provider is designed to use your pre-configured OAuth authentication:
27 |
28 | ```bash
29 | # Launch Gemini CLI and go through the authentication procedure
30 | gemini
31 | ```
32 |
33 | 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.
34 |
35 | ### Alternative Method: API Key
36 |
37 | While the primary use case is OAuth authentication, you can also use an API key if needed:
38 |
39 | ```bash
40 | export GEMINI_API_KEY="your-gemini-api-key"
41 | ```
42 |
43 | **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.
44 |
45 | More details on authentication steps and options can be found in the [gemini-cli GitHub README](https://github.com/google-gemini/gemini-cli).
46 |
47 | ## Configuration
48 |
49 | Use the `task-master init` command to run through the guided initialization:
50 |
51 | ```bash
52 | task-master init
53 | ```
54 |
55 | **OR**
56 |
57 | Configure `gemini-cli` as a provider using the Task Master models command:
58 |
59 | ```bash
60 | # Set gemini-cli as your main provider with gemini-2.5-pro
61 | task-master models --set-main gemini-2.5-pro --gemini-cli
62 |
63 | # Or use the faster gemini-2.5-flash model
64 | task-master models --set-main gemini-2.5-flash --gemini-cli
65 | ```
66 |
67 | You can also manually edit your `.taskmaster/config.json`:
68 |
69 | ```json
70 | {
71 | "models": {
72 | "main": {
73 | "provider": "gemini-cli",
74 | "modelId": "gemini-2.5-pro",
75 | "maxTokens": 65536,
76 | "temperature": 0.2
77 | },
78 | "research": {
79 | "provider": "gemini-cli",
80 | "modelId": "gemini-2.5-pro",
81 | "maxTokens": 65536,
82 | "temperature": 0.1
83 | },
84 | "fallback": {
85 | "provider": "gemini-cli",
86 | "modelId": "gemini-2.5-flash",
87 | "maxTokens": 65536,
88 | "temperature": 0.2
89 | }
90 | },
91 | "global": {
92 | "logLevel": "info",
93 | "debug": false,
94 | "defaultNumTasks": 10,
95 | "defaultSubtasks": 5,
96 | "defaultPriority": "medium",
97 | "projectName": "Taskmaster",
98 | "ollamaBaseURL": "http://localhost:11434/api",
99 | "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
100 | "responseLanguage": "English",
101 | "defaultTag": "master",
102 | "azureOpenaiBaseURL": "https://your-endpoint.openai.azure.com/"
103 | },
104 | "claudeCode": {}
105 | }
106 | ```
107 |
108 | ### Available Models
109 |
110 | The gemini-cli provider supports only two models:
111 | - `gemini-2.5-pro` - High performance model (1M token context window, 65,536 max output tokens)
112 | - `gemini-2.5-flash` - Fast, efficient model (1M token context window, 65,536 max output tokens)
113 |
114 | ## Usage Examples
115 |
116 | ### Basic Usage
117 |
118 | Once gemini-cli is installed and authenticated, and Task Master simply use Task Master as normal:
119 |
120 | ```bash
121 | # The provider will automatically use your OAuth credentials
122 | task-master parse-prd my-prd.txt
123 | ```
124 |
125 | ## Troubleshooting
126 |
127 | ### "Authentication failed" Error
128 |
129 | If you get an authentication error:
130 |
131 | 1. **Primary solution**: Run `gemini` to authenticate with your Google account - use `/auth` slash command in **gemini-cli** to change authentication method if desired.
132 | 2. **Check authentication status**: Run `gemini` and use `/about` to verify your Auth Method and GCP Project if applicable.
133 | 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.
134 |
135 | ### "Model not found" Error
136 |
137 | The gemini-cli provider only supports two models:
138 | - `gemini-2.5-pro`
139 | - `gemini-2.5-flash`
140 |
141 | If you need other Gemini models, use the standard `google` provider with an API key instead.
142 |
143 | ### Gemini CLI Not Found
144 |
145 | If you get a "gemini: command not found" error:
146 |
147 | ```bash
148 | # Install the Gemini CLI globally
149 | npm install -g @google/gemini-cli
150 |
151 | # Verify installation
152 | gemini --version
153 | ```
154 |
155 | ## Important Notes
156 |
157 | - **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.
158 | - **Limited Model Support**: Only `gemini-2.5-pro` and `gemini-2.5-flash` are available through gemini-cli.
159 | - **Subscription Benefits**: Using OAuth authentication allows you to leverage any subscription benefits associated with your Google account.
160 | - The provider uses the `ai-sdk-provider-gemini-cli` npm package internally.
161 | - 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
1 | /**
2 | * update-task-by-id.js
3 | * Direct function implementation for updating a single task by ID with new information
4 | */
5 |
6 | import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
7 | import {
8 | enableSilentMode,
9 | disableSilentMode,
10 | isSilentMode
11 | } from '../../../../scripts/modules/utils.js';
12 | import { createLogWrapper } from '../../tools/utils.js';
13 | import { findTasksPath } from '../utils/path-utils.js';
14 |
15 | /**
16 | * Direct function wrapper for updateTaskById with error handling.
17 | *
18 | * @param {Object} args - Command arguments containing id, prompt, useResearch, tasksJsonPath, and projectRoot.
19 | * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
20 | * @param {string} args.id - Task ID (or subtask ID like "1.2").
21 | * @param {string} args.prompt - New information/context prompt.
22 | * @param {boolean} [args.research] - Whether to use research role.
23 | * @param {boolean} [args.append] - Whether to append timestamped information instead of full update.
24 | * @param {string} [args.projectRoot] - Project root path.
25 | * @param {string} [args.tag] - Tag for the task (optional)
26 | * @param {Object} log - Logger object.
27 | * @param {Object} context - Context object containing session data.
28 | * @returns {Promise<Object>} - Result object with success status and data/error information.
29 | */
30 | export async function updateTaskByIdDirect(args, log, context = {}) {
31 | const { session } = context;
32 | // Destructure expected args, including projectRoot
33 | const { tasksJsonPath, id, prompt, research, append, projectRoot, tag } =
34 | args;
35 |
36 | const logWrapper = createLogWrapper(log);
37 |
38 | try {
39 | logWrapper.info(
40 | `Updating task by ID via direct function. ID: ${id}, ProjectRoot: ${projectRoot}`
41 | );
42 |
43 | // Check required parameters (id and prompt)
44 | if (!id) {
45 | const errorMessage =
46 | 'No task ID specified. Please provide a task ID to update.';
47 | logWrapper.error(errorMessage);
48 | return {
49 | success: false,
50 | error: { code: 'INPUT_VALIDATION_ERROR', message: errorMessage }
51 | };
52 | }
53 |
54 | if (!prompt) {
55 | const errorMessage =
56 | 'No prompt specified. Please provide a prompt with new information for the task update.';
57 | logWrapper.error(errorMessage);
58 | return {
59 | success: false,
60 | error: { code: 'INPUT_VALIDATION_ERROR', message: errorMessage }
61 | };
62 | }
63 |
64 | // Parse taskId - handle numeric, alphanumeric, and subtask IDs
65 | let taskId;
66 | if (typeof id === 'string') {
67 | // Keep ID as string - supports numeric (1, 2), alphanumeric (TAS-49, JIRA-123), and subtask IDs (1.2, TAS-49.1)
68 | taskId = id;
69 | } else if (typeof id === 'number') {
70 | // Convert number to string for consistency
71 | taskId = String(id);
72 | } else {
73 | const errorMessage = `Invalid task ID type: ${typeof id}. Task ID must be a string or number.`;
74 | logWrapper.error(errorMessage);
75 | return {
76 | success: false,
77 | error: { code: 'INPUT_VALIDATION_ERROR', message: errorMessage }
78 | };
79 | }
80 |
81 | // Resolve tasks.json path - use provided or find it
82 | const tasksPath =
83 | tasksJsonPath ||
84 | findTasksPath({ projectRoot, file: args.file }, logWrapper);
85 | if (!tasksPath) {
86 | const errorMessage = 'tasks.json path could not be resolved.';
87 | logWrapper.error(errorMessage);
88 | return {
89 | success: false,
90 | error: { code: 'INPUT_VALIDATION_ERROR', message: errorMessage }
91 | };
92 | }
93 |
94 | // Get research flag
95 | const useResearch = research === true;
96 |
97 | logWrapper.info(
98 | `Updating task with ID ${taskId} with prompt "${prompt}" and research: ${useResearch}`
99 | );
100 |
101 | const wasSilent = isSilentMode();
102 | if (!wasSilent) {
103 | enableSilentMode();
104 | }
105 |
106 | try {
107 | // Call legacy script which handles both API and file storage via bridge
108 | const coreResult = await updateTaskById(
109 | tasksPath,
110 | taskId,
111 | prompt,
112 | useResearch,
113 | {
114 | mcpLog: logWrapper,
115 | session,
116 | projectRoot,
117 | tag,
118 | commandName: 'update-task',
119 | outputType: 'mcp'
120 | },
121 | 'json',
122 | append || false
123 | );
124 |
125 | // Check if the core function returned null or an object without success
126 | if (!coreResult || coreResult.updatedTask === null) {
127 | const message = `Task ${taskId} was not updated (likely already completed).`;
128 | logWrapper.info(message);
129 | return {
130 | success: true,
131 | data: {
132 | message: message,
133 | taskId: taskId,
134 | updated: false,
135 | telemetryData: coreResult?.telemetryData,
136 | tagInfo: coreResult?.tagInfo
137 | }
138 | };
139 | }
140 |
141 | const successMessage = `Successfully updated task with ID ${taskId} based on the prompt`;
142 | logWrapper.info(successMessage);
143 | return {
144 | success: true,
145 | data: {
146 | message: successMessage,
147 | taskId: taskId,
148 | tasksPath: tasksPath,
149 | useResearch: useResearch,
150 | updated: true,
151 | updatedTask: coreResult.updatedTask,
152 | telemetryData: coreResult.telemetryData,
153 | tagInfo: coreResult.tagInfo
154 | }
155 | };
156 | } catch (error) {
157 | logWrapper.error(`Error updating task by ID: ${error.message}`);
158 | return {
159 | success: false,
160 | error: {
161 | code: 'UPDATE_TASK_CORE_ERROR',
162 | message: error.message || 'Unknown error updating task'
163 | }
164 | };
165 | } finally {
166 | if (!wasSilent && isSilentMode()) {
167 | disableSilentMode();
168 | }
169 | }
170 | } catch (error) {
171 | logWrapper.error(`Setup error in updateTaskByIdDirect: ${error.message}`);
172 | if (isSilentMode()) disableSilentMode();
173 | return {
174 | success: false,
175 | error: {
176 | code: 'DIRECT_FUNCTION_SETUP_ERROR',
177 | message: error.message || 'Unknown setup error'
178 | }
179 | };
180 | }
181 | }
182 |
```
--------------------------------------------------------------------------------
/apps/cli/tests/unit/commands/autopilot/shared.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview Unit tests for autopilot shared utilities
3 | */
4 |
5 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6 | import {
7 | OutputFormatter,
8 | parseSubtasks,
9 | validateTaskId
10 | } from '../../../../src/commands/autopilot/shared.js';
11 |
12 | // Mock fs-extra
13 | vi.mock('fs-extra', () => ({
14 | default: {
15 | pathExists: vi.fn(),
16 | readJSON: vi.fn(),
17 | writeJSON: vi.fn(),
18 | ensureDir: vi.fn(),
19 | remove: vi.fn()
20 | },
21 | pathExists: vi.fn(),
22 | readJSON: vi.fn(),
23 | writeJSON: vi.fn(),
24 | ensureDir: vi.fn(),
25 | remove: vi.fn()
26 | }));
27 |
28 | describe('Autopilot Shared Utilities', () => {
29 | const projectRoot = '/test/project';
30 | const statePath = `${projectRoot}/.taskmaster/workflow-state.json`;
31 |
32 | beforeEach(() => {
33 | vi.clearAllMocks();
34 | });
35 |
36 | afterEach(() => {
37 | vi.restoreAllMocks();
38 | });
39 |
40 | describe('validateTaskId', () => {
41 | it('should validate simple task IDs', () => {
42 | expect(validateTaskId('1')).toBe(true);
43 | expect(validateTaskId('10')).toBe(true);
44 | expect(validateTaskId('999')).toBe(true);
45 | });
46 |
47 | it('should validate subtask IDs', () => {
48 | expect(validateTaskId('1.1')).toBe(true);
49 | expect(validateTaskId('1.2')).toBe(true);
50 | expect(validateTaskId('10.5')).toBe(true);
51 | });
52 |
53 | it('should validate nested subtask IDs', () => {
54 | expect(validateTaskId('1.1.1')).toBe(true);
55 | expect(validateTaskId('1.2.3')).toBe(true);
56 | });
57 |
58 | it('should reject invalid formats', () => {
59 | expect(validateTaskId('')).toBe(false);
60 | expect(validateTaskId('abc')).toBe(false);
61 | expect(validateTaskId('1.')).toBe(false);
62 | expect(validateTaskId('.1')).toBe(false);
63 | expect(validateTaskId('1..2')).toBe(false);
64 | expect(validateTaskId('1.2.3.')).toBe(false);
65 | });
66 | });
67 |
68 | describe('parseSubtasks', () => {
69 | it('should parse subtasks from task data', () => {
70 | const task = {
71 | id: '1',
72 | title: 'Test Task',
73 | subtasks: [
74 | { id: '1', title: 'Subtask 1', status: 'pending' },
75 | { id: '2', title: 'Subtask 2', status: 'done' },
76 | { id: '3', title: 'Subtask 3', status: 'in-progress' }
77 | ]
78 | };
79 |
80 | const result = parseSubtasks(task, 5);
81 |
82 | expect(result).toHaveLength(3);
83 | expect(result[0]).toEqual({
84 | id: '1',
85 | title: 'Subtask 1',
86 | status: 'pending',
87 | attempts: 0,
88 | maxAttempts: 5
89 | });
90 | expect(result[1]).toEqual({
91 | id: '2',
92 | title: 'Subtask 2',
93 | status: 'completed',
94 | attempts: 0,
95 | maxAttempts: 5
96 | });
97 | });
98 |
99 | it('should return empty array for missing subtasks', () => {
100 | const task = { id: '1', title: 'Test Task' };
101 | expect(parseSubtasks(task)).toEqual([]);
102 | });
103 |
104 | it('should use default maxAttempts', () => {
105 | const task = {
106 | subtasks: [{ id: '1', title: 'Subtask 1', status: 'pending' }]
107 | };
108 |
109 | const result = parseSubtasks(task);
110 | expect(result[0].maxAttempts).toBe(3);
111 | });
112 | });
113 |
114 | // State persistence tests omitted - covered in integration tests
115 |
116 | describe('OutputFormatter', () => {
117 | let consoleLogSpy: any;
118 | let consoleErrorSpy: any;
119 |
120 | beforeEach(() => {
121 | consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
122 | consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
123 | });
124 |
125 | afterEach(() => {
126 | consoleLogSpy.mockRestore();
127 | consoleErrorSpy.mockRestore();
128 | });
129 |
130 | describe('JSON mode', () => {
131 | it('should output JSON for success', () => {
132 | const formatter = new OutputFormatter(true);
133 | formatter.success('Test message', { key: 'value' });
134 |
135 | expect(consoleLogSpy).toHaveBeenCalled();
136 | const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
137 | expect(output.success).toBe(true);
138 | expect(output.message).toBe('Test message');
139 | expect(output.key).toBe('value');
140 | });
141 |
142 | it('should output JSON for error', () => {
143 | const formatter = new OutputFormatter(true);
144 | formatter.error('Error message', { code: 'ERR001' });
145 |
146 | expect(consoleErrorSpy).toHaveBeenCalled();
147 | const output = JSON.parse(consoleErrorSpy.mock.calls[0][0]);
148 | expect(output.error).toBe('Error message');
149 | expect(output.code).toBe('ERR001');
150 | });
151 |
152 | it('should output JSON for data', () => {
153 | const formatter = new OutputFormatter(true);
154 | formatter.output({ test: 'data' });
155 |
156 | expect(consoleLogSpy).toHaveBeenCalled();
157 | const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
158 | expect(output.test).toBe('data');
159 | });
160 | });
161 |
162 | describe('Text mode', () => {
163 | it('should output formatted text for success', () => {
164 | const formatter = new OutputFormatter(false);
165 | formatter.success('Test message');
166 |
167 | expect(consoleLogSpy).toHaveBeenCalledWith(
168 | expect.stringContaining('✓ Test message')
169 | );
170 | });
171 |
172 | it('should output formatted text for error', () => {
173 | const formatter = new OutputFormatter(false);
174 | formatter.error('Error message');
175 |
176 | expect(consoleErrorSpy).toHaveBeenCalledWith(
177 | expect.stringContaining('Error: Error message')
178 | );
179 | });
180 |
181 | it('should output formatted text for warning', () => {
182 | const consoleWarnSpy = vi
183 | .spyOn(console, 'warn')
184 | .mockImplementation(() => {});
185 | const formatter = new OutputFormatter(false);
186 | formatter.warning('Warning message');
187 |
188 | expect(consoleWarnSpy).toHaveBeenCalledWith(
189 | expect.stringContaining('⚠ Warning message')
190 | );
191 | consoleWarnSpy.mockRestore();
192 | });
193 |
194 | it('should not output info in JSON mode', () => {
195 | const formatter = new OutputFormatter(true);
196 | formatter.info('Info message');
197 |
198 | expect(consoleLogSpy).not.toHaveBeenCalled();
199 | });
200 | });
201 | });
202 | });
203 |
```
--------------------------------------------------------------------------------
/.github/workflows/claude-docs-updater.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: Claude Documentation Updater
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | commit_sha:
7 | description: 'The commit SHA that triggered this update'
8 | required: true
9 | type: string
10 | commit_message:
11 | description: 'The commit message'
12 | required: true
13 | type: string
14 | changed_files:
15 | description: 'List of changed files'
16 | required: true
17 | type: string
18 | commit_diff:
19 | description: 'Diff summary of changes'
20 | required: true
21 | type: string
22 |
23 | jobs:
24 | update-docs:
25 | runs-on: ubuntu-latest
26 | permissions:
27 | contents: write
28 | pull-requests: write
29 | issues: write
30 | steps:
31 | - name: Checkout repository
32 | uses: actions/checkout@v4
33 | with:
34 | ref: next
35 | fetch-depth: 0 # Need full history to checkout specific commit
36 |
37 | - name: Create docs update branch
38 | id: create-branch
39 | run: |
40 | BRANCH_NAME="docs/auto-update-$(date +%Y%m%d-%H%M%S)"
41 | git checkout -b $BRANCH_NAME
42 | echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
43 |
44 | - name: Run Claude Code to Update Documentation
45 | uses: anthropics/claude-code-action@beta
46 | with:
47 | anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
48 | timeout_minutes: "30"
49 | mode: "agent"
50 | github_token: ${{ secrets.GITHUB_TOKEN }}
51 | experimental_allowed_domains: |
52 | .anthropic.com
53 | .github.com
54 | api.github.com
55 | .githubusercontent.com
56 | registry.npmjs.org
57 | .task-master.dev
58 | base_branch: "next"
59 | direct_prompt: |
60 | You are a documentation specialist. Analyze the recent changes pushed to the 'next' branch and update the documentation accordingly.
61 |
62 | Recent changes:
63 | - Commit: ${{ inputs.commit_message }}
64 | - Changed files:
65 | ${{ inputs.changed_files }}
66 |
67 | - Changes summary:
68 | ${{ inputs.commit_diff }}
69 |
70 | Your task:
71 | 1. Analyze the changes to understand what functionality was added, modified, or removed
72 | 2. Check if these changes require documentation updates in apps/docs/
73 | 3. If documentation updates are needed:
74 | - Update relevant documentation files in apps/docs/
75 | - Ensure examples are updated if APIs changed
76 | - Update any configuration documentation if config options changed
77 | - Add new documentation pages if new features were added
78 | - Update the changelog or release notes if applicable
79 | 4. If no documentation updates are needed, skip creating changes
80 |
81 | Guidelines:
82 | - Focus only on user-facing changes that need documentation
83 | - Keep documentation clear, concise, and helpful
84 | - Include code examples where appropriate
85 | - Maintain consistent documentation style with existing docs
86 | - Don't document internal implementation details unless they affect users
87 | - Update navigation/menu files if new pages are added
88 |
89 | Only make changes if the documentation truly needs updating based on the code changes.
90 |
91 | - name: Check if changes were made
92 | id: check-changes
93 | run: |
94 | if git diff --quiet; then
95 | echo "has_changes=false" >> $GITHUB_OUTPUT
96 | else
97 | echo "has_changes=true" >> $GITHUB_OUTPUT
98 | git add -A
99 | git config --local user.email "github-actions[bot]@users.noreply.github.com"
100 | git config --local user.name "github-actions[bot]"
101 | git commit -m "docs: auto-update documentation based on changes in next branch
102 |
103 | This PR was automatically generated to update documentation based on recent changes.
104 |
105 | Original commit: ${{ inputs.commit_message }}
106 |
107 | Co-authored-by: Claude <[email protected]>"
108 | fi
109 |
110 | - name: Push changes and create PR
111 | if: steps.check-changes.outputs.has_changes == 'true'
112 | env:
113 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
114 | run: |
115 | git push origin ${{ steps.create-branch.outputs.branch_name }}
116 |
117 | # Create PR using GitHub CLI
118 | gh pr create \
119 | --title "docs: update documentation for recent changes" \
120 | --body "## 📚 Documentation Update
121 |
122 | This PR automatically updates documentation based on recent changes merged to the \`next\` branch.
123 |
124 | ### Original Changes
125 | **Commit:** ${{ inputs.commit_sha }}
126 | **Message:** ${{ inputs.commit_message }}
127 |
128 | ### Changed Files in Original Commit
129 | \`\`\`
130 | ${{ inputs.changed_files }}
131 | \`\`\`
132 |
133 | ### Documentation Updates
134 | This PR includes documentation updates to reflect the changes above. Please review to ensure:
135 | - [ ] Documentation accurately reflects the changes
136 | - [ ] Examples are correct and working
137 | - [ ] No important details are missing
138 | - [ ] Style is consistent with existing documentation
139 |
140 | ---
141 | *This PR was automatically generated by Claude Code GitHub Action*" \
142 | --base next \
143 | --head ${{ steps.create-branch.outputs.branch_name }} \
144 | --label "documentation" \
145 | --label "automated"
146 |
```
--------------------------------------------------------------------------------
/scripts/modules/task-manager/set-task-status.js:
--------------------------------------------------------------------------------
```javascript
1 | import path from 'path';
2 | import chalk from 'chalk';
3 | import boxen from 'boxen';
4 |
5 | import {
6 | log,
7 | readJSON,
8 | writeJSON,
9 | findTaskById,
10 | ensureTagMetadata
11 | } from '../utils.js';
12 | import { displayBanner } from '../ui.js';
13 | import { validateTaskDependencies } from '../dependency-manager.js';
14 | import { getDebugFlag } from '../config-manager.js';
15 | import updateSingleTaskStatus from './update-single-task-status.js';
16 | import generateTaskFiles from './generate-task-files.js';
17 | import {
18 | isValidTaskStatus,
19 | TASK_STATUS_OPTIONS
20 | } from '../../../src/constants/task-status.js';
21 |
22 | /**
23 | * Set the status of a task
24 | * @param {string} tasksPath - Path to the tasks.json file
25 | * @param {string} taskIdInput - Task ID(s) to update
26 | * @param {string} newStatus - New status
27 | * @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot for tag resolution)
28 | * @param {string} [options.projectRoot] - Project root path
29 | * @param {string} [options.tag] - Optional tag to override current tag resolution
30 | * @param {string} [options.mcpLog] - MCP logger object
31 | * @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
32 | */
33 | async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
34 | const { projectRoot, tag } = options;
35 | try {
36 | if (!isValidTaskStatus(newStatus)) {
37 | throw new Error(
38 | `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}`
39 | );
40 | }
41 | // Determine if we're in MCP mode by checking for mcpLog
42 | const isMcpMode = !!options?.mcpLog;
43 |
44 | // Only display UI elements if not in MCP mode
45 | if (!isMcpMode) {
46 | console.log(
47 | boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), {
48 | padding: 1,
49 | borderColor: 'blue',
50 | borderStyle: 'round'
51 | })
52 | );
53 | }
54 |
55 | log('info', `Reading tasks from ${tasksPath}...`);
56 |
57 | // Read the raw data without tag resolution to preserve tagged structure
58 | let rawData = readJSON(tasksPath, projectRoot, tag); // No tag parameter
59 |
60 | // Handle the case where readJSON returns resolved data with _rawTaggedData
61 | if (rawData && rawData._rawTaggedData) {
62 | // Use the raw tagged data and discard the resolved view
63 | rawData = rawData._rawTaggedData;
64 | }
65 |
66 | // Ensure the tag exists in the raw data
67 | if (!rawData || !rawData[tag] || !Array.isArray(rawData[tag].tasks)) {
68 | throw new Error(
69 | `Invalid tasks file or tag "${tag}" not found at ${tasksPath}`
70 | );
71 | }
72 |
73 | // Get the tasks for the current tag
74 | const data = {
75 | tasks: rawData[tag].tasks,
76 | tag,
77 | _rawTaggedData: rawData
78 | };
79 |
80 | if (!data || !data.tasks) {
81 | throw new Error(`No valid tasks found in ${tasksPath}`);
82 | }
83 |
84 | // Handle multiple task IDs (comma-separated)
85 | const taskIds = taskIdInput.split(',').map((id) => id.trim());
86 | const updatedTasks = [];
87 |
88 | // Update each task and capture old status for display
89 | for (const id of taskIds) {
90 | // Capture old status before updating
91 | let oldStatus = 'unknown';
92 |
93 | if (id.includes('.')) {
94 | // Handle subtask
95 | const [parentId, subtaskId] = id
96 | .split('.')
97 | .map((id) => parseInt(id, 10));
98 | const parentTask = data.tasks.find((t) => t.id === parentId);
99 | if (parentTask?.subtasks) {
100 | const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
101 | oldStatus = subtask?.status || 'pending';
102 | }
103 | } else {
104 | // Handle regular task
105 | const taskId = parseInt(id, 10);
106 | const task = data.tasks.find((t) => t.id === taskId);
107 | oldStatus = task?.status || 'pending';
108 | }
109 |
110 | await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode);
111 | updatedTasks.push({ id, oldStatus, newStatus });
112 | }
113 |
114 | // Update the raw data structure with the modified tasks
115 | rawData[tag].tasks = data.tasks;
116 |
117 | // Ensure the tag has proper metadata
118 | ensureTagMetadata(rawData[tag], {
119 | description: `Tasks for ${tag} context`
120 | });
121 |
122 | // Write the updated raw data back to the file
123 | // The writeJSON function will automatically filter out _rawTaggedData
124 | writeJSON(tasksPath, rawData, projectRoot, tag);
125 |
126 | // Validate dependencies after status update
127 | log('info', 'Validating dependencies after status update...');
128 | validateTaskDependencies(data.tasks);
129 |
130 | // Generate individual task files
131 | // log('info', 'Regenerating task files...');
132 | // await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
133 | // mcpLog: options.mcpLog
134 | // });
135 |
136 | // Display success message - only in CLI mode
137 | if (!isMcpMode) {
138 | for (const updateInfo of updatedTasks) {
139 | const { id, oldStatus, newStatus: updatedStatus } = updateInfo;
140 |
141 | console.log(
142 | boxen(
143 | chalk.white.bold(`Successfully updated task ${id} status:`) +
144 | '\n' +
145 | `From: ${chalk.yellow(oldStatus)}\n` +
146 | `To: ${chalk.green(updatedStatus)}`,
147 | { padding: 1, borderColor: 'green', borderStyle: 'round' }
148 | )
149 | );
150 | }
151 | }
152 |
153 | // Return success value for programmatic use
154 | return {
155 | success: true,
156 | updatedTasks: updatedTasks.map(({ id, oldStatus, newStatus }) => ({
157 | id,
158 | oldStatus,
159 | newStatus
160 | }))
161 | };
162 | } catch (error) {
163 | log('error', `Error setting task status: ${error.message}`);
164 |
165 | // Only show error UI in CLI mode
166 | if (!options?.mcpLog) {
167 | console.error(chalk.red(`Error: ${error.message}`));
168 |
169 | // Pass session to getDebugFlag
170 | if (getDebugFlag(options?.session)) {
171 | // Use getter
172 | console.error(error);
173 | }
174 |
175 | process.exit(1);
176 | } else {
177 | // In MCP mode, throw the error for the caller to handle
178 | throw error;
179 | }
180 | }
181 | }
182 |
183 | export default setTaskStatus;
184 |
```
--------------------------------------------------------------------------------
/tests/unit/scripts/modules/dependency-manager/fix-dependencies-command.test.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Unit test to ensure fixDependenciesCommand writes JSON with the correct
3 | * projectRoot and tag arguments so that tag data is preserved.
4 | */
5 |
6 | import { jest } from '@jest/globals';
7 |
8 | // Mock process.exit to prevent test termination
9 | const mockProcessExit = jest.fn();
10 | const originalExit = process.exit;
11 | process.exit = mockProcessExit;
12 |
13 | // Mock utils.js BEFORE importing the module under test
14 | jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
15 | readJSON: jest.fn(),
16 | writeJSON: jest.fn(),
17 | log: jest.fn(),
18 | findProjectRoot: jest.fn(() => '/mock/project/root'),
19 | getCurrentTag: jest.fn(() => 'master'),
20 | taskExists: jest.fn(() => true),
21 | formatTaskId: jest.fn((id) => id),
22 | findCycles: jest.fn(() => []),
23 | traverseDependencies: jest.fn((sourceTasks, allTasks, options = {}) => []),
24 | isSilentMode: jest.fn(() => true),
25 | resolveTag: jest.fn(() => 'master'),
26 | getTasksForTag: jest.fn(() => []),
27 | setTasksForTag: jest.fn(),
28 | enableSilentMode: jest.fn(),
29 | disableSilentMode: jest.fn(),
30 | isEmpty: jest.fn((value) => {
31 | if (value === null || value === undefined) return true;
32 | if (Array.isArray(value)) return value.length === 0;
33 | if (typeof value === 'object' && value !== null)
34 | return Object.keys(value).length === 0;
35 | return false; // Not an array or object
36 | }),
37 | resolveEnvVariable: jest.fn()
38 | }));
39 |
40 | // Mock ui.js
41 | jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
42 | displayBanner: jest.fn(),
43 | formatDependenciesWithStatus: jest.fn()
44 | }));
45 |
46 | // Mock task-manager.js
47 | jest.unstable_mockModule(
48 | '../../../../../scripts/modules/task-manager.js',
49 | () => ({
50 | generateTaskFiles: jest.fn()
51 | })
52 | );
53 |
54 | // Mock external libraries
55 | jest.unstable_mockModule('chalk', () => ({
56 | default: {
57 | green: jest.fn((text) => text),
58 | cyan: jest.fn((text) => text),
59 | bold: jest.fn((text) => text)
60 | }
61 | }));
62 |
63 | jest.unstable_mockModule('boxen', () => ({
64 | default: jest.fn((text) => text)
65 | }));
66 |
67 | // Import the mocked modules
68 | const { readJSON, writeJSON, log, taskExists } = await import(
69 | '../../../../../scripts/modules/utils.js'
70 | );
71 |
72 | // Import the module under test
73 | const { fixDependenciesCommand } = await import(
74 | '../../../../../scripts/modules/dependency-manager.js'
75 | );
76 |
77 | describe('fixDependenciesCommand tag preservation', () => {
78 | beforeEach(() => {
79 | jest.clearAllMocks();
80 | mockProcessExit.mockClear();
81 | });
82 |
83 | afterAll(() => {
84 | // Restore original process.exit
85 | process.exit = originalExit;
86 | });
87 |
88 | it('calls writeJSON with projectRoot and tag parameters when changes are made', async () => {
89 | const tasksPath = '/mock/tasks.json';
90 | const projectRoot = '/mock/project/root';
91 | const tag = 'master';
92 |
93 | // Mock data WITH dependency issues to trigger writeJSON
94 | const tasksDataWithIssues = {
95 | tasks: [
96 | {
97 | id: 1,
98 | title: 'Task 1',
99 | dependencies: [999] // Non-existent dependency to trigger fix
100 | },
101 | {
102 | id: 2,
103 | title: 'Task 2',
104 | dependencies: []
105 | }
106 | ],
107 | tag: 'master',
108 | _rawTaggedData: {
109 | master: {
110 | tasks: [
111 | {
112 | id: 1,
113 | title: 'Task 1',
114 | dependencies: [999]
115 | }
116 | ]
117 | }
118 | }
119 | };
120 |
121 | readJSON.mockReturnValue(tasksDataWithIssues);
122 | taskExists.mockReturnValue(false); // Make dependency invalid to trigger fix
123 |
124 | await fixDependenciesCommand(tasksPath, {
125 | context: { projectRoot, tag }
126 | });
127 |
128 | // Verify readJSON was called with correct parameters
129 | expect(readJSON).toHaveBeenCalledWith(tasksPath, projectRoot, tag);
130 |
131 | // Verify writeJSON was called (should be triggered by removing invalid dependency)
132 | expect(writeJSON).toHaveBeenCalled();
133 |
134 | // Check the writeJSON call parameters
135 | const writeJSONCalls = writeJSON.mock.calls;
136 | const lastWriteCall = writeJSONCalls[writeJSONCalls.length - 1];
137 | const [calledPath, _data, calledProjectRoot, calledTag] = lastWriteCall;
138 |
139 | expect(calledPath).toBe(tasksPath);
140 | expect(calledProjectRoot).toBe(projectRoot);
141 | expect(calledTag).toBe(tag);
142 |
143 | // Verify process.exit was NOT called (meaning the function succeeded)
144 | expect(mockProcessExit).not.toHaveBeenCalled();
145 | });
146 |
147 | it('does not call writeJSON when no changes are needed', async () => {
148 | const tasksPath = '/mock/tasks.json';
149 | const projectRoot = '/mock/project/root';
150 | const tag = 'master';
151 |
152 | // Mock data WITHOUT dependency issues (no changes needed)
153 | const cleanTasksData = {
154 | tasks: [
155 | {
156 | id: 1,
157 | title: 'Task 1',
158 | dependencies: [] // Clean, no issues
159 | }
160 | ],
161 | tag: 'master'
162 | };
163 |
164 | readJSON.mockReturnValue(cleanTasksData);
165 | taskExists.mockReturnValue(true); // All dependencies exist
166 |
167 | await fixDependenciesCommand(tasksPath, {
168 | context: { projectRoot, tag }
169 | });
170 |
171 | // Verify readJSON was called
172 | expect(readJSON).toHaveBeenCalledWith(tasksPath, projectRoot, tag);
173 |
174 | // Verify writeJSON was NOT called (no changes needed)
175 | expect(writeJSON).not.toHaveBeenCalled();
176 |
177 | // Verify process.exit was NOT called
178 | expect(mockProcessExit).not.toHaveBeenCalled();
179 | });
180 |
181 | it('handles early exit when no valid tasks found', async () => {
182 | const tasksPath = '/mock/tasks.json';
183 |
184 | // Mock invalid data to trigger early exit
185 | readJSON.mockReturnValue(null);
186 |
187 | await fixDependenciesCommand(tasksPath, {
188 | context: { projectRoot: '/mock', tag: 'master' }
189 | });
190 |
191 | // Verify readJSON was called
192 | expect(readJSON).toHaveBeenCalled();
193 |
194 | // Verify writeJSON was NOT called (early exit)
195 | expect(writeJSON).not.toHaveBeenCalled();
196 |
197 | // Verify process.exit WAS called due to invalid data
198 | expect(mockProcessExit).toHaveBeenCalledWith(1);
199 | });
200 | });
201 |
```
--------------------------------------------------------------------------------
/src/profiles/zed.js:
--------------------------------------------------------------------------------
```javascript
1 | // Zed profile for rule-transformer
2 | import path from 'path';
3 | import fs from 'fs';
4 | import { isSilentMode, log } from '../../scripts/modules/utils.js';
5 | import { createProfile } from './base-profile.js';
6 |
7 | /**
8 | * Transform standard MCP config format to Zed format
9 | * @param {Object} mcpConfig - Standard MCP configuration object
10 | * @returns {Object} - Transformed Zed configuration object
11 | */
12 | function transformToZedFormat(mcpConfig) {
13 | const zedConfig = {};
14 |
15 | // Transform mcpServers to context_servers
16 | if (mcpConfig.mcpServers) {
17 | zedConfig['context_servers'] = mcpConfig.mcpServers;
18 | }
19 |
20 | // Preserve any other existing settings
21 | for (const [key, value] of Object.entries(mcpConfig)) {
22 | if (key !== 'mcpServers') {
23 | zedConfig[key] = value;
24 | }
25 | }
26 |
27 | return zedConfig;
28 | }
29 |
30 | // Lifecycle functions for Zed profile
31 | function onAddRulesProfile(targetDir, assetsDir) {
32 | // MCP transformation will be handled in onPostConvertRulesProfile
33 | // File copying is handled by the base profile via fileMap
34 | }
35 |
36 | function onRemoveRulesProfile(targetDir) {
37 | // Clean up .rules (Zed uses .rules directly in root)
38 | const userRulesFile = path.join(targetDir, '.rules');
39 |
40 | try {
41 | // Remove Task Master .rules
42 | if (fs.existsSync(userRulesFile)) {
43 | fs.rmSync(userRulesFile, { force: true });
44 | log('debug', `[Zed] Removed ${userRulesFile}`);
45 | }
46 | } catch (err) {
47 | log('error', `[Zed] Failed to remove Zed instructions: ${err.message}`);
48 | }
49 |
50 | // MCP Removal: Remove context_servers section
51 | const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
52 |
53 | if (!fs.existsSync(mcpConfigPath)) {
54 | log('debug', '[Zed] No .zed/settings.json found to clean up');
55 | return;
56 | }
57 |
58 | try {
59 | // Read the current config
60 | const configContent = fs.readFileSync(mcpConfigPath, 'utf8');
61 | const config = JSON.parse(configContent);
62 |
63 | // Check if it has the context_servers section and task-master-ai server
64 | if (
65 | config['context_servers'] &&
66 | config['context_servers']['task-master-ai']
67 | ) {
68 | // Remove task-master-ai server
69 | delete config['context_servers']['task-master-ai'];
70 |
71 | // Check if there are other MCP servers in context_servers
72 | const remainingServers = Object.keys(config['context_servers']);
73 |
74 | if (remainingServers.length === 0) {
75 | // No other servers, remove entire context_servers section
76 | delete config['context_servers'];
77 | log('debug', '[Zed] Removed empty context_servers section');
78 | }
79 |
80 | // Check if config is now empty
81 | const remainingKeys = Object.keys(config);
82 |
83 | if (remainingKeys.length === 0) {
84 | // Config is empty, remove entire file
85 | fs.rmSync(mcpConfigPath, { force: true });
86 | log('info', '[Zed] Removed empty settings.json file');
87 |
88 | // Check if .zed directory is empty
89 | const zedDirPath = path.join(targetDir, '.zed');
90 | if (fs.existsSync(zedDirPath)) {
91 | const remainingContents = fs.readdirSync(zedDirPath);
92 | if (remainingContents.length === 0) {
93 | fs.rmSync(zedDirPath, { recursive: true, force: true });
94 | log('debug', '[Zed] Removed empty .zed directory');
95 | }
96 | }
97 | } else {
98 | // Write back the modified config
99 | fs.writeFileSync(
100 | mcpConfigPath,
101 | JSON.stringify(config, null, '\t') + '\n'
102 | );
103 | log(
104 | 'info',
105 | '[Zed] Removed TaskMaster from settings.json, preserved other configurations'
106 | );
107 | }
108 | } else {
109 | log('debug', '[Zed] TaskMaster not found in context_servers');
110 | }
111 | } catch (error) {
112 | log('error', `[Zed] Failed to clean up settings.json: ${error.message}`);
113 | }
114 | }
115 |
116 | function onPostConvertRulesProfile(targetDir, assetsDir) {
117 | // Handle .rules setup (same as onAddRulesProfile)
118 | onAddRulesProfile(targetDir, assetsDir);
119 |
120 | // Transform MCP config to Zed format
121 | const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
122 |
123 | if (!fs.existsSync(mcpConfigPath)) {
124 | log('debug', '[Zed] No .zed/settings.json found to transform');
125 | return;
126 | }
127 |
128 | try {
129 | // Read the generated standard MCP config
130 | const mcpConfigContent = fs.readFileSync(mcpConfigPath, 'utf8');
131 | const mcpConfig = JSON.parse(mcpConfigContent);
132 |
133 | // Check if it's already in Zed format (has context_servers)
134 | if (mcpConfig['context_servers']) {
135 | log(
136 | 'info',
137 | '[Zed] settings.json already in Zed format, skipping transformation'
138 | );
139 | return;
140 | }
141 |
142 | // Transform to Zed format
143 | const zedConfig = transformToZedFormat(mcpConfig);
144 |
145 | // Add "source": "custom" to task-master-ai server for Zed
146 | if (
147 | zedConfig['context_servers'] &&
148 | zedConfig['context_servers']['task-master-ai']
149 | ) {
150 | zedConfig['context_servers']['task-master-ai'].source = 'custom';
151 | }
152 |
153 | // Write back the transformed config with proper formatting
154 | fs.writeFileSync(
155 | mcpConfigPath,
156 | JSON.stringify(zedConfig, null, '\t') + '\n'
157 | );
158 |
159 | log('info', '[Zed] Transformed settings.json to Zed format');
160 | log('debug', '[Zed] Renamed mcpServers to context_servers');
161 | } catch (error) {
162 | log('error', `[Zed] Failed to transform settings.json: ${error.message}`);
163 | }
164 | }
165 |
166 | // Create and export zed profile using the base factory
167 | export const zedProfile = createProfile({
168 | name: 'zed',
169 | displayName: 'Zed',
170 | url: 'zed.dev',
171 | docsUrl: 'zed.dev/docs',
172 | profileDir: '.zed',
173 | rulesDir: '.',
174 | mcpConfig: true,
175 | mcpConfigName: 'settings.json',
176 | includeDefaultRules: false,
177 | fileMap: {
178 | 'AGENTS.md': '.rules'
179 | },
180 | onAdd: onAddRulesProfile,
181 | onRemove: onRemoveRulesProfile,
182 | onPostConvert: onPostConvertRulesProfile
183 | });
184 |
185 | // Export lifecycle functions separately to avoid naming conflicts
186 | export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };
187 |
```
--------------------------------------------------------------------------------
/apps/mcp/src/tools/autopilot/start.tool.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview autopilot-start MCP tool
3 | * Initialize and start a new TDD workflow for a task
4 | */
5 |
6 | import { z } from 'zod';
7 | import {
8 | handleApiResult,
9 | withNormalizedProjectRoot
10 | } from '../../shared/utils.js';
11 | import type { MCPContext } from '../../shared/types.js';
12 | import { createTmCore } from '@tm/core';
13 | import { WorkflowService } from '@tm/core';
14 | import type { FastMCP } from 'fastmcp';
15 |
16 | const StartWorkflowSchema = z.object({
17 | taskId: z
18 | .string()
19 | .describe(
20 | 'Main task ID to start workflow for (e.g., "1", "2", "HAM-123"). Subtask IDs (e.g., "2.3", "1.1") are not allowed.'
21 | ),
22 | projectRoot: z
23 | .string()
24 | .describe('Absolute path to the project root directory'),
25 | maxAttempts: z
26 | .number()
27 | .optional()
28 | .default(3)
29 | .describe('Maximum attempts per subtask (default: 3)'),
30 | force: z
31 | .boolean()
32 | .optional()
33 | .default(false)
34 | .describe('Force start even if workflow state exists')
35 | });
36 |
37 | type StartWorkflowArgs = z.infer<typeof StartWorkflowSchema>;
38 |
39 | /**
40 | * Check if a task ID is a main task (not a subtask)
41 | * Main tasks: "1", "2", "HAM-123", "PROJ-456"
42 | * Subtasks: "1.1", "2.3", "HAM-123.1"
43 | */
44 | function isMainTaskId(taskId: string): boolean {
45 | // A main task has no dots in the ID after the optional prefix
46 | // Examples: "1" ✓, "HAM-123" ✓, "1.1" ✗, "HAM-123.1" ✗
47 | const parts = taskId.split('.');
48 | return parts.length === 1;
49 | }
50 |
51 | /**
52 | * Register the autopilot_start tool with the MCP server
53 | */
54 | export function registerAutopilotStartTool(server: FastMCP) {
55 | server.addTool({
56 | name: 'autopilot_start',
57 | description:
58 | 'Initialize and start a new TDD workflow for a task. Creates a git branch and sets up the workflow state machine.',
59 | parameters: StartWorkflowSchema,
60 | execute: withNormalizedProjectRoot(
61 | async (args: StartWorkflowArgs, context: MCPContext) => {
62 | const { taskId, projectRoot, maxAttempts, force } = args;
63 |
64 | try {
65 | context.log.info(
66 | `Starting autopilot workflow for task ${taskId} in ${projectRoot}`
67 | );
68 |
69 | // Validate that taskId is a main task (not a subtask)
70 | if (!isMainTaskId(taskId)) {
71 | return handleApiResult({
72 | result: {
73 | success: false,
74 | error: {
75 | message: `Task ID "${taskId}" is a subtask. Autopilot workflows can only be started for main tasks (e.g., "1", "2", "HAM-123"). Please provide the parent task ID instead.`
76 | }
77 | },
78 | log: context.log,
79 | projectRoot
80 | });
81 | }
82 |
83 | // Load task data and get current tag
84 | const core = await createTmCore({
85 | projectPath: projectRoot
86 | });
87 |
88 | // Get current tag from ConfigManager
89 | const currentTag = core.config.getActiveTag();
90 |
91 | const taskResult = await core.tasks.get(taskId);
92 |
93 | if (!taskResult || !taskResult.task) {
94 | return handleApiResult({
95 | result: {
96 | success: false,
97 | error: { message: `Task ${taskId} not found` }
98 | },
99 | log: context.log,
100 | projectRoot
101 | });
102 | }
103 |
104 | const task = taskResult.task;
105 |
106 | // Validate task has subtasks
107 | if (!task.subtasks || task.subtasks.length === 0) {
108 | return handleApiResult({
109 | result: {
110 | success: false,
111 | error: {
112 | message: `Task ${taskId} has no subtasks. Please use expand_task (with id="${taskId}") to create subtasks first. For improved results, consider running analyze_complexity before expanding the task.`
113 | }
114 | },
115 | log: context.log,
116 | projectRoot
117 | });
118 | }
119 |
120 | // Initialize workflow service
121 | const workflowService = new WorkflowService(projectRoot);
122 |
123 | // Check for existing workflow
124 | const hasWorkflow = await workflowService.hasWorkflow();
125 | if (hasWorkflow && !force) {
126 | context.log.warn('Workflow state already exists');
127 | return handleApiResult({
128 | result: {
129 | success: false,
130 | error: {
131 | message:
132 | 'Workflow already in progress. Use force=true to override or resume the existing workflow. Suggestion: Use autopilot_resume to continue the existing workflow'
133 | }
134 | },
135 | log: context.log,
136 | projectRoot
137 | });
138 | }
139 |
140 | // Start workflow
141 | const status = await workflowService.startWorkflow({
142 | taskId,
143 | taskTitle: task.title,
144 | subtasks: task.subtasks.map((st: any) => ({
145 | id: st.id,
146 | title: st.title,
147 | status: st.status,
148 | maxAttempts
149 | })),
150 | maxAttempts,
151 | force,
152 | tag: currentTag // Pass current tag for branch naming
153 | });
154 |
155 | context.log.info(`Workflow started successfully for task ${taskId}`);
156 |
157 | // Get next action with guidance from WorkflowService
158 | const nextAction = workflowService.getNextAction();
159 |
160 | return handleApiResult({
161 | result: {
162 | success: true,
163 | data: {
164 | message: `Workflow started for task ${taskId}`,
165 | taskId,
166 | branchName: status.branchName,
167 | phase: status.phase,
168 | tddPhase: status.tddPhase,
169 | progress: status.progress,
170 | currentSubtask: status.currentSubtask,
171 | nextAction: nextAction.action,
172 | nextSteps: nextAction.nextSteps
173 | }
174 | },
175 | log: context.log,
176 | projectRoot
177 | });
178 | } catch (error: any) {
179 | context.log.error(`Error in autopilot-start: ${error.message}`);
180 | if (error.stack) {
181 | context.log.debug(error.stack);
182 | }
183 | return handleApiResult({
184 | result: {
185 | success: false,
186 | error: { message: `Failed to start workflow: ${error.message}` }
187 | },
188 | log: context.log,
189 | projectRoot
190 | });
191 | }
192 | }
193 | )
194 | });
195 | }
196 |
```
--------------------------------------------------------------------------------
/apps/cli/src/commands/autopilot/shared.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview Shared utilities for autopilot commands
3 | */
4 |
5 | import {
6 | CommitMessageGenerator,
7 | GitAdapter,
8 | WorkflowOrchestrator,
9 | WorkflowStateManager
10 | } from '@tm/core';
11 | import type { SubtaskInfo, WorkflowContext, WorkflowState } from '@tm/core';
12 | import chalk from 'chalk';
13 |
14 | /**
15 | * Base options interface for all autopilot commands
16 | */
17 | export interface AutopilotBaseOptions {
18 | projectRoot?: string;
19 | json?: boolean;
20 | verbose?: boolean;
21 | }
22 |
23 | /**
24 | * Load workflow state from disk using WorkflowStateManager
25 | */
26 | export async function loadWorkflowState(
27 | projectRoot: string
28 | ): Promise<WorkflowState | null> {
29 | const stateManager = new WorkflowStateManager(projectRoot);
30 |
31 | if (!(await stateManager.exists())) {
32 | return null;
33 | }
34 |
35 | try {
36 | return await stateManager.load();
37 | } catch (error) {
38 | throw new Error(
39 | `Failed to load workflow state: ${(error as Error).message}`
40 | );
41 | }
42 | }
43 |
44 | /**
45 | * Save workflow state to disk using WorkflowStateManager
46 | */
47 | export async function saveWorkflowState(
48 | projectRoot: string,
49 | state: WorkflowState
50 | ): Promise<void> {
51 | const stateManager = new WorkflowStateManager(projectRoot);
52 |
53 | try {
54 | await stateManager.save(state);
55 | } catch (error) {
56 | throw new Error(
57 | `Failed to save workflow state: ${(error as Error).message}`
58 | );
59 | }
60 | }
61 |
62 | /**
63 | * Delete workflow state from disk using WorkflowStateManager
64 | */
65 | export async function deleteWorkflowState(projectRoot: string): Promise<void> {
66 | const stateManager = new WorkflowStateManager(projectRoot);
67 | await stateManager.delete();
68 | }
69 |
70 | /**
71 | * Check if workflow state exists using WorkflowStateManager
72 | */
73 | export async function hasWorkflowState(projectRoot: string): Promise<boolean> {
74 | const stateManager = new WorkflowStateManager(projectRoot);
75 | return await stateManager.exists();
76 | }
77 |
78 | /**
79 | * Initialize WorkflowOrchestrator with persistence
80 | */
81 | export function createOrchestrator(
82 | context: WorkflowContext,
83 | projectRoot: string
84 | ): WorkflowOrchestrator {
85 | const orchestrator = new WorkflowOrchestrator(context);
86 | const stateManager = new WorkflowStateManager(projectRoot);
87 |
88 | // Enable auto-persistence
89 | orchestrator.enableAutoPersist(async (state: WorkflowState) => {
90 | await stateManager.save(state);
91 | });
92 |
93 | return orchestrator;
94 | }
95 |
96 | /**
97 | * Initialize GitAdapter for project
98 | */
99 | export function createGitAdapter(projectRoot: string): GitAdapter {
100 | return new GitAdapter(projectRoot);
101 | }
102 |
103 | /**
104 | * Initialize CommitMessageGenerator
105 | */
106 | export function createCommitMessageGenerator(): CommitMessageGenerator {
107 | return new CommitMessageGenerator();
108 | }
109 |
110 | /**
111 | * Output formatter for JSON and text modes
112 | */
113 | export class OutputFormatter {
114 | constructor(private useJson: boolean) {}
115 |
116 | /**
117 | * Output data in appropriate format
118 | */
119 | output(data: Record<string, unknown>): void {
120 | if (this.useJson) {
121 | console.log(JSON.stringify(data, null, 2));
122 | } else {
123 | this.outputText(data);
124 | }
125 | }
126 |
127 | /**
128 | * Output data in human-readable text format
129 | */
130 | private outputText(data: Record<string, unknown>): void {
131 | for (const [key, value] of Object.entries(data)) {
132 | if (typeof value === 'object' && value !== null) {
133 | console.log(chalk.cyan(`${key}:`));
134 | this.outputObject(value as Record<string, unknown>, ' ');
135 | } else {
136 | console.log(chalk.white(`${key}: ${value}`));
137 | }
138 | }
139 | }
140 |
141 | /**
142 | * Output nested object with indentation
143 | */
144 | private outputObject(obj: Record<string, unknown>, indent: string): void {
145 | for (const [key, value] of Object.entries(obj)) {
146 | if (typeof value === 'object' && value !== null) {
147 | console.log(chalk.cyan(`${indent}${key}:`));
148 | this.outputObject(value as Record<string, unknown>, indent + ' ');
149 | } else {
150 | console.log(chalk.gray(`${indent}${key}: ${value}`));
151 | }
152 | }
153 | }
154 |
155 | /**
156 | * Output error message
157 | */
158 | error(message: string, details?: Record<string, unknown>): void {
159 | if (this.useJson) {
160 | console.error(
161 | JSON.stringify(
162 | {
163 | error: message,
164 | ...details
165 | },
166 | null,
167 | 2
168 | )
169 | );
170 | } else {
171 | console.error(chalk.red(`Error: ${message}`));
172 | if (details) {
173 | for (const [key, value] of Object.entries(details)) {
174 | console.error(chalk.gray(` ${key}: ${value}`));
175 | }
176 | }
177 | }
178 | }
179 |
180 | /**
181 | * Output success message
182 | */
183 | success(message: string, data?: Record<string, unknown>): void {
184 | if (this.useJson) {
185 | console.log(
186 | JSON.stringify(
187 | {
188 | success: true,
189 | message,
190 | ...data
191 | },
192 | null,
193 | 2
194 | )
195 | );
196 | } else {
197 | console.log(chalk.green(`✓ ${message}`));
198 | if (data) {
199 | this.output(data);
200 | }
201 | }
202 | }
203 |
204 | /**
205 | * Output warning message
206 | */
207 | warning(message: string): void {
208 | if (this.useJson) {
209 | console.warn(
210 | JSON.stringify(
211 | {
212 | warning: message
213 | },
214 | null,
215 | 2
216 | )
217 | );
218 | } else {
219 | console.warn(chalk.yellow(`⚠️ ${message}`));
220 | }
221 | }
222 |
223 | /**
224 | * Output info message
225 | */
226 | info(message: string): void {
227 | if (this.useJson) {
228 | // Don't output info messages in JSON mode
229 | return;
230 | }
231 | console.log(chalk.blue(`ℹ ${message}`));
232 | }
233 | }
234 |
235 | /**
236 | * Validate task ID format
237 | */
238 | export function validateTaskId(taskId: string): boolean {
239 | // Task ID should be in format: number or number.number (e.g., "1" or "1.2")
240 | const pattern = /^\d+(\.\d+)*$/;
241 | return pattern.test(taskId);
242 | }
243 |
244 | /**
245 | * Parse subtasks from task data
246 | */
247 | export function parseSubtasks(
248 | task: any,
249 | maxAttempts: number = 3
250 | ): SubtaskInfo[] {
251 | if (!task.subtasks || !Array.isArray(task.subtasks)) {
252 | return [];
253 | }
254 |
255 | return task.subtasks.map((subtask: any) => ({
256 | id: subtask.id,
257 | title: subtask.title,
258 | status: subtask.status === 'done' ? 'completed' : 'pending',
259 | attempts: 0,
260 | maxAttempts
261 | }));
262 | }
263 |
```