#
tokens: 48222/50000 18/975 files (page 17/69)
lines: on (toggle) GitHub
raw markdown copy reset
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 | 
```
Page 17/69FirstPrevNextLast