#
tokens: 48801/50000 26/975 files (page 11/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 11 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

--------------------------------------------------------------------------------
/apps/cli/src/ui/formatters/status-formatters.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Status formatting utilities
  3 |  * Provides colored status displays with ASCII icons for tasks and briefs
  4 |  */
  5 | 
  6 | import type { TaskStatus } from '@tm/core';
  7 | import chalk from 'chalk';
  8 | 
  9 | /**
 10 |  * Module-level task status configuration to avoid recreating on every call
 11 |  */
 12 | const TASK_STATUS_CONFIG: Record<
 13 | 	TaskStatus,
 14 | 	{ color: (text: string) => string; icon: string; tableIcon: string }
 15 | > = {
 16 | 	done: {
 17 | 		color: chalk.green,
 18 | 		icon: '✓',
 19 | 		tableIcon: '✓'
 20 | 	},
 21 | 	pending: {
 22 | 		color: chalk.yellow,
 23 | 		icon: '○',
 24 | 		tableIcon: '○'
 25 | 	},
 26 | 	'in-progress': {
 27 | 		color: chalk.hex('#FFA500'),
 28 | 		icon: '▶',
 29 | 		tableIcon: '▶'
 30 | 	},
 31 | 	deferred: {
 32 | 		color: chalk.gray,
 33 | 		icon: 'x',
 34 | 		tableIcon: 'x'
 35 | 	},
 36 | 	review: {
 37 | 		color: chalk.magenta,
 38 | 		icon: '?',
 39 | 		tableIcon: '?'
 40 | 	},
 41 | 	cancelled: {
 42 | 		color: chalk.gray,
 43 | 		icon: 'x',
 44 | 		tableIcon: 'x'
 45 | 	},
 46 | 	blocked: {
 47 | 		color: chalk.red,
 48 | 		icon: '!',
 49 | 		tableIcon: '!'
 50 | 	},
 51 | 	completed: {
 52 | 		color: chalk.green,
 53 | 		icon: '✓',
 54 | 		tableIcon: '✓'
 55 | 	}
 56 | };
 57 | 
 58 | /**
 59 |  * Get colored status display with ASCII icons (matches scripts/modules/ui.js style)
 60 |  */
 61 | export function getStatusWithColor(
 62 | 	status: TaskStatus,
 63 | 	forTable: boolean = false
 64 | ): string {
 65 | 	const config = TASK_STATUS_CONFIG[status] || {
 66 | 		color: chalk.red,
 67 | 		icon: 'X',
 68 | 		tableIcon: 'X'
 69 | 	};
 70 | 
 71 | 	const icon = forTable ? config.tableIcon : config.icon;
 72 | 	return config.color(`${icon} ${status}`);
 73 | }
 74 | 
 75 | /**
 76 |  * Brief status configuration
 77 |  */
 78 | const BRIEF_STATUS_CONFIG: Record<
 79 | 	string,
 80 | 	{ color: (text: string) => string; icon: string; tableIcon: string }
 81 | > = {
 82 | 	draft: {
 83 | 		color: chalk.gray,
 84 | 		icon: '○',
 85 | 		tableIcon: '○'
 86 | 	},
 87 | 	refining: {
 88 | 		color: chalk.yellow,
 89 | 		icon: '◐',
 90 | 		tableIcon: '◐'
 91 | 	},
 92 | 	aligned: {
 93 | 		color: chalk.cyan,
 94 | 		icon: '◎',
 95 | 		tableIcon: '◎'
 96 | 	},
 97 | 	delivering: {
 98 | 		color: chalk.hex('#FFA500'), // orange
 99 | 		icon: '▶',
100 | 		tableIcon: '▶'
101 | 	},
102 | 	delivered: {
103 | 		color: chalk.blue,
104 | 		icon: '◆',
105 | 		tableIcon: '◆'
106 | 	},
107 | 	done: {
108 | 		color: chalk.green,
109 | 		icon: '✓',
110 | 		tableIcon: '✓'
111 | 	},
112 | 	archived: {
113 | 		color: chalk.gray,
114 | 		icon: '■',
115 | 		tableIcon: '■'
116 | 	}
117 | };
118 | 
119 | /**
120 |  * Get the configuration for a brief status
121 |  */
122 | function getBriefStatusConfig(status: string) {
123 | 	// Normalize to lowercase for lookup
124 | 	const normalizedStatus = status.toLowerCase();
125 | 	return (
126 | 		BRIEF_STATUS_CONFIG[normalizedStatus] || {
127 | 			color: chalk.red,
128 | 			icon: '?',
129 | 			tableIcon: '?'
130 | 		}
131 | 	);
132 | }
133 | 
134 | /**
135 |  * Get the icon for a brief status
136 |  */
137 | export function getBriefStatusIcon(
138 | 	status: string | undefined,
139 | 	forTable: boolean = false
140 | ): string {
141 | 	if (!status) return '○';
142 | 	const config = getBriefStatusConfig(status);
143 | 	return forTable ? config.tableIcon : config.icon;
144 | }
145 | 
146 | /**
147 |  * Get the color function for a brief status
148 |  */
149 | export function getBriefStatusColor(
150 | 	status: string | undefined
151 | ): (text: string) => string {
152 | 	if (!status) return chalk.gray;
153 | 	return getBriefStatusConfig(status).color;
154 | }
155 | 
156 | /**
157 |  * Capitalize the first letter of a status
158 |  */
159 | export function capitalizeStatus(status: string): string {
160 | 	return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
161 | }
162 | 
163 | /**
164 |  * Get colored brief/tag status display with ASCII icons
165 |  * Brief statuses: draft, refining, aligned, delivering, delivered, done, archived
166 |  */
167 | export function getBriefStatusWithColor(
168 | 	status: string | undefined,
169 | 	forTable: boolean = false
170 | ): string {
171 | 	if (!status) {
172 | 		return chalk.gray('○ Unknown');
173 | 	}
174 | 
175 | 	const config = getBriefStatusConfig(status);
176 | 	const icon = forTable ? config.tableIcon : config.icon;
177 | 	const displayStatus = capitalizeStatus(status);
178 | 	return config.color(`${icon} ${displayStatus}`);
179 | }
180 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/commands/autopilot/abort.command.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Abort Command - Safely terminate workflow
  3 |  */
  4 | 
  5 | import { WorkflowOrchestrator } from '@tm/core';
  6 | import { Command } from 'commander';
  7 | import inquirer from 'inquirer';
  8 | import {
  9 | 	AutopilotBaseOptions,
 10 | 	OutputFormatter,
 11 | 	deleteWorkflowState,
 12 | 	hasWorkflowState,
 13 | 	loadWorkflowState
 14 | } from './shared.js';
 15 | import { getProjectRoot } from '../../utils/project-root.js';
 16 | 
 17 | interface AbortOptions extends AutopilotBaseOptions {
 18 | 	force?: boolean;
 19 | }
 20 | 
 21 | /**
 22 |  * Abort Command - Safely terminate workflow and clean up state
 23 |  */
 24 | export class AbortCommand extends Command {
 25 | 	constructor() {
 26 | 		super('abort');
 27 | 
 28 | 		this.description('Abort the current TDD workflow and clean up state')
 29 | 			.option('-f, --force', 'Force abort without confirmation')
 30 | 			.action(async (options: AbortOptions) => {
 31 | 				await this.execute(options);
 32 | 			});
 33 | 	}
 34 | 
 35 | 	private async execute(options: AbortOptions): Promise<void> {
 36 | 		// Inherit parent options
 37 | 		const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
 38 | 
 39 | 		// Initialize mergedOptions with defaults (projectRoot will be set in try block)
 40 | 		let mergedOptions: AbortOptions = {
 41 | 			...parentOpts,
 42 | 			...options,
 43 | 			projectRoot: '' // Will be set in try block
 44 | 		};
 45 | 
 46 | 		const formatter = new OutputFormatter(
 47 | 			options.json || parentOpts?.json || false
 48 | 		);
 49 | 
 50 | 		try {
 51 | 			// Resolve project root inside try block to catch any errors
 52 | 			const projectRoot = getProjectRoot(
 53 | 				options.projectRoot || parentOpts?.projectRoot
 54 | 			);
 55 | 
 56 | 			// Update mergedOptions with resolved project root
 57 | 			mergedOptions = {
 58 | 				...mergedOptions,
 59 | 				projectRoot
 60 | 			};
 61 | 			// Check for workflow state
 62 | 			const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
 63 | 			if (!hasState) {
 64 | 				formatter.warning('No active workflow to abort');
 65 | 				return;
 66 | 			}
 67 | 
 68 | 			// Load state
 69 | 			const state = await loadWorkflowState(mergedOptions.projectRoot!);
 70 | 			if (!state) {
 71 | 				formatter.error('Failed to load workflow state');
 72 | 				process.exit(1);
 73 | 			}
 74 | 
 75 | 			// Restore orchestrator
 76 | 			const orchestrator = new WorkflowOrchestrator(state.context);
 77 | 			orchestrator.restoreState(state);
 78 | 
 79 | 			// Get progress before abort
 80 | 			const progress = orchestrator.getProgress();
 81 | 			const currentSubtask = orchestrator.getCurrentSubtask();
 82 | 
 83 | 			// Confirm abort if not forced or in JSON mode
 84 | 			if (!mergedOptions.force && !mergedOptions.json) {
 85 | 				const { confirmed } = await inquirer.prompt([
 86 | 					{
 87 | 						type: 'confirm',
 88 | 						name: 'confirmed',
 89 | 						message:
 90 | 							`This will abort the workflow for task ${state.context.taskId}. ` +
 91 | 							`Progress: ${progress.completed}/${progress.total} subtasks completed. ` +
 92 | 							`Continue?`,
 93 | 						default: false
 94 | 					}
 95 | 				]);
 96 | 
 97 | 				if (!confirmed) {
 98 | 					formatter.info('Abort cancelled');
 99 | 					return;
100 | 				}
101 | 			}
102 | 
103 | 			// Trigger abort in orchestrator
104 | 			orchestrator.transition({ type: 'ABORT' });
105 | 
106 | 			// Delete workflow state
107 | 			await deleteWorkflowState(mergedOptions.projectRoot!);
108 | 
109 | 			// Output result
110 | 			formatter.success('Workflow aborted', {
111 | 				taskId: state.context.taskId,
112 | 				branchName: state.context.branchName,
113 | 				progress: {
114 | 					completed: progress.completed,
115 | 					total: progress.total
116 | 				},
117 | 				lastSubtask: currentSubtask
118 | 					? {
119 | 							id: currentSubtask.id,
120 | 							title: currentSubtask.title
121 | 						}
122 | 					: null,
123 | 				note: 'Branch and commits remain. Clean up manually if needed.'
124 | 			});
125 | 		} catch (error) {
126 | 			formatter.error((error as Error).message);
127 | 			if (mergedOptions.verbose) {
128 | 				console.error((error as Error).stack);
129 | 			}
130 | 			process.exit(1);
131 | 		}
132 | 	}
133 | }
134 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/storage/utils/api-client.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Lightweight API client utility for Hamster backend
  3 |  * Centralizes error handling, auth, and request/response logic
  4 |  */
  5 | 
  6 | import {
  7 | 	ERROR_CODES,
  8 | 	TaskMasterError
  9 | } from '../../../common/errors/task-master-error.js';
 10 | import type { AuthManager } from '../../auth/managers/auth-manager.js';
 11 | 
 12 | export interface ApiClientOptions {
 13 | 	baseUrl: string;
 14 | 	authManager: AuthManager;
 15 | 	accountId?: string;
 16 | }
 17 | 
 18 | export interface ApiErrorResponse {
 19 | 	message: string;
 20 | 	error?: string;
 21 | 	statusCode?: number;
 22 | }
 23 | 
 24 | export class ApiClient {
 25 | 	constructor(private options: ApiClientOptions) {}
 26 | 
 27 | 	/**
 28 | 	 * Make a typed API request with automatic error handling
 29 | 	 */
 30 | 	async request<T = any>(
 31 | 		endpoint: string,
 32 | 		options: RequestInit = {}
 33 | 	): Promise<T> {
 34 | 		const { baseUrl, authManager, accountId } = this.options;
 35 | 
 36 | 		// Get auth session
 37 | 		const session = await authManager.supabaseClient.getSession();
 38 | 		if (!session) {
 39 | 			throw new TaskMasterError(
 40 | 				'Not authenticated',
 41 | 				ERROR_CODES.AUTHENTICATION_ERROR,
 42 | 				{ operation: 'api-request', endpoint }
 43 | 			);
 44 | 		}
 45 | 
 46 | 		// Build full URL
 47 | 		const url = `${baseUrl}${endpoint}`;
 48 | 
 49 | 		// Build headers
 50 | 		const headers: RequestInit['headers'] = {
 51 | 			'Content-Type': 'application/json',
 52 | 			Authorization: `Bearer ${session.access_token}`,
 53 | 			...(accountId ? { 'x-account-id': accountId } : {}),
 54 | 			...options.headers
 55 | 		};
 56 | 
 57 | 		try {
 58 | 			// Make request
 59 | 			const response = await fetch(url, {
 60 | 				...options,
 61 | 				headers
 62 | 			});
 63 | 
 64 | 			// Handle non-2xx responses
 65 | 			if (!response.ok) {
 66 | 				await this.handleErrorResponse(response, endpoint);
 67 | 			}
 68 | 
 69 | 			// Parse successful response
 70 | 			return (await response.json()) as T;
 71 | 		} catch (error) {
 72 | 			// If it's already a TaskMasterError, re-throw
 73 | 			if (error instanceof TaskMasterError) {
 74 | 				throw error;
 75 | 			}
 76 | 
 77 | 			// Wrap other errors
 78 | 			const errorMessage =
 79 | 				error instanceof Error ? error.message : String(error);
 80 | 			throw new TaskMasterError(
 81 | 				errorMessage,
 82 | 				ERROR_CODES.API_ERROR,
 83 | 				{ operation: 'api-request', endpoint },
 84 | 				error as Error
 85 | 			);
 86 | 		}
 87 | 	}
 88 | 
 89 | 	/**
 90 | 	 * Extract and throw a clean error from API response
 91 | 	 */
 92 | 	private async handleErrorResponse(
 93 | 		response: Response,
 94 | 		endpoint: string
 95 | 	): Promise<never> {
 96 | 		let errorMessage: string;
 97 | 
 98 | 		try {
 99 | 			// API returns: { message: "...", error: "...", statusCode: 404 }
100 | 			const errorBody = (await response.json()) as ApiErrorResponse;
101 | 			errorMessage =
102 | 				errorBody.message || errorBody.error || 'Unknown API error';
103 | 		} catch {
104 | 			// Fallback if response isn't JSON
105 | 			errorMessage = (await response.text()) || response.statusText;
106 | 		}
107 | 
108 | 		throw new TaskMasterError(errorMessage, ERROR_CODES.API_ERROR, {
109 | 			operation: 'api-request',
110 | 			endpoint,
111 | 			statusCode: response.status
112 | 		});
113 | 	}
114 | 
115 | 	/**
116 | 	 * Convenience methods for common HTTP verbs
117 | 	 */
118 | 	async get<T = any>(endpoint: string): Promise<T> {
119 | 		return this.request<T>(endpoint, { method: 'GET' });
120 | 	}
121 | 
122 | 	async post<T = any>(endpoint: string, body?: any): Promise<T> {
123 | 		return this.request<T>(endpoint, {
124 | 			method: 'POST',
125 | 			body: body ? JSON.stringify(body) : undefined
126 | 		});
127 | 	}
128 | 
129 | 	async patch<T = any>(endpoint: string, body?: any): Promise<T> {
130 | 		return this.request<T>(endpoint, {
131 | 			method: 'PATCH',
132 | 			body: body ? JSON.stringify(body) : undefined
133 | 		});
134 | 	}
135 | 
136 | 	async put<T = any>(endpoint: string, body?: any): Promise<T> {
137 | 		return this.request<T>(endpoint, {
138 | 			method: 'PUT',
139 | 			body: body ? JSON.stringify(body) : undefined
140 | 		});
141 | 	}
142 | 
143 | 	async delete<T = any>(endpoint: string): Promise<T> {
144 | 		return this.request<T>(endpoint, { method: 'DELETE' });
145 | 	}
146 | }
147 | 
```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/parse-prd/parse-prd-config.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Configuration classes and schemas for PRD parsing
  3 |  */
  4 | 
  5 | import { z } from 'zod';
  6 | import { TASK_PRIORITY_OPTIONS } from '../../../../src/constants/task-priority.js';
  7 | import { getCurrentTag, isSilentMode, log } from '../../utils.js';
  8 | import { Duration } from '../../../../src/utils/timeout-manager.js';
  9 | import { hasCodebaseAnalysis } from '../../config-manager.js';
 10 | 
 11 | // ============================================================================
 12 | // SCHEMAS
 13 | // ============================================================================
 14 | 
 15 | // Define the Zod schema for a SINGLE task object
 16 | export const prdSingleTaskSchema = z.object({
 17 | 	id: z.number(),
 18 | 	title: z.string().min(1),
 19 | 	description: z.string().min(1),
 20 | 	details: z.string(),
 21 | 	testStrategy: z.string(),
 22 | 	priority: z.enum(TASK_PRIORITY_OPTIONS),
 23 | 	dependencies: z.array(z.number()),
 24 | 	status: z.string()
 25 | });
 26 | 
 27 | // Define the Zod schema for the ENTIRE expected AI response object
 28 | export const prdResponseSchema = z.object({
 29 | 	tasks: z.array(prdSingleTaskSchema),
 30 | 	// Use union for better structured outputs compatibility
 31 | 	// Models understand "either return this object OR null" more reliably
 32 | 	metadata: z
 33 | 		.union([
 34 | 			z.object({
 35 | 				projectName: z.string(),
 36 | 				totalTasks: z.number(),
 37 | 				sourceFile: z.string(),
 38 | 				generatedAt: z.string()
 39 | 			}),
 40 | 			z.null()
 41 | 		])
 42 | 		.default(null)
 43 | });
 44 | 
 45 | // ============================================================================
 46 | // CONFIGURATION CLASSES
 47 | // ============================================================================
 48 | 
 49 | /**
 50 |  * Configuration object for PRD parsing
 51 |  */
 52 | export class PrdParseConfig {
 53 | 	constructor(prdPath, tasksPath, numTasks, options = {}) {
 54 | 		this.prdPath = prdPath;
 55 | 		this.tasksPath = tasksPath;
 56 | 		this.numTasks = numTasks;
 57 | 		this.force = options.force || false;
 58 | 		this.append = options.append || false;
 59 | 		this.research = options.research || false;
 60 | 		this.reportProgress = options.reportProgress;
 61 | 		this.mcpLog = options.mcpLog;
 62 | 		this.session = options.session;
 63 | 		this.projectRoot = options.projectRoot;
 64 | 		this.tag = options.tag;
 65 | 		this.streamingTimeout =
 66 | 			options.streamingTimeout || Duration.seconds(180).milliseconds;
 67 | 
 68 | 		// Derived values
 69 | 		this.targetTag = this.tag || getCurrentTag(this.projectRoot) || 'master';
 70 | 		this.isMCP = !!this.mcpLog;
 71 | 		this.outputFormat = this.isMCP && !this.reportProgress ? 'json' : 'text';
 72 | 
 73 | 		// Feature flag: Temporarily disable streaming, use generateObject instead
 74 | 		// TODO: Re-enable streaming once issues are resolved
 75 | 		const ENABLE_STREAMING = false;
 76 | 
 77 | 		this.useStreaming =
 78 | 			ENABLE_STREAMING &&
 79 | 			(typeof this.reportProgress === 'function' ||
 80 | 				this.outputFormat === 'text');
 81 | 	}
 82 | 
 83 | 	/**
 84 | 	 * Check if codebase analysis is available (Claude Code or Gemini CLI)
 85 | 	 */
 86 | 	hasCodebaseAnalysis() {
 87 | 		return hasCodebaseAnalysis(this.research, this.projectRoot, this.session);
 88 | 	}
 89 | }
 90 | 
 91 | /**
 92 |  * Logging configuration and utilities
 93 |  */
 94 | export class LoggingConfig {
 95 | 	constructor(mcpLog, reportProgress) {
 96 | 		this.isMCP = !!mcpLog;
 97 | 		this.outputFormat = this.isMCP && !reportProgress ? 'json' : 'text';
 98 | 
 99 | 		this.logFn = mcpLog || {
100 | 			info: (...args) => log('info', ...args),
101 | 			warn: (...args) => log('warn', ...args),
102 | 			error: (...args) => log('error', ...args),
103 | 			debug: (...args) => log('debug', ...args),
104 | 			success: (...args) => log('success', ...args)
105 | 		};
106 | 	}
107 | 
108 | 	report(message, level = 'info') {
109 | 		if (this.logFn && typeof this.logFn[level] === 'function') {
110 | 			this.logFn[level](message);
111 | 		} else if (!isSilentMode() && this.outputFormat === 'text') {
112 | 			log(level, message);
113 | 		}
114 | 	}
115 | }
116 | 
```

--------------------------------------------------------------------------------
/.github/scripts/validate-changesets.mjs:
--------------------------------------------------------------------------------

```
  1 | #!/usr/bin/env node
  2 | 
  3 | import { readFileSync, readdirSync } from 'node:fs';
  4 | import { join, dirname } from 'node:path';
  5 | import { fileURLToPath } from 'node:url';
  6 | 
  7 | const __filename = fileURLToPath(import.meta.url);
  8 | const __dirname = dirname(__filename);
  9 | const rootDir = join(__dirname, '../..');
 10 | 
 11 | // Define allowed public packages that can be referenced in changesets
 12 | const PUBLIC_PACKAGES = ['task-master-ai', 'extension'];
 13 | 
 14 | /**
 15 |  * Parse a changeset file and extract package names from the frontmatter
 16 |  * This uses a simple YAML parser that's sufficient for changeset files
 17 |  * and doesn't require external dependencies.
 18 |  *
 19 |  * @param {string} filePath - Path to the changeset file
 20 |  * @returns {string[]} - Array of package names
 21 |  * @throws {Error} - If file cannot be read or parsed
 22 |  */
 23 | function parseChangesetFile(filePath) {
 24 | 	try {
 25 | 		const content = readFileSync(filePath, 'utf-8');
 26 | 
 27 | 		// Extract frontmatter between --- markers
 28 | 		const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
 29 | 
 30 | 		if (!frontmatterMatch) {
 31 | 			throw new Error('No valid frontmatter found (missing --- delimiters)');
 32 | 		}
 33 | 
 34 | 		const frontmatter = frontmatterMatch[1];
 35 | 		const packages = [];
 36 | 
 37 | 		// Parse simple YAML format: 'package-name': version
 38 | 		// This handles the standard changeset format without needing a full YAML parser
 39 | 		const lines = frontmatter.split(/\r?\n/);
 40 | 
 41 | 		for (const line of lines) {
 42 | 			const trimmed = line.trim();
 43 | 
 44 | 			// Skip empty lines and comments
 45 | 			if (!trimmed || trimmed.startsWith('#')) {
 46 | 				continue;
 47 | 			}
 48 | 
 49 | 			// Match package declarations: 'package-name': version or "package-name": version
 50 | 			const match = trimmed.match(/^['"]?([^'":\s]+)['"]?\s*:\s*.+$/);
 51 | 
 52 | 			if (match) {
 53 | 				packages.push(match[1]);
 54 | 			}
 55 | 		}
 56 | 
 57 | 		if (packages.length === 0) {
 58 | 			throw new Error('No packages found in frontmatter');
 59 | 		}
 60 | 
 61 | 		return packages;
 62 | 	} catch (error) {
 63 | 		if (error.code === 'ENOENT') {
 64 | 			throw new Error(`File not found: ${filePath}`);
 65 | 		}
 66 | 		throw new Error(`Failed to parse ${filePath}: ${error.message}`);
 67 | 	}
 68 | }
 69 | 
 70 | /**
 71 |  * Validate all changeset files in the .changeset directory
 72 |  * @returns {boolean} - True if all changesets are valid
 73 |  */
 74 | function validateChangesets() {
 75 | 	try {
 76 | 		const changesetsDir = join(rootDir, '.changeset');
 77 | 		const files = readdirSync(changesetsDir);
 78 | 
 79 | 		const errors = [];
 80 | 
 81 | 		for (const file of files) {
 82 | 			// Skip config files and README
 83 | 			if (
 84 | 				file === 'config.json' ||
 85 | 				file === 'README.md' ||
 86 | 				!file.endsWith('.md')
 87 | 			) {
 88 | 				continue;
 89 | 			}
 90 | 
 91 | 			const filePath = join(changesetsDir, file);
 92 | 
 93 | 			try {
 94 | 				const packages = parseChangesetFile(filePath);
 95 | 
 96 | 				for (const pkg of packages) {
 97 | 					// Only allow packages in the PUBLIC_PACKAGES whitelist
 98 | 					if (!PUBLIC_PACKAGES.includes(pkg)) {
 99 | 						errors.push({
100 | 							file,
101 | 							package: pkg,
102 | 							message: `Invalid package "${pkg}". Only these packages are allowed: ${PUBLIC_PACKAGES.join(', ')}`
103 | 						});
104 | 					}
105 | 				}
106 | 			} catch (error) {
107 | 				errors.push({
108 | 					file,
109 | 					package: 'N/A',
110 | 					message: `Parse error: ${error.message}`
111 | 				});
112 | 			}
113 | 		}
114 | 
115 | 		// Print results
116 | 		if (errors.length === 0) {
117 | 			console.log('✅ All changesets are valid!');
118 | 			return true;
119 | 		}
120 | 
121 | 		console.error('\n❌ Changeset validation failed:\n');
122 | 		for (const error of errors) {
123 | 			console.error(`  ${error.file}:`);
124 | 			console.error(`    ${error.message}`);
125 | 			console.error('');
126 | 		}
127 | 
128 | 		return false;
129 | 	} catch (error) {
130 | 		console.error(`\n❌ Fatal error during validation: ${error.message}\n`);
131 | 		return false;
132 | 	}
133 | }
134 | 
135 | const isValid = validateChangesets();
136 | process.exit(isValid ? 0 : 1);
137 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/execution/executors/claude-executor.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Claude executor implementation for Task Master
  3 |  */
  4 | 
  5 | import { spawn } from 'child_process';
  6 | import type { Task } from '../../../common/types/index.js';
  7 | import { BaseExecutor } from '../executors/base-executor.js';
  8 | import type {
  9 | 	ClaudeExecutorConfig,
 10 | 	ExecutionResult,
 11 | 	ExecutorType
 12 | } from '../types.js';
 13 | 
 14 | export class ClaudeExecutor extends BaseExecutor {
 15 | 	private claudeConfig: ClaudeExecutorConfig;
 16 | 	private currentProcess: any = null;
 17 | 
 18 | 	constructor(projectRoot: string, config: ClaudeExecutorConfig = {}) {
 19 | 		super(projectRoot, config);
 20 | 		this.claudeConfig = {
 21 | 			command: config.command || 'claude',
 22 | 			systemPrompt:
 23 | 				config.systemPrompt ||
 24 | 				'You are a helpful AI assistant helping to complete a software development task.',
 25 | 			additionalFlags: config.additionalFlags || []
 26 | 		};
 27 | 	}
 28 | 
 29 | 	getType(): ExecutorType {
 30 | 		return 'claude';
 31 | 	}
 32 | 
 33 | 	async isAvailable(): Promise<boolean> {
 34 | 		return new Promise((resolve) => {
 35 | 			const checkProcess = spawn('which', [this.claudeConfig.command!], {
 36 | 				shell: true
 37 | 			});
 38 | 
 39 | 			checkProcess.on('close', (code) => {
 40 | 				resolve(code === 0);
 41 | 			});
 42 | 
 43 | 			checkProcess.on('error', () => {
 44 | 				resolve(false);
 45 | 			});
 46 | 		});
 47 | 	}
 48 | 
 49 | 	async execute(task: Task): Promise<ExecutionResult> {
 50 | 		const startTime = new Date().toISOString();
 51 | 
 52 | 		try {
 53 | 			// Check if Claude is available
 54 | 			const isAvailable = await this.isAvailable();
 55 | 			if (!isAvailable) {
 56 | 				return this.createResult(
 57 | 					task.id,
 58 | 					false,
 59 | 					undefined,
 60 | 					`Claude CLI not found. Please ensure 'claude' command is available in PATH.`
 61 | 				);
 62 | 			}
 63 | 
 64 | 			// Format the task into a prompt
 65 | 			const taskPrompt = this.formatTaskPrompt(task);
 66 | 			const fullPrompt = `${this.claudeConfig.systemPrompt}\n\nHere is the task to complete:\n\n${taskPrompt}`;
 67 | 
 68 | 			// Execute Claude with the task details
 69 | 			const result = await this.runClaude(fullPrompt, task.id);
 70 | 
 71 | 			return {
 72 | 				...result,
 73 | 				startTime,
 74 | 				endTime: new Date().toISOString()
 75 | 			};
 76 | 		} catch (error: any) {
 77 | 			this.logger.error(`Failed to execute task ${task.id}:`, error);
 78 | 			return this.createResult(
 79 | 				task.id,
 80 | 				false,
 81 | 				undefined,
 82 | 				error.message || 'Unknown error occurred'
 83 | 			);
 84 | 		}
 85 | 	}
 86 | 
 87 | 	private runClaude(prompt: string, taskId: string): Promise<ExecutionResult> {
 88 | 		return new Promise((resolve) => {
 89 | 			const args = [prompt, ...this.claudeConfig.additionalFlags!];
 90 | 
 91 | 			this.logger.info(`Executing Claude for task ${taskId}`);
 92 | 			this.logger.debug(
 93 | 				`Command: ${this.claudeConfig.command} ${args.join(' ')}`
 94 | 			);
 95 | 
 96 | 			this.currentProcess = spawn(this.claudeConfig.command!, args, {
 97 | 				cwd: this.projectRoot,
 98 | 				shell: false,
 99 | 				stdio: 'inherit' // Let Claude handle its own I/O
100 | 			});
101 | 
102 | 			this.currentProcess.on('close', (code: number) => {
103 | 				this.currentProcess = null;
104 | 
105 | 				if (code === 0) {
106 | 					resolve(
107 | 						this.createResult(
108 | 							taskId,
109 | 							true,
110 | 							'Claude session completed successfully'
111 | 						)
112 | 					);
113 | 				} else {
114 | 					resolve(
115 | 						this.createResult(
116 | 							taskId,
117 | 							false,
118 | 							undefined,
119 | 							`Claude exited with code ${code}`
120 | 						)
121 | 					);
122 | 				}
123 | 			});
124 | 
125 | 			this.currentProcess.on('error', (error: any) => {
126 | 				this.currentProcess = null;
127 | 				this.logger.error(`Claude process error:`, error);
128 | 				resolve(
129 | 					this.createResult(
130 | 						taskId,
131 | 						false,
132 | 						undefined,
133 | 						`Failed to spawn Claude: ${error.message}`
134 | 					)
135 | 				);
136 | 			});
137 | 		});
138 | 	}
139 | 
140 | 	async stop(): Promise<void> {
141 | 		if (this.currentProcess) {
142 | 			this.logger.info('Stopping Claude process...');
143 | 			this.currentProcess.kill('SIGTERM');
144 | 			this.currentProcess = null;
145 | 		}
146 | 	}
147 | }
148 | 
```

--------------------------------------------------------------------------------
/apps/mcp/src/tools/tasks/get-task.tool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview get-task MCP tool
  3 |  * Get detailed information about a specific task by ID
  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, Subtask, type Task } from '@tm/core';
 13 | import type { FastMCP } from 'fastmcp';
 14 | 
 15 | const GetTaskSchema = z.object({
 16 | 	id: z
 17 | 		.string()
 18 | 		.describe(
 19 | 			'Task ID(s) to get (can be comma-separated for multiple tasks)'
 20 | 		),
 21 | 	status: z
 22 | 		.string()
 23 | 		.optional()
 24 | 		.describe("Filter subtasks by status (e.g., 'pending', 'done')"),
 25 | 	projectRoot: z
 26 | 		.string()
 27 | 		.describe(
 28 | 			'Absolute path to the project root directory (Optional, usually from session)'
 29 | 		),
 30 | 	tag: z.string().optional().describe('Tag context to operate on')
 31 | });
 32 | 
 33 | type GetTaskArgs = z.infer<typeof GetTaskSchema>;
 34 | 
 35 | /**
 36 |  * Register the get_task tool with the MCP server
 37 |  */
 38 | export function registerGetTaskTool(server: FastMCP) {
 39 | 	server.addTool({
 40 | 		name: 'get_task',
 41 | 		description: 'Get detailed information about a specific task',
 42 | 		parameters: GetTaskSchema,
 43 | 		execute: withNormalizedProjectRoot(
 44 | 			async (args: GetTaskArgs, context: MCPContext) => {
 45 | 				const { id, status, projectRoot, tag } = args;
 46 | 
 47 | 				try {
 48 | 					context.log.info(
 49 | 						`Getting task details for ID: ${id}${status ? ` (filtering subtasks by status: ${status})` : ''} in root: ${projectRoot}`
 50 | 					);
 51 | 
 52 | 					// Create tm-core with logging callback
 53 | 					const tmCore = await createTmCore({
 54 | 						projectPath: projectRoot,
 55 | 						loggerConfig: {
 56 | 							mcpMode: true,
 57 | 							logCallback: context.log
 58 | 						}
 59 | 					});
 60 | 
 61 | 					// Handle comma-separated IDs - parallelize for better performance
 62 | 					const taskIds = id.split(',').map((tid) => tid.trim());
 63 | 					const results = await Promise.all(
 64 | 						taskIds.map((taskId) => tmCore.tasks.get(taskId, tag))
 65 | 					);
 66 | 
 67 | 					const tasks: (Task | Subtask)[] = [];
 68 | 					for (const result of results) {
 69 | 						if (!result.task) continue;
 70 | 
 71 | 						// If status filter is provided, filter subtasks (create copy to avoid mutation)
 72 | 						if (status && result.task.subtasks) {
 73 | 							const statusFilters = status
 74 | 								.split(',')
 75 | 								.map((s) => s.trim().toLowerCase());
 76 | 							const filteredSubtasks = result.task.subtasks.filter((st) =>
 77 | 								statusFilters.includes(String(st.status).toLowerCase())
 78 | 							);
 79 | 							tasks.push({ ...result.task, subtasks: filteredSubtasks });
 80 | 						} else {
 81 | 							tasks.push(result.task);
 82 | 						}
 83 | 					}
 84 | 
 85 | 					if (tasks.length === 0) {
 86 | 						context.log.warn(`No tasks found for ID(s): ${id}`);
 87 | 						return handleApiResult({
 88 | 							result: {
 89 | 								success: false,
 90 | 								error: {
 91 | 									message: `No tasks found for ID(s): ${id}`
 92 | 								}
 93 | 							},
 94 | 							log: context.log,
 95 | 							projectRoot
 96 | 						});
 97 | 					}
 98 | 
 99 | 					context.log.info(
100 | 						`Successfully retrieved ${tasks.length} task(s) for ID(s): ${id}`
101 | 					);
102 | 
103 | 					// Return single task if only one ID was requested, otherwise array
104 | 					const responseData = taskIds.length === 1 ? tasks[0] : tasks;
105 | 
106 | 					return handleApiResult({
107 | 						result: {
108 | 							success: true,
109 | 							data: responseData
110 | 						},
111 | 						log: context.log,
112 | 						projectRoot,
113 | 						tag
114 | 					});
115 | 				} catch (error: any) {
116 | 					context.log.error(`Error in get-task: ${error.message}`);
117 | 					if (error.stack) {
118 | 						context.log.debug(error.stack);
119 | 					}
120 | 					return handleApiResult({
121 | 						result: {
122 | 							success: false,
123 | 							error: {
124 | 								message: `Failed to get task: ${error.message}`
125 | 							}
126 | 						},
127 | 						log: context.log,
128 | 						projectRoot
129 | 					});
130 | 				}
131 | 			}
132 | 		)
133 | 	});
134 | }
135 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/commands/models/fetchers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Model fetching utilities for OpenRouter, Ollama, and other providers
  3 |  */
  4 | 
  5 | import http from 'http';
  6 | import https from 'https';
  7 | import type { FetchResult, OllamaModel, OpenRouterModel } from './types.js';
  8 | 
  9 | /**
 10 |  * Fetch available models from OpenRouter API
 11 |  */
 12 | export async function fetchOpenRouterModels(): Promise<
 13 | 	FetchResult<OpenRouterModel[]>
 14 | > {
 15 | 	return new Promise((resolve) => {
 16 | 		const options = {
 17 | 			hostname: 'openrouter.ai',
 18 | 			path: '/api/v1/models',
 19 | 			method: 'GET',
 20 | 			headers: {
 21 | 				Accept: 'application/json'
 22 | 			}
 23 | 		};
 24 | 
 25 | 		const req = https.request(options, (res) => {
 26 | 			let data = '';
 27 | 
 28 | 			res.on('data', (chunk) => {
 29 | 				data += chunk;
 30 | 			});
 31 | 
 32 | 			res.on('end', () => {
 33 | 				if (res.statusCode === 200) {
 34 | 					try {
 35 | 						const parsedData = JSON.parse(data);
 36 | 						resolve({
 37 | 							success: true,
 38 | 							data: parsedData.data || []
 39 | 						});
 40 | 					} catch (e) {
 41 | 						resolve({
 42 | 							success: false,
 43 | 							error: 'Failed to parse OpenRouter response'
 44 | 						});
 45 | 					}
 46 | 				} else {
 47 | 					resolve({
 48 | 						success: false,
 49 | 						error: `OpenRouter API returned status ${res.statusCode}`
 50 | 					});
 51 | 				}
 52 | 			});
 53 | 		});
 54 | 
 55 | 		req.on('error', (e) => {
 56 | 			resolve({
 57 | 				success: false,
 58 | 				error: `Failed to fetch OpenRouter models: ${e.message}`
 59 | 			});
 60 | 		});
 61 | 
 62 | 		req.end();
 63 | 	});
 64 | }
 65 | 
 66 | /**
 67 |  * Fetch available models from Ollama instance
 68 |  */
 69 | export async function fetchOllamaModels(
 70 | 	baseURL = 'http://localhost:11434/api'
 71 | ): Promise<FetchResult<OllamaModel[]>> {
 72 | 	return new Promise((resolve) => {
 73 | 		try {
 74 | 			// Parse the base URL to extract hostname, port, and base path
 75 | 			const url = new URL(baseURL);
 76 | 			const isHttps = url.protocol === 'https:';
 77 | 			const port = url.port || (isHttps ? 443 : 80);
 78 | 			const basePath = url.pathname.endsWith('/')
 79 | 				? url.pathname.slice(0, -1)
 80 | 				: url.pathname;
 81 | 
 82 | 			const options = {
 83 | 				hostname: url.hostname,
 84 | 				port: parseInt(String(port), 10),
 85 | 				path: `${basePath}/tags`,
 86 | 				method: 'GET',
 87 | 				headers: {
 88 | 					Accept: 'application/json'
 89 | 				}
 90 | 			};
 91 | 
 92 | 			const requestLib = isHttps ? https : http;
 93 | 			const req = requestLib.request(options, (res) => {
 94 | 				let data = '';
 95 | 
 96 | 				res.on('data', (chunk) => {
 97 | 					data += chunk;
 98 | 				});
 99 | 
100 | 				res.on('end', () => {
101 | 					if (res.statusCode === 200) {
102 | 						try {
103 | 							const parsedData = JSON.parse(data);
104 | 							resolve({
105 | 								success: true,
106 | 								data: parsedData.models || []
107 | 							});
108 | 						} catch (e) {
109 | 							resolve({
110 | 								success: false,
111 | 								error: 'Failed to parse Ollama response'
112 | 							});
113 | 						}
114 | 					} else {
115 | 						resolve({
116 | 							success: false,
117 | 							error: `Ollama API returned status ${res.statusCode}`
118 | 						});
119 | 					}
120 | 				});
121 | 			});
122 | 
123 | 			req.on('error', (e) => {
124 | 				resolve({
125 | 					success: false,
126 | 					error: `Failed to connect to Ollama: ${e.message}`
127 | 				});
128 | 			});
129 | 
130 | 			req.end();
131 | 		} catch (e) {
132 | 			resolve({
133 | 				success: false,
134 | 				error: `Invalid Ollama base URL: ${e instanceof Error ? e.message : 'Unknown error'}`
135 | 			});
136 | 		}
137 | 	});
138 | }
139 | 
140 | /**
141 |  * Validate if a model ID exists in OpenRouter
142 |  */
143 | export async function validateOpenRouterModel(
144 | 	modelId: string
145 | ): Promise<boolean> {
146 | 	const result = await fetchOpenRouterModels();
147 | 	if (!result.success || !result.data) {
148 | 		return false;
149 | 	}
150 | 	return result.data.some((m) => m.id === modelId);
151 | }
152 | 
153 | /**
154 |  * Validate if a model ID exists in Ollama instance
155 |  */
156 | export async function validateOllamaModel(
157 | 	modelId: string,
158 | 	baseURL?: string
159 | ): Promise<boolean> {
160 | 	const result = await fetchOllamaModels(baseURL);
161 | 	if (!result.success || !result.data) {
162 | 		return false;
163 | 	}
164 | 	return result.data.some((m) => m.model === modelId);
165 | }
166 | 
```

--------------------------------------------------------------------------------
/src/ai-providers/codex-cli.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * src/ai-providers/codex-cli.js
  3 |  *
  4 |  * Codex CLI provider implementation using the ai-sdk-provider-codex-cli package.
  5 |  * This provider uses the local OpenAI Codex CLI with OAuth (preferred) or
  6 |  * an optional OPENAI_CODEX_API_KEY if provided.
  7 |  */
  8 | 
  9 | import { createCodexCli } from 'ai-sdk-provider-codex-cli';
 10 | import { BaseAIProvider } from './base-provider.js';
 11 | import { execSync } from 'child_process';
 12 | import { log } from '../../scripts/modules/utils.js';
 13 | import {
 14 | 	getCodexCliSettingsForCommand,
 15 | 	getSupportedModelsForProvider
 16 | } from '../../scripts/modules/config-manager.js';
 17 | 
 18 | export class CodexCliProvider extends BaseAIProvider {
 19 | 	constructor() {
 20 | 		super();
 21 | 		this.name = 'Codex CLI';
 22 | 		// Codex CLI has native schema support, no explicit JSON schema mode required
 23 | 		this.needsExplicitJsonSchema = false;
 24 | 		// Codex CLI does not support temperature parameter
 25 | 		this.supportsTemperature = false;
 26 | 		// Load supported models from supported-models.json
 27 | 		this.supportedModels = getSupportedModelsForProvider('codex-cli');
 28 | 
 29 | 		// Validate that models were loaded successfully
 30 | 		if (this.supportedModels.length === 0) {
 31 | 			log(
 32 | 				'warn',
 33 | 				'No supported models found for codex-cli provider. Check supported-models.json configuration.'
 34 | 			);
 35 | 		}
 36 | 
 37 | 		// CLI availability check cache
 38 | 		this._codexCliChecked = false;
 39 | 		this._codexCliAvailable = null;
 40 | 	}
 41 | 
 42 | 	/**
 43 | 	 * Codex CLI does not require an API key when using OAuth via `codex login`.
 44 | 	 * @returns {boolean}
 45 | 	 */
 46 | 	isRequiredApiKey() {
 47 | 		return false;
 48 | 	}
 49 | 
 50 | 	/**
 51 | 	 * Returns the environment variable name used when an API key is provided.
 52 | 	 * Even though the API key is optional for Codex CLI (OAuth-first),
 53 | 	 * downstream resolution expects a non-throwing implementation.
 54 | 	 * Uses OPENAI_CODEX_API_KEY to avoid conflicts with OpenAI provider.
 55 | 	 * @returns {string}
 56 | 	 */
 57 | 	getRequiredApiKeyName() {
 58 | 		return 'OPENAI_CODEX_API_KEY';
 59 | 	}
 60 | 
 61 | 	/**
 62 | 	 * Optional CLI availability check; provide helpful guidance if missing.
 63 | 	 */
 64 | 	validateAuth() {
 65 | 		if (process.env.NODE_ENV === 'test') return;
 66 | 
 67 | 		if (!this._codexCliChecked) {
 68 | 			try {
 69 | 				execSync('codex --version', { stdio: 'pipe', timeout: 1000 });
 70 | 				this._codexCliAvailable = true;
 71 | 			} catch (error) {
 72 | 				this._codexCliAvailable = false;
 73 | 				log(
 74 | 					'warn',
 75 | 					'Codex CLI not detected. Install with: npm i -g @openai/codex or enable fallback with allowNpx.'
 76 | 				);
 77 | 			} finally {
 78 | 				this._codexCliChecked = true;
 79 | 			}
 80 | 		}
 81 | 	}
 82 | 
 83 | 	/**
 84 | 	 * Creates a Codex CLI client instance
 85 | 	 * @param {object} params
 86 | 	 * @param {string} [params.commandName] - Command name for settings lookup
 87 | 	 * @param {string} [params.apiKey] - Optional API key (injected as OPENAI_API_KEY for Codex CLI)
 88 | 	 * @returns {Function}
 89 | 	 */
 90 | 	getClient(params = {}) {
 91 | 		try {
 92 | 			// Merge global + command-specific settings from config
 93 | 			const settings = getCodexCliSettingsForCommand(params.commandName) || {};
 94 | 
 95 | 			// Inject API key only if explicitly provided; OAuth is the primary path
 96 | 			const defaultSettings = {
 97 | 				...settings,
 98 | 				...(params.apiKey
 99 | 					? { env: { ...(settings.env || {}), OPENAI_API_KEY: params.apiKey } }
100 | 					: {})
101 | 			};
102 | 
103 | 			return createCodexCli({ defaultSettings });
104 | 		} catch (error) {
105 | 			const msg = String(error?.message || '');
106 | 			const code = error?.code;
107 | 			if (code === 'ENOENT' || /codex/i.test(msg)) {
108 | 				const enhancedError = new Error(
109 | 					`Codex CLI not available. Please install Codex CLI first. Original error: ${error.message}`
110 | 				);
111 | 				enhancedError.cause = error;
112 | 				this.handleError('Codex CLI initialization', enhancedError);
113 | 			} else {
114 | 				this.handleError('client initialization', error);
115 | 			}
116 | 		}
117 | 	}
118 | }
119 | 
```

