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

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/add-subtask.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Tests for the addSubtask function
  3 |  */
  4 | import { jest } from '@jest/globals';
  5 | 
  6 | // Mock dependencies before importing the module
  7 | const mockUtils = {
  8 | 	readJSON: jest.fn(),
  9 | 	writeJSON: jest.fn(),
 10 | 	log: jest.fn(),
 11 | 	getCurrentTag: jest.fn()
 12 | };
 13 | const mockTaskManager = {
 14 | 	isTaskDependentOn: jest.fn()
 15 | };
 16 | const mockGenerateTaskFiles = jest.fn();
 17 | 
 18 | jest.unstable_mockModule(
 19 | 	'../../../../../scripts/modules/utils.js',
 20 | 	() => mockUtils
 21 | );
 22 | jest.unstable_mockModule(
 23 | 	'../../../../../scripts/modules/task-manager.js',
 24 | 	() => mockTaskManager
 25 | );
 26 | jest.unstable_mockModule(
 27 | 	'../../../../../scripts/modules/task-manager/generate-task-files.js',
 28 | 	() => ({
 29 | 		default: mockGenerateTaskFiles
 30 | 	})
 31 | );
 32 | 
 33 | const addSubtask = (
 34 | 	await import('../../../../../scripts/modules/task-manager/add-subtask.js')
 35 | ).default;
 36 | 
 37 | describe('addSubtask function', () => {
 38 | 	const multiTagData = {
 39 | 		master: {
 40 | 			tasks: [{ id: 1, title: 'Master Task', subtasks: [] }],
 41 | 			metadata: { description: 'Master tasks' }
 42 | 		},
 43 | 		'feature-branch': {
 44 | 			tasks: [{ id: 1, title: 'Feature Task', subtasks: [] }],
 45 | 			metadata: { description: 'Feature tasks' }
 46 | 		}
 47 | 	};
 48 | 
 49 | 	beforeEach(() => {
 50 | 		jest.clearAllMocks();
 51 | 		mockTaskManager.isTaskDependentOn.mockReturnValue(false);
 52 | 	});
 53 | 
 54 | 	test('should add a new subtask and preserve other tags', async () => {
 55 | 		const context = { projectRoot: '/fake/root', tag: 'feature-branch' };
 56 | 		const newSubtaskData = { title: 'My New Subtask' };
 57 | 		mockUtils.readJSON.mockReturnValueOnce({
 58 | 			tasks: [{ id: 1, title: 'Feature Task', subtasks: [] }],
 59 | 			metadata: { description: 'Feature tasks' }
 60 | 		});
 61 | 
 62 | 		await addSubtask('tasks.json', '1', null, newSubtaskData, true, context);
 63 | 
 64 | 		expect(mockUtils.writeJSON).toHaveBeenCalledWith(
 65 | 			'tasks.json',
 66 | 			expect.any(Object),
 67 | 			'/fake/root',
 68 | 			'feature-branch'
 69 | 		);
 70 | 		const writtenData = mockUtils.writeJSON.mock.calls[0][1];
 71 | 		const parentTask = writtenData.tasks.find((t) => t.id === 1);
 72 | 		expect(parentTask.subtasks).toHaveLength(1);
 73 | 		expect(parentTask.subtasks[0].title).toBe('My New Subtask');
 74 | 	});
 75 | 
 76 | 	test('should add a new subtask to a parent task', async () => {
 77 | 		mockUtils.readJSON.mockReturnValueOnce({
 78 | 			tasks: [{ id: 1, title: 'Parent Task', subtasks: [] }]
 79 | 		});
 80 | 		const context = {};
 81 | 		const newSubtask = await addSubtask(
 82 | 			'tasks.json',
 83 | 			'1',
 84 | 			null,
 85 | 			{ title: 'New Subtask' },
 86 | 			true,
 87 | 			context
 88 | 		);
 89 | 		expect(newSubtask).toBeDefined();
 90 | 		expect(newSubtask.id).toBe(1);
 91 | 		expect(newSubtask.parentTaskId).toBe(1);
 92 | 		expect(mockUtils.writeJSON).toHaveBeenCalled();
 93 | 		const writeCallArgs = mockUtils.writeJSON.mock.calls[0][1]; // data is the second arg now
 94 | 		const parentTask = writeCallArgs.tasks.find((t) => t.id === 1);
 95 | 		expect(parentTask.subtasks).toHaveLength(1);
 96 | 		expect(parentTask.subtasks[0].title).toBe('New Subtask');
 97 | 	});
 98 | 
 99 | 	test('should convert an existing task to a subtask', async () => {
100 | 		mockUtils.readJSON.mockReturnValueOnce({
101 | 			tasks: [
102 | 				{ id: 1, title: 'Parent Task', subtasks: [] },
103 | 				{ id: 2, title: 'Existing Task 2', subtasks: [] }
104 | 			]
105 | 		});
106 | 		const context = {};
107 | 		const convertedSubtask = await addSubtask(
108 | 			'tasks.json',
109 | 			'1',
110 | 			'2',
111 | 			null,
112 | 			true,
113 | 			context
114 | 		);
115 | 		expect(convertedSubtask.id).toBe(1);
116 | 		expect(convertedSubtask.parentTaskId).toBe(1);
117 | 		expect(convertedSubtask.title).toBe('Existing Task 2');
118 | 		expect(mockUtils.writeJSON).toHaveBeenCalled();
119 | 		const writeCallArgs = mockUtils.writeJSON.mock.calls[0][1];
120 | 		const parentTask = writeCallArgs.tasks.find((t) => t.id === 1);
121 | 		expect(parentTask.subtasks).toHaveLength(1);
122 | 		expect(parentTask.subtasks[0].title).toBe('Existing Task 2');
123 | 	});
124 | 
125 | 	test('should throw an error if parent task does not exist', async () => {
126 | 		mockUtils.readJSON.mockReturnValueOnce({
127 | 			tasks: [{ id: 1, title: 'Task 1', subtasks: [] }]
128 | 		});
129 | 		const context = {};
130 | 		await expect(
131 | 			addSubtask(
132 | 				'tasks.json',
133 | 				'99',
134 | 				null,
135 | 				{ title: 'New Subtask' },
136 | 				true,
137 | 				context
138 | 			)
139 | 		).rejects.toThrow('Parent task with ID 99 not found');
140 | 	});
141 | 
142 | 	test('should throw an error if trying to convert a non-existent task', async () => {
143 | 		mockUtils.readJSON.mockReturnValueOnce({
144 | 			tasks: [{ id: 1, title: 'Parent Task', subtasks: [] }]
145 | 		});
146 | 		const context = {};
147 | 		await expect(
148 | 			addSubtask('tasks.json', '1', '99', null, true, context)
149 | 		).rejects.toThrow('Task with ID 99 not found');
150 | 	});
151 | 
152 | 	test('should throw an error for circular dependency', async () => {
153 | 		mockUtils.readJSON.mockReturnValueOnce({
154 | 			tasks: [
155 | 				{ id: 1, title: 'Parent Task', subtasks: [] },
156 | 				{ id: 2, title: 'Child Task', subtasks: [] }
157 | 			]
158 | 		});
159 | 		mockTaskManager.isTaskDependentOn.mockImplementation(
160 | 			(tasks, parentTask, existingTaskIdNum) => {
161 | 				return parentTask.id === 1 && existingTaskIdNum === 2;
162 | 			}
163 | 		);
164 | 		const context = {};
165 | 		await expect(
166 | 			addSubtask('tasks.json', '1', '2', null, true, context)
167 | 		).rejects.toThrow(
168 | 			'Cannot create circular dependency: task 1 is already a subtask or dependent of task 2'
169 | 		);
170 | 	});
171 | });
172 | 
```

--------------------------------------------------------------------------------
/apps/docs/archive/ai-development-workflow.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "AI Development Workflow"
  3 | description: "Learn how Task Master and Cursor AI work together to streamline your development workflow"
  4 | ---
  5 | 
  6 | <Tip>The Cursor agent is pre-configured (via the rules file) to follow this workflow</Tip>
  7 | 
  8 | <AccordionGroup>
  9 |   <Accordion title="1. Task Discovery and Selection">
 10 |     Ask the agent to list available tasks:
 11 | 
 12 |     ```
 13 |     What tasks are available to work on next?
 14 |     ```
 15 | 
 16 |     The agent will:
 17 | 
 18 |     - Run `task-master list` to see all tasks
 19 |     - Run `task-master next` to determine the next task to work on
 20 |     - Analyze dependencies to determine which tasks are ready to be worked on
 21 |     - Prioritize tasks based on priority level and ID order
 22 |     - Suggest the next task(s) to implement
 23 |   </Accordion>
 24 | 
 25 |   <Accordion title="2. Task Implementation">
 26 |     When implementing a task, the agent will:
 27 | 
 28 |     - Reference the task's details section for implementation specifics
 29 |     - Consider dependencies on previous tasks
 30 |     - Follow the project's coding standards
 31 |     - Create appropriate tests based on the task's testStrategy
 32 | 
 33 |     You can ask:
 34 | 
 35 |     ```
 36 |     Let's implement task 3. What does it involve?
 37 |     ```
 38 |   </Accordion>
 39 | 
 40 |   <Accordion title="3. Task Verification">
 41 |     Before marking a task as complete, verify it according to:
 42 | 
 43 |     - The task's specified testStrategy
 44 |     - Any automated tests in the codebase
 45 |     - Manual verification if required
 46 |   </Accordion>
 47 | 
 48 |   <Accordion title="4. Task Completion">
 49 |     When a task is completed, tell the agent:
 50 | 
 51 |     ```
 52 |     Task 3 is now complete. Please update its status.
 53 |     ```
 54 | 
 55 |     The agent will execute:
 56 | 
 57 |     ```bash
 58 |     task-master set-status --id=3 --status=done
 59 |     ```
 60 |   </Accordion>
 61 | 
 62 |   <Accordion title="5. Handling Implementation Drift">
 63 |     If during implementation, you discover that:
 64 | 
 65 |     - The current approach differs significantly from what was planned
 66 |     - Future tasks need to be modified due to current implementation choices
 67 |     - New dependencies or requirements have emerged
 68 | 
 69 |     Tell the agent:
 70 | 
 71 |     ```
 72 |     We've changed our approach. We're now using Express instead of Fastify. Please update all future tasks to reflect this change.
 73 |     ```
 74 | 
 75 |     The agent will execute:
 76 | 
 77 |     ```bash
 78 |     task-master update --from=4 --prompt="Now we are using Express instead of Fastify."
 79 |     ```
 80 | 
 81 |     This will rewrite or re-scope subsequent tasks in tasks.json while preserving completed work.
 82 |   </Accordion>
 83 | 
 84 |   <Accordion title="6. Breaking Down Complex Tasks">
 85 |     For complex tasks that need more granularity:
 86 | 
 87 |     ```
 88 |     Task 5 seems complex. Can you break it down into subtasks?
 89 |     ```
 90 | 
 91 |     The agent will execute:
 92 | 
 93 |     ```bash
 94 |     task-master expand --id=5 --num=3
 95 |     ```
 96 | 
 97 |     You can provide additional context:
 98 | 
 99 |     ```
100 |     Please break down task 5 with a focus on security considerations.
101 |     ```
102 | 
103 |     The agent will execute:
104 | 
105 |     ```bash
106 |     task-master expand --id=5 --prompt="Focus on security aspects"
107 |     ```
108 | 
109 |     You can also expand all pending tasks:
110 | 
111 |     ```
112 |     Please break down all pending tasks into subtasks.
113 |     ```
114 | 
115 |     The agent will execute:
116 | 
117 |     ```bash
118 |     task-master expand --all
119 |     ```
120 | 
121 |     For research-backed subtask generation using Perplexity AI:
122 | 
123 |     ```
124 |     Please break down task 5 using research-backed generation.
125 |     ```
126 | 
127 |     The agent will execute:
128 | 
129 |     ```bash
130 |     task-master expand --id=5 --research
131 |     ```
132 |   </Accordion>
133 | </AccordionGroup>
134 | 
135 | ## Example Cursor AI Interactions
136 | 
137 | <AccordionGroup>
138 |   <Accordion title="Starting a new project">
139 |     ```
140 |     I've just initialized a new project with Claude Task Master. I have a PRD at scripts/prd.txt.
141 |     Can you help me parse it and set up the initial tasks?
142 |     ```
143 |   </Accordion>
144 |   <Accordion title="Working on tasks">
145 |     ```
146 |     What's the next task I should work on? Please consider dependencies and priorities.
147 |     ```
148 |   </Accordion>
149 |   <Accordion title="Implementing a specific task">
150 |     ```
151 |     I'd like to implement task 4. Can you help me understand what needs to be done and how to approach it?
152 |     ```
153 |   </Accordion>
154 |   <Accordion title="Managing subtasks">
155 |     ```
156 |     I need to regenerate the subtasks for task 3 with a different approach. Can you help me clear and regenerate them?
157 |     ```
158 |   </Accordion>
159 |   <Accordion title="Handling changes">
160 |     ```
161 |     We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change?
162 |     ```
163 |   </Accordion>
164 |   <Accordion title="Completing work">
165 |     ```
166 |     I've finished implementing the authentication system described in task 2. All tests are passing.
167 |     Please mark it as complete and tell me what I should work on next.
168 |     ```
169 |   </Accordion>
170 |   <Accordion title="Analyzing complexity">
171 |     ```
172 |     Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further?
173 |     ```
174 |   </Accordion>
175 |   <Accordion title="Viewing complexity report">
176 |     ```
177 |     Can you show me the complexity report in a more readable format?
178 |     ```
179 |   </Accordion>
180 | </AccordionGroup>
181 | 
```

--------------------------------------------------------------------------------
/apps/extension/src/webview/reducers/appReducer.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Main application state reducer
  3 |  */
  4 | 
  5 | import type { AppState, AppAction } from '../types';
  6 | import { logger } from '../utils/logger';
  7 | 
  8 | export const appReducer = (state: AppState, action: AppAction): AppState => {
  9 | 	logger.debug(
 10 | 		'Reducer action:',
 11 | 		action.type,
 12 | 		'payload' in action ? action.payload : 'no payload'
 13 | 	);
 14 | 	switch (action.type) {
 15 | 		case 'SET_TASKS':
 16 | 			const newTasks = Array.isArray(action.payload) ? action.payload : [];
 17 | 			logger.debug('SET_TASKS reducer - updating tasks:', {
 18 | 				oldCount: state.tasks.length,
 19 | 				newCount: newTasks.length,
 20 | 				newTasks
 21 | 			});
 22 | 			return {
 23 | 				...state,
 24 | 				tasks: newTasks,
 25 | 				loading: false,
 26 | 				error: undefined
 27 | 			};
 28 | 		case 'SET_LOADING':
 29 | 			return { ...state, loading: action.payload };
 30 | 		case 'SET_ERROR':
 31 | 			return { ...state, error: action.payload, loading: false };
 32 | 		case 'CLEAR_ERROR':
 33 | 			return { ...state, error: undefined };
 34 | 		case 'INCREMENT_REQUEST_ID':
 35 | 			return { ...state, requestId: state.requestId + 1 };
 36 | 		case 'UPDATE_TASK_STATUS': {
 37 | 			const { taskId, newStatus } = action.payload;
 38 | 			return {
 39 | 				...state,
 40 | 				tasks: state.tasks.map((task) =>
 41 | 					task.id === taskId ? { ...task, status: newStatus } : task
 42 | 				)
 43 | 			};
 44 | 		}
 45 | 		case 'UPDATE_TASK_CONTENT': {
 46 | 			const { taskId, updates } = action.payload;
 47 | 			return {
 48 | 				...state,
 49 | 				tasks: state.tasks.map((task) =>
 50 | 					task.id === taskId ? { ...task, ...updates } : task
 51 | 				)
 52 | 			};
 53 | 		}
 54 | 		case 'SET_CONNECTION_STATUS':
 55 | 			return {
 56 | 				...state,
 57 | 				isConnected: action.payload.isConnected,
 58 | 				connectionStatus: action.payload.status
 59 | 			};
 60 | 		case 'SET_EDITING_TASK':
 61 | 			return {
 62 | 				...state,
 63 | 				editingTask: action.payload
 64 | 			};
 65 | 		case 'SET_POLLING_STATUS':
 66 | 			return {
 67 | 				...state,
 68 | 				polling: {
 69 | 					...state.polling,
 70 | 					isActive: action.payload.isActive,
 71 | 					errorCount: action.payload.errorCount ?? state.polling.errorCount,
 72 | 					lastUpdate: action.payload.isActive
 73 | 						? Date.now()
 74 | 						: state.polling.lastUpdate
 75 | 				}
 76 | 			};
 77 | 		case 'SET_USER_INTERACTING':
 78 | 			return {
 79 | 				...state,
 80 | 				polling: {
 81 | 					...state.polling,
 82 | 					isUserInteracting: action.payload
 83 | 				}
 84 | 			};
 85 | 		case 'TASKS_UPDATED_FROM_POLLING':
 86 | 			return {
 87 | 				...state,
 88 | 				tasks: Array.isArray(action.payload) ? action.payload : [],
 89 | 				polling: {
 90 | 					...state.polling,
 91 | 					lastUpdate: Date.now()
 92 | 				}
 93 | 			};
 94 | 		case 'SET_NETWORK_STATUS':
 95 | 			return {
 96 | 				...state,
 97 | 				polling: {
 98 | 					...state.polling,
 99 | 					isOfflineMode: action.payload.isOfflineMode,
100 | 					connectionStatus: action.payload.connectionStatus,
101 | 					reconnectAttempts:
102 | 						action.payload.reconnectAttempts !== undefined
103 | 							? action.payload.reconnectAttempts
104 | 							: state.polling.reconnectAttempts,
105 | 					maxReconnectAttempts:
106 | 						action.payload.maxReconnectAttempts !== undefined
107 | 							? action.payload.maxReconnectAttempts
108 | 							: state.polling.maxReconnectAttempts,
109 | 					lastSuccessfulConnection:
110 | 						action.payload.lastSuccessfulConnection !== undefined
111 | 							? action.payload.lastSuccessfulConnection
112 | 							: state.polling.lastSuccessfulConnection
113 | 				}
114 | 			};
115 | 		case 'LOAD_CACHED_TASKS':
116 | 			return {
117 | 				...state,
118 | 				tasks: Array.isArray(action.payload) ? action.payload : []
119 | 			};
120 | 		case 'ADD_TOAST':
121 | 			return {
122 | 				...state,
123 | 				toastNotifications: [...state.toastNotifications, action.payload]
124 | 			};
125 | 		case 'REMOVE_TOAST':
126 | 			return {
127 | 				...state,
128 | 				toastNotifications: state.toastNotifications.filter(
129 | 					(notification) => notification.id !== action.payload
130 | 				)
131 | 			};
132 | 		case 'CLEAR_ALL_TOASTS':
133 | 			return { ...state, toastNotifications: [] };
134 | 		case 'NAVIGATE_TO_TASK':
135 | 			logger.debug('📍 Reducer: Navigating to task:', action.payload);
136 | 			return {
137 | 				...state,
138 | 				currentView: 'task-details',
139 | 				selectedTaskId: action.payload
140 | 			};
141 | 		case 'NAVIGATE_TO_KANBAN':
142 | 			logger.debug('📍 Reducer: Navigating to kanban');
143 | 			return { ...state, currentView: 'kanban', selectedTaskId: undefined };
144 | 		case 'NAVIGATE_TO_CONFIG':
145 | 			logger.debug('📍 Reducer: Navigating to config');
146 | 			return { ...state, currentView: 'config', selectedTaskId: undefined };
147 | 		case 'SET_CURRENT_TAG':
148 | 			return {
149 | 				...state,
150 | 				currentTag: action.payload
151 | 			};
152 | 		case 'SET_AVAILABLE_TAGS':
153 | 			return {
154 | 				...state,
155 | 				availableTags: action.payload
156 | 			};
157 | 		case 'SET_TAG_DATA':
158 | 			return {
159 | 				...state,
160 | 				currentTag: action.payload.currentTag,
161 | 				availableTags: action.payload.availableTags
162 | 			};
163 | 		default:
164 | 			return state;
165 | 	}
166 | };
167 | 
168 | export const initialState: AppState = {
169 | 	tasks: [],
170 | 	loading: true,
171 | 	requestId: 0,
172 | 	isConnected: false,
173 | 	connectionStatus: 'Connecting...',
174 | 	editingTask: { taskId: null },
175 | 	polling: {
176 | 		isActive: false,
177 | 		errorCount: 0,
178 | 		lastUpdate: undefined,
179 | 		isUserInteracting: false,
180 | 		isOfflineMode: false,
181 | 		reconnectAttempts: 0,
182 | 		maxReconnectAttempts: 0,
183 | 		lastSuccessfulConnection: undefined,
184 | 		connectionStatus: 'online'
185 | 	},
186 | 	toastNotifications: [],
187 | 	currentView: 'kanban',
188 | 	selectedTaskId: undefined,
189 | 	// Tag-related state
190 | 	currentTag: 'master',
191 | 	availableTags: ['master']
192 | };
193 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/add-tag.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * add-tag.js
  3 |  * Direct function implementation for creating a new tag
  4 |  */
  5 | 
  6 | import {
  7 | 	createTag,
  8 | 	createTagFromBranch
  9 | } from '../../../../scripts/modules/task-manager/tag-management.js';
 10 | import {
 11 | 	enableSilentMode,
 12 | 	disableSilentMode
 13 | } from '../../../../scripts/modules/utils.js';
 14 | import { createLogWrapper } from '../../tools/utils.js';
 15 | 
 16 | /**
 17 |  * Direct function wrapper for creating a new tag with error handling.
 18 |  *
 19 |  * @param {Object} args - Command arguments
 20 |  * @param {string} args.name - Name of the new tag to create
 21 |  * @param {boolean} [args.copyFromCurrent=false] - Whether to copy tasks from current tag
 22 |  * @param {string} [args.copyFromTag] - Specific tag to copy tasks from
 23 |  * @param {boolean} [args.fromBranch=false] - Create tag name from current git branch
 24 |  * @param {string} [args.description] - Optional description for the tag
 25 |  * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
 26 |  * @param {string} [args.projectRoot] - Project root path
 27 |  * @param {Object} log - Logger object
 28 |  * @param {Object} context - Additional context (session)
 29 |  * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
 30 |  */
 31 | export async function addTagDirect(args, log, context = {}) {
 32 | 	// Destructure expected args
 33 | 	const {
 34 | 		tasksJsonPath,
 35 | 		name,
 36 | 		copyFromCurrent = false,
 37 | 		copyFromTag,
 38 | 		fromBranch = false,
 39 | 		description,
 40 | 		projectRoot
 41 | 	} = args;
 42 | 	const { session } = context;
 43 | 
 44 | 	// Enable silent mode to prevent console logs from interfering with JSON response
 45 | 	enableSilentMode();
 46 | 
 47 | 	// Create logger wrapper using the utility
 48 | 	const mcpLog = createLogWrapper(log);
 49 | 
 50 | 	try {
 51 | 		// Check if tasksJsonPath was provided
 52 | 		if (!tasksJsonPath) {
 53 | 			log.error('addTagDirect called without tasksJsonPath');
 54 | 			disableSilentMode();
 55 | 			return {
 56 | 				success: false,
 57 | 				error: {
 58 | 					code: 'MISSING_ARGUMENT',
 59 | 					message: 'tasksJsonPath is required'
 60 | 				}
 61 | 			};
 62 | 		}
 63 | 
 64 | 		// Handle --from-branch option
 65 | 		if (fromBranch) {
 66 | 			log.info('Creating tag from current git branch');
 67 | 
 68 | 			// Import git utilities
 69 | 			const gitUtils = await import(
 70 | 				'../../../../scripts/modules/utils/git-utils.js'
 71 | 			);
 72 | 
 73 | 			// Check if we're in a git repository
 74 | 			if (!(await gitUtils.isGitRepository(projectRoot))) {
 75 | 				log.error('Not in a git repository');
 76 | 				disableSilentMode();
 77 | 				return {
 78 | 					success: false,
 79 | 					error: {
 80 | 						code: 'NOT_GIT_REPO',
 81 | 						message: 'Not in a git repository. Cannot use fromBranch option.'
 82 | 					}
 83 | 				};
 84 | 			}
 85 | 
 86 | 			// Get current git branch
 87 | 			const currentBranch = await gitUtils.getCurrentBranch(projectRoot);
 88 | 			if (!currentBranch) {
 89 | 				log.error('Could not determine current git branch');
 90 | 				disableSilentMode();
 91 | 				return {
 92 | 					success: false,
 93 | 					error: {
 94 | 						code: 'NO_CURRENT_BRANCH',
 95 | 						message: 'Could not determine current git branch.'
 96 | 					}
 97 | 				};
 98 | 			}
 99 | 
100 | 			// Prepare options for branch-based tag creation
101 | 			const branchOptions = {
102 | 				copyFromCurrent,
103 | 				copyFromTag,
104 | 				description:
105 | 					description || `Tag created from git branch "${currentBranch}"`
106 | 			};
107 | 
108 | 			// Call the createTagFromBranch function
109 | 			const result = await createTagFromBranch(
110 | 				tasksJsonPath,
111 | 				currentBranch,
112 | 				branchOptions,
113 | 				{
114 | 					session,
115 | 					mcpLog,
116 | 					projectRoot
117 | 				},
118 | 				'json' // outputFormat - use 'json' to suppress CLI UI
119 | 			);
120 | 
121 | 			// Restore normal logging
122 | 			disableSilentMode();
123 | 
124 | 			return {
125 | 				success: true,
126 | 				data: {
127 | 					branchName: result.branchName,
128 | 					tagName: result.tagName,
129 | 					created: result.created,
130 | 					mappingUpdated: result.mappingUpdated,
131 | 					message: `Successfully created tag "${result.tagName}" from git branch "${result.branchName}"`
132 | 				}
133 | 			};
134 | 		} else {
135 | 			// Check required parameters for regular tag creation
136 | 			if (!name || typeof name !== 'string') {
137 | 				log.error('Missing required parameter: name');
138 | 				disableSilentMode();
139 | 				return {
140 | 					success: false,
141 | 					error: {
142 | 						code: 'MISSING_PARAMETER',
143 | 						message: 'Tag name is required and must be a string'
144 | 					}
145 | 				};
146 | 			}
147 | 
148 | 			log.info(`Creating new tag: ${name}`);
149 | 
150 | 			// Prepare options
151 | 			const options = {
152 | 				copyFromCurrent,
153 | 				copyFromTag,
154 | 				description
155 | 			};
156 | 
157 | 			// Call the createTag function
158 | 			const result = await createTag(
159 | 				tasksJsonPath,
160 | 				name,
161 | 				options,
162 | 				{
163 | 					session,
164 | 					mcpLog,
165 | 					projectRoot
166 | 				},
167 | 				'json' // outputFormat - use 'json' to suppress CLI UI
168 | 			);
169 | 
170 | 			// Restore normal logging
171 | 			disableSilentMode();
172 | 
173 | 			return {
174 | 				success: true,
175 | 				data: {
176 | 					tagName: result.tagName,
177 | 					created: result.created,
178 | 					tasksCopied: result.tasksCopied,
179 | 					sourceTag: result.sourceTag,
180 | 					description: result.description,
181 | 					message: `Successfully created tag "${result.tagName}"`
182 | 				}
183 | 			};
184 | 		}
185 | 	} catch (error) {
186 | 		// Make sure to restore normal logging even if there's an error
187 | 		disableSilentMode();
188 | 
189 | 		log.error(`Error in addTagDirect: ${error.message}`);
190 | 		return {
191 | 			success: false,
192 | 			error: {
193 | 				code: error.code || 'ADD_TAG_ERROR',
194 | 				message: error.message
195 | 			}
196 | 		};
197 | 	}
198 | }
199 | 
```

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

```typescript
  1 | import React from 'react';
  2 | import { ExternalLink, Terminal, MessageSquare, Plus } from 'lucide-react';
  3 | import { TaskMasterLogo } from '../../components/TaskMasterLogo';
  4 | 
  5 | interface EmptyStateProps {
  6 | 	currentTag: string;
  7 | }
  8 | 
  9 | export const EmptyState: React.FC<EmptyStateProps> = ({ currentTag }) => {
 10 | 	return (
 11 | 		<div className="flex items-center justify-center h-full overflow-auto">
 12 | 			<div className="max-w-2xl mx-auto text-center p-8">
 13 | 				{/* Empty state illustration */}
 14 | 				<div className="mb-8 max-w-96 mx-auto">
 15 | 					<TaskMasterLogo className="w-32 h-32 mx-auto text-vscode-foreground/20" />
 16 | 				</div>
 17 | 
 18 | 				<h2 className="text-2xl font-semibold mb-2 text-vscode-foreground">
 19 | 					No tasks in "{currentTag}" tag
 20 | 				</h2>
 21 | 				<p className="text-vscode-foreground/70 mb-8">
 22 | 					Get started by adding tasks to this tag using the commands below
 23 | 				</p>
 24 | 
 25 | 				{/* Command suggestions */}
 26 | 				<div className="space-y-4 text-left">
 27 | 					<div className="bg-vscode-editor-background/50 border border-vscode-panel-border rounded-lg p-4">
 28 | 						<div className="flex items-center gap-2 mb-2">
 29 | 							<Terminal className="w-4 h-4 text-vscode-terminal-ansiGreen" />
 30 | 							<h3 className="font-medium">CLI Commands</h3>
 31 | 						</div>
 32 | 						<div className="space-y-2">
 33 | 							<div className="bg-vscode-editor-background rounded p-2 font-mono text-sm">
 34 | 								<span className="text-vscode-terminal-ansiYellow">
 35 | 									task-master
 36 | 								</span>{' '}
 37 | 								<span className="text-vscode-terminal-ansiCyan">parse-prd</span>{' '}
 38 | 								<span className="text-vscode-foreground/70">
 39 | 									&lt;path-to-prd&gt;
 40 | 								</span>{' '}
 41 | 								<span className="text-vscode-terminal-ansiMagenta">
 42 | 									--append
 43 | 								</span>
 44 | 								<div className="text-xs text-vscode-foreground/50 mt-1">
 45 | 									Parse a PRD and append tasks to current tag
 46 | 								</div>
 47 | 							</div>
 48 | 							<div className="bg-vscode-editor-background rounded p-2 font-mono text-sm">
 49 | 								<span className="text-vscode-terminal-ansiYellow">
 50 | 									task-master
 51 | 								</span>{' '}
 52 | 								<span className="text-vscode-terminal-ansiCyan">add-task</span>{' '}
 53 | 								<span className="text-vscode-terminal-ansiMagenta">
 54 | 									--prompt
 55 | 								</span>{' '}
 56 | 								<span className="text-vscode-foreground/70">
 57 | 									"Your task description"
 58 | 								</span>
 59 | 								<div className="text-xs text-vscode-foreground/50 mt-1">
 60 | 									Add a single task with AI assistance
 61 | 								</div>
 62 | 							</div>
 63 | 							<div className="bg-vscode-editor-background rounded p-2 font-mono text-sm">
 64 | 								<span className="text-vscode-terminal-ansiYellow">
 65 | 									task-master
 66 | 								</span>{' '}
 67 | 								<span className="text-vscode-terminal-ansiCyan">add-task</span>{' '}
 68 | 								<span className="text-vscode-terminal-ansiMagenta">--help</span>
 69 | 								<div className="text-xs text-vscode-foreground/50 mt-1">
 70 | 									View all options for adding tasks
 71 | 								</div>
 72 | 							</div>
 73 | 						</div>
 74 | 					</div>
 75 | 
 76 | 					<div className="bg-vscode-editor-background/50 border border-vscode-panel-border rounded-lg p-4">
 77 | 						<div className="flex items-center gap-2 mb-2">
 78 | 							<MessageSquare className="w-4 h-4 text-vscode-textLink-foreground" />
 79 | 							<h3 className="font-medium">MCP Examples</h3>
 80 | 						</div>
 81 | 						<div className="space-y-2 text-sm">
 82 | 							<div className="flex items-start gap-2">
 83 | 								<Plus className="w-4 h-4 mt-0.5 text-vscode-foreground/50" />
 84 | 								<div>
 85 | 									<div className="text-vscode-foreground">
 86 | 										"Add a task to tag {currentTag}: Implement user
 87 | 										authentication"
 88 | 									</div>
 89 | 								</div>
 90 | 							</div>
 91 | 							<div className="flex items-start gap-2">
 92 | 								<Plus className="w-4 h-4 mt-0.5 text-vscode-foreground/50" />
 93 | 								<div>
 94 | 									<div className="text-vscode-foreground">
 95 | 										"Parse this PRD and add tasks to {currentTag}: [paste PRD
 96 | 										content]"
 97 | 									</div>
 98 | 								</div>
 99 | 							</div>
100 | 							<div className="flex items-start gap-2">
101 | 								<Plus className="w-4 h-4 mt-0.5 text-vscode-foreground/50" />
102 | 								<div>
103 | 									<div className="text-vscode-foreground">
104 | 										"Create 5 tasks for building a REST API in tag {currentTag}"
105 | 									</div>
106 | 								</div>
107 | 							</div>
108 | 						</div>
109 | 					</div>
110 | 
111 | 					{/* Documentation link */}
112 | 					<div className="flex justify-center pt-4">
113 | 						<a
114 | 							href="https://docs.task-master.dev"
115 | 							className="inline-flex items-center gap-2 text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground transition-colors"
116 | 							onClick={(e) => {
117 | 								e.preventDefault();
118 | 								// Use VS Code API to open external link
119 | 								if (window.acquireVsCodeApi) {
120 | 									const vscode = window.acquireVsCodeApi();
121 | 									vscode.postMessage({
122 | 										type: 'openExternal',
123 | 										url: 'https://docs.task-master.dev'
124 | 									});
125 | 								}
126 | 							}}
127 | 						>
128 | 							<ExternalLink className="w-4 h-4" />
129 | 							<span className="text-sm font-medium">
130 | 								View TaskMaster Documentation
131 | 							</span>
132 | 						</a>
133 | 					</div>
134 | 				</div>
135 | 			</div>
136 | 		</div>
137 | 	);
138 | };
139 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/workflow/managers/workflow-state-manager.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Tests for WorkflowStateManager path sanitization
  3 |  */
  4 | 
  5 | import os from 'node:os';
  6 | import path from 'node:path';
  7 | import { describe, expect, it } from 'vitest';
  8 | import { WorkflowStateManager } from './workflow-state-manager.js';
  9 | 
 10 | describe('WorkflowStateManager', () => {
 11 | 	describe('getProjectIdentifier', () => {
 12 | 		it('should sanitize paths like Claude Code', () => {
 13 | 			const projectRoot =
 14 | 				'/Volumes/Workspace/workspace/contrib/task-master/demos/nextjs-todo-tdd';
 15 | 			const manager = new WorkflowStateManager(projectRoot);
 16 | 
 17 | 			const sessionDir = manager.getSessionDir();
 18 | 			const homeDir = os.homedir();
 19 | 
 20 | 			// Expected structure: ~/.taskmaster/{project-id}/sessions/
 21 | 			const expectedPath = path.join(
 22 | 				homeDir,
 23 | 				'.taskmaster',
 24 | 				'-Volumes-Workspace-workspace-contrib-task-master-demos-nextjs-todo-tdd',
 25 | 				'sessions'
 26 | 			);
 27 | 
 28 | 			expect(sessionDir).toBe(expectedPath);
 29 | 		});
 30 | 
 31 | 		it('should preserve case in paths', () => {
 32 | 			const projectRoot = '/Users/Alice/Projects/MyApp';
 33 | 			const manager = new WorkflowStateManager(projectRoot);
 34 | 
 35 | 			const sessionDir = manager.getSessionDir();
 36 | 			// Extract project ID from: ~/.taskmaster/{project-id}/sessions/
 37 | 			const projectId = sessionDir.split(path.sep).slice(-2, -1)[0];
 38 | 
 39 | 			// Case should be preserved
 40 | 			expect(projectId).toContain('Users');
 41 | 			expect(projectId).toContain('Alice');
 42 | 			expect(projectId).toContain('Projects');
 43 | 			expect(projectId).toContain('MyApp');
 44 | 		});
 45 | 
 46 | 		it('should handle paths with special characters', () => {
 47 | 			const projectRoot = '/tmp/my-project_v2.0/test';
 48 | 			const manager = new WorkflowStateManager(projectRoot);
 49 | 
 50 | 			const sessionDir = manager.getSessionDir();
 51 | 			// Extract project ID from: ~/.taskmaster/{project-id}/sessions/
 52 | 			const projectId = sessionDir.split(path.sep).slice(-2, -1)[0];
 53 | 
 54 | 			// Special chars should be replaced with dashes
 55 | 			expect(projectId).toBe('-tmp-my-project-v2-0-test');
 56 | 		});
 57 | 
 58 | 		it('should create unique identifiers for different paths', () => {
 59 | 			const project1 = '/Users/alice/task-master';
 60 | 			const project2 = '/Users/bob/task-master';
 61 | 
 62 | 			const manager1 = new WorkflowStateManager(project1);
 63 | 			const manager2 = new WorkflowStateManager(project2);
 64 | 
 65 | 			// Extract project IDs from: ~/.taskmaster/{project-id}/sessions/
 66 | 			const id1 = manager1.getSessionDir().split(path.sep).slice(-2, -1)[0];
 67 | 			const id2 = manager2.getSessionDir().split(path.sep).slice(-2, -1)[0];
 68 | 
 69 | 			// Same basename but different full paths should be unique
 70 | 			expect(id1).not.toBe(id2);
 71 | 			expect(id1).toContain('alice');
 72 | 			expect(id2).toContain('bob');
 73 | 		});
 74 | 
 75 | 		it('should collapse multiple dashes', () => {
 76 | 			const projectRoot = '/path//with///multiple////slashes';
 77 | 			const manager = new WorkflowStateManager(projectRoot);
 78 | 
 79 | 			const sessionDir = manager.getSessionDir();
 80 | 			// Extract project ID from: ~/.taskmaster/{project-id}/sessions/
 81 | 			const projectId = sessionDir.split(path.sep).slice(-2, -1)[0];
 82 | 
 83 | 			// Multiple dashes should be collapsed to single dash
 84 | 			expect(projectId).not.toContain('--');
 85 | 			expect(projectId).toBe('-path-with-multiple-slashes');
 86 | 		});
 87 | 
 88 | 		it('should not have trailing dashes', () => {
 89 | 			const projectRoot = '/path/to/project';
 90 | 			const manager = new WorkflowStateManager(projectRoot);
 91 | 
 92 | 			const sessionDir = manager.getSessionDir();
 93 | 			// Extract project ID from: ~/.taskmaster/{project-id}/sessions/
 94 | 			const projectId = sessionDir.split(path.sep).slice(-2, -1)[0];
 95 | 
 96 | 			// Should not end with dash
 97 | 			expect(projectId).not.toMatch(/-$/);
 98 | 		});
 99 | 
100 | 		it('should start with a dash like Claude Code', () => {
101 | 			const projectRoot = '/any/path';
102 | 			const manager = new WorkflowStateManager(projectRoot);
103 | 
104 | 			const sessionDir = manager.getSessionDir();
105 | 			// Extract project ID from: ~/.taskmaster/{project-id}/sessions/
106 | 			const projectId = sessionDir.split(path.sep).slice(-2, -1)[0];
107 | 
108 | 			// Should start with dash like Claude Code's pattern
109 | 			expect(projectId).toMatch(/^-/);
110 | 		});
111 | 	});
112 | 
113 | 	describe('session paths', () => {
114 | 		it('should place sessions in global ~/.taskmaster/{project-id}/sessions/', () => {
115 | 			const projectRoot = '/some/project';
116 | 			const manager = new WorkflowStateManager(projectRoot);
117 | 
118 | 			const sessionDir = manager.getSessionDir();
119 | 			const homeDir = os.homedir();
120 | 
121 | 			// Should be: ~/.taskmaster/{project-id}/sessions/
122 | 			expect(sessionDir).toContain(path.join(homeDir, '.taskmaster'));
123 | 			expect(sessionDir).toMatch(/\.taskmaster\/.*\/sessions$/);
124 | 		});
125 | 
126 | 		it('should include workflow-state.json in session dir', () => {
127 | 			const projectRoot = '/some/project';
128 | 			const manager = new WorkflowStateManager(projectRoot);
129 | 
130 | 			const statePath = manager.getStatePath();
131 | 			const sessionDir = manager.getSessionDir();
132 | 
133 | 			expect(statePath).toBe(path.join(sessionDir, 'workflow-state.json'));
134 | 		});
135 | 
136 | 		it('should include backups dir in session dir', () => {
137 | 			const projectRoot = '/some/project';
138 | 			const manager = new WorkflowStateManager(projectRoot);
139 | 
140 | 			const backupDir = manager.getBackupDir();
141 | 			const sessionDir = manager.getSessionDir();
142 | 
143 | 			expect(backupDir).toBe(path.join(sessionDir, 'backups'));
144 | 		});
145 | 	});
146 | });
147 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/services/template-engine.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * TemplateEngine - Configurable template system for generating text from templates
  3 |  *
  4 |  * Supports:
  5 |  * - Variable substitution using {{variableName}} syntax
  6 |  * - Custom templates via constructor or setTemplate
  7 |  * - Template validation with required variables
  8 |  * - Variable extraction from templates
  9 |  * - Multiple template storage and retrieval
 10 |  */
 11 | 
 12 | export interface TemplateValidationResult {
 13 | 	isValid: boolean;
 14 | 	missingVars: string[];
 15 | }
 16 | 
 17 | export interface TemplateVariables {
 18 | 	[key: string]: string | number | boolean | undefined;
 19 | }
 20 | 
 21 | export interface TemplateCollection {
 22 | 	[templateName: string]: string;
 23 | }
 24 | 
 25 | export interface TemplateEngineOptions {
 26 | 	customTemplates?: TemplateCollection;
 27 | 	preservePlaceholders?: boolean;
 28 | }
 29 | 
 30 | const DEFAULT_TEMPLATES: TemplateCollection = {
 31 | 	commitMessage: `{{type}}{{#scope}}({{scope}}){{/scope}}{{#breaking}}!{{/breaking}}: {{description}}
 32 | 
 33 | {{#body}}{{body}}
 34 | 
 35 | {{/body}}{{#taskId}}Task: {{taskId}}{{/taskId}}{{#phase}}
 36 | Phase: {{phase}}{{/phase}}{{#testsPassing}}
 37 | Tests: {{testsPassing}} passing{{#testsFailing}}, {{testsFailing}} failing{{/testsFailing}}{{/testsPassing}}`
 38 | };
 39 | 
 40 | export class TemplateEngine {
 41 | 	private templates: TemplateCollection;
 42 | 	private preservePlaceholders: boolean;
 43 | 
 44 | 	constructor(
 45 | 		optionsOrTemplates: TemplateEngineOptions | TemplateCollection = {}
 46 | 	) {
 47 | 		// Backward compatibility: support old signature (TemplateCollection) and new signature (TemplateEngineOptions)
 48 | 		const isOptions =
 49 | 			'customTemplates' in optionsOrTemplates ||
 50 | 			'preservePlaceholders' in optionsOrTemplates;
 51 | 		const options: TemplateEngineOptions = isOptions
 52 | 			? (optionsOrTemplates as TemplateEngineOptions)
 53 | 			: { customTemplates: optionsOrTemplates as TemplateCollection };
 54 | 
 55 | 		this.templates = {
 56 | 			...DEFAULT_TEMPLATES,
 57 | 			...(options.customTemplates || {})
 58 | 		};
 59 | 		this.preservePlaceholders = options.preservePlaceholders ?? false;
 60 | 	}
 61 | 
 62 | 	/**
 63 | 	 * Render a template with provided variables
 64 | 	 */
 65 | 	render(
 66 | 		templateName: string,
 67 | 		variables: TemplateVariables,
 68 | 		inlineTemplate?: string
 69 | 	): string {
 70 | 		const template =
 71 | 			inlineTemplate !== undefined
 72 | 				? inlineTemplate
 73 | 				: this.templates[templateName];
 74 | 
 75 | 		if (template === undefined) {
 76 | 			throw new Error(`Template "${templateName}" not found`);
 77 | 		}
 78 | 
 79 | 		return this.substituteVariables(template, variables);
 80 | 	}
 81 | 
 82 | 	/**
 83 | 	 * Set or update a template
 84 | 	 */
 85 | 	setTemplate(name: string, template: string): void {
 86 | 		this.templates[name] = template;
 87 | 	}
 88 | 
 89 | 	/**
 90 | 	 * Get a template by name
 91 | 	 */
 92 | 	getTemplate(name: string): string | undefined {
 93 | 		return this.templates[name];
 94 | 	}
 95 | 
 96 | 	/**
 97 | 	 * Check if a template exists
 98 | 	 */
 99 | 	hasTemplate(name: string): boolean {
100 | 		return name in this.templates;
101 | 	}
102 | 
103 | 	/**
104 | 	 * Validate that a template contains all required variables
105 | 	 */
106 | 	validateTemplate(
107 | 		template: string,
108 | 		requiredVars: string[]
109 | 	): TemplateValidationResult {
110 | 		const templateVars = this.extractVariables(template);
111 | 		const missingVars = requiredVars.filter(
112 | 			(varName) => !templateVars.includes(varName)
113 | 		);
114 | 
115 | 		return {
116 | 			isValid: missingVars.length === 0,
117 | 			missingVars
118 | 		};
119 | 	}
120 | 
121 | 	/**
122 | 	 * Extract all variable names from a template
123 | 	 */
124 | 	extractVariables(template: string): string[] {
125 | 		const regex = /\{\{\s*([^}#/\s]+)\s*\}\}/g;
126 | 		const matches = template.matchAll(regex);
127 | 		const variables = new Set<string>();
128 | 
129 | 		for (const match of matches) {
130 | 			variables.add(match[1]);
131 | 		}
132 | 
133 | 		return Array.from(variables);
134 | 	}
135 | 
136 | 	/**
137 | 	 * Substitute variables in template
138 | 	 * Supports both {{variable}} and {{#variable}}...{{/variable}} (conditional blocks)
139 | 	 */
140 | 	private substituteVariables(
141 | 		template: string,
142 | 		variables: TemplateVariables
143 | 	): string {
144 | 		let result = template;
145 | 
146 | 		// Handle conditional blocks first ({{#var}}...{{/var}})
147 | 		result = this.processConditionalBlocks(result, variables);
148 | 
149 | 		// Handle simple variable substitution ({{var}})
150 | 		result = result.replace(/\{\{\s*([^}#/\s]+)\s*\}\}/g, (_, varName) => {
151 | 			const value = variables[varName];
152 | 			return value !== undefined && value !== null
153 | 				? String(value)
154 | 				: this.preservePlaceholders
155 | 					? `{{${varName}}}`
156 | 					: '';
157 | 		});
158 | 
159 | 		return result;
160 | 	}
161 | 
162 | 	/**
163 | 	 * Process conditional blocks in template
164 | 	 * {{#variable}}content{{/variable}} - shows content only if variable is truthy
165 | 	 * Processes innermost blocks first to handle nesting
166 | 	 */
167 | 	private processConditionalBlocks(
168 | 		template: string,
169 | 		variables: TemplateVariables
170 | 	): string {
171 | 		let result = template;
172 | 		let hasChanges = true;
173 | 
174 | 		// Keep processing until no more conditional blocks are found
175 | 		while (hasChanges) {
176 | 			const before = result;
177 | 
178 | 			// Find and replace innermost conditional blocks (non-greedy match)
179 | 			result = result.replace(
180 | 				/\{\{#([^}]+)\}\}((?:(?!\{\{#).)*?)\{\{\/\1\}\}/gs,
181 | 				(_, varName, content) => {
182 | 					const value = variables[varName.trim()];
183 | 
184 | 					// Show content if variable is truthy (not undefined, null, false, or empty string)
185 | 					if (
186 | 						value !== undefined &&
187 | 						value !== null &&
188 | 						value !== false &&
189 | 						value !== ''
190 | 					) {
191 | 						return content;
192 | 					}
193 | 
194 | 					return '';
195 | 				}
196 | 			);
197 | 
198 | 			hasChanges = result !== before;
199 | 		}
200 | 
201 | 		return result;
202 | 	}
203 | }
204 | 
```

--------------------------------------------------------------------------------
/tests/unit/profiles/roo-integration.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | import fs from 'fs';
  3 | import path from 'path';
  4 | import os from 'os';
  5 | 
  6 | // Mock external modules
  7 | jest.mock('child_process', () => ({
  8 | 	execSync: jest.fn()
  9 | }));
 10 | 
 11 | // Mock console methods
 12 | jest.mock('console', () => ({
 13 | 	log: jest.fn(),
 14 | 	info: jest.fn(),
 15 | 	warn: jest.fn(),
 16 | 	error: jest.fn(),
 17 | 	clear: jest.fn()
 18 | }));
 19 | 
 20 | describe('Roo Integration', () => {
 21 | 	let tempDir;
 22 | 
 23 | 	beforeEach(() => {
 24 | 		jest.clearAllMocks();
 25 | 
 26 | 		// Create a temporary directory for testing
 27 | 		tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
 28 | 
 29 | 		// Spy on fs methods
 30 | 		jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
 31 | 		jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
 32 | 			if (filePath.toString().includes('.roomodes')) {
 33 | 				return 'Existing roomodes content';
 34 | 			}
 35 | 			if (filePath.toString().includes('-rules')) {
 36 | 				return 'Existing mode rules content';
 37 | 			}
 38 | 			return '{}';
 39 | 		});
 40 | 		jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
 41 | 		jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
 42 | 	});
 43 | 
 44 | 	afterEach(() => {
 45 | 		// Clean up the temporary directory
 46 | 		try {
 47 | 			fs.rmSync(tempDir, { recursive: true, force: true });
 48 | 		} catch (err) {
 49 | 			console.error(`Error cleaning up: ${err.message}`);
 50 | 		}
 51 | 	});
 52 | 
 53 | 	// Test function that simulates the createProjectStructure behavior for Roo files
 54 | 	function mockCreateRooStructure() {
 55 | 		// Create main .roo directory
 56 | 		fs.mkdirSync(path.join(tempDir, '.roo'), { recursive: true });
 57 | 
 58 | 		// Create rules directory
 59 | 		fs.mkdirSync(path.join(tempDir, '.roo', 'rules'), { recursive: true });
 60 | 
 61 | 		// Create mode-specific rule directories
 62 | 		const rooModes = [
 63 | 			'architect',
 64 | 			'ask',
 65 | 			'orchestrator',
 66 | 			'code',
 67 | 			'debug',
 68 | 			'test'
 69 | 		];
 70 | 		for (const mode of rooModes) {
 71 | 			fs.mkdirSync(path.join(tempDir, '.roo', `rules-${mode}`), {
 72 | 				recursive: true
 73 | 			});
 74 | 			fs.writeFileSync(
 75 | 				path.join(tempDir, '.roo', `rules-${mode}`, `${mode}-rules`),
 76 | 				`Content for ${mode} rules`
 77 | 			);
 78 | 		}
 79 | 
 80 | 		// Create additional directories
 81 | 		fs.mkdirSync(path.join(tempDir, '.roo', 'config'), { recursive: true });
 82 | 		fs.mkdirSync(path.join(tempDir, '.roo', 'templates'), { recursive: true });
 83 | 		fs.mkdirSync(path.join(tempDir, '.roo', 'logs'), { recursive: true });
 84 | 
 85 | 		// Copy .roomodes file
 86 | 		fs.writeFileSync(path.join(tempDir, '.roomodes'), 'Roomodes file content');
 87 | 	}
 88 | 
 89 | 	test('creates all required .roo directories', () => {
 90 | 		// Act
 91 | 		mockCreateRooStructure();
 92 | 
 93 | 		// Assert
 94 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.roo'), {
 95 | 			recursive: true
 96 | 		});
 97 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
 98 | 			path.join(tempDir, '.roo', 'rules'),
 99 | 			{ recursive: true }
100 | 		);
101 | 
102 | 		// Verify all mode directories are created
103 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
104 | 			path.join(tempDir, '.roo', 'rules-architect'),
105 | 			{ recursive: true }
106 | 		);
107 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
108 | 			path.join(tempDir, '.roo', 'rules-ask'),
109 | 			{ recursive: true }
110 | 		);
111 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
112 | 			path.join(tempDir, '.roo', 'rules-orchestrator'),
113 | 			{ recursive: true }
114 | 		);
115 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
116 | 			path.join(tempDir, '.roo', 'rules-code'),
117 | 			{ recursive: true }
118 | 		);
119 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
120 | 			path.join(tempDir, '.roo', 'rules-debug'),
121 | 			{ recursive: true }
122 | 		);
123 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
124 | 			path.join(tempDir, '.roo', 'rules-test'),
125 | 			{ recursive: true }
126 | 		);
127 | 	});
128 | 
129 | 	test('creates rule files for all modes', () => {
130 | 		// Act
131 | 		mockCreateRooStructure();
132 | 
133 | 		// Assert - check all rule files are created
134 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
135 | 			path.join(tempDir, '.roo', 'rules-architect', 'architect-rules'),
136 | 			expect.any(String)
137 | 		);
138 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
139 | 			path.join(tempDir, '.roo', 'rules-ask', 'ask-rules'),
140 | 			expect.any(String)
141 | 		);
142 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
143 | 			path.join(tempDir, '.roo', 'rules-orchestrator', 'orchestrator-rules'),
144 | 			expect.any(String)
145 | 		);
146 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
147 | 			path.join(tempDir, '.roo', 'rules-code', 'code-rules'),
148 | 			expect.any(String)
149 | 		);
150 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
151 | 			path.join(tempDir, '.roo', 'rules-debug', 'debug-rules'),
152 | 			expect.any(String)
153 | 		);
154 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
155 | 			path.join(tempDir, '.roo', 'rules-test', 'test-rules'),
156 | 			expect.any(String)
157 | 		);
158 | 	});
159 | 
160 | 	test('creates .roomodes file in project root', () => {
161 | 		// Act
162 | 		mockCreateRooStructure();
163 | 
164 | 		// Assert
165 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
166 | 			path.join(tempDir, '.roomodes'),
167 | 			expect.any(String)
168 | 		);
169 | 	});
170 | 
171 | 	test('creates additional required Roo directories', () => {
172 | 		// Act
173 | 		mockCreateRooStructure();
174 | 
175 | 		// Assert
176 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
177 | 			path.join(tempDir, '.roo', 'config'),
178 | 			{ recursive: true }
179 | 		);
180 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
181 | 			path.join(tempDir, '.roo', 'templates'),
182 | 			{ recursive: true }
183 | 		);
184 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
185 | 			path.join(tempDir, '.roo', 'logs'),
186 | 			{ recursive: true }
187 | 		);
188 | 	});
189 | });
190 | 
```

--------------------------------------------------------------------------------
/apps/extension/src/components/TaskDetails/DetailsSection.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import type React from 'react';
  2 | import { CollapsibleSection } from '@/components/ui/CollapsibleSection';
  3 | 
  4 | interface MarkdownRendererProps {
  5 | 	content: string;
  6 | 	className?: string;
  7 | }
  8 | 
  9 | const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
 10 | 	content,
 11 | 	className = ''
 12 | }) => {
 13 | 	const parseMarkdown = (text: string) => {
 14 | 		const parts = [];
 15 | 		const lines = text.split('\n');
 16 | 		let currentBlock = [];
 17 | 		let inCodeBlock = false;
 18 | 		let codeLanguage = '';
 19 | 
 20 | 		for (let i = 0; i < lines.length; i++) {
 21 | 			const line = lines[i];
 22 | 
 23 | 			if (line.startsWith('```')) {
 24 | 				if (inCodeBlock) {
 25 | 					if (currentBlock.length > 0) {
 26 | 						parts.push({
 27 | 							type: 'code',
 28 | 							content: currentBlock.join('\n'),
 29 | 							language: codeLanguage
 30 | 						});
 31 | 						currentBlock = [];
 32 | 					}
 33 | 					inCodeBlock = false;
 34 | 					codeLanguage = '';
 35 | 				} else {
 36 | 					if (currentBlock.length > 0) {
 37 | 						parts.push({
 38 | 							type: 'text',
 39 | 							content: currentBlock.join('\n')
 40 | 						});
 41 | 						currentBlock = [];
 42 | 					}
 43 | 					inCodeBlock = true;
 44 | 					codeLanguage = line.substring(3).trim();
 45 | 				}
 46 | 			} else {
 47 | 				currentBlock.push(line);
 48 | 			}
 49 | 		}
 50 | 
 51 | 		if (currentBlock.length > 0) {
 52 | 			parts.push({
 53 | 				type: inCodeBlock ? 'code' : 'text',
 54 | 				content: currentBlock.join('\n'),
 55 | 				language: codeLanguage
 56 | 			});
 57 | 		}
 58 | 
 59 | 		return parts;
 60 | 	};
 61 | 
 62 | 	const parts = parseMarkdown(content);
 63 | 
 64 | 	return (
 65 | 		<div className={className}>
 66 | 			{parts.map((part, index) => {
 67 | 				if (part.type === 'code') {
 68 | 					return (
 69 | 						<pre
 70 | 							key={index}
 71 | 							className="bg-vscode-editor-background rounded-md p-4 overflow-x-auto mb-4 border border-vscode-editor-lineHighlightBorder"
 72 | 						>
 73 | 							<code className="text-sm text-vscode-editor-foreground font-mono">
 74 | 								{part.content}
 75 | 							</code>
 76 | 						</pre>
 77 | 					);
 78 | 				}
 79 | 				return (
 80 | 					<div key={index} className="whitespace-pre-wrap mb-4 last:mb-0">
 81 | 						{part.content.split('\n').map((line, lineIndex) => {
 82 | 							const bulletMatch = line.match(/^(\s*)([-*])\s(.+)$/);
 83 | 							if (bulletMatch) {
 84 | 								const indent = bulletMatch[1].length;
 85 | 								return (
 86 | 									<div
 87 | 										key={lineIndex}
 88 | 										className="flex gap-2 mb-1"
 89 | 										style={{ paddingLeft: `${indent * 16}px` }}
 90 | 									>
 91 | 										<span className="text-vscode-foreground/60">•</span>
 92 | 										<span className="flex-1">{bulletMatch[3]}</span>
 93 | 									</div>
 94 | 								);
 95 | 							}
 96 | 
 97 | 							const numberedMatch = line.match(/^(\s*)(\d+\.)\s(.+)$/);
 98 | 							if (numberedMatch) {
 99 | 								const indent = numberedMatch[1].length;
100 | 								return (
101 | 									<div
102 | 										key={lineIndex}
103 | 										className="flex gap-2 mb-1"
104 | 										style={{ paddingLeft: `${indent * 16}px` }}
105 | 									>
106 | 										<span className="text-vscode-foreground/60 font-mono">
107 | 											{numberedMatch[2]}
108 | 										</span>
109 | 										<span className="flex-1">{numberedMatch[3]}</span>
110 | 									</div>
111 | 								);
112 | 							}
113 | 
114 | 							const headingMatch = line.match(/^(#{1,6})\s(.+)$/);
115 | 							if (headingMatch) {
116 | 								const level = headingMatch[1].length;
117 | 								const headingLevel = Math.min(level + 2, 6);
118 | 								const headingClassName =
119 | 									'font-semibold text-vscode-foreground mb-2 mt-4 first:mt-0';
120 | 
121 | 								switch (headingLevel) {
122 | 									case 3:
123 | 										return (
124 | 											<h3 key={lineIndex} className={headingClassName}>
125 | 												{headingMatch[2]}
126 | 											</h3>
127 | 										);
128 | 									case 4:
129 | 										return (
130 | 											<h4 key={lineIndex} className={headingClassName}>
131 | 												{headingMatch[2]}
132 | 											</h4>
133 | 										);
134 | 									case 5:
135 | 										return (
136 | 											<h5 key={lineIndex} className={headingClassName}>
137 | 												{headingMatch[2]}
138 | 											</h5>
139 | 										);
140 | 									case 6:
141 | 										return (
142 | 											<h6 key={lineIndex} className={headingClassName}>
143 | 												{headingMatch[2]}
144 | 											</h6>
145 | 										);
146 | 									default:
147 | 										return (
148 | 											<h3 key={lineIndex} className={headingClassName}>
149 | 												{headingMatch[2]}
150 | 											</h3>
151 | 										);
152 | 								}
153 | 							}
154 | 
155 | 							if (line.trim() === '') {
156 | 								return <div key={lineIndex} className="h-2" />;
157 | 							}
158 | 
159 | 							return (
160 | 								<div key={lineIndex} className="mb-2 last:mb-0">
161 | 									{line}
162 | 								</div>
163 | 							);
164 | 						})}
165 | 					</div>
166 | 				);
167 | 			})}
168 | 		</div>
169 | 	);
170 | };
171 | 
172 | interface DetailsSectionProps {
173 | 	title: string;
174 | 	content?: string;
175 | 	error?: string | null;
176 | 	emptyMessage?: string;
177 | 	defaultExpanded?: boolean;
178 | }
179 | 
180 | export const DetailsSection: React.FC<DetailsSectionProps> = ({
181 | 	title,
182 | 	content,
183 | 	error,
184 | 	emptyMessage = 'No details available',
185 | 	defaultExpanded = false
186 | }) => {
187 | 	return (
188 | 		<CollapsibleSection title={title} defaultExpanded={defaultExpanded}>
189 | 			<div className={title.toLowerCase().replace(/\s+/g, '-') + '-content'}>
190 | 				{error ? (
191 | 					<div className="text-sm text-red-400 py-2">
192 | 						Error loading {title.toLowerCase()}: {error}
193 | 					</div>
194 | 				) : content !== undefined && content !== '' ? (
195 | 					<MarkdownRenderer content={content} />
196 | 				) : (
197 | 					<div className="text-sm text-vscode-foreground/50 py-2">
198 | 						{emptyMessage}
199 | 					</div>
200 | 				)}
201 | 			</div>
202 | 		</CollapsibleSection>
203 | 	);
204 | };
205 | 
```

--------------------------------------------------------------------------------
/src/utils/timeout-manager.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { StreamingError, STREAMING_ERROR_CODES } from './stream-parser.js';
  2 | 
  3 | /**
  4 |  * Utility class for managing timeouts in async operations
  5 |  * Reduces code duplication for timeout handling patterns
  6 |  */
  7 | export class TimeoutManager {
  8 | 	/**
  9 | 	 * Wraps a promise with a timeout that will reject if not resolved in time
 10 | 	 *
 11 | 	 * @param {Promise} promise - The promise to wrap with timeout
 12 | 	 * @param {number} timeoutMs - Timeout duration in milliseconds
 13 | 	 * @param {string} operationName - Name of the operation for error messages
 14 | 	 * @returns {Promise} The result of the promise or throws timeout error
 15 | 	 *
 16 | 	 * @example
 17 | 	 * const result = await TimeoutManager.withTimeout(
 18 | 	 *   fetchData(),
 19 | 	 *   5000,
 20 | 	 *   'Data fetch operation'
 21 | 	 * );
 22 | 	 */
 23 | 	static async withTimeout(promise, timeoutMs, operationName = 'Operation') {
 24 | 		let timeoutHandle;
 25 | 
 26 | 		const timeoutPromise = new Promise((_, reject) => {
 27 | 			timeoutHandle = setTimeout(() => {
 28 | 				reject(
 29 | 					new StreamingError(
 30 | 						`${operationName} timed out after ${timeoutMs / 1000} seconds`,
 31 | 						STREAMING_ERROR_CODES.STREAM_PROCESSING_FAILED
 32 | 					)
 33 | 				);
 34 | 			}, timeoutMs);
 35 | 		});
 36 | 
 37 | 		try {
 38 | 			// Race between the actual promise and the timeout
 39 | 			const result = await Promise.race([promise, timeoutPromise]);
 40 | 			// Clear timeout if promise resolved first
 41 | 			clearTimeout(timeoutHandle);
 42 | 			return result;
 43 | 		} catch (error) {
 44 | 			// Always clear timeout on error
 45 | 			clearTimeout(timeoutHandle);
 46 | 			throw error;
 47 | 		}
 48 | 	}
 49 | 
 50 | 	/**
 51 | 	 * Wraps a promise with a timeout, but returns undefined instead of throwing on timeout
 52 | 	 * Useful for optional operations that shouldn't fail the main flow
 53 | 	 *
 54 | 	 * @param {Promise} promise - The promise to wrap with timeout
 55 | 	 * @param {number} timeoutMs - Timeout duration in milliseconds
 56 | 	 * @param {*} defaultValue - Value to return on timeout (default: undefined)
 57 | 	 * @returns {Promise} The result of the promise or defaultValue on timeout
 58 | 	 *
 59 | 	 * @example
 60 | 	 * const usage = await TimeoutManager.withSoftTimeout(
 61 | 	 *   getUsageStats(),
 62 | 	 *   1000,
 63 | 	 *   { tokens: 0 }
 64 | 	 * );
 65 | 	 */
 66 | 	static async withSoftTimeout(promise, timeoutMs, defaultValue = undefined) {
 67 | 		let timeoutHandle;
 68 | 
 69 | 		const timeoutPromise = new Promise((resolve) => {
 70 | 			timeoutHandle = setTimeout(() => {
 71 | 				resolve(defaultValue);
 72 | 			}, timeoutMs);
 73 | 		});
 74 | 
 75 | 		try {
 76 | 			const result = await Promise.race([promise, timeoutPromise]);
 77 | 			clearTimeout(timeoutHandle);
 78 | 			return result;
 79 | 		} catch (error) {
 80 | 			// On error, clear timeout and return default value
 81 | 			clearTimeout(timeoutHandle);
 82 | 			return defaultValue;
 83 | 		}
 84 | 	}
 85 | 
 86 | 	/**
 87 | 	 * Creates a reusable timeout controller for multiple operations
 88 | 	 * Useful when you need to apply the same timeout to multiple promises
 89 | 	 *
 90 | 	 * @param {number} timeoutMs - Timeout duration in milliseconds
 91 | 	 * @param {string} operationName - Base name for operations
 92 | 	 * @returns {Object} Controller with wrap method
 93 | 	 *
 94 | 	 * @example
 95 | 	 * const controller = TimeoutManager.createController(60000, 'AI Service');
 96 | 	 * const result1 = await controller.wrap(service.call1(), 'call 1');
 97 | 	 * const result2 = await controller.wrap(service.call2(), 'call 2');
 98 | 	 */
 99 | 	static createController(timeoutMs, operationName = 'Operation') {
100 | 		return {
101 | 			timeoutMs,
102 | 			operationName,
103 | 
104 | 			async wrap(promise, specificName = null) {
105 | 				const fullName = specificName
106 | 					? `${operationName} - ${specificName}`
107 | 					: operationName;
108 | 				return TimeoutManager.withTimeout(promise, timeoutMs, fullName);
109 | 			},
110 | 
111 | 			async wrapSoft(promise, defaultValue = undefined) {
112 | 				return TimeoutManager.withSoftTimeout(promise, timeoutMs, defaultValue);
113 | 			}
114 | 		};
115 | 	}
116 | 
117 | 	/**
118 | 	 * Checks if an error is a timeout error from this manager
119 | 	 *
120 | 	 * @param {Error} error - The error to check
121 | 	 * @returns {boolean} True if this is a timeout error
122 | 	 */
123 | 	static isTimeoutError(error) {
124 | 		return (
125 | 			error instanceof StreamingError &&
126 | 			error.code === STREAMING_ERROR_CODES.STREAM_PROCESSING_FAILED &&
127 | 			error.message.includes('timed out')
128 | 		);
129 | 	}
130 | }
131 | 
132 | /**
133 |  * Duration helper class for more readable timeout specifications
134 |  */
135 | export class Duration {
136 | 	constructor(value, unit = 'ms') {
137 | 		this.milliseconds = this._toMilliseconds(value, unit);
138 | 	}
139 | 
140 | 	static milliseconds(value) {
141 | 		return new Duration(value, 'ms');
142 | 	}
143 | 
144 | 	static seconds(value) {
145 | 		return new Duration(value, 's');
146 | 	}
147 | 
148 | 	static minutes(value) {
149 | 		return new Duration(value, 'm');
150 | 	}
151 | 
152 | 	static hours(value) {
153 | 		return new Duration(value, 'h');
154 | 	}
155 | 
156 | 	get seconds() {
157 | 		return this.milliseconds / 1000;
158 | 	}
159 | 
160 | 	get minutes() {
161 | 		return this.milliseconds / 60000;
162 | 	}
163 | 
164 | 	get hours() {
165 | 		return this.milliseconds / 3600000;
166 | 	}
167 | 
168 | 	toString() {
169 | 		if (this.milliseconds < 1000) {
170 | 			return `${this.milliseconds}ms`;
171 | 		} else if (this.milliseconds < 60000) {
172 | 			return `${this.seconds}s`;
173 | 		} else if (this.milliseconds < 3600000) {
174 | 			return `${Math.floor(this.minutes)}m ${Math.floor(this.seconds % 60)}s`;
175 | 		} else {
176 | 			return `${Math.floor(this.hours)}h ${Math.floor(this.minutes % 60)}m`;
177 | 		}
178 | 	}
179 | 
180 | 	_toMilliseconds(value, unit) {
181 | 		const conversions = {
182 | 			ms: 1,
183 | 			s: 1000,
184 | 			m: 60000,
185 | 			h: 3600000
186 | 		};
187 | 		return value * (conversions[unit] || 1);
188 | 	}
189 | }
190 | 
```

--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/errors.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for error handling utilities
  3 |  */
  4 | 
  5 | import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';
  6 | import { describe, expect, it } from 'vitest';
  7 | import {
  8 | 	createAPICallError,
  9 | 	createAuthenticationError,
 10 | 	createInstallationError,
 11 | 	createTimeoutError,
 12 | 	getErrorMetadata,
 13 | 	isAuthenticationError,
 14 | 	isInstallationError,
 15 | 	isTimeoutError
 16 | } from './errors.js';
 17 | 
 18 | describe('createAPICallError', () => {
 19 | 	it('should create APICallError with metadata', () => {
 20 | 		const error = createAPICallError({
 21 | 			message: 'Test error',
 22 | 			code: 'TEST_ERROR',
 23 | 			exitCode: 1,
 24 | 			stderr: 'Error output',
 25 | 			stdout: 'Success output',
 26 | 			promptExcerpt: 'Test prompt',
 27 | 			isRetryable: true
 28 | 		});
 29 | 
 30 | 		expect(error).toBeInstanceOf(APICallError);
 31 | 		expect(error.message).toBe('Test error');
 32 | 		expect(error.isRetryable).toBe(true);
 33 | 		expect(error.url).toBe('grok-cli://command');
 34 | 		expect(error.data).toEqual({
 35 | 			code: 'TEST_ERROR',
 36 | 			exitCode: 1,
 37 | 			stderr: 'Error output',
 38 | 			stdout: 'Success output',
 39 | 			promptExcerpt: 'Test prompt'
 40 | 		});
 41 | 	});
 42 | 
 43 | 	it('should create APICallError with minimal parameters', () => {
 44 | 		const error = createAPICallError({
 45 | 			message: 'Simple error'
 46 | 		});
 47 | 
 48 | 		expect(error).toBeInstanceOf(APICallError);
 49 | 		expect(error.message).toBe('Simple error');
 50 | 		expect(error.isRetryable).toBe(false);
 51 | 	});
 52 | });
 53 | 
 54 | describe('createAuthenticationError', () => {
 55 | 	it('should create LoadAPIKeyError with custom message', () => {
 56 | 		const error = createAuthenticationError({
 57 | 			message: 'Custom auth error'
 58 | 		});
 59 | 
 60 | 		expect(error).toBeInstanceOf(LoadAPIKeyError);
 61 | 		expect(error.message).toBe('Custom auth error');
 62 | 	});
 63 | 
 64 | 	it('should create LoadAPIKeyError with default message', () => {
 65 | 		const error = createAuthenticationError({});
 66 | 
 67 | 		expect(error).toBeInstanceOf(LoadAPIKeyError);
 68 | 		expect(error.message).toContain('Authentication failed');
 69 | 	});
 70 | });
 71 | 
 72 | describe('createTimeoutError', () => {
 73 | 	it('should create APICallError for timeout', () => {
 74 | 		const error = createTimeoutError({
 75 | 			message: 'Operation timed out',
 76 | 			timeoutMs: 5000,
 77 | 			promptExcerpt: 'Test prompt'
 78 | 		});
 79 | 
 80 | 		expect(error).toBeInstanceOf(APICallError);
 81 | 		expect(error.message).toBe('Operation timed out');
 82 | 		expect(error.isRetryable).toBe(true);
 83 | 		expect(error.data).toEqual({
 84 | 			code: 'TIMEOUT',
 85 | 			promptExcerpt: 'Test prompt',
 86 | 			timeoutMs: 5000
 87 | 		});
 88 | 	});
 89 | });
 90 | 
 91 | describe('createInstallationError', () => {
 92 | 	it('should create APICallError for installation issues', () => {
 93 | 		const error = createInstallationError({
 94 | 			message: 'CLI not found'
 95 | 		});
 96 | 
 97 | 		expect(error).toBeInstanceOf(APICallError);
 98 | 		expect(error.message).toBe('CLI not found');
 99 | 		expect(error.isRetryable).toBe(false);
100 | 		expect(error.url).toBe('grok-cli://installation');
101 | 	});
102 | 
103 | 	it('should create APICallError with default message', () => {
104 | 		const error = createInstallationError({});
105 | 
106 | 		expect(error).toBeInstanceOf(APICallError);
107 | 		expect(error.message).toContain('Grok CLI is not installed');
108 | 	});
109 | });
110 | 
111 | describe('isAuthenticationError', () => {
112 | 	it('should return true for LoadAPIKeyError', () => {
113 | 		const error = new LoadAPIKeyError({ message: 'Auth failed' });
114 | 		expect(isAuthenticationError(error)).toBe(true);
115 | 	});
116 | 
117 | 	it('should return true for APICallError with 401 exit code', () => {
118 | 		const error = new APICallError({
119 | 			message: 'Unauthorized',
120 | 			data: { exitCode: 401 }
121 | 		});
122 | 		expect(isAuthenticationError(error)).toBe(true);
123 | 	});
124 | 
125 | 	it('should return false for other errors', () => {
126 | 		const error = new Error('Generic error');
127 | 		expect(isAuthenticationError(error)).toBe(false);
128 | 	});
129 | });
130 | 
131 | describe('isTimeoutError', () => {
132 | 	it('should return true for timeout APICallError', () => {
133 | 		const error = new APICallError({
134 | 			message: 'Timeout',
135 | 			data: { code: 'TIMEOUT' }
136 | 		});
137 | 		expect(isTimeoutError(error)).toBe(true);
138 | 	});
139 | 
140 | 	it('should return false for other errors', () => {
141 | 		const error = new APICallError({ message: 'Other error' });
142 | 		expect(isTimeoutError(error)).toBe(false);
143 | 	});
144 | });
145 | 
146 | describe('isInstallationError', () => {
147 | 	it('should return true for installation APICallError', () => {
148 | 		const error = new APICallError({
149 | 			message: 'Not installed',
150 | 			url: 'grok-cli://installation'
151 | 		});
152 | 		expect(isInstallationError(error)).toBe(true);
153 | 	});
154 | 
155 | 	it('should return false for other errors', () => {
156 | 		const error = new APICallError({ message: 'Other error' });
157 | 		expect(isInstallationError(error)).toBe(false);
158 | 	});
159 | });
160 | 
161 | describe('getErrorMetadata', () => {
162 | 	it('should return metadata from APICallError', () => {
163 | 		const metadata = {
164 | 			code: 'TEST_ERROR',
165 | 			exitCode: 1,
166 | 			stderr: 'Error output'
167 | 		};
168 | 		const error = new APICallError({
169 | 			message: 'Test error',
170 | 			data: metadata
171 | 		});
172 | 
173 | 		const result = getErrorMetadata(error);
174 | 		expect(result).toEqual(metadata);
175 | 	});
176 | 
177 | 	it('should return undefined for errors without metadata', () => {
178 | 		const error = new Error('Generic error');
179 | 		const result = getErrorMetadata(error);
180 | 		expect(result).toBeUndefined();
181 | 	});
182 | 
183 | 	it('should return undefined for APICallError without data', () => {
184 | 		const error = new APICallError({ message: 'Test error' });
185 | 		const result = getErrorMetadata(error);
186 | 		expect(result).toBeUndefined();
187 | 	});
188 | });
189 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/services/scope-detector.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * ScopeDetector - Intelligent scope detection from changed files
  3 |  *
  4 |  * Automatically determines conventional commit scopes based on file paths
  5 |  * using configurable pattern matching and priority-based resolution.
  6 |  * // TODO: remove this
  7 |  */
  8 | 
  9 | export interface ScopeMapping {
 10 | 	[pattern: string]: string;
 11 | }
 12 | 
 13 | export interface ScopePriority {
 14 | 	[scope: string]: number;
 15 | }
 16 | 
 17 | // Ordered from most specific to least specific
 18 | const DEFAULT_SCOPE_MAPPINGS: Array<[string, string]> = [
 19 | 	// Special file types (check first - most specific)
 20 | 	['**/*.test.*', 'test'],
 21 | 	['**/*.spec.*', 'test'],
 22 | 	['**/test/**', 'test'],
 23 | 	['**/tests/**', 'test'],
 24 | 	['**/__tests__/**', 'test'],
 25 | 
 26 | 	// Dependencies (specific files)
 27 | 	['**/package-lock.json', 'deps'],
 28 | 	['package-lock.json', 'deps'],
 29 | 	['**/pnpm-lock.yaml', 'deps'],
 30 | 	['pnpm-lock.yaml', 'deps'],
 31 | 	['**/yarn.lock', 'deps'],
 32 | 	['yarn.lock', 'deps'],
 33 | 
 34 | 	// Configuration files (before packages so root configs don't match package patterns)
 35 | 	['**/package.json', 'config'],
 36 | 	['package.json', 'config'],
 37 | 	['**/tsconfig*.json', 'config'],
 38 | 	['tsconfig*.json', 'config'],
 39 | 	['**/.eslintrc*', 'config'],
 40 | 	['.eslintrc*', 'config'],
 41 | 	['**/vite.config.*', 'config'],
 42 | 	['vite.config.*', 'config'],
 43 | 	['**/vitest.config.*', 'config'],
 44 | 	['vitest.config.*', 'config'],
 45 | 
 46 | 	// Package-level scopes (more specific than feature-level)
 47 | 	['packages/cli/**', 'cli'],
 48 | 	['packages/tm-core/**', 'core'],
 49 | 	['packages/mcp-server/**', 'mcp'],
 50 | 
 51 | 	// Feature-level scopes (within any package)
 52 | 	['**/workflow/**', 'workflow'],
 53 | 	['**/git/**', 'git'],
 54 | 	['**/storage/**', 'storage'],
 55 | 	['**/auth/**', 'auth'],
 56 | 	['**/config/**', 'config'],
 57 | 
 58 | 	// Documentation (least specific)
 59 | 	['**/*.md', 'docs'],
 60 | 	['**/docs/**', 'docs'],
 61 | 	['README*', 'docs'],
 62 | 	['CHANGELOG*', 'docs']
 63 | ];
 64 | 
 65 | const DEFAULT_SCOPE_PRIORITIES: ScopePriority = {
 66 | 	core: 100,
 67 | 	cli: 90,
 68 | 	mcp: 85,
 69 | 	workflow: 80,
 70 | 	git: 75,
 71 | 	storage: 70,
 72 | 	auth: 65,
 73 | 	config: 60,
 74 | 	test: 50,
 75 | 	docs: 30,
 76 | 	deps: 20,
 77 | 	repo: 10
 78 | };
 79 | 
 80 | export class ScopeDetector {
 81 | 	private scopeMappings: Array<[string, string]>;
 82 | 	private scopePriorities: ScopePriority;
 83 | 
 84 | 	constructor(customMappings?: ScopeMapping, customPriorities?: ScopePriority) {
 85 | 		// Start with default mappings
 86 | 		this.scopeMappings = [...DEFAULT_SCOPE_MAPPINGS];
 87 | 
 88 | 		// Add custom mappings at the start (highest priority)
 89 | 		if (customMappings) {
 90 | 			const customEntries = Object.entries(customMappings);
 91 | 			this.scopeMappings = [...customEntries, ...this.scopeMappings];
 92 | 		}
 93 | 
 94 | 		this.scopePriorities = {
 95 | 			...DEFAULT_SCOPE_PRIORITIES,
 96 | 			...customPriorities
 97 | 		};
 98 | 	}
 99 | 
100 | 	/**
101 | 	 * Detect the most relevant scope from a list of changed files
102 | 	 * Returns the scope with the highest priority
103 | 	 */
104 | 	detectScope(files: string[]): string {
105 | 		if (files.length === 0) {
106 | 			return 'repo';
107 | 		}
108 | 
109 | 		const scopeCounts = new Map<string, number>();
110 | 
111 | 		// Count occurrences of each scope
112 | 		for (const file of files) {
113 | 			const scope = this.getMatchingScope(file);
114 | 			if (scope) {
115 | 				scopeCounts.set(scope, (scopeCounts.get(scope) || 0) + 1);
116 | 			}
117 | 		}
118 | 
119 | 		// If no scopes matched, default to 'repo'
120 | 		if (scopeCounts.size === 0) {
121 | 			return 'repo';
122 | 		}
123 | 
124 | 		// Find scope with highest priority (considering both priority and count)
125 | 		let bestScope = 'repo';
126 | 		let bestScore = 0;
127 | 
128 | 		for (const [scope, count] of scopeCounts) {
129 | 			const priority = this.getScopePriority(scope);
130 | 			// Score = priority * count (files in that scope)
131 | 			const score = priority * count;
132 | 
133 | 			if (score > bestScore) {
134 | 				bestScore = score;
135 | 				bestScope = scope;
136 | 			}
137 | 		}
138 | 
139 | 		return bestScope;
140 | 	}
141 | 
142 | 	/**
143 | 	 * Get all matching scopes for the given files
144 | 	 */
145 | 	getAllMatchingScopes(files: string[]): string[] {
146 | 		const scopes = new Set<string>();
147 | 
148 | 		for (const file of files) {
149 | 			const scope = this.getMatchingScope(file);
150 | 			if (scope) {
151 | 				scopes.add(scope);
152 | 			}
153 | 		}
154 | 
155 | 		return Array.from(scopes);
156 | 	}
157 | 
158 | 	/**
159 | 	 * Get the matching scope for a single file
160 | 	 * Returns the first matching scope (order matters!)
161 | 	 */
162 | 	getMatchingScope(file: string): string | null {
163 | 		// Normalize path separators
164 | 		const normalizedFile = file.replace(/\\/g, '/');
165 | 
166 | 		for (const [pattern, scope] of this.scopeMappings) {
167 | 			if (this.matchesPattern(normalizedFile, pattern)) {
168 | 				return scope;
169 | 			}
170 | 		}
171 | 
172 | 		return null;
173 | 	}
174 | 
175 | 	/**
176 | 	 * Get the priority of a scope
177 | 	 */
178 | 	getScopePriority(scope: string): number {
179 | 		return this.scopePriorities[scope] || 0;
180 | 	}
181 | 
182 | 	/**
183 | 	 * Match a file path against a glob-like pattern
184 | 	 * Supports:
185 | 	 * - ** for multi-level directory matching
186 | 	 * - * for single-level matching
187 | 	 */
188 | 	private matchesPattern(filePath: string, pattern: string): boolean {
189 | 		// Replace ** first with a unique placeholder
190 | 		let regexPattern = pattern.replace(/\*\*/g, '§GLOBSTAR§');
191 | 
192 | 		// Escape special regex characters (but not our placeholder or *)
193 | 		regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
194 | 
195 | 		// Replace single * with [^/]* (matches anything except /)
196 | 		regexPattern = regexPattern.replace(/\*/g, '[^/]*');
197 | 
198 | 		// Replace placeholder with .* (matches anything including /)
199 | 		regexPattern = regexPattern.replace(/§GLOBSTAR§/g, '.*');
200 | 
201 | 		const regex = new RegExp(`^${regexPattern}$`);
202 | 		return regex.test(filePath);
203 | 	}
204 | }
205 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 | 	"name": "task-master-ai",
  3 | 	"version": "0.33.0",
  4 | 	"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
  5 | 	"main": "index.js",
  6 | 	"type": "module",
  7 | 	"bin": {
  8 | 		"task-master": "dist/task-master.js",
  9 | 		"task-master-mcp": "dist/mcp-server.js",
 10 | 		"task-master-ai": "dist/mcp-server.js"
 11 | 	},
 12 | 	"workspaces": ["apps/*", "packages/*", "."],
 13 | 	"scripts": {
 14 | 		"build": "npm run build:build-config && cross-env NODE_ENV=production tsdown",
 15 | 		"dev": "tsdown --watch",
 16 | 		"turbo:dev": "turbo dev",
 17 | 		"turbo:build": "turbo build",
 18 | 		"turbo:typecheck": "turbo typecheck",
 19 | 		"build:build-config": "npm run build -w @tm/build-config",
 20 | 		"test": "cross-env NODE_ENV=test node --experimental-vm-modules node_modules/.bin/jest",
 21 | 		"test:unit": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=unit",
 22 | 		"test:integration": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=integration",
 23 | 		"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",
 24 | 		"test:watch": "node --experimental-vm-modules node_modules/.bin/jest --watch",
 25 | 		"test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage",
 26 | 		"test:ci": "node --experimental-vm-modules node_modules/.bin/jest --coverage --ci",
 27 | 		"test:e2e": "./tests/e2e/run_e2e.sh",
 28 | 		"test:e2e-report": "./tests/e2e/run_e2e.sh --analyze-log",
 29 | 		"postpack": "chmod +x dist/task-master.js dist/mcp-server.js",
 30 | 		"changeset": "changeset",
 31 | 		"changeset:validate": "node .github/scripts/validate-changesets.mjs",
 32 | 		"release": "changeset publish",
 33 | 		"publish-packages": "turbo run build lint test && changeset version && changeset publish",
 34 | 		"inspector": "npx @modelcontextprotocol/inspector node dist/mcp-server.js",
 35 | 		"mcp-server": "node dist/mcp-server.js",
 36 | 		"format-check": "biome format .",
 37 | 		"format": "biome format . --write",
 38 | 		"deps:check": "manypkg check || echo 'Note: Workspace package version warnings are expected for internal @tm/* packages'",
 39 | 		"deps:fix": "manypkg fix"
 40 | 	},
 41 | 	"keywords": [
 42 | 		"claude",
 43 | 		"task",
 44 | 		"management",
 45 | 		"ai",
 46 | 		"development",
 47 | 		"cursor",
 48 | 		"anthropic",
 49 | 		"llm",
 50 | 		"mcp",
 51 | 		"context"
 52 | 	],
 53 | 	"author": "Eyal Toledano",
 54 | 	"license": "MIT WITH Commons-Clause",
 55 | 	"dependencies": {
 56 | 		"@ai-sdk/amazon-bedrock": "^3.0.23",
 57 | 		"@ai-sdk/anthropic": "^2.0.18",
 58 | 		"@ai-sdk/azure": "^2.0.34",
 59 | 		"@ai-sdk/google": "^2.0.16",
 60 | 		"@ai-sdk/google-vertex": "^3.0.29",
 61 | 		"@ai-sdk/groq": "^2.0.21",
 62 | 		"@ai-sdk/mistral": "^2.0.16",
 63 | 		"@ai-sdk/openai": "^2.0.34",
 64 | 		"@ai-sdk/openai-compatible": "^1.0.25",
 65 | 		"@ai-sdk/perplexity": "^2.0.10",
 66 | 		"@ai-sdk/provider": "^2.0.0",
 67 | 		"@ai-sdk/provider-utils": "^3.0.10",
 68 | 		"@ai-sdk/xai": "^2.0.22",
 69 | 		"@aws-sdk/credential-providers": "^3.895.0",
 70 | 		"@inquirer/search": "^3.0.15",
 71 | 		"@openrouter/ai-sdk-provider": "^1.2.0",
 72 | 		"@streamparser/json": "^0.0.22",
 73 | 		"@supabase/supabase-js": "^2.57.4",
 74 | 		"ai": "^5.0.51",
 75 | 		"ai-sdk-provider-claude-code": "^2.1.0",
 76 | 		"ai-sdk-provider-codex-cli": "^0.3.0",
 77 | 		"ai-sdk-provider-gemini-cli": "^1.1.1",
 78 | 		"ajv": "^8.17.1",
 79 | 		"ajv-formats": "^3.0.1",
 80 | 		"boxen": "^8.0.1",
 81 | 		"chalk": "5.6.2",
 82 | 		"cli-highlight": "^2.1.11",
 83 | 		"cli-progress": "^3.12.0",
 84 | 		"cli-table3": "^0.6.5",
 85 | 		"commander": "^12.1.0",
 86 | 		"cors": "^2.8.5",
 87 | 		"date-fns": "^4.1.0",
 88 | 		"dotenv": "^16.6.1",
 89 | 		"express": "^4.21.2",
 90 | 		"fastmcp": "^3.23.1",
 91 | 		"figlet": "^1.8.0",
 92 | 		"fs-extra": "^11.3.0",
 93 | 		"fuse.js": "^7.1.0",
 94 | 		"gpt-tokens": "^1.3.14",
 95 | 		"gradient-string": "^3.0.0",
 96 | 		"helmet": "^8.1.0",
 97 | 		"inquirer": "^12.5.0",
 98 | 		"jsonc-parser": "^3.3.1",
 99 | 		"jsonrepair": "^3.13.0",
100 | 		"jsonwebtoken": "^9.0.2",
101 | 		"lru-cache": "^10.2.0",
102 | 		"marked": "^15.0.12",
103 | 		"marked-terminal": "^7.3.0",
104 | 		"ollama-ai-provider-v2": "^1.3.1",
105 | 		"open": "^10.2.0",
106 | 		"ora": "^8.2.0",
107 | 		"simple-git": "^3.28.0",
108 | 		"steno": "^4.0.2",
109 | 		"undici": "^7.16.0",
110 | 		"uuid": "^11.1.0",
111 | 		"zod": "^4.1.12"
112 | 	},
113 | 	"optionalDependencies": {
114 | 		"@anthropic-ai/claude-code": "^1.0.88",
115 | 		"@biomejs/cli-linux-x64": "^1.9.4"
116 | 	},
117 | 	"engines": {
118 | 		"node": ">=18.0.0"
119 | 	},
120 | 	"packageManager": "[email protected]",
121 | 	"repository": {
122 | 		"type": "git",
123 | 		"url": "git+https://github.com/eyaltoledano/claude-task-master.git"
124 | 	},
125 | 	"homepage": "https://github.com/eyaltoledano/claude-task-master#readme",
126 | 	"bugs": {
127 | 		"url": "https://github.com/eyaltoledano/claude-task-master/issues"
128 | 	},
129 | 	"files": [
130 | 		"dist/**",
131 | 		"README-task-master.md",
132 | 		"README.md",
133 | 		"LICENSE",
134 | 		"CHANGELOG.md"
135 | 	],
136 | 	"overrides": {
137 | 		"node-fetch": "^2.6.12",
138 | 		"whatwg-url": "^11.0.0"
139 | 	},
140 | 	"devDependencies": {
141 | 		"@biomejs/biome": "^1.9.4",
142 | 		"@changesets/changelog-github": "^0.5.1",
143 | 		"@changesets/cli": "^2.28.1",
144 | 		"@manypkg/cli": "^0.25.1",
145 | 		"@tm/ai-sdk-provider-grok-cli": "*",
146 | 		"@tm/cli": "*",
147 | 		"@types/fs-extra": "^11.0.4",
148 | 		"@types/jest": "^29.5.14",
149 | 		"@types/marked-terminal": "^6.1.1",
150 | 		"concurrently": "^9.2.1",
151 | 		"cross-env": "^10.0.0",
152 | 		"execa": "^8.0.1",
153 | 		"jest": "^29.7.0",
154 | 		"jest-environment-node": "^29.7.0",
155 | 		"mock-fs": "^5.5.0",
156 | 		"prettier": "^3.5.3",
157 | 		"supertest": "^7.1.0",
158 | 		"ts-jest": "^29.4.2",
159 | 		"tsdown": "^0.15.2",
160 | 		"tsx": "^4.20.4",
161 | 		"turbo": "2.5.6",
162 | 		"typescript": "^5.9.2"
163 | 	}
164 | }
165 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/core/task-master-core.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * task-master-core.js
  3 |  * Central module that imports and re-exports all direct function implementations
  4 |  * for improved organization and maintainability.
  5 |  */
  6 | 
  7 | // Import direct function implementations
  8 | import { getCacheStatsDirect } from './direct-functions/cache-stats.js';
  9 | import { parsePRDDirect } from './direct-functions/parse-prd.js';
 10 | import { updateTasksDirect } from './direct-functions/update-tasks.js';
 11 | import { updateTaskByIdDirect } from './direct-functions/update-task-by-id.js';
 12 | import { updateSubtaskByIdDirect } from './direct-functions/update-subtask-by-id.js';
 13 | import { generateTaskFilesDirect } from './direct-functions/generate-task-files.js';
 14 | import { setTaskStatusDirect } from './direct-functions/set-task-status.js';
 15 | import { nextTaskDirect } from './direct-functions/next-task.js';
 16 | import { expandTaskDirect } from './direct-functions/expand-task.js';
 17 | import { addTaskDirect } from './direct-functions/add-task.js';
 18 | import { addSubtaskDirect } from './direct-functions/add-subtask.js';
 19 | import { removeSubtaskDirect } from './direct-functions/remove-subtask.js';
 20 | import { analyzeTaskComplexityDirect } from './direct-functions/analyze-task-complexity.js';
 21 | import { clearSubtasksDirect } from './direct-functions/clear-subtasks.js';
 22 | import { expandAllTasksDirect } from './direct-functions/expand-all-tasks.js';
 23 | import { removeDependencyDirect } from './direct-functions/remove-dependency.js';
 24 | import { validateDependenciesDirect } from './direct-functions/validate-dependencies.js';
 25 | import { fixDependenciesDirect } from './direct-functions/fix-dependencies.js';
 26 | import { complexityReportDirect } from './direct-functions/complexity-report.js';
 27 | import { addDependencyDirect } from './direct-functions/add-dependency.js';
 28 | import { removeTaskDirect } from './direct-functions/remove-task.js';
 29 | import { initializeProjectDirect } from './direct-functions/initialize-project.js';
 30 | import { modelsDirect } from './direct-functions/models.js';
 31 | import { moveTaskDirect } from './direct-functions/move-task.js';
 32 | import { moveTaskCrossTagDirect } from './direct-functions/move-task-cross-tag.js';
 33 | import { researchDirect } from './direct-functions/research.js';
 34 | import { addTagDirect } from './direct-functions/add-tag.js';
 35 | import { deleteTagDirect } from './direct-functions/delete-tag.js';
 36 | import { listTagsDirect } from './direct-functions/list-tags.js';
 37 | import { useTagDirect } from './direct-functions/use-tag.js';
 38 | import { renameTagDirect } from './direct-functions/rename-tag.js';
 39 | import { copyTagDirect } from './direct-functions/copy-tag.js';
 40 | import { scopeUpDirect } from './direct-functions/scope-up.js';
 41 | import { scopeDownDirect } from './direct-functions/scope-down.js';
 42 | 
 43 | // Re-export utility functions
 44 | export { findTasksPath } from './utils/path-utils.js';
 45 | 
 46 | // Use Map for potential future enhancements like introspection or dynamic dispatch
 47 | export const directFunctions = new Map([
 48 | 	['getCacheStatsDirect', getCacheStatsDirect],
 49 | 	['parsePRDDirect', parsePRDDirect],
 50 | 	['updateTasksDirect', updateTasksDirect],
 51 | 	['updateTaskByIdDirect', updateTaskByIdDirect],
 52 | 	['updateSubtaskByIdDirect', updateSubtaskByIdDirect],
 53 | 	['generateTaskFilesDirect', generateTaskFilesDirect],
 54 | 	['setTaskStatusDirect', setTaskStatusDirect],
 55 | 	['nextTaskDirect', nextTaskDirect],
 56 | 	['expandTaskDirect', expandTaskDirect],
 57 | 	['addTaskDirect', addTaskDirect],
 58 | 	['addSubtaskDirect', addSubtaskDirect],
 59 | 	['removeSubtaskDirect', removeSubtaskDirect],
 60 | 	['analyzeTaskComplexityDirect', analyzeTaskComplexityDirect],
 61 | 	['clearSubtasksDirect', clearSubtasksDirect],
 62 | 	['expandAllTasksDirect', expandAllTasksDirect],
 63 | 	['removeDependencyDirect', removeDependencyDirect],
 64 | 	['validateDependenciesDirect', validateDependenciesDirect],
 65 | 	['fixDependenciesDirect', fixDependenciesDirect],
 66 | 	['complexityReportDirect', complexityReportDirect],
 67 | 	['addDependencyDirect', addDependencyDirect],
 68 | 	['removeTaskDirect', removeTaskDirect],
 69 | 	['initializeProjectDirect', initializeProjectDirect],
 70 | 	['modelsDirect', modelsDirect],
 71 | 	['moveTaskDirect', moveTaskDirect],
 72 | 	['moveTaskCrossTagDirect', moveTaskCrossTagDirect],
 73 | 	['researchDirect', researchDirect],
 74 | 	['addTagDirect', addTagDirect],
 75 | 	['deleteTagDirect', deleteTagDirect],
 76 | 	['listTagsDirect', listTagsDirect],
 77 | 	['useTagDirect', useTagDirect],
 78 | 	['renameTagDirect', renameTagDirect],
 79 | 	['copyTagDirect', copyTagDirect],
 80 | 	['scopeUpDirect', scopeUpDirect],
 81 | 	['scopeDownDirect', scopeDownDirect]
 82 | ]);
 83 | 
 84 | // Re-export all direct function implementations
 85 | export {
 86 | 	getCacheStatsDirect,
 87 | 	parsePRDDirect,
 88 | 	updateTasksDirect,
 89 | 	updateTaskByIdDirect,
 90 | 	updateSubtaskByIdDirect,
 91 | 	generateTaskFilesDirect,
 92 | 	setTaskStatusDirect,
 93 | 	nextTaskDirect,
 94 | 	expandTaskDirect,
 95 | 	addTaskDirect,
 96 | 	addSubtaskDirect,
 97 | 	removeSubtaskDirect,
 98 | 	analyzeTaskComplexityDirect,
 99 | 	clearSubtasksDirect,
100 | 	expandAllTasksDirect,
101 | 	removeDependencyDirect,
102 | 	validateDependenciesDirect,
103 | 	fixDependenciesDirect,
104 | 	complexityReportDirect,
105 | 	addDependencyDirect,
106 | 	removeTaskDirect,
107 | 	initializeProjectDirect,
108 | 	modelsDirect,
109 | 	moveTaskDirect,
110 | 	moveTaskCrossTagDirect,
111 | 	researchDirect,
112 | 	addTagDirect,
113 | 	deleteTagDirect,
114 | 	listTagsDirect,
115 | 	useTagDirect,
116 | 	renameTagDirect,
117 | 	copyTagDirect,
118 | 	scopeUpDirect,
119 | 	scopeDownDirect
120 | };
121 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/git-domain.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Git Domain Facade
  3 |  * Public API for Git operations
  4 |  */
  5 | 
  6 | import type { StatusResult } from 'simple-git';
  7 | import { GitAdapter } from './adapters/git-adapter.js';
  8 | import { CommitMessageGenerator } from './services/commit-message-generator.js';
  9 | import type { CommitMessageOptions } from './services/commit-message-generator.js';
 10 | 
 11 | /**
 12 |  * Git Domain - Unified API for Git operations
 13 |  */
 14 | export class GitDomain {
 15 | 	private gitAdapter: GitAdapter;
 16 | 	private commitGenerator: CommitMessageGenerator;
 17 | 
 18 | 	constructor(projectPath: string) {
 19 | 		this.gitAdapter = new GitAdapter(projectPath);
 20 | 		this.commitGenerator = new CommitMessageGenerator();
 21 | 	}
 22 | 
 23 | 	// ========== Repository Validation ==========
 24 | 
 25 | 	/**
 26 | 	 * Check if directory is a git repository
 27 | 	 */
 28 | 	async isGitRepository(): Promise<boolean> {
 29 | 		return this.gitAdapter.isGitRepository();
 30 | 	}
 31 | 
 32 | 	/**
 33 | 	 * Ensure we're in a valid git repository
 34 | 	 */
 35 | 	async ensureGitRepository(): Promise<void> {
 36 | 		return this.gitAdapter.ensureGitRepository();
 37 | 	}
 38 | 
 39 | 	/**
 40 | 	 * Get repository root path
 41 | 	 */
 42 | 	async getRepositoryRoot(): Promise<string> {
 43 | 		return this.gitAdapter.getRepositoryRoot();
 44 | 	}
 45 | 
 46 | 	// ========== Working Tree Status ==========
 47 | 
 48 | 	/**
 49 | 	 * Check if working tree is clean
 50 | 	 */
 51 | 	async isWorkingTreeClean(): Promise<boolean> {
 52 | 		return this.gitAdapter.isWorkingTreeClean();
 53 | 	}
 54 | 
 55 | 	/**
 56 | 	 * Get git status
 57 | 	 */
 58 | 	async getStatus(): Promise<StatusResult> {
 59 | 		return this.gitAdapter.getStatus();
 60 | 	}
 61 | 
 62 | 	/**
 63 | 	 * Get status summary
 64 | 	 */
 65 | 	async getStatusSummary(): Promise<{
 66 | 		isClean: boolean;
 67 | 		staged: number;
 68 | 		modified: number;
 69 | 		deleted: number;
 70 | 		untracked: number;
 71 | 		totalChanges: number;
 72 | 	}> {
 73 | 		return this.gitAdapter.getStatusSummary();
 74 | 	}
 75 | 
 76 | 	/**
 77 | 	 * Check if there are uncommitted changes
 78 | 	 */
 79 | 	async hasUncommittedChanges(): Promise<boolean> {
 80 | 		return this.gitAdapter.hasUncommittedChanges();
 81 | 	}
 82 | 
 83 | 	/**
 84 | 	 * Check if there are staged changes
 85 | 	 */
 86 | 	async hasStagedChanges(): Promise<boolean> {
 87 | 		return this.gitAdapter.hasStagedChanges();
 88 | 	}
 89 | 
 90 | 	// ========== Branch Operations ==========
 91 | 
 92 | 	/**
 93 | 	 * Get current branch name
 94 | 	 */
 95 | 	async getCurrentBranch(): Promise<string> {
 96 | 		return this.gitAdapter.getCurrentBranch();
 97 | 	}
 98 | 
 99 | 	/**
100 | 	 * List all local branches
101 | 	 */
102 | 	async listBranches(): Promise<string[]> {
103 | 		return this.gitAdapter.listBranches();
104 | 	}
105 | 
106 | 	/**
107 | 	 * Check if a branch exists
108 | 	 */
109 | 	async branchExists(branchName: string): Promise<boolean> {
110 | 		return this.gitAdapter.branchExists(branchName);
111 | 	}
112 | 
113 | 	/**
114 | 	 * Create a new branch
115 | 	 */
116 | 	async createBranch(
117 | 		branchName: string,
118 | 		options?: { checkout?: boolean }
119 | 	): Promise<void> {
120 | 		return this.gitAdapter.createBranch(branchName, options);
121 | 	}
122 | 
123 | 	/**
124 | 	 * Checkout an existing branch
125 | 	 */
126 | 	async checkoutBranch(
127 | 		branchName: string,
128 | 		options?: { force?: boolean }
129 | 	): Promise<void> {
130 | 		return this.gitAdapter.checkoutBranch(branchName, options);
131 | 	}
132 | 
133 | 	/**
134 | 	 * Create and checkout a new branch
135 | 	 */
136 | 	async createAndCheckoutBranch(branchName: string): Promise<void> {
137 | 		return this.gitAdapter.createAndCheckoutBranch(branchName);
138 | 	}
139 | 
140 | 	/**
141 | 	 * Delete a branch
142 | 	 */
143 | 	async deleteBranch(
144 | 		branchName: string,
145 | 		options?: { force?: boolean }
146 | 	): Promise<void> {
147 | 		return this.gitAdapter.deleteBranch(branchName, options);
148 | 	}
149 | 
150 | 	/**
151 | 	 * Get default branch name
152 | 	 */
153 | 	async getDefaultBranch(): Promise<string> {
154 | 		return this.gitAdapter.getDefaultBranch();
155 | 	}
156 | 
157 | 	/**
158 | 	 * Check if on default branch
159 | 	 */
160 | 	async isOnDefaultBranch(): Promise<boolean> {
161 | 		return this.gitAdapter.isOnDefaultBranch();
162 | 	}
163 | 
164 | 	// ========== Commit Operations ==========
165 | 
166 | 	/**
167 | 	 * Stage files for commit
168 | 	 */
169 | 	async stageFiles(files: string[]): Promise<void> {
170 | 		return this.gitAdapter.stageFiles(files);
171 | 	}
172 | 
173 | 	/**
174 | 	 * Unstage files
175 | 	 */
176 | 	async unstageFiles(files: string[]): Promise<void> {
177 | 		return this.gitAdapter.unstageFiles(files);
178 | 	}
179 | 
180 | 	/**
181 | 	 * Create a commit
182 | 	 */
183 | 	async createCommit(
184 | 		message: string,
185 | 		options?: {
186 | 			metadata?: Record<string, string>;
187 | 			allowEmpty?: boolean;
188 | 			enforceNonDefaultBranch?: boolean;
189 | 			force?: boolean;
190 | 		}
191 | 	): Promise<void> {
192 | 		return this.gitAdapter.createCommit(message, options);
193 | 	}
194 | 
195 | 	/**
196 | 	 * Get commit log
197 | 	 */
198 | 	async getCommitLog(options?: { maxCount?: number }): Promise<any[]> {
199 | 		return this.gitAdapter.getCommitLog(options);
200 | 	}
201 | 
202 | 	/**
203 | 	 * Get last commit
204 | 	 */
205 | 	async getLastCommit(): Promise<any> {
206 | 		return this.gitAdapter.getLastCommit();
207 | 	}
208 | 
209 | 	// ========== Remote Operations ==========
210 | 
211 | 	/**
212 | 	 * Check if repository has remotes
213 | 	 */
214 | 	async hasRemote(): Promise<boolean> {
215 | 		return this.gitAdapter.hasRemote();
216 | 	}
217 | 
218 | 	/**
219 | 	 * Get all configured remotes
220 | 	 */
221 | 	async getRemotes(): Promise<any[]> {
222 | 		return this.gitAdapter.getRemotes();
223 | 	}
224 | 
225 | 	// ========== Commit Message Generation ==========
226 | 
227 | 	/**
228 | 	 * Generate a conventional commit message
229 | 	 */
230 | 	generateCommitMessage(options: CommitMessageOptions): string {
231 | 		return this.commitGenerator.generateMessage(options);
232 | 	}
233 | 
234 | 	/**
235 | 	 * Validate a conventional commit message
236 | 	 */
237 | 	validateCommitMessage(message: string) {
238 | 		return this.commitGenerator.validateConventionalCommit(message);
239 | 	}
240 | 
241 | 	/**
242 | 	 * Parse a commit message
243 | 	 */
244 | 	parseCommitMessage(message: string) {
245 | 		return this.commitGenerator.parseCommitMessage(message);
246 | 	}
247 | }
248 | 
```

--------------------------------------------------------------------------------
/tests/unit/profiles/kilo-integration.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | import fs from 'fs';
  3 | import path from 'path';
  4 | import os from 'os';
  5 | 
  6 | // Mock external modules
  7 | jest.mock('child_process', () => ({
  8 | 	execSync: jest.fn()
  9 | }));
 10 | 
 11 | // Mock console methods
 12 | jest.mock('console', () => ({
 13 | 	log: jest.fn(),
 14 | 	info: jest.fn(),
 15 | 	warn: jest.fn(),
 16 | 	error: jest.fn(),
 17 | 	clear: jest.fn()
 18 | }));
 19 | 
 20 | describe('Kilo Integration', () => {
 21 | 	let tempDir;
 22 | 
 23 | 	beforeEach(() => {
 24 | 		jest.clearAllMocks();
 25 | 
 26 | 		// Create a temporary directory for testing
 27 | 		tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
 28 | 
 29 | 		// Spy on fs methods
 30 | 		jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
 31 | 		jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
 32 | 			if (filePath.toString().includes('.kilocodemodes')) {
 33 | 				return 'Existing kilocodemodes content';
 34 | 			}
 35 | 			if (filePath.toString().includes('-rules')) {
 36 | 				return 'Existing mode rules content';
 37 | 			}
 38 | 			return '{}';
 39 | 		});
 40 | 		jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
 41 | 		jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
 42 | 	});
 43 | 
 44 | 	afterEach(() => {
 45 | 		// Clean up the temporary directory
 46 | 		try {
 47 | 			fs.rmSync(tempDir, { recursive: true, force: true });
 48 | 		} catch (err) {
 49 | 			console.error(`Error cleaning up: ${err.message}`);
 50 | 		}
 51 | 	});
 52 | 
 53 | 	// Test function that simulates the createProjectStructure behavior for Kilo files
 54 | 	function mockCreateKiloStructure() {
 55 | 		// Create main .kilo directory
 56 | 		fs.mkdirSync(path.join(tempDir, '.kilo'), { recursive: true });
 57 | 
 58 | 		// Create rules directory
 59 | 		fs.mkdirSync(path.join(tempDir, '.kilo', 'rules'), { recursive: true });
 60 | 
 61 | 		// Create mode-specific rule directories
 62 | 		const kiloModes = [
 63 | 			'architect',
 64 | 			'ask',
 65 | 			'orchestrator',
 66 | 			'code',
 67 | 			'debug',
 68 | 			'test'
 69 | 		];
 70 | 		for (const mode of kiloModes) {
 71 | 			fs.mkdirSync(path.join(tempDir, '.kilo', `rules-${mode}`), {
 72 | 				recursive: true
 73 | 			});
 74 | 			fs.writeFileSync(
 75 | 				path.join(tempDir, '.kilo', `rules-${mode}`, `${mode}-rules`),
 76 | 				`Content for ${mode} rules`
 77 | 			);
 78 | 		}
 79 | 
 80 | 		// Create additional directories
 81 | 		fs.mkdirSync(path.join(tempDir, '.kilo', 'config'), { recursive: true });
 82 | 		fs.mkdirSync(path.join(tempDir, '.kilo', 'templates'), { recursive: true });
 83 | 		fs.mkdirSync(path.join(tempDir, '.kilo', 'logs'), { recursive: true });
 84 | 
 85 | 		// Copy .kilocodemodes file
 86 | 		fs.writeFileSync(
 87 | 			path.join(tempDir, '.kilocodemodes'),
 88 | 			'Kilocodemodes file content'
 89 | 		);
 90 | 	}
 91 | 
 92 | 	test('creates all required .kilo directories', () => {
 93 | 		// Act
 94 | 		mockCreateKiloStructure();
 95 | 
 96 | 		// Assert
 97 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.kilo'), {
 98 | 			recursive: true
 99 | 		});
100 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
101 | 			path.join(tempDir, '.kilo', 'rules'),
102 | 			{ recursive: true }
103 | 		);
104 | 
105 | 		// Verify all mode directories are created
106 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
107 | 			path.join(tempDir, '.kilo', 'rules-architect'),
108 | 			{ recursive: true }
109 | 		);
110 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
111 | 			path.join(tempDir, '.kilo', 'rules-ask'),
112 | 			{ recursive: true }
113 | 		);
114 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
115 | 			path.join(tempDir, '.kilo', 'rules-orchestrator'),
116 | 			{ recursive: true }
117 | 		);
118 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
119 | 			path.join(tempDir, '.kilo', 'rules-code'),
120 | 			{ recursive: true }
121 | 		);
122 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
123 | 			path.join(tempDir, '.kilo', 'rules-debug'),
124 | 			{ recursive: true }
125 | 		);
126 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
127 | 			path.join(tempDir, '.kilo', 'rules-test'),
128 | 			{ recursive: true }
129 | 		);
130 | 	});
131 | 
132 | 	test('creates rule files for all modes', () => {
133 | 		// Act
134 | 		mockCreateKiloStructure();
135 | 
136 | 		// Assert - check all rule files are created
137 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
138 | 			path.join(tempDir, '.kilo', 'rules-architect', 'architect-rules'),
139 | 			expect.any(String)
140 | 		);
141 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
142 | 			path.join(tempDir, '.kilo', 'rules-ask', 'ask-rules'),
143 | 			expect.any(String)
144 | 		);
145 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
146 | 			path.join(tempDir, '.kilo', 'rules-orchestrator', 'orchestrator-rules'),
147 | 			expect.any(String)
148 | 		);
149 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
150 | 			path.join(tempDir, '.kilo', 'rules-code', 'code-rules'),
151 | 			expect.any(String)
152 | 		);
153 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
154 | 			path.join(tempDir, '.kilo', 'rules-debug', 'debug-rules'),
155 | 			expect.any(String)
156 | 		);
157 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
158 | 			path.join(tempDir, '.kilo', 'rules-test', 'test-rules'),
159 | 			expect.any(String)
160 | 		);
161 | 	});
162 | 
163 | 	test('creates .kilocodemodes file in project root', () => {
164 | 		// Act
165 | 		mockCreateKiloStructure();
166 | 
167 | 		// Assert
168 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
169 | 			path.join(tempDir, '.kilocodemodes'),
170 | 			expect.any(String)
171 | 		);
172 | 	});
173 | 
174 | 	test('creates additional required Kilo directories', () => {
175 | 		// Act
176 | 		mockCreateKiloStructure();
177 | 
178 | 		// Assert
179 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
180 | 			path.join(tempDir, '.kilo', 'config'),
181 | 			{ recursive: true }
182 | 		);
183 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
184 | 			path.join(tempDir, '.kilo', 'templates'),
185 | 			{ recursive: true }
186 | 		);
187 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
188 | 			path.join(tempDir, '.kilo', 'logs'),
189 | 			{ recursive: true }
190 | 		);
191 | 	});
192 | });
193 | 
```

--------------------------------------------------------------------------------
/apps/cli/tests/unit/commands/list.command.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Unit tests for ListTasksCommand
  3 |  */
  4 | 
  5 | import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
  6 | import type { TmCore } from '@tm/core';
  7 | 
  8 | // Mock dependencies
  9 | vi.mock('@tm/core', () => ({
 10 | 	createTmCore: vi.fn(),
 11 | 	OUTPUT_FORMATS: ['text', 'json', 'compact'],
 12 | 	TASK_STATUSES: [
 13 | 		'pending',
 14 | 		'in-progress',
 15 | 		'done',
 16 | 		'review',
 17 | 		'deferred',
 18 | 		'cancelled'
 19 | 	],
 20 | 	STATUS_ICONS: {
 21 | 		pending: '⏳',
 22 | 		'in-progress': '🔄',
 23 | 		done: '✅',
 24 | 		review: '👀',
 25 | 		deferred: '⏸️',
 26 | 		cancelled: '❌'
 27 | 	}
 28 | }));
 29 | 
 30 | vi.mock('../../../src/utils/project-root.js', () => ({
 31 | 	getProjectRoot: vi.fn((path?: string) => path || '/test/project')
 32 | }));
 33 | 
 34 | vi.mock('../../../src/utils/error-handler.js', () => ({
 35 | 	displayError: vi.fn()
 36 | }));
 37 | 
 38 | vi.mock('../../../src/utils/display-helpers.js', () => ({
 39 | 	displayCommandHeader: vi.fn()
 40 | }));
 41 | 
 42 | vi.mock('../../../src/ui/index.js', () => ({
 43 | 	calculateDependencyStatistics: vi.fn(() => ({ total: 0, blocked: 0 })),
 44 | 	calculateSubtaskStatistics: vi.fn(() => ({ total: 0, completed: 0 })),
 45 | 	calculateTaskStatistics: vi.fn(() => ({ total: 0, completed: 0 })),
 46 | 	displayDashboards: vi.fn(),
 47 | 	displayRecommendedNextTask: vi.fn(),
 48 | 	displaySuggestedNextSteps: vi.fn(),
 49 | 	getPriorityBreakdown: vi.fn(() => ({})),
 50 | 	getTaskDescription: vi.fn(() => 'Test description')
 51 | }));
 52 | 
 53 | vi.mock('../../../src/utils/ui.js', () => ({
 54 | 	createTaskTable: vi.fn(() => 'Table output'),
 55 | 	displayWarning: vi.fn()
 56 | }));
 57 | 
 58 | import { ListTasksCommand } from '../../../src/commands/list.command.js';
 59 | 
 60 | describe('ListTasksCommand', () => {
 61 | 	let consoleLogSpy: any;
 62 | 	let mockTmCore: Partial<TmCore>;
 63 | 
 64 | 	beforeEach(() => {
 65 | 		consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
 66 | 
 67 | 		mockTmCore = {
 68 | 			tasks: {
 69 | 				list: vi.fn().mockResolvedValue({
 70 | 					tasks: [{ id: '1', title: 'Test Task', status: 'pending' }],
 71 | 					total: 1,
 72 | 					filtered: 1,
 73 | 					storageType: 'json'
 74 | 				}),
 75 | 				getStorageType: vi.fn().mockReturnValue('json')
 76 | 			} as any,
 77 | 			config: {
 78 | 				getActiveTag: vi.fn().mockReturnValue('master')
 79 | 			} as any
 80 | 		};
 81 | 	});
 82 | 
 83 | 	afterEach(() => {
 84 | 		vi.clearAllMocks();
 85 | 		consoleLogSpy.mockRestore();
 86 | 	});
 87 | 
 88 | 	describe('JSON output format', () => {
 89 | 		it('should use JSON format when --json flag is set', async () => {
 90 | 			const command = new ListTasksCommand();
 91 | 
 92 | 			// Mock the tmCore initialization
 93 | 			(command as any).tmCore = mockTmCore;
 94 | 
 95 | 			// Execute with --json flag
 96 | 			await (command as any).executeCommand({
 97 | 				json: true,
 98 | 				format: 'text' // Should be overridden by --json
 99 | 			});
100 | 
101 | 			// Verify JSON output was called
102 | 			expect(consoleLogSpy).toHaveBeenCalled();
103 | 			const output = consoleLogSpy.mock.calls[0][0];
104 | 
105 | 			// Should be valid JSON
106 | 			expect(() => JSON.parse(output)).not.toThrow();
107 | 
108 | 			const parsed = JSON.parse(output);
109 | 			expect(parsed).toHaveProperty('tasks');
110 | 			expect(parsed).toHaveProperty('metadata');
111 | 		});
112 | 
113 | 		it('should override --format when --json is set', async () => {
114 | 			const command = new ListTasksCommand();
115 | 			(command as any).tmCore = mockTmCore;
116 | 
117 | 			await (command as any).executeCommand({
118 | 				json: true,
119 | 				format: 'compact' // Should be overridden
120 | 			});
121 | 
122 | 			// Should output JSON, not compact format
123 | 			const output = consoleLogSpy.mock.calls[0][0];
124 | 			expect(() => JSON.parse(output)).not.toThrow();
125 | 		});
126 | 
127 | 		it('should use specified format when --json is not set', async () => {
128 | 			const command = new ListTasksCommand();
129 | 			(command as any).tmCore = mockTmCore;
130 | 
131 | 			await (command as any).executeCommand({
132 | 				format: 'compact'
133 | 			});
134 | 
135 | 			// Should use compact format (not JSON)
136 | 			const output = consoleLogSpy.mock.calls;
137 | 			// In compact mode, output is not JSON
138 | 			expect(output.length).toBeGreaterThan(0);
139 | 		});
140 | 
141 | 		it('should default to text format when neither flag is set', async () => {
142 | 			const command = new ListTasksCommand();
143 | 			(command as any).tmCore = mockTmCore;
144 | 
145 | 			await (command as any).executeCommand({});
146 | 
147 | 			// Should use text format (not JSON)
148 | 			// If any console.log was called, verify it's not JSON
149 | 			if (consoleLogSpy.mock.calls.length > 0) {
150 | 				const output = consoleLogSpy.mock.calls[0][0];
151 | 				// Text format output should not be parseable JSON
152 | 				// or should be the table string we mocked
153 | 				expect(
154 | 					output === 'Table output' ||
155 | 						(() => {
156 | 							try {
157 | 								JSON.parse(output);
158 | 								return false;
159 | 							} catch {
160 | 								return true;
161 | 							}
162 | 						})()
163 | 				).toBe(true);
164 | 			}
165 | 		});
166 | 	});
167 | 
168 | 	describe('format validation', () => {
169 | 		it('should accept valid formats', () => {
170 | 			const command = new ListTasksCommand();
171 | 
172 | 			expect((command as any).validateOptions({ format: 'text' })).toBe(true);
173 | 			expect((command as any).validateOptions({ format: 'json' })).toBe(true);
174 | 			expect((command as any).validateOptions({ format: 'compact' })).toBe(
175 | 				true
176 | 			);
177 | 		});
178 | 
179 | 		it('should reject invalid formats', () => {
180 | 			const consoleErrorSpy = vi
181 | 				.spyOn(console, 'error')
182 | 				.mockImplementation(() => {});
183 | 			const command = new ListTasksCommand();
184 | 
185 | 			expect((command as any).validateOptions({ format: 'invalid' })).toBe(
186 | 				false
187 | 			);
188 | 			expect(consoleErrorSpy).toHaveBeenCalledWith(
189 | 				expect.stringContaining('Invalid format: invalid')
190 | 			);
191 | 
192 | 			consoleErrorSpy.mockRestore();
193 | 		});
194 | 	});
195 | });
196 | 
```

--------------------------------------------------------------------------------
/apps/extension/package.mjs:
--------------------------------------------------------------------------------

```
  1 | import { execSync } from 'child_process';
  2 | import path from 'path';
  3 | import { fileURLToPath } from 'url';
  4 | import fs from 'fs-extra';
  5 | 
  6 | // --- Configuration ---
  7 | const __filename = fileURLToPath(import.meta.url);
  8 | const __dirname = path.dirname(__filename);
  9 | 
 10 | const packageDir = path.resolve(__dirname, 'vsix-build');
 11 | // --- End Configuration ---
 12 | 
 13 | try {
 14 | 	console.log('🚀 Starting packaging process...');
 15 | 
 16 | 	// 1. Build Project
 17 | 	console.log('\nBuilding JavaScript...');
 18 | 	execSync('npm run build:js', { stdio: 'inherit' });
 19 | 	console.log('\nBuilding CSS...');
 20 | 	execSync('npm run build:css', { stdio: 'inherit' });
 21 | 
 22 | 	// 2. Prepare Clean Directory
 23 | 	console.log(`\nPreparing clean directory at: ${packageDir}`);
 24 | 	fs.emptyDirSync(packageDir);
 25 | 
 26 | 	// 3. Copy Build Artifacts (excluding source maps)
 27 | 	console.log('Copying build artifacts...');
 28 | 	const distDir = path.resolve(__dirname, 'dist');
 29 | 	const targetDistDir = path.resolve(packageDir, 'dist');
 30 | 	fs.ensureDirSync(targetDistDir);
 31 | 
 32 | 	// Only copy the files we need (exclude .map files)
 33 | 	const filesToCopy = ['extension.js', 'index.js', 'index.css', 'sidebar.js'];
 34 | 	for (const file of filesToCopy) {
 35 | 		const srcFile = path.resolve(distDir, file);
 36 | 		const destFile = path.resolve(targetDistDir, file);
 37 | 		if (fs.existsSync(srcFile)) {
 38 | 			fs.copySync(srcFile, destFile);
 39 | 			console.log(`  - Copied dist/${file}`);
 40 | 		}
 41 | 	}
 42 | 
 43 | 	// 4. Copy additional files
 44 | 	const additionalFiles = ['README.md', 'CHANGELOG.md', 'AGENTS.md'];
 45 | 	for (const file of additionalFiles) {
 46 | 		if (fs.existsSync(path.resolve(__dirname, file))) {
 47 | 			fs.copySync(
 48 | 				path.resolve(__dirname, file),
 49 | 				path.resolve(packageDir, file)
 50 | 			);
 51 | 			console.log(`  - Copied ${file}`);
 52 | 		}
 53 | 	}
 54 | 
 55 | 	// 5. Sync versions and prepare the final package.json
 56 | 	console.log('Syncing versions and preparing the final package.json...');
 57 | 
 58 | 	// Read current versions
 59 | 	const devPackagePath = path.resolve(__dirname, 'package.json');
 60 | 	const publishPackagePath = path.resolve(__dirname, 'package.publish.json');
 61 | 
 62 | 	const devPackage = JSON.parse(fs.readFileSync(devPackagePath, 'utf8'));
 63 | 	const publishPackage = JSON.parse(
 64 | 		fs.readFileSync(publishPackagePath, 'utf8')
 65 | 	);
 66 | 
 67 | 	// Handle RC versions for VS Code Marketplace
 68 | 	let finalVersion = devPackage.version;
 69 | 	if (finalVersion.includes('-rc.')) {
 70 | 		console.log(
 71 | 			'  - Detected RC version, transforming for VS Code Marketplace...'
 72 | 		);
 73 | 
 74 | 		// Extract base version and RC number
 75 | 		const baseVersion = finalVersion.replace(/-rc\.\d+$/, '');
 76 | 		const rcMatch = finalVersion.match(/rc\.(\d+)/);
 77 | 		const rcNumber = rcMatch ? parseInt(rcMatch[1]) : 0;
 78 | 
 79 | 		// For each RC iteration, increment the patch version
 80 | 		// This ensures unique versions in VS Code Marketplace
 81 | 		if (rcNumber > 0) {
 82 | 			const [major, minor, patch] = baseVersion.split('.').map(Number);
 83 | 			finalVersion = `${major}.${minor}.${patch + rcNumber}`;
 84 | 			console.log(
 85 | 				`  - RC version mapping: ${devPackage.version} → ${finalVersion}`
 86 | 			);
 87 | 		} else {
 88 | 			finalVersion = baseVersion;
 89 | 			console.log(
 90 | 				`  - RC version mapping: ${devPackage.version} → ${finalVersion}`
 91 | 			);
 92 | 		}
 93 | 	}
 94 | 
 95 | 	// Check if versions need updating
 96 | 	if (publishPackage.version !== finalVersion) {
 97 | 		console.log(
 98 | 			`  - Version sync needed: ${publishPackage.version} → ${finalVersion}`
 99 | 		);
100 | 		publishPackage.version = finalVersion;
101 | 
102 | 		// Update the source package.publish.json file with the final version
103 | 		fs.writeFileSync(
104 | 			publishPackagePath,
105 | 			JSON.stringify(publishPackage, null, '\t') + '\n'
106 | 		);
107 | 		console.log(`  - Updated package.publish.json version to ${finalVersion}`);
108 | 	} else {
109 | 		console.log(`  - Versions already in sync: ${finalVersion}`);
110 | 	}
111 | 
112 | 	// Copy the (now synced) package.publish.json as package.json
113 | 	fs.copySync(publishPackagePath, path.resolve(packageDir, 'package.json'));
114 | 	console.log('  - Copied package.publish.json as package.json');
115 | 
116 | 	// 6. Copy .vscodeignore if it exists
117 | 	if (fs.existsSync(path.resolve(__dirname, '.vscodeignore'))) {
118 | 		fs.copySync(
119 | 			path.resolve(__dirname, '.vscodeignore'),
120 | 			path.resolve(packageDir, '.vscodeignore')
121 | 		);
122 | 		console.log('  - Copied .vscodeignore');
123 | 	}
124 | 
125 | 	// 7. Copy LICENSE if it exists
126 | 	if (fs.existsSync(path.resolve(__dirname, 'LICENSE'))) {
127 | 		fs.copySync(
128 | 			path.resolve(__dirname, 'LICENSE'),
129 | 			path.resolve(packageDir, 'LICENSE')
130 | 		);
131 | 		console.log('  - Copied LICENSE');
132 | 	}
133 | 
134 | 	// 7a. Copy assets directory if it exists
135 | 	const assetsDir = path.resolve(__dirname, 'assets');
136 | 	if (fs.existsSync(assetsDir)) {
137 | 		const targetAssetsDir = path.resolve(packageDir, 'assets');
138 | 		fs.copySync(assetsDir, targetAssetsDir);
139 | 		console.log('  - Copied assets directory');
140 | 	}
141 | 
142 | 	// Small delay to ensure file system operations complete
143 | 	await new Promise((resolve) => setTimeout(resolve, 100));
144 | 
145 | 	// 8. Final step - manual packaging
146 | 	console.log('\n✅ Build preparation complete!');
147 | 	console.log('\nTo create the VSIX package, run:');
148 | 	console.log(
149 | 		'\x1b[36m%s\x1b[0m',
150 | 		`cd vsix-build && npx vsce package --no-dependencies`
151 | 	);
152 | 
153 | 	// Use the transformed version for output
154 | 	console.log(
155 | 		`\nYour extension will be packaged to: vsix-build/task-master-${finalVersion}.vsix`
156 | 	);
157 | } catch (error) {
158 | 	console.error('\n❌ Packaging failed!');
159 | 	console.error(error.message);
160 | 	process.exit(1);
161 | }
162 | 
```
Page 16/69FirstPrevNextLast