--------------------------------------------------------------------------------
/tests/integration/profiles/roo-init-functionality.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | import fs from 'fs';
  3 | import path from 'path';
  4 | import { rooProfile } from '../../../src/profiles/roo.js';
  5 | import { COMMON_TOOL_MAPPINGS } from '../../../src/profiles/base-profile.js';
  6 | 
  7 | describe('Roo Profile Initialization Functionality', () => {
  8 | 	let rooProfileContent;
  9 | 
 10 | 	beforeAll(() => {
 11 | 		// Read the roo.js profile file content once for all tests
 12 | 		const rooJsPath = path.join(process.cwd(), 'src', 'profiles', 'roo.js');
 13 | 		rooProfileContent = fs.readFileSync(rooJsPath, 'utf8');
 14 | 	});
 15 | 
 16 | 	test('roo.js uses factory pattern with correct configuration', () => {
 17 | 		// Check for explicit, non-default values in the source file
 18 | 		expect(rooProfileContent).toContain("name: 'roo'");
 19 | 		expect(rooProfileContent).toContain("displayName: 'Roo Code'");
 20 | 		expect(rooProfileContent).toContain(
 21 | 			'toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE'
 22 | 		);
 23 | 
 24 | 		// Check the final computed properties on the profile object
 25 | 		expect(rooProfile.profileName).toBe('roo');
 26 | 		expect(rooProfile.displayName).toBe('Roo Code');
 27 | 		expect(rooProfile.profileDir).toBe('.roo'); // default
 28 | 		expect(rooProfile.rulesDir).toBe('.roo/rules'); // default
 29 | 		expect(rooProfile.mcpConfig).toBe(true); // now uses standard MCP configuration with Roo enhancements
 30 | 	});
 31 | 
 32 | 	test('roo.js uses custom ROO_STYLE tool mappings', () => {
 33 | 		// Check that the profile uses the correct, non-standard tool mappings
 34 | 		expect(rooProfileContent).toContain(
 35 | 			'toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE'
 36 | 		);
 37 | 
 38 | 		// Verify the result: roo uses custom tool names
 39 | 		expect(rooProfile.conversionConfig.toolNames.edit_file).toBe('apply_diff');
 40 | 		expect(rooProfile.conversionConfig.toolNames.search).toBe('search_files');
 41 | 	});
 42 | 
 43 | 	test('roo.js profile ensures Roo directory structure via onAddRulesProfile', () => {
 44 | 		// Check if onAddRulesProfile function exists
 45 | 		expect(rooProfileContent).toContain(
 46 | 			'onAddRulesProfile(targetDir, assetsDir)'
 47 | 		);
 48 | 
 49 | 		// Check for the general copy of assets/roocode which includes .roo base structure
 50 | 		expect(rooProfileContent).toContain(
 51 | 			"const sourceDir = path.join(assetsDir, 'roocode');"
 52 | 		);
 53 | 		expect(rooProfileContent).toContain(
 54 | 			'copyRecursiveSync(sourceDir, targetDir);'
 55 | 		);
 56 | 
 57 | 		// Check for the specific .roo modes directory handling
 58 | 		expect(rooProfileContent).toContain(
 59 | 			"const rooModesDir = path.join(sourceDir, '.roo');"
 60 | 		);
 61 | 
 62 | 		// Check for import of ROO_MODES from profiles.js instead of local definition
 63 | 		expect(rooProfileContent).toContain(
 64 | 			"import { ROO_MODES } from '../constants/profiles.js';"
 65 | 		);
 66 | 	});
 67 | 
 68 | 	test('roo.js profile copies .roomodes file via onAddRulesProfile', () => {
 69 | 		expect(rooProfileContent).toContain(
 70 | 			'onAddRulesProfile(targetDir, assetsDir)'
 71 | 		);
 72 | 
 73 | 		// Check for the specific .roomodes copy logic
 74 | 		expect(rooProfileContent).toContain(
 75 | 			"const roomodesSrc = path.join(sourceDir, '.roomodes');"
 76 | 		);
 77 | 		expect(rooProfileContent).toContain(
 78 | 			"const roomodesDest = path.join(targetDir, '.roomodes');"
 79 | 		);
 80 | 		expect(rooProfileContent).toContain(
 81 | 			'fs.copyFileSync(roomodesSrc, roomodesDest);'
 82 | 		);
 83 | 	});
 84 | 
 85 | 	test('roo.js profile copies mode-specific rule files via onAddRulesProfile', () => {
 86 | 		expect(rooProfileContent).toContain(
 87 | 			'onAddRulesProfile(targetDir, assetsDir)'
 88 | 		);
 89 | 		expect(rooProfileContent).toContain('for (const mode of ROO_MODES)');
 90 | 
 91 | 		// Check for the specific mode rule file copy logic
 92 | 		expect(rooProfileContent).toContain(
 93 | 			'const src = path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`);'
 94 | 		);
 95 | 		expect(rooProfileContent).toContain(
 96 | 			"const dest = path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`);"
 97 | 		);
 98 | 	});
 99 | });
100 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/update-tasks.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * update-tasks.js
  3 |  * Direct function implementation for updating tasks based on new context
  4 |  */
  5 | 
  6 | import path from 'path';
  7 | import { updateTasks } from '../../../../scripts/modules/task-manager.js';
  8 | import { createLogWrapper } from '../../tools/utils.js';
  9 | import {
 10 | 	enableSilentMode,
 11 | 	disableSilentMode
 12 | } from '../../../../scripts/modules/utils.js';
 13 | 
 14 | /**
 15 |  * Direct function wrapper for updating tasks based on new context.
 16 |  *
 17 |  * @param {Object} args - Command arguments containing projectRoot, from, prompt, research options.
 18 |  * @param {string} args.from - The ID of the task to update.
 19 |  * @param {string} args.prompt - The prompt to update the task with.
 20 |  * @param {boolean} args.research - Whether to use research mode.
 21 |  * @param {string} args.tasksJsonPath - Path to the tasks.json file.
 22 |  * @param {string} args.projectRoot - Project root path (for MCP/env fallback)
 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 updateTasksDirect(args, log, context = {}) {
 29 | 	const { session } = context;
 30 | 	const { from, prompt, research, tasksJsonPath, projectRoot, tag } = args;
 31 | 
 32 | 	// Create the standard logger wrapper
 33 | 	const logWrapper = createLogWrapper(log);
 34 | 
 35 | 	// --- Input Validation ---
 36 | 	if (!projectRoot) {
 37 | 		logWrapper.error('updateTasksDirect requires a projectRoot argument.');
 38 | 		return {
 39 | 			success: false,
 40 | 			error: {
 41 | 				code: 'MISSING_ARGUMENT',
 42 | 				message: 'projectRoot is required.'
 43 | 			}
 44 | 		};
 45 | 	}
 46 | 
 47 | 	if (!from) {
 48 | 		logWrapper.error('updateTasksDirect called without from ID');
 49 | 		return {
 50 | 			success: false,
 51 | 			error: {
 52 | 				code: 'MISSING_ARGUMENT',
 53 | 				message: 'Starting task ID (from) is required'
 54 | 			}
 55 | 		};
 56 | 	}
 57 | 
 58 | 	if (!prompt) {
 59 | 		logWrapper.error('updateTasksDirect called without prompt');
 60 | 		return {
 61 | 			success: false,
 62 | 			error: {
 63 | 				code: 'MISSING_ARGUMENT',
 64 | 				message: 'Update prompt is required'
 65 | 			}
 66 | 		};
 67 | 	}
 68 | 
 69 | 	logWrapper.info(
 70 | 		`Updating tasks via direct function. From: ${from}, Research: ${research}, File: ${tasksJsonPath}, ProjectRoot: ${projectRoot}`
 71 | 	);
 72 | 
 73 | 	enableSilentMode(); // Enable silent mode
 74 | 	try {
 75 | 		// Call the core updateTasks function
 76 | 		const result = await updateTasks(
 77 | 			tasksJsonPath,
 78 | 			from,
 79 | 			prompt,
 80 | 			research,
 81 | 			{
 82 | 				session,
 83 | 				mcpLog: logWrapper,
 84 | 				projectRoot,
 85 | 				tag
 86 | 			},
 87 | 			'json'
 88 | 		);
 89 | 
 90 | 		if (result && result.success && Array.isArray(result.updatedTasks)) {
 91 | 			logWrapper.success(
 92 | 				`Successfully updated ${result.updatedTasks.length} tasks.`
 93 | 			);
 94 | 			return {
 95 | 				success: true,
 96 | 				data: {
 97 | 					message: `Successfully updated ${result.updatedTasks.length} tasks.`,
 98 | 					tasksPath: tasksJsonPath,
 99 | 					updatedCount: result.updatedTasks.length,
100 | 					telemetryData: result.telemetryData,
101 | 					tagInfo: result.tagInfo
102 | 				}
103 | 			};
104 | 		} else {
105 | 			// Handle case where core function didn't return expected success structure
106 | 			logWrapper.error(
107 | 				'Core updateTasks function did not return a successful structure.'
108 | 			);
109 | 			return {
110 | 				success: false,
111 | 				error: {
112 | 					code: 'CORE_FUNCTION_ERROR',
113 | 					message:
114 | 						result?.message ||
115 | 						'Core function failed to update tasks or returned unexpected result.'
116 | 				}
117 | 			};
118 | 		}
119 | 	} catch (error) {
120 | 		logWrapper.error(`Error executing core updateTasks: ${error.message}`);
121 | 		return {
122 | 			success: false,
123 | 			error: {
124 | 				code: 'UPDATE_TASKS_CORE_ERROR',
125 | 				message: error.message || 'Unknown error updating tasks'
126 | 			}
127 | 		};
128 | 	} finally {
129 | 		disableSilentMode(); // Ensure silent mode is disabled
130 | 	}
131 | }
132 | 
```

--------------------------------------------------------------------------------
/src/prompts/update-tasks.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"id": "update-tasks",
 3 | 	"version": "1.0.0",
 4 | 	"description": "Update multiple tasks based on new context or changes",
 5 | 	"metadata": {
 6 | 		"author": "system",
 7 | 		"created": "2024-01-01T00:00:00Z",
 8 | 		"updated": "2024-01-01T00:00:00Z",
 9 | 		"tags": ["update", "bulk", "context-change"]
10 | 	},
11 | 	"parameters": {
12 | 		"tasks": {
13 | 			"type": "array",
14 | 			"required": true,
15 | 			"description": "Array of tasks to update"
16 | 		},
17 | 		"updatePrompt": {
18 | 			"type": "string",
19 | 			"required": true,
20 | 			"description": "Description of changes to apply"
21 | 		},
22 | 		"useResearch": {
23 | 			"type": "boolean",
24 | 			"default": false,
25 | 			"description": "Use research mode"
26 | 		},
27 | 		"projectContext": {
28 | 			"type": "string",
29 | 			"description": "Additional project context"
30 | 		},
31 | 		"hasCodebaseAnalysis": {
32 | 			"type": "boolean",
33 | 			"required": false,
34 | 			"default": false,
35 | 			"description": "Whether codebase analysis is available"
36 | 		},
37 | 		"projectRoot": {
38 | 			"type": "string",
39 | 			"required": false,
40 | 			"default": "",
41 | 			"description": "Project root path for context"
42 | 		}
43 | 	},
44 | 	"prompts": {
45 | 		"default": {
46 | 			"system": "You are an AI assistant helping to update software development tasks based on new context.\nYou will be given a set of tasks and a prompt describing changes or new implementation details.\nYour job is to update the tasks to reflect these changes, while preserving their basic structure.\n\nGuidelines:\n1. Maintain the same IDs, statuses, and dependencies unless specifically mentioned in the prompt\n2. Update titles, descriptions, details, and test strategies to reflect the new information\n3. Do not change anything unnecessarily - just adapt what needs to change based on the prompt\n4. Return ALL the tasks in order, not just the modified ones\n5. VERY IMPORTANT: Preserve all subtasks marked as \"done\" or \"completed\" - do not modify their content\n6. For tasks with completed subtasks, build upon what has already been done rather than rewriting everything\n7. If an existing completed subtask needs to be changed/undone based on the new context, DO NOT modify it directly\n8. Instead, add a new subtask that clearly indicates what needs to be changed or replaced\n9. Use the existence of completed subtasks as an opportunity to make new subtasks more specific and targeted",
47 | 			"user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before updating tasks:\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 relevant files and understand current implementation\n4. Analyze how the new changes relate to the existing codebase\n\nBased on your analysis:\n- Update task details to reference specific files, functions, or patterns from the codebase\n- Ensure implementation details align with the project's current architecture\n- Include specific code examples or file references where appropriate\n- Consider how changes impact existing components\n\nProject Root: {{projectRoot}}\n\n{{/if}}Here are the tasks to update:\n{{{json tasks}}}\n\nPlease update these tasks based on the following new context:\n{{updatePrompt}}\n\nIMPORTANT: In the tasks above, any subtasks with \"status\": \"done\" or \"status\": \"completed\" should be preserved exactly as is. Build your changes around these completed items.{{#if projectContext}}\n\n# Project Context\n\n{{projectContext}}{{/if}}\n\nIMPORTANT: Your response must be a JSON object with a single property named \"tasks\" containing the updated array of tasks."
48 | 		}
49 | 	}
50 | }
51 | 
```

--------------------------------------------------------------------------------
/tests/unit/ai-providers/zai-provider.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | 
  3 | // Mock the OpenAI-compatible creation
  4 | const mockCreateOpenAICompatible = jest.fn(() => jest.fn());
  5 | 
  6 | jest.unstable_mockModule('@ai-sdk/openai-compatible', () => ({
  7 | 	createOpenAICompatible: mockCreateOpenAICompatible
  8 | }));
  9 | 
 10 | // Mock utils
 11 | jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
 12 | 	log: jest.fn(),
 13 | 	resolveEnvVariable: jest.fn((key) => process.env[key]),
 14 | 	findProjectRoot: jest.fn(() => process.cwd()),
 15 | 	isEmpty: jest.fn(() => false)
 16 | }));
 17 | 
 18 | jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
 19 | 	isProxyEnabled: jest.fn(() => false)
 20 | }));
 21 | 
 22 | // Import after mocking
 23 | const { ZAIProvider } = await import('../../../src/ai-providers/zai.js');
 24 | const { ZAICodingProvider } = await import(
 25 | 	'../../../src/ai-providers/zai-coding.js'
 26 | );
 27 | 
 28 | describe('ZAI Provider', () => {
 29 | 	let provider;
 30 | 
 31 | 	beforeEach(() => {
 32 | 		jest.clearAllMocks();
 33 | 		provider = new ZAIProvider();
 34 | 	});
 35 | 
 36 | 	describe('Configuration', () => {
 37 | 		it('should have correct base configuration', () => {
 38 | 			expect(provider.name).toBe('Z.ai');
 39 | 			expect(provider.apiKeyEnvVar).toBe('ZAI_API_KEY');
 40 | 			expect(provider.requiresApiKey).toBe(true);
 41 | 			expect(provider.defaultBaseURL).toBe('https://api.z.ai/api/paas/v4/');
 42 | 			expect(provider.supportsStructuredOutputs).toBe(true);
 43 | 		});
 44 | 	});
 45 | 
 46 | 	describe('Token Parameter Handling', () => {
 47 | 		it('should not include max_tokens in requests', () => {
 48 | 			// ZAI API rejects max_tokens parameter (error code 1210)
 49 | 			const result = provider.prepareTokenParam('glm-4.6', 2000);
 50 | 
 51 | 			// Should return empty object instead of { maxOutputTokens: 2000 }
 52 | 			expect(result).toEqual({});
 53 | 		});
 54 | 
 55 | 		it('should return empty object even with undefined maxTokens', () => {
 56 | 			const result = provider.prepareTokenParam('glm-4.6', undefined);
 57 | 			expect(result).toEqual({});
 58 | 		});
 59 | 
 60 | 		it('should return empty object even with very large maxTokens', () => {
 61 | 			// ZAI may have lower limits than other providers
 62 | 			const result = provider.prepareTokenParam('glm-4.6', 204800);
 63 | 			expect(result).toEqual({});
 64 | 		});
 65 | 	});
 66 | 
 67 | 	describe('API Key Handling', () => {
 68 | 		it('should require API key', () => {
 69 | 			expect(provider.isRequiredApiKey()).toBe(true);
 70 | 			expect(provider.getRequiredApiKeyName()).toBe('ZAI_API_KEY');
 71 | 		});
 72 | 
 73 | 		it('should validate when API key is missing', () => {
 74 | 			expect(() => provider.validateAuth({})).toThrow(
 75 | 				'Z.ai API key is required'
 76 | 			);
 77 | 		});
 78 | 
 79 | 		it('should pass validation when API key is provided', () => {
 80 | 			expect(() => provider.validateAuth({ apiKey: 'test-key' })).not.toThrow();
 81 | 		});
 82 | 	});
 83 | });
 84 | 
 85 | describe('ZAI Coding Provider', () => {
 86 | 	let provider;
 87 | 
 88 | 	beforeEach(() => {
 89 | 		jest.clearAllMocks();
 90 | 		provider = new ZAICodingProvider();
 91 | 	});
 92 | 
 93 | 	describe('Configuration', () => {
 94 | 		it('should have correct base configuration', () => {
 95 | 			expect(provider.name).toBe('Z.ai (Coding Plan)');
 96 | 			expect(provider.apiKeyEnvVar).toBe('ZAI_API_KEY');
 97 | 			expect(provider.requiresApiKey).toBe(true);
 98 | 			expect(provider.defaultBaseURL).toBe(
 99 | 				'https://api.z.ai/api/coding/paas/v4/'
100 | 			);
101 | 			expect(provider.supportsStructuredOutputs).toBe(true);
102 | 		});
103 | 	});
104 | 
105 | 	describe('Token Parameter Handling', () => {
106 | 		it('should not include max_tokens in requests', () => {
107 | 			// ZAI Coding API also rejects max_tokens parameter
108 | 			const result = provider.prepareTokenParam('glm-4.6', 2000);
109 | 
110 | 			// Should return empty object instead of { maxOutputTokens: 2000 }
111 | 			expect(result).toEqual({});
112 | 		});
113 | 
114 | 		it('should return empty object even with undefined maxTokens', () => {
115 | 			const result = provider.prepareTokenParam('glm-4.6', undefined);
116 | 			expect(result).toEqual({});
117 | 		});
118 | 	});
119 | });
120 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/models.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * models.js
  3 |  * Direct function for managing AI model configurations via MCP
  4 |  */
  5 | 
  6 | import {
  7 | 	getModelConfiguration,
  8 | 	getAvailableModelsList,
  9 | 	setModel
 10 | } from '../../../../scripts/modules/task-manager/models.js';
 11 | import {
 12 | 	enableSilentMode,
 13 | 	disableSilentMode
 14 | } from '../../../../scripts/modules/utils.js';
 15 | import { createLogWrapper } from '../../tools/utils.js';
 16 | import { CUSTOM_PROVIDERS_ARRAY } from '@tm/core';
 17 | 
 18 | // Define supported roles for model setting
 19 | const MODEL_ROLES = ['main', 'research', 'fallback'];
 20 | 
 21 | /**
 22 |  * Determine provider hint from custom provider flags
 23 |  * @param {Object} args - Arguments containing provider flags
 24 |  * @returns {string|undefined} Provider hint or undefined if no custom provider flag is set
 25 |  */
 26 | function getProviderHint(args) {
 27 | 	return CUSTOM_PROVIDERS_ARRAY.find((provider) => args[provider]);
 28 | }
 29 | 
 30 | /**
 31 |  * Handle setting models for different roles
 32 |  * @param {Object} args - Arguments containing role-specific model IDs
 33 |  * @param {Object} context - Context object with session, mcpLog, projectRoot
 34 |  * @returns {Object|null} Result if a model was set, null if no model setting was requested
 35 |  */
 36 | async function handleModelSetting(args, context) {
 37 | 	for (const role of MODEL_ROLES) {
 38 | 		const roleKey = `set${role.charAt(0).toUpperCase() + role.slice(1)}`; // setMain, setResearch, setFallback
 39 | 
 40 | 		if (args[roleKey]) {
 41 | 			const providerHint = getProviderHint(args);
 42 | 
 43 | 			return await setModel(role, args[roleKey], {
 44 | 				...context,
 45 | 				providerHint,
 46 | 				...(args.baseURL && { baseURL: args.baseURL })
 47 | 			});
 48 | 		}
 49 | 	}
 50 | 	return null; // No model setting was requested
 51 | }
 52 | 
 53 | /**
 54 |  * Get or update model configuration
 55 |  * @param {Object} args - Arguments passed by the MCP tool
 56 |  * @param {Object} log - MCP logger
 57 |  * @param {Object} context - MCP context (contains session)
 58 |  * @returns {Object} Result object with success, data/error fields
 59 |  */
 60 | export async function modelsDirect(args, log, context = {}) {
 61 | 	const { session } = context;
 62 | 	const { projectRoot } = args; // Extract projectRoot from args
 63 | 
 64 | 	// Create a logger wrapper that the core functions can use
 65 | 	const mcpLog = createLogWrapper(log);
 66 | 
 67 | 	log.info(`Executing models_direct with args: ${JSON.stringify(args)}`);
 68 | 	log.info(`Using project root: ${projectRoot}`);
 69 | 
 70 | 	// Validate flags: only one custom provider flag can be used simultaneously
 71 | 	const customProviderFlags = CUSTOM_PROVIDERS_ARRAY.filter(
 72 | 		(provider) => args[provider]
 73 | 	);
 74 | 
 75 | 	if (customProviderFlags.length > 1) {
 76 | 		log.error(
 77 | 			'Error: Cannot use multiple custom provider flags simultaneously.'
 78 | 		);
 79 | 		return {
 80 | 			success: false,
 81 | 			error: {
 82 | 				code: 'INVALID_ARGS',
 83 | 				message:
 84 | 					'Cannot use multiple custom provider flags simultaneously. Choose only one: openrouter, ollama, bedrock, azure, vertex, or openai-compatible.'
 85 | 			}
 86 | 		};
 87 | 	}
 88 | 
 89 | 	try {
 90 | 		enableSilentMode();
 91 | 
 92 | 		try {
 93 | 			// Check for the listAvailableModels flag
 94 | 			if (args.listAvailableModels === true) {
 95 | 				return await getAvailableModelsList({
 96 | 					session,
 97 | 					mcpLog,
 98 | 					projectRoot
 99 | 				});
100 | 			}
101 | 
102 | 			// Handle setting any model role using unified function
103 | 			const modelContext = { session, mcpLog, projectRoot };
104 | 			const modelSetResult = await handleModelSetting(args, modelContext);
105 | 			if (modelSetResult) {
106 | 				return modelSetResult;
107 | 			}
108 | 
109 | 			// Default action: get current configuration
110 | 			return await getModelConfiguration({
111 | 				session,
112 | 				mcpLog,
113 | 				projectRoot
114 | 			});
115 | 		} finally {
116 | 			disableSilentMode();
117 | 		}
118 | 	} catch (error) {
119 | 		log.error(`Error in models_direct: ${error.message}`);
120 | 		return {
121 | 			success: false,
122 | 			error: {
123 | 				code: 'DIRECT_FUNCTION_ERROR',
124 | 				message: error.message,
125 | 				details: error.stack
126 | 			}
127 | 		};
128 | 	}
129 | }
130 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/ui/components/next-task.component.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Next task recommendation component
  3 |  * Displays detailed information about the recommended next task
  4 |  */
  5 | 
  6 | import type { Task } from '@tm/core';
  7 | import boxen from 'boxen';
  8 | import chalk from 'chalk';
  9 | import { getBoxWidth, getComplexityWithColor } from '../../utils/ui.js';
 10 | 
 11 | /**
 12 |  * Next task display options
 13 |  */
 14 | export interface NextTaskDisplayOptions {
 15 | 	id: string | number;
 16 | 	title: string;
 17 | 	priority?: string;
 18 | 	status?: string;
 19 | 	dependencies?: (string | number)[];
 20 | 	description?: string;
 21 | 	complexity?: number;
 22 | }
 23 | 
 24 | /**
 25 |  * Display the recommended next task section
 26 |  */
 27 | export function displayRecommendedNextTask(
 28 | 	task: NextTaskDisplayOptions | undefined
 29 | ): void {
 30 | 	if (!task) {
 31 | 		// If no task available, show a message
 32 | 		console.log(
 33 | 			boxen(
 34 | 				chalk.yellow(
 35 | 					'No tasks available to work on. All tasks are either completed, blocked by dependencies, or in progress.'
 36 | 				),
 37 | 				{
 38 | 					padding: 1,
 39 | 					borderStyle: 'round',
 40 | 					borderColor: 'yellow',
 41 | 					title: '⚠️ NO TASKS AVAILABLE ⚠️',
 42 | 					titleAlignment: 'center'
 43 | 				}
 44 | 			)
 45 | 		);
 46 | 		return;
 47 | 	}
 48 | 
 49 | 	// Build the content for the next task box
 50 | 	const content = [];
 51 | 
 52 | 	// Task header with ID and title
 53 | 	content.push(
 54 | 		`🔥 ${chalk.hex('#FF8800').bold('Next Task to Work On:')} ${chalk.yellow(`#${task.id}`)}${chalk.hex('#FF8800').bold(` - ${task.title}`)}`
 55 | 	);
 56 | 	content.push('');
 57 | 
 58 | 	// Priority and Status line
 59 | 	const statusLine = [];
 60 | 	if (task.priority) {
 61 | 		const priorityColor =
 62 | 			task.priority === 'high'
 63 | 				? chalk.red
 64 | 				: task.priority === 'medium'
 65 | 					? chalk.yellow
 66 | 					: chalk.gray;
 67 | 		statusLine.push(`Priority: ${priorityColor.bold(task.priority)}`);
 68 | 	}
 69 | 	if (task.status) {
 70 | 		const statusDisplay =
 71 | 			task.status === 'pending'
 72 | 				? chalk.yellow('○ pending')
 73 | 				: task.status === 'in-progress'
 74 | 					? chalk.blue('▶ in-progress')
 75 | 					: chalk.gray(task.status);
 76 | 		statusLine.push(`Status: ${statusDisplay}`);
 77 | 	}
 78 | 	content.push(statusLine.join('  '));
 79 | 
 80 | 	// Dependencies
 81 | 	const depsDisplay =
 82 | 		!task.dependencies || task.dependencies.length === 0
 83 | 			? chalk.gray('None')
 84 | 			: chalk.cyan(task.dependencies.join(', '));
 85 | 	content.push(`Dependencies: ${depsDisplay}`);
 86 | 
 87 | 	// Complexity with color and label
 88 | 	if (typeof task.complexity === 'number') {
 89 | 		content.push(`Complexity: ${getComplexityWithColor(task.complexity)}`);
 90 | 	}
 91 | 
 92 | 	// Description if available
 93 | 	if (task.description) {
 94 | 		content.push('');
 95 | 		content.push(`Description: ${chalk.white(task.description)}`);
 96 | 	}
 97 | 
 98 | 	// Action commands
 99 | 	content.push('');
100 | 	content.push(
101 | 		`${chalk.cyan('Start working:')} ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}`
102 | 	);
103 | 	content.push(
104 | 		`${chalk.cyan('View details:')} ${chalk.yellow(`task-master show ${task.id}`)}`
105 | 	);
106 | 
107 | 	// Display in a styled box with orange border
108 | 	console.log(
109 | 		boxen(content.join('\n'), {
110 | 			padding: 1,
111 | 			margin: { top: 1, bottom: 1 },
112 | 			borderStyle: 'round',
113 | 			borderColor: '#FFA500', // Orange color
114 | 			title: chalk.hex('#FFA500')('⚡ RECOMMENDED NEXT TASK ⚡'),
115 | 			titleAlignment: 'center',
116 | 			width: getBoxWidth(0.97),
117 | 			fullscreen: false
118 | 		})
119 | 	);
120 | }
121 | 
122 | /**
123 |  * Get task description from the full task object
124 |  */
125 | export function getTaskDescription(task: Task): string | undefined {
126 | 	// Try to get description from the task
127 | 	// This could be from task.description or the first line of task.details
128 | 	if ('description' in task && task.description) {
129 | 		return task.description as string;
130 | 	}
131 | 
132 | 	if ('details' in task && task.details) {
133 | 		// Take first sentence or line from details
134 | 		const details = task.details as string;
135 | 		const firstLine = details.split('\n')[0];
136 | 		const firstSentence = firstLine.split('.')[0];
137 | 		return firstSentence;
138 | 	}
139 | 
140 | 	return undefined;
141 | }
142 | 
```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/clear-subtasks.js:
--------------------------------------------------------------------------------

```javascript
  1 | import path from 'path';
  2 | import chalk from 'chalk';
  3 | import boxen from 'boxen';
  4 | import Table from 'cli-table3';
  5 | 
  6 | import { log, readJSON, writeJSON, truncate, isSilentMode } from '../utils.js';
  7 | import { displayBanner } from '../ui.js';
  8 | 
  9 | /**
 10 |  * Clear subtasks from specified tasks
 11 |  * @param {string} tasksPath - Path to the tasks.json file
 12 |  * @param {string} taskIds - Task IDs to clear subtasks from
 13 |  * @param {Object} context - Context object containing projectRoot and tag
 14 |  * @param {string} [context.projectRoot] - Project root path
 15 |  * @param {string} [context.tag] - Tag for the task
 16 |  */
 17 | function clearSubtasks(tasksPath, taskIds, context = {}) {
 18 | 	const { projectRoot, tag } = context;
 19 | 	log('info', `Reading tasks from ${tasksPath}...`);
 20 | 	const data = readJSON(tasksPath, projectRoot, tag);
 21 | 	if (!data || !data.tasks) {
 22 | 		log('error', 'No valid tasks found.');
 23 | 		process.exit(1);
 24 | 	}
 25 | 
 26 | 	if (!isSilentMode()) {
 27 | 		console.log(
 28 | 			boxen(chalk.white.bold('Clearing Subtasks'), {
 29 | 				padding: 1,
 30 | 				borderColor: 'blue',
 31 | 				borderStyle: 'round',
 32 | 				margin: { top: 1, bottom: 1 }
 33 | 			})
 34 | 		);
 35 | 	}
 36 | 
 37 | 	// Handle multiple task IDs (comma-separated)
 38 | 	const taskIdArray = taskIds.split(',').map((id) => id.trim());
 39 | 	let clearedCount = 0;
 40 | 
 41 | 	// Create a summary table for the cleared subtasks
 42 | 	const summaryTable = new Table({
 43 | 		head: [
 44 | 			chalk.cyan.bold('Task ID'),
 45 | 			chalk.cyan.bold('Task Title'),
 46 | 			chalk.cyan.bold('Subtasks Cleared')
 47 | 		],
 48 | 		colWidths: [10, 50, 20],
 49 | 		style: { head: [], border: [] }
 50 | 	});
 51 | 
 52 | 	taskIdArray.forEach((taskId) => {
 53 | 		const id = parseInt(taskId, 10);
 54 | 		if (Number.isNaN(id)) {
 55 | 			log('error', `Invalid task ID: ${taskId}`);
 56 | 			return;
 57 | 		}
 58 | 
 59 | 		const task = data.tasks.find((t) => t.id === id);
 60 | 		if (!task) {
 61 | 			log('error', `Task ${id} not found`);
 62 | 			return;
 63 | 		}
 64 | 
 65 | 		if (!task.subtasks || task.subtasks.length === 0) {
 66 | 			log('info', `Task ${id} has no subtasks to clear`);
 67 | 			summaryTable.push([
 68 | 				id.toString(),
 69 | 				truncate(task.title, 47),
 70 | 				chalk.yellow('No subtasks')
 71 | 			]);
 72 | 			return;
 73 | 		}
 74 | 
 75 | 		const subtaskCount = task.subtasks.length;
 76 | 		task.subtasks = [];
 77 | 		clearedCount++;
 78 | 		log('info', `Cleared ${subtaskCount} subtasks from task ${id}`);
 79 | 
 80 | 		summaryTable.push([
 81 | 			id.toString(),
 82 | 			truncate(task.title, 47),
 83 | 			chalk.green(`${subtaskCount} subtasks cleared`)
 84 | 		]);
 85 | 	});
 86 | 
 87 | 	if (clearedCount > 0) {
 88 | 		writeJSON(tasksPath, data, projectRoot, tag);
 89 | 
 90 | 		// Show summary table
 91 | 		if (!isSilentMode()) {
 92 | 			console.log(
 93 | 				boxen(chalk.white.bold('Subtask Clearing Summary:'), {
 94 | 					padding: { left: 2, right: 2, top: 0, bottom: 0 },
 95 | 					margin: { top: 1, bottom: 0 },
 96 | 					borderColor: 'blue',
 97 | 					borderStyle: 'round'
 98 | 				})
 99 | 			);
100 | 			console.log(summaryTable.toString());
101 | 		}
102 | 
103 | 		// Success message
104 | 		if (!isSilentMode()) {
105 | 			console.log(
106 | 				boxen(
107 | 					chalk.green(
108 | 						`Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)`
109 | 					),
110 | 					{
111 | 						padding: 1,
112 | 						borderColor: 'green',
113 | 						borderStyle: 'round',
114 | 						margin: { top: 1 }
115 | 					}
116 | 				)
117 | 			);
118 | 
119 | 			// Next steps suggestion
120 | 			console.log(
121 | 				boxen(
122 | 					chalk.white.bold('Next Steps:') +
123 | 						'\n\n' +
124 | 						`${chalk.cyan('1.')} Run ${chalk.yellow('task-master expand --id=<id>')} to generate new subtasks\n` +
125 | 						`${chalk.cyan('2.')} Run ${chalk.yellow('task-master list --with-subtasks')} to verify changes`,
126 | 					{
127 | 						padding: 1,
128 | 						borderColor: 'cyan',
129 | 						borderStyle: 'round',
130 | 						margin: { top: 1 }
131 | 					}
132 | 				)
133 | 			);
134 | 		}
135 | 	} else {
136 | 		if (!isSilentMode()) {
137 | 			console.log(
138 | 				boxen(chalk.yellow('No subtasks were cleared'), {
139 | 					padding: 1,
140 | 					borderColor: 'yellow',
141 | 					borderStyle: 'round',
142 | 					margin: { top: 1 }
143 | 				})
144 | 			);
145 | 		}
146 | 	}
147 | }
148 | 
149 | export default clearSubtasks;
150 | 
```

--------------------------------------------------------------------------------
/.taskmaster/docs/prd-tm-start.txt:
--------------------------------------------------------------------------------

```
 1 | <context>
 2 | # Overview
 3 | Add a new CLI command: `task-master start <task_id>` (alias: `tm start <task_id>`). This command hard-codes `claude-code` as the executor, fetches task details, builds a standardized prompt, runs claude-code, shows the result, checks for git changes, and auto-marks the task as done if successful.
 4 | 
 5 | We follow the Commander class pattern, reuse task retrieval from `show` command flow. Extremely minimal for 1-hour hackathon timeline.
 6 | 
 7 | # Core Features
 8 | - `start` command (Commander class style) 
 9 | - Hard-coded executor: `claude-code`
10 | - Standardized prompt designed for minimal changes following existing patterns
11 | - Shows claude-code output (no streaming)
12 | - Git status check for success detection
13 | - Auto-mark task done if successful
14 | 
15 | # User Experience
16 | ```
17 | task-master start 12
18 | ```
19 | 1) Fetches Task #12 details
20 | 2) Builds standardized prompt with task context
21 | 3) Runs claude-code with the prompt
22 | 4) Shows output
23 | 5) Checks git status for changes
24 | 6) Auto-marks task done if changes detected
25 | </context>
26 | 
27 | <PRD>
28 | # Technical Architecture
29 | 
30 | - Command pattern:
31 |   - Create `apps/cli/src/commands/start.command.ts` modeled on [list.command.ts](mdc:apps/cli/src/commands/list.command.ts) and task lookup from [show.command.ts](mdc:apps/cli/src/commands/show.command.ts)
32 | 
33 | - Task retrieval:
34 |   - Use `@tm/core` via `createTaskMasterCore` to get task by ID
35 |   - Extract: id, title, description, details
36 | 
37 | - Executor (ultra-simple approach):
38 |   - Execute `claude "full prompt here"` command directly
39 |   - The prompt tells Claude to first run `tm show <task_id>` to get task details
40 |   - Then tells Claude to implement the code changes
41 |   - This opens Claude CLI interface naturally in the current terminal
42 |   - No subprocess management needed - just execute the command
43 | 
44 | - Execution flow:
45 |   1) Validate `<task_id>` exists; exit with error if not
46 |   2) Build standardized prompt that includes instructions to run `tm show <task_id>`
47 |   3) Execute `claude "prompt"` command directly in terminal
48 |   4) Claude CLI opens, runs `tm show`, then implements changes
49 |   5) After Claude session ends, run `git status --porcelain` to detect changes
50 |   6) If changes detected, auto-run `task-master set-status --id=<task_id> --status=done`
51 | 
52 | - Success criteria:
53 |   - Success = exit code 0 AND git shows modified/created files
54 |   - Print changed file paths; warn if no changes detected
55 | 
56 | # Development Roadmap
57 | 
58 | MVP (ship in ~1 hour):
59 | 1) Implement `start.command.ts` (Commander class), parse `<task_id>`
60 | 2) Validate task exists via tm-core 
61 | 3) Build prompt that tells Claude to run `tm show <task_id>` then implement
62 | 4) Execute `claude "prompt"` command, then check git status and auto-mark done
63 | 
64 | # Risks and Mitigations
65 | - Executor availability: Error clearly if `claude-code` provider fails
66 | - False success: Git-change heuristic acceptable for hackathon MVP
67 | 
68 | # Appendix
69 | 
70 | **Standardized Prompt Template:**
71 | ```
72 | You are an AI coding assistant with access to this repository's codebase.
73 | 
74 | First, run this command to get the task details:
75 | tm show <task_id>
76 | 
77 | Then implement the task with these requirements:
78 | - Make the SMALLEST number of code changes possible
79 | - Follow ALL existing patterns in the codebase (you have access to analyze the code)
80 | - Do NOT over-engineer the solution
81 | - Use existing files/functions/patterns wherever possible
82 | - When complete, print: COMPLETED: <brief summary of changes>
83 | 
84 | Begin by running tm show <task_id> to understand what needs to be implemented.
85 | ```
86 | 
87 | **Key References:**
88 | - [list.command.ts](mdc:apps/cli/src/commands/list.command.ts) - Command structure
89 | - [show.command.ts](mdc:apps/cli/src/commands/show.command.ts) - Task validation
90 | - Node.js `child_process.exec()` - For executing `claude "prompt"` command
91 | </PRD>
```

--------------------------------------------------------------------------------
/packages/tm-core/src/subpath-exports.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Test file documenting subpath export usage
  3 |  * This demonstrates how consumers can use granular imports for better tree-shaking
  4 |  */
  5 | 
  6 | import { describe, expect, it } from 'vitest';
  7 | 
  8 | describe('Subpath Exports', () => {
  9 | 	it('should allow importing from auth subpath', async () => {
 10 | 		// Instead of: import { AuthManager } from '@tm/core';
 11 | 		// Use: import { AuthManager } from '@tm/core/auth';
 12 | 		const authModule = await import('./auth');
 13 | 		expect(authModule.AuthManager).toBeDefined();
 14 | 		expect(authModule.AuthenticationError).toBeDefined();
 15 | 	});
 16 | 
 17 | 	it('should allow importing from storage subpath', async () => {
 18 | 		// Instead of: import { FileStorage } from '@tm/core';
 19 | 		// Use: import { FileStorage } from '@tm/core/storage';
 20 | 		const storageModule = await import('./storage');
 21 | 		expect(storageModule.FileStorage).toBeDefined();
 22 | 		expect(storageModule.ApiStorage).toBeDefined();
 23 | 		expect(storageModule.StorageFactory).toBeDefined();
 24 | 	});
 25 | 
 26 | 	it('should allow importing from config subpath', async () => {
 27 | 		// Instead of: import { ConfigManager } from '@tm/core';
 28 | 		// Use: import { ConfigManager } from '@tm/core/config';
 29 | 		const configModule = await import('./config');
 30 | 		expect(configModule.ConfigManager).toBeDefined();
 31 | 	});
 32 | 
 33 | 	it('should allow importing from errors subpath', async () => {
 34 | 		// Instead of: import { TaskMasterError } from '@tm/core';
 35 | 		// Use: import { TaskMasterError } from '@tm/core/errors';
 36 | 		const errorsModule = await import('./errors');
 37 | 		expect(errorsModule.TaskMasterError).toBeDefined();
 38 | 		expect(errorsModule.ERROR_CODES).toBeDefined();
 39 | 	});
 40 | 
 41 | 	it('should allow importing from logger subpath', async () => {
 42 | 		// Instead of: import { getLogger } from '@tm/core';
 43 | 		// Use: import { getLogger } from '@tm/core/logger';
 44 | 		const loggerModule = await import('./logger');
 45 | 		expect(loggerModule.getLogger).toBeDefined();
 46 | 		expect(loggerModule.createLogger).toBeDefined();
 47 | 	});
 48 | 
 49 | 	it('should allow importing from providers subpath', async () => {
 50 | 		// Instead of: import { BaseProvider } from '@tm/core';
 51 | 		// Use: import { BaseProvider } from '@tm/core/providers';
 52 | 		const providersModule = await import('./providers');
 53 | 		expect(providersModule.BaseProvider).toBeDefined();
 54 | 	});
 55 | 
 56 | 	it('should allow importing from services subpath', async () => {
 57 | 		// Instead of: import { TaskService } from '@tm/core';
 58 | 		// Use: import { TaskService } from '@tm/core/services';
 59 | 		const servicesModule = await import('./services');
 60 | 		expect(servicesModule.TaskService).toBeDefined();
 61 | 	});
 62 | 
 63 | 	it('should allow importing from utils subpath', async () => {
 64 | 		// Instead of: import { generateId } from '@tm/core';
 65 | 		// Use: import { generateId } from '@tm/core/utils';
 66 | 		const utilsModule = await import('./utils');
 67 | 		expect(utilsModule.generateId).toBeDefined();
 68 | 	});
 69 | });
 70 | 
 71 | /**
 72 |  * Usage Examples for Consumers:
 73 |  *
 74 |  * 1. Import only authentication (smaller bundle):
 75 |  * ```typescript
 76 |  * import { AuthManager, AuthenticationError } from '@tm/core/auth';
 77 |  * ```
 78 |  *
 79 |  * 2. Import only storage (no auth code bundled):
 80 |  * ```typescript
 81 |  * import { FileStorage, StorageFactory } from '@tm/core/storage';
 82 |  * ```
 83 |  *
 84 |  * 3. Import only errors (minimal bundle):
 85 |  * ```typescript
 86 |  * import { TaskMasterError, ERROR_CODES } from '@tm/core/errors';
 87 |  * ```
 88 |  *
 89 |  * 4. Still support convenience imports (larger bundle but better DX):
 90 |  * ```typescript
 91 |  * import { AuthManager, FileStorage, TaskMasterError } from '@tm/core';
 92 |  * ```
 93 |  *
 94 |  * Benefits:
 95 |  * - Better tree-shaking: unused modules are not bundled
 96 |  * - Clearer dependencies: explicit about what parts of the library you use
 97 |  * - Faster builds: bundlers can optimize better with granular imports
 98 |  * - Smaller bundles: especially important for browser/edge deployments
 99 |  */
100 | 
```

--------------------------------------------------------------------------------
/src/prompts/research.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"id": "research",
 3 | 	"version": "1.0.0",
 4 | 	"description": "Perform AI-powered research with project context",
 5 | 	"metadata": {
 6 | 		"author": "system",
 7 | 		"created": "2024-01-01T00:00:00Z",
 8 | 		"updated": "2024-01-01T00:00:00Z",
 9 | 		"tags": ["research", "context-aware", "information-gathering"]
10 | 	},
11 | 	"parameters": {
12 | 		"query": {
13 | 			"type": "string",
14 | 			"required": true,
15 | 			"description": "Research query"
16 | 		},
17 | 		"gatheredContext": {
18 | 			"type": "string",
19 | 			"default": "",
20 | 			"description": "Gathered project context"
21 | 		},
22 | 		"detailLevel": {
23 | 			"type": "string",
24 | 			"enum": ["low", "medium", "high"],
25 | 			"default": "medium",
26 | 			"description": "Level of detail for the response"
27 | 		},
28 | 		"projectInfo": {
29 | 			"type": "object",
30 | 			"description": "Project information",
31 | 			"properties": {
32 | 				"root": {
33 | 					"type": "string",
34 | 					"description": "Project root path"
35 | 				},
36 | 				"taskCount": {
37 | 					"type": "number",
38 | 					"description": "Number of related tasks"
39 | 				},
40 | 				"fileCount": {
41 | 					"type": "number",
42 | 					"description": "Number of related files"
43 | 				}
44 | 			}
45 | 		}
46 | 	},
47 | 	"prompts": {
48 | 		"default": {
49 | 			"system": "You are an expert AI research assistant helping with a software development project. You have access to project context including tasks, files, and project structure.\n\nYour role is to provide comprehensive, accurate, and actionable research responses based on the user's query and the provided project context.\n{{#if (eq detailLevel \"low\")}}\n**Response Style: Concise & Direct**\n- Provide brief, focused answers (2-4 paragraphs maximum)\n- Focus on the most essential information\n- Use bullet points for key takeaways\n- Avoid lengthy explanations unless critical\n- Skip pleasantries, introductions, and conclusions\n- No phrases like \"Based on your project context\" or \"I'll provide guidance\"\n- No summary outros or alignment statements\n- Get straight to the actionable information\n- Use simple, direct language - users want info, not explanation{{/if}}{{#if (eq detailLevel \"medium\")}}\n**Response Style: Balanced & Comprehensive**\n- Provide thorough but well-structured responses (4-8 paragraphs)\n- Include relevant examples and explanations\n- Balance depth with readability\n- Use headings and bullet points for organization{{/if}}{{#if (eq detailLevel \"high\")}}\n**Response Style: Detailed & Exhaustive**\n- Provide comprehensive, in-depth analysis (8+ paragraphs)\n- Include multiple perspectives and approaches\n- Provide detailed examples, code snippets, and step-by-step guidance\n- Cover edge cases and potential pitfalls\n- Use clear structure with headings, subheadings, and lists{{/if}}\n\n**Guidelines:**\n- Always consider the project context when formulating responses\n- Reference specific tasks, files, or project elements when relevant\n- Provide actionable insights that can be applied to the project\n- If the query relates to existing project tasks, suggest how the research applies to those tasks\n- Use markdown formatting for better readability\n- Be precise and avoid speculation unless clearly marked as such\n{{#if (eq detailLevel \"low\")}}\n**For LOW detail level specifically:**\n- Start immediately with the core information\n- No introductory phrases or context acknowledgments\n- No concluding summaries or project alignment statements\n- Focus purely on facts, steps, and actionable items{{/if}}",
50 | 			"user": "# Research Query\n\n{{query}}\n{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}\n{{/if}}\n\n# Instructions\n\nPlease research and provide a {{detailLevel}}-detail response to the query above. Consider the project context provided and make your response as relevant and actionable as possible for this specific project."
51 | 		}
52 | 	}
53 | }
54 | 
```

--------------------------------------------------------------------------------
/packages/tm-bridge/src/use-tag-bridge.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import boxen from 'boxen';
  2 | import chalk from 'chalk';
  3 | import ora from 'ora';
  4 | import type { BaseBridgeParams } from './bridge-types.js';
  5 | import { checkStorageType } from './bridge-utils.js';
  6 | 
  7 | /**
  8 |  * Parameters for the use-tag bridge function
  9 |  */
 10 | export interface UseTagBridgeParams extends BaseBridgeParams {
 11 | 	/** Tag name to switch to */
 12 | 	tagName: string;
 13 | }
 14 | 
 15 | /**
 16 |  * Result returned when API storage handles the tag switch
 17 |  */
 18 | export interface RemoteUseTagResult {
 19 | 	success: boolean;
 20 | 	previousTag: string | null;
 21 | 	currentTag: string;
 22 | 	switched: boolean;
 23 | 	taskCount: number;
 24 | 	message: string;
 25 | }
 26 | 
 27 | /**
 28 |  * Shared bridge function for use-tag command.
 29 |  * Checks if using API storage and delegates to remote service if so.
 30 |  *
 31 |  * For API storage, tags are called "briefs" and switching tags means
 32 |  * changing the current brief context.
 33 |  *
 34 |  * @param params - Bridge parameters
 35 |  * @returns Result object if API storage handled it, null if should fall through to file storage
 36 |  */
 37 | export async function tryUseTagViaRemote(
 38 | 	params: UseTagBridgeParams
 39 | ): Promise<RemoteUseTagResult | null> {
 40 | 	const {
 41 | 		tagName,
 42 | 		projectRoot,
 43 | 		isMCP = false,
 44 | 		outputFormat = 'text',
 45 | 		report
 46 | 	} = params;
 47 | 
 48 | 	// Check storage type using shared utility
 49 | 	const { isApiStorage, tmCore } = await checkStorageType(
 50 | 		projectRoot,
 51 | 		report,
 52 | 		'falling back to file-based tag switching'
 53 | 	);
 54 | 
 55 | 	if (!isApiStorage || !tmCore) {
 56 | 		// Not API storage - signal caller to fall through to file-based logic
 57 | 		return null;
 58 | 	}
 59 | 
 60 | 	// API STORAGE PATH: Switch brief in Hamster
 61 | 	report('info', `Switching to tag (brief) "${tagName}" in Hamster`);
 62 | 
 63 | 	// Show CLI output if not MCP
 64 | 	if (!isMCP && outputFormat === 'text') {
 65 | 		console.log(
 66 | 			boxen(chalk.blue.bold(`Switching Tag in Hamster`), {
 67 | 				padding: 1,
 68 | 				borderColor: 'blue',
 69 | 				borderStyle: 'round',
 70 | 				margin: { top: 1, bottom: 1 }
 71 | 			})
 72 | 		);
 73 | 	}
 74 | 
 75 | 	const spinner =
 76 | 		!isMCP && outputFormat === 'text'
 77 | 			? ora({ text: `Switching to tag "${tagName}"...`, color: 'cyan' }).start()
 78 | 			: null;
 79 | 
 80 | 	try {
 81 | 		// Get current context before switching
 82 | 		const previousContext = tmCore.auth.getContext();
 83 | 		const previousTag = previousContext?.briefName || null;
 84 | 
 85 | 		// Switch to the new tag/brief
 86 | 		// This will look up the brief by name and update the context
 87 | 		await tmCore.tasks.switchTag(tagName);
 88 | 
 89 | 		// Get updated context after switching
 90 | 		const newContext = tmCore.auth.getContext();
 91 | 		const currentTag = newContext?.briefName || tagName;
 92 | 
 93 | 		// Get task count for the new tag
 94 | 		const tasks = await tmCore.tasks.list();
 95 | 		const taskCount = tasks.tasks.length;
 96 | 
 97 | 		if (spinner) {
 98 | 			spinner.succeed(`Switched to tag "${currentTag}"`);
 99 | 		}
100 | 
101 | 		if (outputFormat === 'text' && !isMCP) {
102 | 			// Display success message
103 | 			const briefId = newContext?.briefId
104 | 				? newContext.briefId.slice(-8)
105 | 				: 'unknown';
106 | 			console.log(
107 | 				boxen(
108 | 					chalk.green.bold('✓ Tag Switched Successfully') +
109 | 						'\n\n' +
110 | 						(previousTag
111 | 							? chalk.white(`Previous Tag: ${chalk.cyan(previousTag)}\n`)
112 | 							: '') +
113 | 						chalk.white(`Current Tag: ${chalk.green.bold(currentTag)}`) +
114 | 						'\n' +
115 | 						chalk.gray(`Brief ID: ${briefId}`) +
116 | 						'\n' +
117 | 						chalk.white(`Available Tasks: ${chalk.yellow(taskCount)}`),
118 | 					{
119 | 						padding: 1,
120 | 						borderColor: 'green',
121 | 						borderStyle: 'round',
122 | 						margin: { top: 1, bottom: 0 }
123 | 					}
124 | 				)
125 | 			);
126 | 		}
127 | 
128 | 		// Return success result - signals that we handled it
129 | 		return {
130 | 			success: true,
131 | 			previousTag,
132 | 			currentTag,
133 | 			switched: true,
134 | 			taskCount,
135 | 			message: `Successfully switched to tag "${currentTag}"`
136 | 		};
137 | 	} catch (error) {
138 | 		if (spinner) {
139 | 			spinner.fail('Failed to switch tag');
140 | 		}
141 | 
142 | 		// tm-core already formatted the error properly, just re-throw
143 | 		throw error;
144 | 	}
145 | }
146 | 
```

--------------------------------------------------------------------------------
/packages/tm-bridge/src/update-bridge.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import boxen from 'boxen';
  2 | import chalk from 'chalk';
  3 | import ora from 'ora';
  4 | import type { BaseBridgeParams } from './bridge-types.js';
  5 | import { checkStorageType } from './bridge-utils.js';
  6 | 
  7 | /**
  8 |  * Parameters for the update bridge function
  9 |  */
 10 | export interface UpdateBridgeParams extends BaseBridgeParams {
 11 | 	/** Task ID (can be numeric "1", alphanumeric "TAS-49", or dotted "1.2" or "TAS-49.1") */
 12 | 	taskId: string | number;
 13 | 	/** Update prompt for AI */
 14 | 	prompt: string;
 15 | 	/** Whether to append or full update (default: false) */
 16 | 	appendMode?: boolean;
 17 | }
 18 | 
 19 | /**
 20 |  * Result returned when API storage handles the update
 21 |  */
 22 | export interface RemoteUpdateResult {
 23 | 	success: boolean;
 24 | 	taskId: string | number;
 25 | 	message: string;
 26 | 	telemetryData: null;
 27 | 	tagInfo: null;
 28 | }
 29 | 
 30 | /**
 31 |  * Shared bridge function for update-task and update-subtask commands.
 32 |  * Checks if using API storage and delegates to remote AI service if so.
 33 |  *
 34 |  * In API storage, tasks and subtasks are treated identically - there's no
 35 |  * parent/child hierarchy, so update-task and update-subtask can be used
 36 |  * interchangeably.
 37 |  *
 38 |  * @param params - Bridge parameters
 39 |  * @returns Result object if API storage handled it, null if should fall through to file storage
 40 |  */
 41 | export async function tryUpdateViaRemote(
 42 | 	params: UpdateBridgeParams
 43 | ): Promise<RemoteUpdateResult | null> {
 44 | 	const {
 45 | 		taskId,
 46 | 		prompt,
 47 | 		projectRoot,
 48 | 		tag,
 49 | 		appendMode = false,
 50 | 		isMCP = false,
 51 | 		outputFormat = 'text',
 52 | 		report
 53 | 	} = params;
 54 | 
 55 | 	// Check storage type using shared utility
 56 | 	const { isApiStorage, tmCore } = await checkStorageType(
 57 | 		projectRoot,
 58 | 		report,
 59 | 		'falling back to file-based update'
 60 | 	);
 61 | 
 62 | 	if (!isApiStorage || !tmCore) {
 63 | 		// Not API storage - signal caller to fall through to file-based logic
 64 | 		return null;
 65 | 	}
 66 | 
 67 | 	// API STORAGE PATH: Delegate to remote AI service
 68 | 	report('info', `Delegating update to Hamster for task ${taskId}`);
 69 | 
 70 | 	const mode = appendMode ? 'append' : 'update';
 71 | 
 72 | 	// Show CLI output if not MCP
 73 | 	if (!isMCP && outputFormat === 'text') {
 74 | 		const showDebug = process.env.TM_DEBUG === '1';
 75 | 		const promptPreview = showDebug
 76 | 			? `${prompt.substring(0, 60)}${prompt.length > 60 ? '...' : ''}`
 77 | 			: '[hidden]';
 78 | 
 79 | 		console.log(
 80 | 			boxen(
 81 | 				chalk.blue.bold(`Updating Task via Hamster`) +
 82 | 					'\n\n' +
 83 | 					chalk.white(`Task ID: ${taskId}`) +
 84 | 					'\n' +
 85 | 					chalk.white(`Mode: ${mode}`) +
 86 | 					'\n' +
 87 | 					chalk.white(`Prompt: ${promptPreview}`),
 88 | 				{
 89 | 					padding: 1,
 90 | 					borderColor: 'blue',
 91 | 					borderStyle: 'round',
 92 | 					margin: { top: 1, bottom: 1 }
 93 | 				}
 94 | 			)
 95 | 		);
 96 | 	}
 97 | 
 98 | 	const spinner =
 99 | 		!isMCP && outputFormat === 'text'
100 | 			? ora({ text: 'Updating task on Hamster...', color: 'cyan' }).start()
101 | 			: null;
102 | 
103 | 	try {
104 | 		// Call the API storage method which handles the remote update
105 | 		await tmCore.tasks.updateWithPrompt(String(taskId), prompt, tag, {
106 | 			mode
107 | 		});
108 | 
109 | 		if (spinner) {
110 | 			spinner.succeed('Task updated successfully');
111 | 		}
112 | 
113 | 		if (outputFormat === 'text') {
114 | 			console.log(
115 | 				boxen(
116 | 					chalk.green(`Successfully updated task ${taskId} via remote AI`) +
117 | 						'\n\n' +
118 | 						chalk.white('The task has been updated on the remote server.') +
119 | 						'\n' +
120 | 						chalk.white(
121 | 							`Run ${chalk.yellow(`task-master show ${taskId}`)} to view the updated task.`
122 | 						),
123 | 					{
124 | 						padding: 1,
125 | 						borderColor: 'green',
126 | 						borderStyle: 'round'
127 | 					}
128 | 				)
129 | 			);
130 | 		}
131 | 
132 | 		// Return success result - signals that we handled it
133 | 		return {
134 | 			success: true,
135 | 			taskId: taskId,
136 | 			message: 'Task updated via remote AI service',
137 | 			telemetryData: null,
138 | 			tagInfo: null
139 | 		};
140 | 	} catch (updateError) {
141 | 		if (spinner) {
142 | 			spinner.fail('Update failed');
143 | 		}
144 | 
145 | 		// tm-core already formatted the error properly, just re-throw
146 | 		throw updateError;
147 | 	}
148 | }
149 | 
```

--------------------------------------------------------------------------------
/apps/extension/src/webview/components/TagDropdown.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import React, { useState, useEffect, useRef } from 'react';
  2 | 
  3 | interface TagDropdownProps {
  4 | 	currentTag: string;
  5 | 	availableTags: string[];
  6 | 	onTagSwitch: (tagName: string) => Promise<void>;
  7 | 	sendMessage: (message: any) => Promise<any>;
  8 | 	dispatch: React.Dispatch<any>;
  9 | }
 10 | 
 11 | export const TagDropdown: React.FC<TagDropdownProps> = ({
 12 | 	currentTag,
 13 | 	availableTags,
 14 | 	onTagSwitch,
 15 | 	sendMessage,
 16 | 	dispatch
 17 | }) => {
 18 | 	const [isOpen, setIsOpen] = useState(false);
 19 | 	const [isLoading, setIsLoading] = useState(false);
 20 | 	const dropdownRef = useRef<HTMLDivElement>(null);
 21 | 
 22 | 	// Fetch tags when component mounts
 23 | 	useEffect(() => {
 24 | 		fetchTags();
 25 | 	}, []);
 26 | 
 27 | 	// Handle click outside to close dropdown
 28 | 	useEffect(() => {
 29 | 		const handleClickOutside = (event: MouseEvent) => {
 30 | 			if (
 31 | 				dropdownRef.current &&
 32 | 				!dropdownRef.current.contains(event.target as Node)
 33 | 			) {
 34 | 				setIsOpen(false);
 35 | 			}
 36 | 		};
 37 | 
 38 | 		if (isOpen) {
 39 | 			document.addEventListener('mousedown', handleClickOutside);
 40 | 			return () => {
 41 | 				document.removeEventListener('mousedown', handleClickOutside);
 42 | 			};
 43 | 		}
 44 | 	}, [isOpen]);
 45 | 
 46 | 	const fetchTags = async () => {
 47 | 		try {
 48 | 			const result = await sendMessage({ type: 'getTags' });
 49 | 
 50 | 			if (result?.tags && result?.currentTag) {
 51 | 				const tagNames = result.tags.map((tag: any) => tag.name || tag);
 52 | 				dispatch({
 53 | 					type: 'SET_TAG_DATA',
 54 | 					payload: {
 55 | 						currentTag: result.currentTag,
 56 | 						availableTags: tagNames
 57 | 					}
 58 | 				});
 59 | 			}
 60 | 		} catch (error) {
 61 | 			console.error('Failed to fetch tags:', error);
 62 | 		}
 63 | 	};
 64 | 
 65 | 	const handleTagSwitch = async (tagName: string) => {
 66 | 		if (tagName === currentTag) {
 67 | 			setIsOpen(false);
 68 | 			return;
 69 | 		}
 70 | 
 71 | 		setIsLoading(true);
 72 | 		try {
 73 | 			await onTagSwitch(tagName);
 74 | 			dispatch({ type: 'SET_CURRENT_TAG', payload: tagName });
 75 | 			setIsOpen(false);
 76 | 		} catch (error) {
 77 | 			console.error('Failed to switch tag:', error);
 78 | 		} finally {
 79 | 			setIsLoading(false);
 80 | 		}
 81 | 	};
 82 | 
 83 | 	return (
 84 | 		<div className="relative" ref={dropdownRef}>
 85 | 			<button
 86 | 				onClick={() => setIsOpen(!isOpen)}
 87 | 				disabled={isLoading}
 88 | 				className="flex items-center gap-2 px-3 py-1.5 text-sm bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded hover:bg-vscode-list-hoverBackground transition-colors"
 89 | 			>
 90 | 				<span className="font-medium">{currentTag}</span>
 91 | 				<svg
 92 | 					className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''}`}
 93 | 					fill="none"
 94 | 					stroke="currentColor"
 95 | 					viewBox="0 0 24 24"
 96 | 				>
 97 | 					<path
 98 | 						strokeLinecap="round"
 99 | 						strokeLinejoin="round"
100 | 						strokeWidth={2}
101 | 						d="M19 9l-7 7-7-7"
102 | 					/>
103 | 				</svg>
104 | 			</button>
105 | 
106 | 			{isOpen && (
107 | 				<div className="absolute top-full mt-1 right-0 bg-background border border-vscode-dropdown-border rounded shadow-lg z-50 min-w-[200px] py-1">
108 | 					{availableTags.map((tag) => (
109 | 						<button
110 | 							key={tag}
111 | 							onClick={() => handleTagSwitch(tag)}
112 | 							className={`w-full text-left px-3 py-2 text-sm transition-colors flex items-center justify-between group
113 | 								${
114 | 									tag === currentTag
115 | 										? 'bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground'
116 | 										: 'hover:bg-vscode-list-hoverBackground text-vscode-dropdown-foreground'
117 | 								}`}
118 | 						>
119 | 							<span className="truncate pr-2">{tag}</span>
120 | 							{tag === currentTag && (
121 | 								<svg
122 | 									className="w-4 h-4 flex-shrink-0 text-vscode-textLink-foreground"
123 | 									fill="none"
124 | 									stroke="currentColor"
125 | 									viewBox="0 0 24 24"
126 | 								>
127 | 									<path
128 | 										strokeLinecap="round"
129 | 										strokeLinejoin="round"
130 | 										strokeWidth={2}
131 | 										d="M5 13l4 4L19 7"
132 | 									/>
133 | 								</svg>
134 | 							)}
135 | 						</button>
136 | 					))}
137 | 				</div>
138 | 			)}
139 | 		</div>
140 | 	);
141 | };
142 | 
```

--------------------------------------------------------------------------------
/tests/integration/profiles/opencode-init-functionality.test.js:
--------------------------------------------------------------------------------

```javascript
 1 | import fs from 'fs';
 2 | import path from 'path';
 3 | import { opencodeProfile } from '../../../src/profiles/opencode.js';
 4 | 
 5 | describe('OpenCode Profile Initialization Functionality', () => {
 6 | 	let opencodeProfileContent;
 7 | 
 8 | 	beforeAll(() => {
 9 | 		const opencodeJsPath = path.join(
10 | 			process.cwd(),
11 | 			'src',
12 | 			'profiles',
13 | 			'opencode.js'
14 | 		);
15 | 		opencodeProfileContent = fs.readFileSync(opencodeJsPath, 'utf8');
16 | 	});
17 | 
18 | 	test('opencode.js has correct asset-only profile configuration', () => {
19 | 		// Check for explicit, non-default values in the source file
20 | 		expect(opencodeProfileContent).toContain("name: 'opencode'");
21 | 		expect(opencodeProfileContent).toContain("displayName: 'OpenCode'");
22 | 		expect(opencodeProfileContent).toContain("url: 'opencode.ai'");
23 | 		expect(opencodeProfileContent).toContain("docsUrl: 'opencode.ai/docs/'");
24 | 		expect(opencodeProfileContent).toContain("profileDir: '.'"); // non-default
25 | 		expect(opencodeProfileContent).toContain("rulesDir: '.'"); // non-default
26 | 		expect(opencodeProfileContent).toContain("mcpConfigName: 'opencode.json'"); // non-default
27 | 		expect(opencodeProfileContent).toContain('includeDefaultRules: false'); // non-default
28 | 		expect(opencodeProfileContent).toContain("'AGENTS.md': 'AGENTS.md'");
29 | 
30 | 		// Check the final computed properties on the profile object
31 | 		expect(opencodeProfile.profileName).toBe('opencode');
32 | 		expect(opencodeProfile.displayName).toBe('OpenCode');
33 | 		expect(opencodeProfile.profileDir).toBe('.');
34 | 		expect(opencodeProfile.rulesDir).toBe('.');
35 | 		expect(opencodeProfile.mcpConfig).toBe(true); // computed from mcpConfigName
36 | 		expect(opencodeProfile.mcpConfigName).toBe('opencode.json');
37 | 		expect(opencodeProfile.mcpConfigPath).toBe('opencode.json'); // computed
38 | 		expect(opencodeProfile.includeDefaultRules).toBe(false);
39 | 		expect(opencodeProfile.fileMap['AGENTS.md']).toBe('AGENTS.md');
40 | 	});
41 | 
42 | 	test('opencode.js has lifecycle functions for MCP config transformation', () => {
43 | 		expect(opencodeProfileContent).toContain(
44 | 			'function onPostConvertRulesProfile'
45 | 		);
46 | 		expect(opencodeProfileContent).toContain('function onRemoveRulesProfile');
47 | 		expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
48 | 	});
49 | 
50 | 	test('opencode.js handles opencode.json transformation in lifecycle functions', () => {
51 | 		expect(opencodeProfileContent).toContain('opencode.json');
52 | 		expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
53 | 		expect(opencodeProfileContent).toContain('$schema');
54 | 		expect(opencodeProfileContent).toContain('mcpServers');
55 | 		expect(opencodeProfileContent).toContain('mcp');
56 | 	});
57 | 
58 | 	test('opencode.js has proper error handling in lifecycle functions', () => {
59 | 		expect(opencodeProfileContent).toContain('try {');
60 | 		expect(opencodeProfileContent).toContain('} catch (error) {');
61 | 		expect(opencodeProfileContent).toContain('log(');
62 | 	});
63 | 
64 | 	test('opencode.js uses custom MCP config name', () => {
65 | 		// OpenCode uses opencode.json instead of mcp.json
66 | 		expect(opencodeProfileContent).toContain("mcpConfigName: 'opencode.json'");
67 | 		// Should not contain mcp.json as a config value (comments are OK)
68 | 		expect(opencodeProfileContent).not.toMatch(
69 | 			/mcpConfigName:\s*['"]mcp\.json['"]/
70 | 		);
71 | 	});
72 | 
73 | 	test('opencode.js has transformation logic for OpenCode format', () => {
74 | 		// Check for transformation function
75 | 		expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
76 | 
77 | 		// Check for specific transformation logic
78 | 		expect(opencodeProfileContent).toContain('mcpServers');
79 | 		expect(opencodeProfileContent).toContain('command');
80 | 		expect(opencodeProfileContent).toContain('args');
81 | 		expect(opencodeProfileContent).toContain('environment');
82 | 		expect(opencodeProfileContent).toContain('enabled');
83 | 		expect(opencodeProfileContent).toContain('type');
84 | 	});
85 | });
86 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/storage/adapters/file-storage/file-operations.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview File operations with atomic writes and locking
  3 |  */
  4 | 
  5 | import { constants } from 'node:fs';
  6 | import fs from 'node:fs/promises';
  7 | import type { FileStorageData } from './format-handler.js';
  8 | 
  9 | /**
 10 |  * Handles atomic file operations with locking mechanism
 11 |  */
 12 | export class FileOperations {
 13 | 	private fileLocks: Map<string, Promise<void>> = new Map();
 14 | 
 15 | 	/**
 16 | 	 * Read and parse JSON file
 17 | 	 */
 18 | 	async readJson(filePath: string): Promise<any> {
 19 | 		try {
 20 | 			const content = await fs.readFile(filePath, 'utf-8');
 21 | 			return JSON.parse(content);
 22 | 		} catch (error: any) {
 23 | 			if (error.code === 'ENOENT') {
 24 | 				throw error; // Re-throw ENOENT for caller to handle
 25 | 			}
 26 | 			if (error instanceof SyntaxError) {
 27 | 				throw new Error(`Invalid JSON in file ${filePath}: ${error.message}`);
 28 | 			}
 29 | 			throw new Error(`Failed to read file ${filePath}: ${error.message}`);
 30 | 		}
 31 | 	}
 32 | 
 33 | 	/**
 34 | 	 * Write JSON file with atomic operation and locking
 35 | 	 */
 36 | 	async writeJson(
 37 | 		filePath: string,
 38 | 		data: FileStorageData | any
 39 | 	): Promise<void> {
 40 | 		// Use file locking to prevent concurrent writes
 41 | 		const lockKey = filePath;
 42 | 		const existingLock = this.fileLocks.get(lockKey);
 43 | 
 44 | 		if (existingLock) {
 45 | 			await existingLock;
 46 | 		}
 47 | 
 48 | 		const lockPromise = this.performAtomicWrite(filePath, data);
 49 | 		this.fileLocks.set(lockKey, lockPromise);
 50 | 
 51 | 		try {
 52 | 			await lockPromise;
 53 | 		} finally {
 54 | 			this.fileLocks.delete(lockKey);
 55 | 		}
 56 | 	}
 57 | 
 58 | 	/**
 59 | 	 * Perform atomic write operation using temporary file
 60 | 	 */
 61 | 	private async performAtomicWrite(filePath: string, data: any): Promise<void> {
 62 | 		const tempPath = `${filePath}.tmp`;
 63 | 
 64 | 		try {
 65 | 			// Write to temp file first
 66 | 			const content = JSON.stringify(data, null, 2);
 67 | 			await fs.writeFile(tempPath, content, 'utf-8');
 68 | 
 69 | 			// Atomic rename
 70 | 			await fs.rename(tempPath, filePath);
 71 | 		} catch (error: any) {
 72 | 			// Clean up temp file if it exists
 73 | 			try {
 74 | 				await fs.unlink(tempPath);
 75 | 			} catch {
 76 | 				// Ignore cleanup errors
 77 | 			}
 78 | 
 79 | 			throw new Error(`Failed to write file ${filePath}: ${error.message}`);
 80 | 		}
 81 | 	}
 82 | 
 83 | 	/**
 84 | 	 * Check if file exists
 85 | 	 */
 86 | 	async exists(filePath: string): Promise<boolean> {
 87 | 		try {
 88 | 			await fs.access(filePath, constants.F_OK);
 89 | 			return true;
 90 | 		} catch {
 91 | 			return false;
 92 | 		}
 93 | 	}
 94 | 
 95 | 	/**
 96 | 	 * Get file stats
 97 | 	 */
 98 | 	async getStats(filePath: string) {
 99 | 		return fs.stat(filePath);
100 | 	}
101 | 
102 | 	/**
103 | 	 * Read directory contents
104 | 	 */
105 | 	async readDir(dirPath: string): Promise<string[]> {
106 | 		return fs.readdir(dirPath);
107 | 	}
108 | 
109 | 	/**
110 | 	 * Create directory recursively
111 | 	 */
112 | 	async ensureDir(dirPath: string): Promise<void> {
113 | 		try {
114 | 			await fs.mkdir(dirPath, { recursive: true });
115 | 		} catch (error: any) {
116 | 			throw new Error(
117 | 				`Failed to create directory ${dirPath}: ${error.message}`
118 | 			);
119 | 		}
120 | 	}
121 | 
122 | 	/**
123 | 	 * Delete file
124 | 	 */
125 | 	async deleteFile(filePath: string): Promise<void> {
126 | 		try {
127 | 			await fs.unlink(filePath);
128 | 		} catch (error: any) {
129 | 			if (error.code !== 'ENOENT') {
130 | 				throw new Error(`Failed to delete file ${filePath}: ${error.message}`);
131 | 			}
132 | 		}
133 | 	}
134 | 
135 | 	/**
136 | 	 * Rename/move file
137 | 	 */
138 | 	async moveFile(oldPath: string, newPath: string): Promise<void> {
139 | 		try {
140 | 			await fs.rename(oldPath, newPath);
141 | 		} catch (error: any) {
142 | 			throw new Error(
143 | 				`Failed to move file from ${oldPath} to ${newPath}: ${error.message}`
144 | 			);
145 | 		}
146 | 	}
147 | 
148 | 	/**
149 | 	 * Copy file
150 | 	 */
151 | 	async copyFile(srcPath: string, destPath: string): Promise<void> {
152 | 		try {
153 | 			await fs.copyFile(srcPath, destPath);
154 | 		} catch (error: any) {
155 | 			throw new Error(
156 | 				`Failed to copy file from ${srcPath} to ${destPath}: ${error.message}`
157 | 			);
158 | 		}
159 | 	}
160 | 
161 | 	/**
162 | 	 * Clean up all pending file operations
163 | 	 */
164 | 	async cleanup(): Promise<void> {
165 | 		const locks = Array.from(this.fileLocks.values());
166 | 		if (locks.length > 0) {
167 | 			await Promise.all(locks);
168 | 		}
169 | 		this.fileLocks.clear();
170 | 	}
171 | }
172 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/remove-task.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * remove-task.js
  3 |  * Direct function implementation for removing a task
  4 |  */
  5 | 
  6 | import {
  7 | 	removeTask,
  8 | 	taskExists
  9 | } from '../../../../scripts/modules/task-manager.js';
 10 | import {
 11 | 	enableSilentMode,
 12 | 	disableSilentMode,
 13 | 	readJSON
 14 | } from '../../../../scripts/modules/utils.js';
 15 | 
 16 | /**
 17 |  * Direct function wrapper for removeTask with error handling.
 18 |  * Supports removing multiple tasks at once with comma-separated IDs.
 19 |  *
 20 |  * @param {Object} args - Command arguments
 21 |  * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
 22 |  * @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple).
 23 |  * @param {string} args.projectRoot - Project root path (for MCP/env fallback)
 24 |  * @param {string} args.tag - Tag for the task (optional)
 25 |  * @param {Object} log - Logger object
 26 |  * @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } }
 27 |  */
 28 | export async function removeTaskDirect(args, log, context = {}) {
 29 | 	// Destructure expected args
 30 | 	const { tasksJsonPath, id, projectRoot, tag } = args;
 31 | 	const { session } = context;
 32 | 	try {
 33 | 		// Check if tasksJsonPath was provided
 34 | 		if (!tasksJsonPath) {
 35 | 			log.error('removeTaskDirect called without tasksJsonPath');
 36 | 			return {
 37 | 				success: false,
 38 | 				error: {
 39 | 					code: 'MISSING_ARGUMENT',
 40 | 					message: 'tasksJsonPath is required'
 41 | 				}
 42 | 			};
 43 | 		}
 44 | 
 45 | 		// Validate task ID parameter
 46 | 		if (!id) {
 47 | 			log.error('Task ID is required');
 48 | 			return {
 49 | 				success: false,
 50 | 				error: {
 51 | 					code: 'INPUT_VALIDATION_ERROR',
 52 | 					message: 'Task ID is required'
 53 | 				}
 54 | 			};
 55 | 		}
 56 | 
 57 | 		// Split task IDs if comma-separated
 58 | 		const taskIdArray = id.split(',').map((taskId) => taskId.trim());
 59 | 
 60 | 		log.info(
 61 | 			`Removing ${taskIdArray.length} task(s) with ID(s): ${taskIdArray.join(', ')} from ${tasksJsonPath}${tag ? ` in tag '${tag}'` : ''}`
 62 | 		);
 63 | 
 64 | 		// Validate all task IDs exist before proceeding
 65 | 		const data = readJSON(tasksJsonPath, projectRoot, tag);
 66 | 		if (!data || !data.tasks) {
 67 | 			return {
 68 | 				success: false,
 69 | 				error: {
 70 | 					code: 'INVALID_TASKS_FILE',
 71 | 					message: `No valid tasks found in ${tasksJsonPath}${tag ? ` for tag '${tag}'` : ''}`
 72 | 				}
 73 | 			};
 74 | 		}
 75 | 
 76 | 		const invalidTasks = taskIdArray.filter(
 77 | 			(taskId) => !taskExists(data.tasks, taskId)
 78 | 		);
 79 | 
 80 | 		if (invalidTasks.length > 0) {
 81 | 			return {
 82 | 				success: false,
 83 | 				error: {
 84 | 					code: 'INVALID_TASK_ID',
 85 | 					message: `The following tasks were not found${tag ? ` in tag '${tag}'` : ''}: ${invalidTasks.join(', ')}`
 86 | 				}
 87 | 			};
 88 | 		}
 89 | 
 90 | 		// Enable silent mode to prevent console logs from interfering with JSON response
 91 | 		enableSilentMode();
 92 | 
 93 | 		try {
 94 | 			// Call removeTask with proper context including tag
 95 | 			const result = await removeTask(tasksJsonPath, id, {
 96 | 				projectRoot,
 97 | 				tag
 98 | 			});
 99 | 
100 | 			if (!result.success) {
101 | 				return {
102 | 					success: false,
103 | 					error: {
104 | 						code: 'REMOVE_TASK_ERROR',
105 | 						message: result.error || 'Failed to remove tasks'
106 | 					}
107 | 				};
108 | 			}
109 | 
110 | 			log.info(`Successfully removed ${result.removedTasks.length} task(s)`);
111 | 
112 | 			return {
113 | 				success: true,
114 | 				data: {
115 | 					totalTasks: taskIdArray.length,
116 | 					successful: result.removedTasks.length,
117 | 					failed: taskIdArray.length - result.removedTasks.length,
118 | 					removedTasks: result.removedTasks,
119 | 					message: result.message,
120 | 					tasksPath: tasksJsonPath,
121 | 					tag
122 | 				}
123 | 			};
124 | 		} finally {
125 | 			// Restore normal logging
126 | 			disableSilentMode();
127 | 		}
128 | 	} catch (error) {
129 | 		// Ensure silent mode is disabled even if an outer error occurs
130 | 		disableSilentMode();
131 | 
132 | 		// Catch any unexpected errors
133 | 		log.error(`Unexpected error in removeTaskDirect: ${error.message}`);
134 | 		return {
135 | 			success: false,
136 | 			error: {
137 | 				code: 'UNEXPECTED_ERROR',
138 | 				message: error.message
139 | 			}
140 | 		};
141 | 	}
142 | }
143 | 
```

--------------------------------------------------------------------------------
/tests/integration/profiles/cursor-init-functionality.test.js:
--------------------------------------------------------------------------------

```javascript
 1 | import fs from 'fs';
 2 | import path from 'path';
 3 | import { cursorProfile } from '../../../src/profiles/cursor.js';
 4 | 
 5 | describe('Cursor Profile Initialization Functionality', () => {
 6 | 	let cursorProfileContent;
 7 | 
 8 | 	beforeAll(() => {
 9 | 		const cursorJsPath = path.join(
10 | 			process.cwd(),
11 | 			'src',
12 | 			'profiles',
13 | 			'cursor.js'
14 | 		);
15 | 		cursorProfileContent = fs.readFileSync(cursorJsPath, 'utf8');
16 | 	});
17 | 
18 | 	test('cursor.js uses factory pattern with correct configuration', () => {
19 | 		// Check for explicit, non-default values in the source file
20 | 		expect(cursorProfileContent).toContain("name: 'cursor'");
21 | 		expect(cursorProfileContent).toContain("displayName: 'Cursor'");
22 | 		expect(cursorProfileContent).toContain("url: 'cursor.so'");
23 | 		expect(cursorProfileContent).toContain("docsUrl: 'docs.cursor.com'");
24 | 		expect(cursorProfileContent).toContain("targetExtension: '.mdc'"); // non-default
25 | 
26 | 		// Check the final computed properties on the profile object
27 | 		expect(cursorProfile.profileName).toBe('cursor');
28 | 		expect(cursorProfile.displayName).toBe('Cursor');
29 | 		expect(cursorProfile.profileDir).toBe('.cursor'); // default
30 | 		expect(cursorProfile.rulesDir).toBe('.cursor/rules'); // default
31 | 		expect(cursorProfile.mcpConfig).toBe(true); // default
32 | 		expect(cursorProfile.mcpConfigName).toBe('mcp.json'); // default
33 | 	});
34 | 
35 | 	test('cursor.js preserves .mdc extension in both input and output', () => {
36 | 		// Check that the profile object has the correct file mapping behavior (cursor keeps .mdc)
37 | 		expect(cursorProfile.fileMap['rules/cursor_rules.mdc']).toBe(
38 | 			'cursor_rules.mdc'
39 | 		);
40 | 		// Also check that targetExtension is explicitly set in the file
41 | 		expect(cursorProfileContent).toContain("targetExtension: '.mdc'");
42 | 	});
43 | 
44 | 	test('cursor.js uses standard tool mappings (no tool renaming)', () => {
45 | 		// Check that the profile uses default tool mappings (equivalent to COMMON_TOOL_MAPPINGS.STANDARD)
46 | 		// This verifies the architectural pattern: no custom toolMappings = standard tool names
47 | 		expect(cursorProfileContent).not.toContain('toolMappings:');
48 | 		expect(cursorProfileContent).not.toContain('apply_diff');
49 | 		expect(cursorProfileContent).not.toContain('search_files');
50 | 
51 | 		// Verify the result: default mappings means tools keep their original names
52 | 		expect(cursorProfile.conversionConfig.toolNames.edit_file).toBe(
53 | 			'edit_file'
54 | 		);
55 | 		expect(cursorProfile.conversionConfig.toolNames.search).toBe('search');
56 | 	});
57 | 
58 | 	test('cursor.js has lifecycle functions for command copying', () => {
59 | 		// Check that the source file contains our new lifecycle functions
60 | 		expect(cursorProfileContent).toContain('function onAddRulesProfile');
61 | 		expect(cursorProfileContent).toContain('function onRemoveRulesProfile');
62 | 		expect(cursorProfileContent).toContain('copyRecursiveSync');
63 | 		expect(cursorProfileContent).toContain('removeDirectoryRecursive');
64 | 	});
65 | 
66 | 	test('cursor.js copies commands from claude/commands to .cursor/commands', () => {
67 | 		// Check that the onAddRulesProfile function copies from the correct source
68 | 		expect(cursorProfileContent).toContain(
69 | 			"path.join(assetsDir, 'claude', 'commands')"
70 | 		);
71 | 		// Destination path is built via a resolver to handle both project root and rules dir
72 | 		expect(cursorProfileContent).toContain('resolveCursorProfileDir(');
73 | 		expect(cursorProfileContent).toMatch(
74 | 			/path\.join\(\s*profileDir\s*,\s*['"]commands['"]\s*\)/
75 | 		);
76 | 		expect(cursorProfileContent).toContain(
77 | 			'copyRecursiveSync(commandsSourceDir, commandsDestDir)'
78 | 		);
79 | 
80 | 		// Check that lifecycle functions are properly registered with the profile
81 | 		expect(cursorProfile.onAddRulesProfile).toBeDefined();
82 | 		expect(cursorProfile.onRemoveRulesProfile).toBeDefined();
83 | 		expect(typeof cursorProfile.onAddRulesProfile).toBe('function');
84 | 		expect(typeof cursorProfile.onRemoveRulesProfile).toBe('function');
85 | 	});
86 | });
87 | 
```
Page 11/69FirstPrevNextLast