#
tokens: 49366/50000 21/975 files (page 14/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 14 of 69. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── commands
│   │   └── dedupe.md
│   └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│   └── marketplace.json
├── .coderabbit.yaml
├── .cursor
│   ├── mcp.json
│   └── rules
│       ├── ai_providers.mdc
│       ├── ai_services.mdc
│       ├── architecture.mdc
│       ├── changeset.mdc
│       ├── commands.mdc
│       ├── context_gathering.mdc
│       ├── cursor_rules.mdc
│       ├── dependencies.mdc
│       ├── dev_workflow.mdc
│       ├── git_workflow.mdc
│       ├── glossary.mdc
│       ├── mcp.mdc
│       ├── new_features.mdc
│       ├── self_improve.mdc
│       ├── tags.mdc
│       ├── taskmaster.mdc
│       ├── tasks.mdc
│       ├── telemetry.mdc
│       ├── test_workflow.mdc
│       ├── tests.mdc
│       ├── ui.mdc
│       └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   ├── enhancements---feature-requests.md
│   │   └── feedback.md
│   ├── PULL_REQUEST_TEMPLATE
│   │   ├── bugfix.md
│   │   ├── config.yml
│   │   ├── feature.md
│   │   └── integration.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── scripts
│   │   ├── auto-close-duplicates.mjs
│   │   ├── backfill-duplicate-comments.mjs
│   │   ├── check-pre-release-mode.mjs
│   │   ├── parse-metrics.mjs
│   │   ├── release.mjs
│   │   ├── tag-extension.mjs
│   │   ├── utils.mjs
│   │   └── validate-changesets.mjs
│   └── workflows
│       ├── auto-close-duplicates.yml
│       ├── backfill-duplicate-comments.yml
│       ├── ci.yml
│       ├── claude-dedupe-issues.yml
│       ├── claude-docs-trigger.yml
│       ├── claude-docs-updater.yml
│       ├── claude-issue-triage.yml
│       ├── claude.yml
│       ├── extension-ci.yml
│       ├── extension-release.yml
│       ├── log-issue-events.yml
│       ├── pre-release.yml
│       ├── release-check.yml
│       ├── release.yml
│       ├── update-models-md.yml
│       └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│   ├── hooks
│   │   ├── tm-code-change-task-tracker.kiro.hook
│   │   ├── tm-complexity-analyzer.kiro.hook
│   │   ├── tm-daily-standup-assistant.kiro.hook
│   │   ├── tm-git-commit-task-linker.kiro.hook
│   │   ├── tm-pr-readiness-checker.kiro.hook
│   │   ├── tm-task-dependency-auto-progression.kiro.hook
│   │   └── tm-test-success-task-completer.kiro.hook
│   ├── settings
│   │   └── mcp.json
│   └── steering
│       ├── dev_workflow.md
│       ├── kiro_rules.md
│       ├── self_improve.md
│       ├── taskmaster_hooks_workflow.md
│       └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│   ├── CLAUDE.md
│   ├── config.json
│   ├── docs
│   │   ├── autonomous-tdd-git-workflow.md
│   │   ├── MIGRATION-ROADMAP.md
│   │   ├── prd-tm-start.txt
│   │   ├── prd.txt
│   │   ├── README.md
│   │   ├── research
│   │   │   ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│   │   │   ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│   │   │   ├── 2025-06-14_test-save-functionality.md
│   │   │   ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│   │   │   └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│   │   ├── task-template-importing-prd.txt
│   │   ├── tdd-workflow-phase-0-spike.md
│   │   ├── tdd-workflow-phase-1-core-rails.md
│   │   ├── tdd-workflow-phase-1-orchestrator.md
│   │   ├── tdd-workflow-phase-2-pr-resumability.md
│   │   ├── tdd-workflow-phase-3-extensibility-guardrails.md
│   │   ├── test-prd.txt
│   │   └── tm-core-phase-1.txt
│   ├── reports
│   │   ├── task-complexity-report_autonomous-tdd-git-workflow.json
│   │   ├── task-complexity-report_cc-kiro-hooks.json
│   │   ├── task-complexity-report_tdd-phase-1-core-rails.json
│   │   ├── task-complexity-report_tdd-workflow-phase-0.json
│   │   ├── task-complexity-report_test-prd-tag.json
│   │   ├── task-complexity-report_tm-core-phase-1.json
│   │   ├── task-complexity-report.json
│   │   └── tm-core-complexity.json
│   ├── state.json
│   ├── tasks
│   │   ├── task_001_tm-start.txt
│   │   ├── task_002_tm-start.txt
│   │   ├── task_003_tm-start.txt
│   │   ├── task_004_tm-start.txt
│   │   ├── task_007_tm-start.txt
│   │   └── tasks.json
│   └── templates
│       ├── example_prd_rpg.md
│       └── example_prd.md
├── .vscode
│   ├── extensions.json
│   └── settings.json
├── apps
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── command-registry.ts
│   │   │   ├── commands
│   │   │   │   ├── auth.command.ts
│   │   │   │   ├── autopilot
│   │   │   │   │   ├── abort.command.ts
│   │   │   │   │   ├── commit.command.ts
│   │   │   │   │   ├── complete.command.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── next.command.ts
│   │   │   │   │   ├── resume.command.ts
│   │   │   │   │   ├── shared.ts
│   │   │   │   │   ├── start.command.ts
│   │   │   │   │   └── status.command.ts
│   │   │   │   ├── briefs.command.ts
│   │   │   │   ├── context.command.ts
│   │   │   │   ├── export.command.ts
│   │   │   │   ├── list.command.ts
│   │   │   │   ├── models
│   │   │   │   │   ├── custom-providers.ts
│   │   │   │   │   ├── fetchers.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── prompts.ts
│   │   │   │   │   ├── setup.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── next.command.ts
│   │   │   │   ├── set-status.command.ts
│   │   │   │   ├── show.command.ts
│   │   │   │   ├── start.command.ts
│   │   │   │   └── tags.command.ts
│   │   │   ├── index.ts
│   │   │   ├── lib
│   │   │   │   └── model-management.ts
│   │   │   ├── types
│   │   │   │   └── tag-management.d.ts
│   │   │   ├── ui
│   │   │   │   ├── components
│   │   │   │   │   ├── cardBox.component.ts
│   │   │   │   │   ├── dashboard.component.ts
│   │   │   │   │   ├── header.component.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── next-task.component.ts
│   │   │   │   │   ├── suggested-steps.component.ts
│   │   │   │   │   └── task-detail.component.ts
│   │   │   │   ├── display
│   │   │   │   │   ├── messages.ts
│   │   │   │   │   └── tables.ts
│   │   │   │   ├── formatters
│   │   │   │   │   ├── complexity-formatters.ts
│   │   │   │   │   ├── dependency-formatters.ts
│   │   │   │   │   ├── priority-formatters.ts
│   │   │   │   │   ├── status-formatters.spec.ts
│   │   │   │   │   └── status-formatters.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── layout
│   │   │   │       ├── helpers.spec.ts
│   │   │   │       └── helpers.ts
│   │   │   └── utils
│   │   │       ├── auth-helpers.ts
│   │   │       ├── auto-update.ts
│   │   │       ├── brief-selection.ts
│   │   │       ├── display-helpers.ts
│   │   │       ├── error-handler.ts
│   │   │       ├── index.ts
│   │   │       ├── project-root.ts
│   │   │       ├── task-status.ts
│   │   │       ├── ui.spec.ts
│   │   │       └── ui.ts
│   │   ├── tests
│   │   │   ├── integration
│   │   │   │   └── commands
│   │   │   │       └── autopilot
│   │   │   │           └── workflow.test.ts
│   │   │   └── unit
│   │   │       ├── commands
│   │   │       │   ├── autopilot
│   │   │       │   │   └── shared.test.ts
│   │   │       │   ├── list.command.spec.ts
│   │   │       │   └── show.command.spec.ts
│   │   │       └── ui
│   │   │           └── dashboard.component.spec.ts
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   ├── docs
│   │   ├── archive
│   │   │   ├── ai-client-utils-example.mdx
│   │   │   ├── ai-development-workflow.mdx
│   │   │   ├── command-reference.mdx
│   │   │   ├── configuration.mdx
│   │   │   ├── cursor-setup.mdx
│   │   │   ├── examples.mdx
│   │   │   └── Installation.mdx
│   │   ├── best-practices
│   │   │   ├── advanced-tasks.mdx
│   │   │   ├── configuration-advanced.mdx
│   │   │   └── index.mdx
│   │   ├── capabilities
│   │   │   ├── cli-root-commands.mdx
│   │   │   ├── index.mdx
│   │   │   ├── mcp.mdx
│   │   │   ├── rpg-method.mdx
│   │   │   └── task-structure.mdx
│   │   ├── CHANGELOG.md
│   │   ├── command-reference.mdx
│   │   ├── configuration.mdx
│   │   ├── docs.json
│   │   ├── favicon.svg
│   │   ├── getting-started
│   │   │   ├── api-keys.mdx
│   │   │   ├── contribute.mdx
│   │   │   ├── faq.mdx
│   │   │   └── quick-start
│   │   │       ├── configuration-quick.mdx
│   │   │       ├── execute-quick.mdx
│   │   │       ├── installation.mdx
│   │   │       ├── moving-forward.mdx
│   │   │       ├── prd-quick.mdx
│   │   │       ├── quick-start.mdx
│   │   │       ├── requirements.mdx
│   │   │       ├── rules-quick.mdx
│   │   │       └── tasks-quick.mdx
│   │   ├── introduction.mdx
│   │   ├── licensing.md
│   │   ├── logo
│   │   │   ├── dark.svg
│   │   │   ├── light.svg
│   │   │   └── task-master-logo.png
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── style.css
│   │   ├── tdd-workflow
│   │   │   ├── ai-agent-integration.mdx
│   │   │   └── quickstart.mdx
│   │   ├── vercel.json
│   │   └── whats-new.mdx
│   ├── extension
│   │   ├── .vscodeignore
│   │   ├── assets
│   │   │   ├── banner.png
│   │   │   ├── icon-dark.svg
│   │   │   ├── icon-light.svg
│   │   │   ├── icon.png
│   │   │   ├── screenshots
│   │   │   │   ├── kanban-board.png
│   │   │   │   └── task-details.png
│   │   │   └── sidebar-icon.svg
│   │   ├── CHANGELOG.md
│   │   ├── components.json
│   │   ├── docs
│   │   │   ├── extension-CI-setup.md
│   │   │   └── extension-development-guide.md
│   │   ├── esbuild.js
│   │   ├── LICENSE
│   │   ├── package.json
│   │   ├── package.mjs
│   │   ├── package.publish.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── components
│   │   │   │   ├── ConfigView.tsx
│   │   │   │   ├── constants.ts
│   │   │   │   ├── TaskDetails
│   │   │   │   │   ├── AIActionsSection.tsx
│   │   │   │   │   ├── DetailsSection.tsx
│   │   │   │   │   ├── PriorityBadge.tsx
│   │   │   │   │   ├── SubtasksSection.tsx
│   │   │   │   │   ├── TaskMetadataSidebar.tsx
│   │   │   │   │   └── useTaskDetails.ts
│   │   │   │   ├── TaskDetailsView.tsx
│   │   │   │   ├── TaskMasterLogo.tsx
│   │   │   │   └── ui
│   │   │   │       ├── badge.tsx
│   │   │   │       ├── breadcrumb.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── collapsible.tsx
│   │   │   │       ├── CollapsibleSection.tsx
│   │   │   │       ├── dropdown-menu.tsx
│   │   │   │       ├── label.tsx
│   │   │   │       ├── scroll-area.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       ├── shadcn-io
│   │   │   │       │   └── kanban
│   │   │   │       │       └── index.tsx
│   │   │   │       └── textarea.tsx
│   │   │   ├── extension.ts
│   │   │   ├── index.ts
│   │   │   ├── lib
│   │   │   │   └── utils.ts
│   │   │   ├── services
│   │   │   │   ├── config-service.ts
│   │   │   │   ├── error-handler.ts
│   │   │   │   ├── notification-preferences.ts
│   │   │   │   ├── polling-service.ts
│   │   │   │   ├── polling-strategies.ts
│   │   │   │   ├── sidebar-webview-manager.ts
│   │   │   │   ├── task-repository.ts
│   │   │   │   ├── terminal-manager.ts
│   │   │   │   └── webview-manager.ts
│   │   │   ├── test
│   │   │   │   └── extension.test.ts
│   │   │   ├── utils
│   │   │   │   ├── configManager.ts
│   │   │   │   ├── connectionManager.ts
│   │   │   │   ├── errorHandler.ts
│   │   │   │   ├── event-emitter.ts
│   │   │   │   ├── logger.ts
│   │   │   │   ├── mcpClient.ts
│   │   │   │   ├── notificationPreferences.ts
│   │   │   │   └── task-master-api
│   │   │   │       ├── cache
│   │   │   │       │   └── cache-manager.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── mcp-client.ts
│   │   │   │       ├── transformers
│   │   │   │       │   └── task-transformer.ts
│   │   │   │       └── types
│   │   │   │           └── index.ts
│   │   │   └── webview
│   │   │       ├── App.tsx
│   │   │       ├── components
│   │   │       │   ├── AppContent.tsx
│   │   │       │   ├── EmptyState.tsx
│   │   │       │   ├── ErrorBoundary.tsx
│   │   │       │   ├── PollingStatus.tsx
│   │   │       │   ├── PriorityBadge.tsx
│   │   │       │   ├── SidebarView.tsx
│   │   │       │   ├── TagDropdown.tsx
│   │   │       │   ├── TaskCard.tsx
│   │   │       │   ├── TaskEditModal.tsx
│   │   │       │   ├── TaskMasterKanban.tsx
│   │   │       │   ├── ToastContainer.tsx
│   │   │       │   └── ToastNotification.tsx
│   │   │       ├── constants
│   │   │       │   └── index.ts
│   │   │       ├── contexts
│   │   │       │   └── VSCodeContext.tsx
│   │   │       ├── hooks
│   │   │       │   ├── useTaskQueries.ts
│   │   │       │   ├── useVSCodeMessages.ts
│   │   │       │   └── useWebviewHeight.ts
│   │   │       ├── index.css
│   │   │       ├── index.tsx
│   │   │       ├── providers
│   │   │       │   └── QueryProvider.tsx
│   │   │       ├── reducers
│   │   │       │   └── appReducer.ts
│   │   │       ├── sidebar.tsx
│   │   │       ├── types
│   │   │       │   └── index.ts
│   │   │       └── utils
│   │   │           ├── logger.ts
│   │   │           └── toast.ts
│   │   └── tsconfig.json
│   └── mcp
│       ├── CHANGELOG.md
│       ├── package.json
│       ├── src
│       │   ├── index.ts
│       │   ├── shared
│       │   │   ├── types.ts
│       │   │   └── utils.ts
│       │   └── tools
│       │       ├── autopilot
│       │       │   ├── abort.tool.ts
│       │       │   ├── commit.tool.ts
│       │       │   ├── complete.tool.ts
│       │       │   ├── finalize.tool.ts
│       │       │   ├── index.ts
│       │       │   ├── next.tool.ts
│       │       │   ├── resume.tool.ts
│       │       │   ├── start.tool.ts
│       │       │   └── status.tool.ts
│       │       ├── README-ZOD-V3.md
│       │       └── tasks
│       │           ├── get-task.tool.ts
│       │           ├── get-tasks.tool.ts
│       │           └── index.ts
│       ├── tsconfig.json
│       └── vitest.config.ts
├── assets
│   ├── .windsurfrules
│   ├── AGENTS.md
│   ├── claude
│   │   └── TM_COMMANDS_GUIDE.md
│   ├── config.json
│   ├── env.example
│   ├── example_prd_rpg.txt
│   ├── example_prd.txt
│   ├── GEMINI.md
│   ├── gitignore
│   ├── kiro-hooks
│   │   ├── tm-code-change-task-tracker.kiro.hook
│   │   ├── tm-complexity-analyzer.kiro.hook
│   │   ├── tm-daily-standup-assistant.kiro.hook
│   │   ├── tm-git-commit-task-linker.kiro.hook
│   │   ├── tm-pr-readiness-checker.kiro.hook
│   │   ├── tm-task-dependency-auto-progression.kiro.hook
│   │   └── tm-test-success-task-completer.kiro.hook
│   ├── roocode
│   │   ├── .roo
│   │   │   ├── rules-architect
│   │   │   │   └── architect-rules
│   │   │   ├── rules-ask
│   │   │   │   └── ask-rules
│   │   │   ├── rules-code
│   │   │   │   └── code-rules
│   │   │   ├── rules-debug
│   │   │   │   └── debug-rules
│   │   │   ├── rules-orchestrator
│   │   │   │   └── orchestrator-rules
│   │   │   └── rules-test
│   │   │       └── test-rules
│   │   └── .roomodes
│   ├── rules
│   │   ├── cursor_rules.mdc
│   │   ├── dev_workflow.mdc
│   │   ├── self_improve.mdc
│   │   ├── taskmaster_hooks_workflow.mdc
│   │   └── taskmaster.mdc
│   └── scripts_README.md
├── bin
│   └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE_CODE_PLUGIN.md
├── CLAUDE.md
├── context
│   ├── chats
│   │   ├── add-task-dependencies-1.md
│   │   └── max-min-tokens.txt.md
│   ├── fastmcp-core.txt
│   ├── fastmcp-docs.txt
│   ├── MCP_INTEGRATION.md
│   ├── mcp-js-sdk-docs.txt
│   ├── mcp-protocol-repo.txt
│   ├── mcp-protocol-schema-03262025.json
│   └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│   ├── claude-code-integration.md
│   ├── CLI-COMMANDER-PATTERN.md
│   ├── command-reference.md
│   ├── configuration.md
│   ├── contributor-docs
│   │   ├── testing-roo-integration.md
│   │   └── worktree-setup.md
│   ├── cross-tag-task-movement.md
│   ├── examples
│   │   ├── claude-code-usage.md
│   │   └── codex-cli-usage.md
│   ├── examples.md
│   ├── licensing.md
│   ├── mcp-provider-guide.md
│   ├── mcp-provider.md
│   ├── migration-guide.md
│   ├── models.md
│   ├── providers
│   │   ├── codex-cli.md
│   │   └── gemini-cli.md
│   ├── README.md
│   ├── scripts
│   │   └── models-json-to-markdown.js
│   ├── task-structure.md
│   └── tutorial.md
├── images
│   ├── hamster-hiring.png
│   └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│   ├── server.js
│   └── src
│       ├── core
│       │   ├── __tests__
│       │   │   └── context-manager.test.js
│       │   ├── context-manager.js
│       │   ├── direct-functions
│       │   │   ├── add-dependency.js
│       │   │   ├── add-subtask.js
│       │   │   ├── add-tag.js
│       │   │   ├── add-task.js
│       │   │   ├── analyze-task-complexity.js
│       │   │   ├── cache-stats.js
│       │   │   ├── clear-subtasks.js
│       │   │   ├── complexity-report.js
│       │   │   ├── copy-tag.js
│       │   │   ├── create-tag-from-branch.js
│       │   │   ├── delete-tag.js
│       │   │   ├── expand-all-tasks.js
│       │   │   ├── expand-task.js
│       │   │   ├── fix-dependencies.js
│       │   │   ├── generate-task-files.js
│       │   │   ├── initialize-project.js
│       │   │   ├── list-tags.js
│       │   │   ├── models.js
│       │   │   ├── move-task-cross-tag.js
│       │   │   ├── move-task.js
│       │   │   ├── next-task.js
│       │   │   ├── parse-prd.js
│       │   │   ├── remove-dependency.js
│       │   │   ├── remove-subtask.js
│       │   │   ├── remove-task.js
│       │   │   ├── rename-tag.js
│       │   │   ├── research.js
│       │   │   ├── response-language.js
│       │   │   ├── rules.js
│       │   │   ├── scope-down.js
│       │   │   ├── scope-up.js
│       │   │   ├── set-task-status.js
│       │   │   ├── update-subtask-by-id.js
│       │   │   ├── update-task-by-id.js
│       │   │   ├── update-tasks.js
│       │   │   ├── use-tag.js
│       │   │   └── validate-dependencies.js
│       │   ├── task-master-core.js
│       │   └── utils
│       │       ├── env-utils.js
│       │       └── path-utils.js
│       ├── custom-sdk
│       │   ├── errors.js
│       │   ├── index.js
│       │   ├── json-extractor.js
│       │   ├── language-model.js
│       │   ├── message-converter.js
│       │   └── schema-converter.js
│       ├── index.js
│       ├── logger.js
│       ├── providers
│       │   └── mcp-provider.js
│       └── tools
│           ├── add-dependency.js
│           ├── add-subtask.js
│           ├── add-tag.js
│           ├── add-task.js
│           ├── analyze.js
│           ├── clear-subtasks.js
│           ├── complexity-report.js
│           ├── copy-tag.js
│           ├── delete-tag.js
│           ├── expand-all.js
│           ├── expand-task.js
│           ├── fix-dependencies.js
│           ├── generate.js
│           ├── get-operation-status.js
│           ├── index.js
│           ├── initialize-project.js
│           ├── list-tags.js
│           ├── models.js
│           ├── move-task.js
│           ├── next-task.js
│           ├── parse-prd.js
│           ├── README-ZOD-V3.md
│           ├── remove-dependency.js
│           ├── remove-subtask.js
│           ├── remove-task.js
│           ├── rename-tag.js
│           ├── research.js
│           ├── response-language.js
│           ├── rules.js
│           ├── scope-down.js
│           ├── scope-up.js
│           ├── set-task-status.js
│           ├── tool-registry.js
│           ├── update-subtask.js
│           ├── update-task.js
│           ├── update.js
│           ├── use-tag.js
│           ├── utils.js
│           └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│   ├── ai-sdk-provider-grok-cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── errors.test.ts
│   │   │   ├── errors.ts
│   │   │   ├── grok-cli-language-model.ts
│   │   │   ├── grok-cli-provider.test.ts
│   │   │   ├── grok-cli-provider.ts
│   │   │   ├── index.ts
│   │   │   ├── json-extractor.test.ts
│   │   │   ├── json-extractor.ts
│   │   │   ├── message-converter.test.ts
│   │   │   ├── message-converter.ts
│   │   │   └── types.ts
│   │   └── tsconfig.json
│   ├── build-config
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   └── tsdown.base.ts
│   │   └── tsconfig.json
│   ├── claude-code-plugin
│   │   ├── .claude-plugin
│   │   │   └── plugin.json
│   │   ├── .gitignore
│   │   ├── agents
│   │   │   ├── task-checker.md
│   │   │   ├── task-executor.md
│   │   │   └── task-orchestrator.md
│   │   ├── CHANGELOG.md
│   │   ├── commands
│   │   │   ├── add-dependency.md
│   │   │   ├── add-subtask.md
│   │   │   ├── add-task.md
│   │   │   ├── analyze-complexity.md
│   │   │   ├── analyze-project.md
│   │   │   ├── auto-implement-tasks.md
│   │   │   ├── command-pipeline.md
│   │   │   ├── complexity-report.md
│   │   │   ├── convert-task-to-subtask.md
│   │   │   ├── expand-all-tasks.md
│   │   │   ├── expand-task.md
│   │   │   ├── fix-dependencies.md
│   │   │   ├── generate-tasks.md
│   │   │   ├── help.md
│   │   │   ├── init-project-quick.md
│   │   │   ├── init-project.md
│   │   │   ├── install-taskmaster.md
│   │   │   ├── learn.md
│   │   │   ├── list-tasks-by-status.md
│   │   │   ├── list-tasks-with-subtasks.md
│   │   │   ├── list-tasks.md
│   │   │   ├── next-task.md
│   │   │   ├── parse-prd-with-research.md
│   │   │   ├── parse-prd.md
│   │   │   ├── project-status.md
│   │   │   ├── quick-install-taskmaster.md
│   │   │   ├── remove-all-subtasks.md
│   │   │   ├── remove-dependency.md
│   │   │   ├── remove-subtask.md
│   │   │   ├── remove-subtasks.md
│   │   │   ├── remove-task.md
│   │   │   ├── setup-models.md
│   │   │   ├── show-task.md
│   │   │   ├── smart-workflow.md
│   │   │   ├── sync-readme.md
│   │   │   ├── tm-main.md
│   │   │   ├── to-cancelled.md
│   │   │   ├── to-deferred.md
│   │   │   ├── to-done.md
│   │   │   ├── to-in-progress.md
│   │   │   ├── to-pending.md
│   │   │   ├── to-review.md
│   │   │   ├── update-single-task.md
│   │   │   ├── update-task.md
│   │   │   ├── update-tasks-from-id.md
│   │   │   ├── validate-dependencies.md
│   │   │   └── view-models.md
│   │   ├── mcp.json
│   │   └── package.json
│   ├── tm-bridge
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── add-tag-bridge.ts
│   │   │   ├── bridge-types.ts
│   │   │   ├── bridge-utils.ts
│   │   │   ├── expand-bridge.ts
│   │   │   ├── index.ts
│   │   │   ├── tags-bridge.ts
│   │   │   ├── update-bridge.ts
│   │   │   └── use-tag-bridge.ts
│   │   └── tsconfig.json
│   └── tm-core
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── docs
│       │   └── listTasks-architecture.md
│       ├── package.json
│       ├── POC-STATUS.md
│       ├── README.md
│       ├── src
│       │   ├── common
│       │   │   ├── constants
│       │   │   │   ├── index.ts
│       │   │   │   ├── paths.ts
│       │   │   │   └── providers.ts
│       │   │   ├── errors
│       │   │   │   ├── index.ts
│       │   │   │   └── task-master-error.ts
│       │   │   ├── interfaces
│       │   │   │   ├── configuration.interface.ts
│       │   │   │   ├── index.ts
│       │   │   │   └── storage.interface.ts
│       │   │   ├── logger
│       │   │   │   ├── factory.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── logger.spec.ts
│       │   │   │   └── logger.ts
│       │   │   ├── mappers
│       │   │   │   ├── TaskMapper.test.ts
│       │   │   │   └── TaskMapper.ts
│       │   │   ├── types
│       │   │   │   ├── database.types.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── legacy.ts
│       │   │   │   └── repository-types.ts
│       │   │   └── utils
│       │   │       ├── git-utils.ts
│       │   │       ├── id-generator.ts
│       │   │       ├── index.ts
│       │   │       ├── path-helpers.ts
│       │   │       ├── path-normalizer.spec.ts
│       │   │       ├── path-normalizer.ts
│       │   │       ├── project-root-finder.spec.ts
│       │   │       ├── project-root-finder.ts
│       │   │       ├── run-id-generator.spec.ts
│       │   │       └── run-id-generator.ts
│       │   ├── index.ts
│       │   ├── modules
│       │   │   ├── ai
│       │   │   │   ├── index.ts
│       │   │   │   ├── interfaces
│       │   │   │   │   └── ai-provider.interface.ts
│       │   │   │   └── providers
│       │   │   │       ├── base-provider.ts
│       │   │   │       └── index.ts
│       │   │   ├── auth
│       │   │   │   ├── auth-domain.spec.ts
│       │   │   │   ├── auth-domain.ts
│       │   │   │   ├── config.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── managers
│       │   │   │   │   ├── auth-manager.spec.ts
│       │   │   │   │   └── auth-manager.ts
│       │   │   │   ├── services
│       │   │   │   │   ├── context-store.ts
│       │   │   │   │   ├── oauth-service.ts
│       │   │   │   │   ├── organization.service.ts
│       │   │   │   │   ├── supabase-session-storage.spec.ts
│       │   │   │   │   └── supabase-session-storage.ts
│       │   │   │   └── types.ts
│       │   │   ├── briefs
│       │   │   │   ├── briefs-domain.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── services
│       │   │   │   │   └── brief-service.ts
│       │   │   │   ├── types.ts
│       │   │   │   └── utils
│       │   │   │       └── url-parser.ts
│       │   │   ├── commands
│       │   │   │   └── index.ts
│       │   │   ├── config
│       │   │   │   ├── config-domain.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── managers
│       │   │   │   │   ├── config-manager.spec.ts
│       │   │   │   │   └── config-manager.ts
│       │   │   │   └── services
│       │   │   │       ├── config-loader.service.spec.ts
│       │   │   │       ├── config-loader.service.ts
│       │   │   │       ├── config-merger.service.spec.ts
│       │   │   │       ├── config-merger.service.ts
│       │   │   │       ├── config-persistence.service.spec.ts
│       │   │   │       ├── config-persistence.service.ts
│       │   │   │       ├── environment-config-provider.service.spec.ts
│       │   │   │       ├── environment-config-provider.service.ts
│       │   │   │       ├── index.ts
│       │   │   │       ├── runtime-state-manager.service.spec.ts
│       │   │   │       └── runtime-state-manager.service.ts
│       │   │   ├── dependencies
│       │   │   │   └── index.ts
│       │   │   ├── execution
│       │   │   │   ├── executors
│       │   │   │   │   ├── base-executor.ts
│       │   │   │   │   ├── claude-executor.ts
│       │   │   │   │   └── executor-factory.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── services
│       │   │   │   │   └── executor-service.ts
│       │   │   │   └── types.ts
│       │   │   ├── git
│       │   │   │   ├── adapters
│       │   │   │   │   ├── git-adapter.test.ts
│       │   │   │   │   └── git-adapter.ts
│       │   │   │   ├── git-domain.ts
│       │   │   │   ├── index.ts
│       │   │   │   └── services
│       │   │   │       ├── branch-name-generator.spec.ts
│       │   │   │       ├── branch-name-generator.ts
│       │   │   │       ├── commit-message-generator.test.ts
│       │   │   │       ├── commit-message-generator.ts
│       │   │   │       ├── scope-detector.test.ts
│       │   │   │       ├── scope-detector.ts
│       │   │   │       ├── template-engine.test.ts
│       │   │   │       └── template-engine.ts
│       │   │   ├── integration
│       │   │   │   ├── clients
│       │   │   │   │   ├── index.ts
│       │   │   │   │   └── supabase-client.ts
│       │   │   │   ├── integration-domain.ts
│       │   │   │   └── services
│       │   │   │       ├── export.service.ts
│       │   │   │       ├── task-expansion.service.ts
│       │   │   │       └── task-retrieval.service.ts
│       │   │   ├── reports
│       │   │   │   ├── index.ts
│       │   │   │   ├── managers
│       │   │   │   │   └── complexity-report-manager.ts
│       │   │   │   └── types.ts
│       │   │   ├── storage
│       │   │   │   ├── adapters
│       │   │   │   │   ├── activity-logger.ts
│       │   │   │   │   ├── api-storage.ts
│       │   │   │   │   └── file-storage
│       │   │   │   │       ├── file-operations.ts
│       │   │   │   │       ├── file-storage.ts
│       │   │   │   │       ├── format-handler.ts
│       │   │   │   │       ├── index.ts
│       │   │   │   │       └── path-resolver.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── services
│       │   │   │   │   └── storage-factory.ts
│       │   │   │   └── utils
│       │   │   │       └── api-client.ts
│       │   │   ├── tasks
│       │   │   │   ├── entities
│       │   │   │   │   └── task.entity.ts
│       │   │   │   ├── parser
│       │   │   │   │   └── index.ts
│       │   │   │   ├── repositories
│       │   │   │   │   ├── supabase
│       │   │   │   │   │   ├── dependency-fetcher.ts
│       │   │   │   │   │   ├── index.ts
│       │   │   │   │   │   └── supabase-repository.ts
│       │   │   │   │   └── task-repository.interface.ts
│       │   │   │   ├── services
│       │   │   │   │   ├── preflight-checker.service.ts
│       │   │   │   │   ├── tag.service.ts
│       │   │   │   │   ├── task-execution-service.ts
│       │   │   │   │   ├── task-loader.service.ts
│       │   │   │   │   └── task-service.ts
│       │   │   │   └── tasks-domain.ts
│       │   │   ├── ui
│       │   │   │   └── index.ts
│       │   │   └── workflow
│       │   │       ├── managers
│       │   │       │   ├── workflow-state-manager.spec.ts
│       │   │       │   └── workflow-state-manager.ts
│       │   │       ├── orchestrators
│       │   │       │   ├── workflow-orchestrator.test.ts
│       │   │       │   └── workflow-orchestrator.ts
│       │   │       ├── services
│       │   │       │   ├── test-result-validator.test.ts
│       │   │       │   ├── test-result-validator.ts
│       │   │       │   ├── test-result-validator.types.ts
│       │   │       │   ├── workflow-activity-logger.ts
│       │   │       │   └── workflow.service.ts
│       │   │       ├── types.ts
│       │   │       └── workflow-domain.ts
│       │   ├── subpath-exports.test.ts
│       │   ├── tm-core.ts
│       │   └── utils
│       │       └── time.utils.ts
│       ├── tests
│       │   ├── auth
│       │   │   └── auth-refresh.test.ts
│       │   ├── integration
│       │   │   ├── auth-token-refresh.test.ts
│       │   │   ├── list-tasks.test.ts
│       │   │   └── storage
│       │   │       └── activity-logger.test.ts
│       │   ├── mocks
│       │   │   └── mock-provider.ts
│       │   ├── setup.ts
│       │   └── unit
│       │       ├── base-provider.test.ts
│       │       ├── executor.test.ts
│       │       └── smoke.test.ts
│       ├── tsconfig.json
│       └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│   ├── create-worktree.sh
│   ├── dev.js
│   ├── init.js
│   ├── list-worktrees.sh
│   ├── modules
│   │   ├── ai-services-unified.js
│   │   ├── bridge-utils.js
│   │   ├── commands.js
│   │   ├── config-manager.js
│   │   ├── dependency-manager.js
│   │   ├── index.js
│   │   ├── prompt-manager.js
│   │   ├── supported-models.json
│   │   ├── sync-readme.js
│   │   ├── task-manager
│   │   │   ├── add-subtask.js
│   │   │   ├── add-task.js
│   │   │   ├── analyze-task-complexity.js
│   │   │   ├── clear-subtasks.js
│   │   │   ├── expand-all-tasks.js
│   │   │   ├── expand-task.js
│   │   │   ├── find-next-task.js
│   │   │   ├── generate-task-files.js
│   │   │   ├── is-task-dependent.js
│   │   │   ├── list-tasks.js
│   │   │   ├── migrate.js
│   │   │   ├── models.js
│   │   │   ├── move-task.js
│   │   │   ├── parse-prd
│   │   │   │   ├── index.js
│   │   │   │   ├── parse-prd-config.js
│   │   │   │   ├── parse-prd-helpers.js
│   │   │   │   ├── parse-prd-non-streaming.js
│   │   │   │   ├── parse-prd-streaming.js
│   │   │   │   └── parse-prd.js
│   │   │   ├── remove-subtask.js
│   │   │   ├── remove-task.js
│   │   │   ├── research.js
│   │   │   ├── response-language.js
│   │   │   ├── scope-adjustment.js
│   │   │   ├── set-task-status.js
│   │   │   ├── tag-management.js
│   │   │   ├── task-exists.js
│   │   │   ├── update-single-task-status.js
│   │   │   ├── update-subtask-by-id.js
│   │   │   ├── update-task-by-id.js
│   │   │   └── update-tasks.js
│   │   ├── task-manager.js
│   │   ├── ui.js
│   │   ├── update-config-tokens.js
│   │   ├── utils
│   │   │   ├── contextGatherer.js
│   │   │   ├── fuzzyTaskSearch.js
│   │   │   └── git-utils.js
│   │   └── utils.js
│   ├── task-complexity-report.json
│   ├── test-claude-errors.js
│   └── test-claude.js
├── sonar-project.properties
├── src
│   ├── ai-providers
│   │   ├── anthropic.js
│   │   ├── azure.js
│   │   ├── base-provider.js
│   │   ├── bedrock.js
│   │   ├── claude-code.js
│   │   ├── codex-cli.js
│   │   ├── gemini-cli.js
│   │   ├── google-vertex.js
│   │   ├── google.js
│   │   ├── grok-cli.js
│   │   ├── groq.js
│   │   ├── index.js
│   │   ├── lmstudio.js
│   │   ├── ollama.js
│   │   ├── openai-compatible.js
│   │   ├── openai.js
│   │   ├── openrouter.js
│   │   ├── perplexity.js
│   │   ├── xai.js
│   │   ├── zai-coding.js
│   │   └── zai.js
│   ├── constants
│   │   ├── commands.js
│   │   ├── paths.js
│   │   ├── profiles.js
│   │   ├── rules-actions.js
│   │   ├── task-priority.js
│   │   └── task-status.js
│   ├── profiles
│   │   ├── amp.js
│   │   ├── base-profile.js
│   │   ├── claude.js
│   │   ├── cline.js
│   │   ├── codex.js
│   │   ├── cursor.js
│   │   ├── gemini.js
│   │   ├── index.js
│   │   ├── kilo.js
│   │   ├── kiro.js
│   │   ├── opencode.js
│   │   ├── roo.js
│   │   ├── trae.js
│   │   ├── vscode.js
│   │   ├── windsurf.js
│   │   └── zed.js
│   ├── progress
│   │   ├── base-progress-tracker.js
│   │   ├── cli-progress-factory.js
│   │   ├── parse-prd-tracker.js
│   │   ├── progress-tracker-builder.js
│   │   └── tracker-ui.js
│   ├── prompts
│   │   ├── add-task.json
│   │   ├── analyze-complexity.json
│   │   ├── expand-task.json
│   │   ├── parse-prd.json
│   │   ├── README.md
│   │   ├── research.json
│   │   ├── schemas
│   │   │   ├── parameter.schema.json
│   │   │   ├── prompt-template.schema.json
│   │   │   ├── README.md
│   │   │   └── variant.schema.json
│   │   ├── update-subtask.json
│   │   ├── update-task.json
│   │   └── update-tasks.json
│   ├── provider-registry
│   │   └── index.js
│   ├── schemas
│   │   ├── add-task.js
│   │   ├── analyze-complexity.js
│   │   ├── base-schemas.js
│   │   ├── expand-task.js
│   │   ├── parse-prd.js
│   │   ├── registry.js
│   │   ├── update-subtask.js
│   │   ├── update-task.js
│   │   └── update-tasks.js
│   ├── task-master.js
│   ├── ui
│   │   ├── confirm.js
│   │   ├── indicators.js
│   │   └── parse-prd.js
│   └── utils
│       ├── asset-resolver.js
│       ├── create-mcp-config.js
│       ├── format.js
│       ├── getVersion.js
│       ├── logger-utils.js
│       ├── manage-gitignore.js
│       ├── path-utils.js
│       ├── profiles.js
│       ├── rule-transformer.js
│       ├── stream-parser.js
│       └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│   ├── e2e
│   │   ├── e2e_helpers.sh
│   │   ├── parse_llm_output.cjs
│   │   ├── run_e2e.sh
│   │   ├── run_fallback_verification.sh
│   │   └── test_llm_analysis.sh
│   ├── fixtures
│   │   ├── .taskmasterconfig
│   │   ├── sample-claude-response.js
│   │   ├── sample-prd.txt
│   │   └── sample-tasks.js
│   ├── helpers
│   │   └── tool-counts.js
│   ├── integration
│   │   ├── claude-code-error-handling.test.js
│   │   ├── claude-code-optional.test.js
│   │   ├── cli
│   │   │   ├── commands.test.js
│   │   │   ├── complex-cross-tag-scenarios.test.js
│   │   │   └── move-cross-tag.test.js
│   │   ├── manage-gitignore.test.js
│   │   ├── mcp-server
│   │   │   └── direct-functions.test.js
│   │   ├── move-task-cross-tag.integration.test.js
│   │   ├── move-task-simple.integration.test.js
│   │   ├── profiles
│   │   │   ├── amp-init-functionality.test.js
│   │   │   ├── claude-init-functionality.test.js
│   │   │   ├── cline-init-functionality.test.js
│   │   │   ├── codex-init-functionality.test.js
│   │   │   ├── cursor-init-functionality.test.js
│   │   │   ├── gemini-init-functionality.test.js
│   │   │   ├── opencode-init-functionality.test.js
│   │   │   ├── roo-files-inclusion.test.js
│   │   │   ├── roo-init-functionality.test.js
│   │   │   ├── rules-files-inclusion.test.js
│   │   │   ├── trae-init-functionality.test.js
│   │   │   ├── vscode-init-functionality.test.js
│   │   │   └── windsurf-init-functionality.test.js
│   │   └── providers
│   │       └── temperature-support.test.js
│   ├── manual
│   │   ├── progress
│   │   │   ├── parse-prd-analysis.js
│   │   │   ├── test-parse-prd.js
│   │   │   └── TESTING_GUIDE.md
│   │   └── prompts
│   │       ├── prompt-test.js
│   │       └── README.md
│   ├── README.md
│   ├── setup.js
│   └── unit
│       ├── ai-providers
│       │   ├── base-provider.test.js
│       │   ├── claude-code.test.js
│       │   ├── codex-cli.test.js
│       │   ├── gemini-cli.test.js
│       │   ├── lmstudio.test.js
│       │   ├── mcp-components.test.js
│       │   ├── openai-compatible.test.js
│       │   ├── openai.test.js
│       │   ├── provider-registry.test.js
│       │   ├── zai-coding.test.js
│       │   ├── zai-provider.test.js
│       │   ├── zai-schema-introspection.test.js
│       │   └── zai.test.js
│       ├── ai-services-unified.test.js
│       ├── commands.test.js
│       ├── config-manager.test.js
│       ├── config-manager.test.mjs
│       ├── dependency-manager.test.js
│       ├── init.test.js
│       ├── initialize-project.test.js
│       ├── kebab-case-validation.test.js
│       ├── manage-gitignore.test.js
│       ├── mcp
│       │   └── tools
│       │       ├── __mocks__
│       │       │   └── move-task.js
│       │       ├── add-task.test.js
│       │       ├── analyze-complexity.test.js
│       │       ├── expand-all.test.js
│       │       ├── get-tasks.test.js
│       │       ├── initialize-project.test.js
│       │       ├── move-task-cross-tag-options.test.js
│       │       ├── move-task-cross-tag.test.js
│       │       ├── remove-task.test.js
│       │       └── tool-registration.test.js
│       ├── mcp-providers
│       │   ├── mcp-components.test.js
│       │   └── mcp-provider.test.js
│       ├── parse-prd.test.js
│       ├── profiles
│       │   ├── amp-integration.test.js
│       │   ├── claude-integration.test.js
│       │   ├── cline-integration.test.js
│       │   ├── codex-integration.test.js
│       │   ├── cursor-integration.test.js
│       │   ├── gemini-integration.test.js
│       │   ├── kilo-integration.test.js
│       │   ├── kiro-integration.test.js
│       │   ├── mcp-config-validation.test.js
│       │   ├── opencode-integration.test.js
│       │   ├── profile-safety-check.test.js
│       │   ├── roo-integration.test.js
│       │   ├── rule-transformer-cline.test.js
│       │   ├── rule-transformer-cursor.test.js
│       │   ├── rule-transformer-gemini.test.js
│       │   ├── rule-transformer-kilo.test.js
│       │   ├── rule-transformer-kiro.test.js
│       │   ├── rule-transformer-opencode.test.js
│       │   ├── rule-transformer-roo.test.js
│       │   ├── rule-transformer-trae.test.js
│       │   ├── rule-transformer-vscode.test.js
│       │   ├── rule-transformer-windsurf.test.js
│       │   ├── rule-transformer-zed.test.js
│       │   ├── rule-transformer.test.js
│       │   ├── selective-profile-removal.test.js
│       │   ├── subdirectory-support.test.js
│       │   ├── trae-integration.test.js
│       │   ├── vscode-integration.test.js
│       │   ├── windsurf-integration.test.js
│       │   └── zed-integration.test.js
│       ├── progress
│       │   └── base-progress-tracker.test.js
│       ├── prompt-manager.test.js
│       ├── prompts
│       │   ├── expand-task-prompt.test.js
│       │   └── prompt-migration.test.js
│       ├── scripts
│       │   └── modules
│       │       ├── commands
│       │       │   ├── move-cross-tag.test.js
│       │       │   └── README.md
│       │       ├── dependency-manager
│       │       │   ├── circular-dependencies.test.js
│       │       │   ├── cross-tag-dependencies.test.js
│       │       │   └── fix-dependencies-command.test.js
│       │       ├── task-manager
│       │       │   ├── add-subtask.test.js
│       │       │   ├── add-task.test.js
│       │       │   ├── analyze-task-complexity.test.js
│       │       │   ├── clear-subtasks.test.js
│       │       │   ├── complexity-report-tag-isolation.test.js
│       │       │   ├── expand-all-tasks.test.js
│       │       │   ├── expand-task.test.js
│       │       │   ├── find-next-task.test.js
│       │       │   ├── generate-task-files.test.js
│       │       │   ├── list-tasks.test.js
│       │       │   ├── models-baseurl.test.js
│       │       │   ├── move-task-cross-tag.test.js
│       │       │   ├── move-task.test.js
│       │       │   ├── parse-prd-schema.test.js
│       │       │   ├── parse-prd.test.js
│       │       │   ├── remove-subtask.test.js
│       │       │   ├── remove-task.test.js
│       │       │   ├── research.test.js
│       │       │   ├── scope-adjustment.test.js
│       │       │   ├── set-task-status.test.js
│       │       │   ├── setup.js
│       │       │   ├── update-single-task-status.test.js
│       │       │   ├── update-subtask-by-id.test.js
│       │       │   ├── update-task-by-id.test.js
│       │       │   └── update-tasks.test.js
│       │       ├── ui
│       │       │   └── cross-tag-error-display.test.js
│       │       └── utils-tag-aware-paths.test.js
│       ├── task-finder.test.js
│       ├── task-manager
│       │   ├── clear-subtasks.test.js
│       │   ├── move-task.test.js
│       │   ├── tag-boundary.test.js
│       │   └── tag-management.test.js
│       ├── task-master.test.js
│       ├── ui
│       │   └── indicators.test.js
│       ├── ui.test.js
│       ├── utils-strip-ansi.test.js
│       └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
├── turbo.json
└── update-task-migration-plan.md
```

# Files

--------------------------------------------------------------------------------
/apps/docs/getting-started/quick-start/configuration-quick.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Configuration
  3 | sidebarTitle: "Configuration"
  4 | 
  5 | ---
  6 | 
  7 | Before getting started with Task Master, you'll need to set up your API keys. There are a couple of ways to do this depending on whether you're using the CLI or working inside MCP. It's also a good time to start getting familiar with the other configuration options available — even if you don’t need to adjust them yet, knowing what’s possible will help down the line.
  8 | 
  9 | ## API Key Setup
 10 | 
 11 | Task Master uses environment variables to securely store provider API keys and optional endpoint URLs.
 12 | 
 13 | ### MCP Usage: mcp.json file
 14 | 
 15 | For MCP/Cursor usage: Configure keys in the env section of your .cursor/mcp.json file.
 16 | 
 17 | ```java .env lines icon="java"
 18 | {
 19 | 	"mcpServers": {
 20 | 		"task-master-ai": {
 21 | 			"command": "npx",
 22 | 			"args": ["-y", "task-master-ai"],
 23 | 			"env": {
 24 | 				"ANTHROPIC_API_KEY": "ANTHROPIC_API_KEY_HERE",
 25 | 				"PERPLEXITY_API_KEY": "PERPLEXITY_API_KEY_HERE",
 26 | 				"OPENAI_API_KEY": "OPENAI_API_KEY_HERE",
 27 | 				"GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE",
 28 | 				"XAI_API_KEY": "XAI_API_KEY_HERE",
 29 | 				"OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE",
 30 | 				"MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE",
 31 | 				"AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE",
 32 | 				"OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE",
 33 | 				"GITHUB_API_KEY": "GITHUB_API_KEY_HERE"
 34 | 			}
 35 | 		}
 36 | 	}
 37 | }
 38 | ```
 39 | 
 40 | <Tip>
 41 | **Optimize Context Usage**: You can control which Task Master MCP tools are loaded using the `TASK_MASTER_TOOLS` environment variable. This helps reduce LLM context usage by only loading the tools you need.
 42 | 
 43 | Options:
 44 | - `all` (default) - All 36 tools
 45 | - `standard` - 15 commonly used tools
 46 | - `core` or `lean` - 7 essential tools
 47 | 
 48 | Example:
 49 | ```json
 50 | "env": {
 51 |   "TASK_MASTER_TOOLS": "standard",
 52 |   "ANTHROPIC_API_KEY": "your_key_here"
 53 | }
 54 | ```
 55 | 
 56 | See the [MCP Tools documentation](/capabilities/mcp#configurable-tool-loading) for details.
 57 | </Tip>
 58 | 
 59 | ### CLI Usage: `.env` File
 60 | 
 61 | Create a `.env` file in your project root and include the keys for the providers you plan to use:
 62 | 
 63 | 
 64 | 
 65 | ```java .env lines icon="java"
 66 | # Required API keys for providers configured in .taskmaster/config.json
 67 | ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
 68 | PERPLEXITY_API_KEY=pplx-your-key-here
 69 | # OPENAI_API_KEY=sk-your-key-here
 70 | # GOOGLE_API_KEY=AIzaSy...
 71 | # AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
 72 | # etc.
 73 | 
 74 | # Optional Endpoint Overrides
 75 | # Use a specific provider's base URL, e.g., for an OpenAI-compatible API
 76 | # OPENAI_BASE_URL=https://api.third-party.com/v1
 77 | #
 78 | # Azure OpenAI Configuration
 79 | # AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/ or https://your-endpoint-name.cognitiveservices.azure.com/openai/deployments
 80 | # OLLAMA_BASE_URL=http://custom-ollama-host:11434/api
 81 | 
 82 | # Google Vertex AI Configuration (Required if using 'vertex' provider)
 83 | # VERTEX_PROJECT_ID=your-gcp-project-id
 84 | ```
 85 | 
 86 | ## What Else Can Be Configured?
 87 | 
 88 | The main configuration file (`.taskmaster/config.json`) allows you to control nearly every aspect of Task Master’s behavior. Here’s a high-level look at what you can customize:
 89 | 
 90 | <Tip>
 91 | You don’t need to configure everything up front. Most settings can be left as defaults or updated later as your workflow evolves.
 92 | </Tip>
 93 | 
 94 | <Accordion title="View Configuration Options">
 95 | 
 96 | ### Models and Providers
 97 | - Role-based model setup: `main`, `research`, `fallback`
 98 | - Provider selection (Anthropic, OpenAI, Perplexity, etc.)
 99 | - Model IDs per role
100 | - Temperature, max tokens, and other generation settings
101 | - Custom base URLs for OpenAI-compatible APIs
102 | 
103 | ### Global Settings
104 | - `logLevel`: Logging verbosity
105 | - `debug`: Enable/disable debug mode
106 | - `projectName`: Optional name for your project
107 | - `defaultTag`: Default tag for task grouping
108 | - `defaultSubtasks`: Number of subtasks to auto-generate
109 | - `defaultPriority`: Priority level for new tasks
110 | 
111 | ### API Endpoint Overrides
112 | - `ollamaBaseURL`: Custom Ollama server URL
113 | - `azureBaseURL`: Global Azure endpoint
114 | - `vertexProjectId`: Google Vertex AI project ID
115 | - `vertexLocation`: Region for Vertex AI models
116 | 
117 | ### Tag and Git Integration
118 | - Default tag context per project
119 | - Support for task isolation by tag
120 | - Manual tag creation from Git branches
121 | 
122 | ### State Management
123 | - Active tag tracking
124 | - Migration state
125 | - Last tag switch timestamp
126 | 
127 | </Accordion>
128 | 
129 | <Note>
130 | For advanced configuration options and detailed customization, see our [Advanced Configuration Guide](/best-practices/configuration-advanced) page.
131 | </Note>
```

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

```typescript
  1 | import boxen from 'boxen';
  2 | import chalk from 'chalk';
  3 | import ora from 'ora';
  4 | import type { BaseBridgeParams } from './bridge-types.js';
  5 | import { checkStorageType } from './bridge-utils.js';
  6 | 
  7 | /**
  8 |  * Parameters for the expand bridge function
  9 |  */
 10 | export interface ExpandBridgeParams extends BaseBridgeParams {
 11 | 	/** Task ID (can be numeric "1" or alphanumeric "TAS-49") */
 12 | 	taskId: string | number;
 13 | 	/** Number of subtasks to generate (optional) */
 14 | 	numSubtasks?: number;
 15 | 	/** Whether to use research AI */
 16 | 	useResearch?: boolean;
 17 | 	/** Additional context for generation */
 18 | 	additionalContext?: string;
 19 | 	/** Force regeneration even if subtasks exist */
 20 | 	force?: boolean;
 21 | }
 22 | 
 23 | /**
 24 |  * Result returned when API storage handles the expansion
 25 |  */
 26 | export interface RemoteExpandResult {
 27 | 	success: boolean;
 28 | 	taskId: string | number;
 29 | 	message: string;
 30 | 	telemetryData: null;
 31 | 	tagInfo: null;
 32 | }
 33 | 
 34 | /**
 35 |  * Shared bridge function for expand-task command.
 36 |  * Checks if using API storage and delegates to remote AI service if so.
 37 |  *
 38 |  * @param params - Bridge parameters
 39 |  * @returns Result object if API storage handled it, null if should fall through to file storage
 40 |  */
 41 | export async function tryExpandViaRemote(
 42 | 	params: ExpandBridgeParams
 43 | ): Promise<RemoteExpandResult | null> {
 44 | 	const {
 45 | 		taskId,
 46 | 		numSubtasks,
 47 | 		useResearch = false,
 48 | 		additionalContext,
 49 | 		force = false,
 50 | 		projectRoot,
 51 | 		tag,
 52 | 		isMCP = false,
 53 | 		outputFormat = 'text',
 54 | 		report
 55 | 	} = params;
 56 | 
 57 | 	// Check storage type using shared utility
 58 | 	const { isApiStorage, tmCore } = await checkStorageType(
 59 | 		projectRoot,
 60 | 		report,
 61 | 		'falling back to file-based expansion'
 62 | 	);
 63 | 
 64 | 	if (!isApiStorage || !tmCore) {
 65 | 		// Not API storage - signal caller to fall through to file-based logic
 66 | 		return null;
 67 | 	}
 68 | 
 69 | 	// API STORAGE PATH: Delegate to remote AI service
 70 | 	report('info', `Delegating expansion to Hamster for task ${taskId}`);
 71 | 
 72 | 	// Show CLI output if not MCP
 73 | 	if (!isMCP && outputFormat === 'text') {
 74 | 		const showDebug = process.env.TM_DEBUG === '1';
 75 | 		const contextPreview =
 76 | 			showDebug && additionalContext
 77 | 				? `${additionalContext.substring(0, 60)}${additionalContext.length > 60 ? '...' : ''}`
 78 | 				: additionalContext
 79 | 					? '[provided]'
 80 | 					: '[none]';
 81 | 
 82 | 		console.log(
 83 | 			boxen(
 84 | 				chalk.blue.bold(`Expanding Task via Hamster`) +
 85 | 					'\n\n' +
 86 | 					chalk.white(`Task ID: ${taskId}`) +
 87 | 					'\n' +
 88 | 					chalk.white(`Subtasks: ${numSubtasks || 'auto'}`) +
 89 | 					'\n' +
 90 | 					chalk.white(`Use Research: ${useResearch ? 'yes' : 'no'}`) +
 91 | 					'\n' +
 92 | 					chalk.white(`Force: ${force ? 'yes' : 'no'}`) +
 93 | 					'\n' +
 94 | 					chalk.white(`Context: ${contextPreview}`),
 95 | 				{
 96 | 					padding: 1,
 97 | 					borderColor: 'blue',
 98 | 					borderStyle: 'round',
 99 | 					margin: { top: 1, bottom: 1 }
100 | 				}
101 | 			)
102 | 		);
103 | 	}
104 | 
105 | 	const spinner =
106 | 		!isMCP && outputFormat === 'text'
107 | 			? ora({ text: 'Expanding task on Hamster...', color: 'cyan' }).start()
108 | 			: null;
109 | 
110 | 	try {
111 | 		// Call the API storage method which handles the remote expansion
112 | 		const result = await tmCore.tasks.expand(String(taskId), tag, {
113 | 			numSubtasks,
114 | 			useResearch,
115 | 			additionalContext,
116 | 			force
117 | 		});
118 | 
119 | 		if (spinner) {
120 | 			spinner.succeed('Task expansion queued successfully');
121 | 		}
122 | 
123 | 		if (outputFormat === 'text') {
124 | 			// Build message conditionally based on result
125 | 			let messageLines = [
126 | 				chalk.green(`Successfully queued expansion for task ${taskId}`),
127 | 				'',
128 | 				chalk.white('The task expansion has been queued on Hamster'),
129 | 				chalk.white('Subtasks will be generated in the background.')
130 | 			];
131 | 
132 | 			// Add task link if available
133 | 			if (result?.taskLink) {
134 | 				messageLines.push('');
135 | 				messageLines.push(
136 | 					chalk.white('View task: ') + chalk.blue.underline(result.taskLink)
137 | 				);
138 | 			}
139 | 
140 | 			// Always add CLI alternative
141 | 			messageLines.push('');
142 | 			messageLines.push(
143 | 				chalk.dim(`Or run: ${chalk.yellow(`task-master show ${taskId}`)}`)
144 | 			);
145 | 
146 | 			console.log(
147 | 				boxen(messageLines.join('\n'), {
148 | 					padding: 1,
149 | 					borderColor: 'green',
150 | 					borderStyle: 'round'
151 | 				})
152 | 			);
153 | 		}
154 | 
155 | 		// Return success result - signals that we handled it
156 | 		return {
157 | 			success: true,
158 | 			taskId: taskId,
159 | 			message: result?.message || 'Task expansion queued via remote AI service',
160 | 			telemetryData: null,
161 | 			tagInfo: null
162 | 		};
163 | 	} catch (expandError) {
164 | 		if (spinner) {
165 | 			spinner.fail('Expansion failed');
166 | 		}
167 | 
168 | 		// tm-core already formatted the error properly, just re-throw
169 | 		throw expandError;
170 | 	}
171 | }
172 | 
```

--------------------------------------------------------------------------------
/apps/mcp/src/tools/autopilot/complete.tool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview autopilot-complete MCP tool
  3 |  * Complete the current TDD phase with test result validation
  4 |  */
  5 | 
  6 | import { z } from 'zod';
  7 | import {
  8 | 	handleApiResult,
  9 | 	withNormalizedProjectRoot
 10 | } from '../../shared/utils.js';
 11 | import type { MCPContext } from '../../shared/types.js';
 12 | import { WorkflowService } from '@tm/core';
 13 | import type { FastMCP } from 'fastmcp';
 14 | 
 15 | const CompletePhaseSchema = z.object({
 16 | 	projectRoot: z
 17 | 		.string()
 18 | 		.describe('Absolute path to the project root directory'),
 19 | 	testResults: z
 20 | 		.object({
 21 | 			total: z.number().describe('Total number of tests'),
 22 | 			passed: z.number().describe('Number of passing tests'),
 23 | 			failed: z.number().describe('Number of failing tests'),
 24 | 			skipped: z.number().optional().describe('Number of skipped tests')
 25 | 		})
 26 | 		.describe('Test results from running the test suite')
 27 | });
 28 | 
 29 | type CompletePhaseArgs = z.infer<typeof CompletePhaseSchema>;
 30 | 
 31 | /**
 32 |  * Register the autopilot_complete_phase tool with the MCP server
 33 |  */
 34 | export function registerAutopilotCompleteTool(server: FastMCP) {
 35 | 	server.addTool({
 36 | 		name: 'autopilot_complete_phase',
 37 | 		description:
 38 | 			'Complete the current TDD phase (RED, GREEN, or COMMIT) with test result validation. RED phase: expects failures (if 0 failures, feature is already implemented and subtask auto-completes). GREEN phase: expects all tests passing.',
 39 | 		parameters: CompletePhaseSchema,
 40 | 		execute: withNormalizedProjectRoot(
 41 | 			async (args: CompletePhaseArgs, context: MCPContext) => {
 42 | 				const { projectRoot, testResults } = args;
 43 | 
 44 | 				try {
 45 | 					context.log.info(
 46 | 						`Completing current phase in workflow for ${projectRoot}`
 47 | 					);
 48 | 
 49 | 					const workflowService = new WorkflowService(projectRoot);
 50 | 
 51 | 					// Check if workflow exists
 52 | 					if (!(await workflowService.hasWorkflow())) {
 53 | 						return handleApiResult({
 54 | 							result: {
 55 | 								success: false,
 56 | 								error: {
 57 | 									message:
 58 | 										'No active workflow found. Start a workflow with autopilot_start'
 59 | 								}
 60 | 							},
 61 | 							log: context.log,
 62 | 							projectRoot
 63 | 						});
 64 | 					}
 65 | 
 66 | 					// Resume workflow to get current state
 67 | 					await workflowService.resumeWorkflow();
 68 | 					const currentStatus = workflowService.getStatus();
 69 | 
 70 | 					// Validate that we're in a TDD phase (RED or GREEN)
 71 | 					if (!currentStatus.tddPhase) {
 72 | 						return handleApiResult({
 73 | 							result: {
 74 | 								success: false,
 75 | 								error: {
 76 | 									message: `Cannot complete phase: not in a TDD phase (current phase: ${currentStatus.phase})`
 77 | 								}
 78 | 							},
 79 | 							log: context.log,
 80 | 							projectRoot
 81 | 						});
 82 | 					}
 83 | 
 84 | 					// COMMIT phase completion is handled by autopilot_commit tool
 85 | 					if (currentStatus.tddPhase === 'COMMIT') {
 86 | 						return handleApiResult({
 87 | 							result: {
 88 | 								success: false,
 89 | 								error: {
 90 | 									message:
 91 | 										'Cannot complete COMMIT phase with this tool. Use autopilot_commit instead'
 92 | 								}
 93 | 							},
 94 | 							log: context.log,
 95 | 							projectRoot
 96 | 						});
 97 | 					}
 98 | 
 99 | 					// Map TDD phase to TestResult phase (only RED or GREEN allowed)
100 | 					const phase = currentStatus.tddPhase as 'RED' | 'GREEN';
101 | 
102 | 					// Construct full TestResult with phase
103 | 					const fullTestResults = {
104 | 						total: testResults.total,
105 | 						passed: testResults.passed,
106 | 						failed: testResults.failed,
107 | 						skipped: testResults.skipped ?? 0,
108 | 						phase
109 | 					};
110 | 
111 | 					// Complete phase with test results
112 | 					const status = await workflowService.completePhase(fullTestResults);
113 | 					const nextAction = workflowService.getNextAction();
114 | 
115 | 					context.log.info(
116 | 						`Phase completed. New phase: ${status.tddPhase || status.phase}`
117 | 					);
118 | 
119 | 					return handleApiResult({
120 | 						result: {
121 | 							success: true,
122 | 							data: {
123 | 								message: `Phase completed. Transitioned to ${status.tddPhase || status.phase}`,
124 | 								...status,
125 | 								nextAction: nextAction.action,
126 | 								actionDescription: nextAction.description,
127 | 								nextSteps: nextAction.nextSteps
128 | 							}
129 | 						},
130 | 						log: context.log,
131 | 						projectRoot
132 | 					});
133 | 				} catch (error: any) {
134 | 					context.log.error(`Error in autopilot-complete: ${error.message}`);
135 | 					if (error.stack) {
136 | 						context.log.debug(error.stack);
137 | 					}
138 | 					return handleApiResult({
139 | 						result: {
140 | 							success: false,
141 | 							error: {
142 | 								message: `Failed to complete phase: ${error.message}`
143 | 							}
144 | 						},
145 | 						log: context.log,
146 | 						projectRoot
147 | 					});
148 | 				}
149 | 			}
150 | 		)
151 | 	});
152 | }
153 | 
```

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

```typescript
  1 | /**
  2 |  * @fileoverview Commit Command - Create commit with enhanced message generation
  3 |  */
  4 | 
  5 | import { CommitMessageGenerator, GitAdapter, WorkflowService } from '@tm/core';
  6 | import { Command } from 'commander';
  7 | import { AutopilotBaseOptions, OutputFormatter } from './shared.js';
  8 | import { getProjectRoot } from '../../utils/project-root.js';
  9 | 
 10 | type CommitOptions = AutopilotBaseOptions;
 11 | 
 12 | /**
 13 |  * Commit Command - Create commit using enhanced message generator
 14 |  */
 15 | export class CommitCommand extends Command {
 16 | 	constructor() {
 17 | 		super('commit');
 18 | 
 19 | 		this.description('Create a commit for the completed GREEN phase').action(
 20 | 			async (options: CommitOptions) => {
 21 | 				await this.execute(options);
 22 | 			}
 23 | 		);
 24 | 	}
 25 | 
 26 | 	private async execute(options: CommitOptions): Promise<void> {
 27 | 		// Inherit parent options
 28 | 		const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
 29 | 		const mergedOptions: CommitOptions = {
 30 | 			...parentOpts,
 31 | 			...options,
 32 | 			projectRoot: getProjectRoot(
 33 | 				options.projectRoot || parentOpts?.projectRoot
 34 | 			)
 35 | 		};
 36 | 
 37 | 		const formatter = new OutputFormatter(mergedOptions.json || false);
 38 | 
 39 | 		try {
 40 | 			const projectRoot = mergedOptions.projectRoot!;
 41 | 
 42 | 			// Create workflow service (manages WorkflowStateManager internally)
 43 | 			const workflowService = new WorkflowService(projectRoot);
 44 | 
 45 | 			// Check if workflow exists
 46 | 			if (!(await workflowService.hasWorkflow())) {
 47 | 				formatter.error('No active workflow', {
 48 | 					suggestion: 'Start a workflow with: autopilot start <taskId>'
 49 | 				});
 50 | 				process.exit(1);
 51 | 			}
 52 | 
 53 | 			// Resume workflow (loads state with single WorkflowStateManager instance)
 54 | 			await workflowService.resumeWorkflow();
 55 | 			const status = workflowService.getStatus();
 56 | 			const workflowContext = workflowService.getContext();
 57 | 
 58 | 			// Verify in COMMIT phase
 59 | 			if (status.tddPhase !== 'COMMIT') {
 60 | 				formatter.error('Not in COMMIT phase', {
 61 | 					currentPhase: status.tddPhase || status.phase,
 62 | 					suggestion: 'Complete RED and GREEN phases first'
 63 | 				});
 64 | 				process.exit(1);
 65 | 			}
 66 | 
 67 | 			// Verify there's an active subtask
 68 | 			if (!status.currentSubtask) {
 69 | 				formatter.error('No current subtask');
 70 | 				process.exit(1);
 71 | 			}
 72 | 
 73 | 			// Initialize git adapter
 74 | 			const gitAdapter = new GitAdapter(projectRoot);
 75 | 			await gitAdapter.ensureGitRepository();
 76 | 
 77 | 			// Check for staged changes
 78 | 			const hasStagedChanges = await gitAdapter.hasStagedChanges();
 79 | 			if (!hasStagedChanges) {
 80 | 				// Stage all changes
 81 | 				formatter.info('No staged changes, staging all changes...');
 82 | 				await gitAdapter.stageFiles(['.']);
 83 | 			}
 84 | 
 85 | 			// Get changed files for scope detection
 86 | 			const gitStatus = await gitAdapter.getStatus();
 87 | 			const changedFiles = [...gitStatus.staged, ...gitStatus.modified];
 88 | 
 89 | 			// Generate commit message
 90 | 			const messageGenerator = new CommitMessageGenerator();
 91 | 			const testResults = workflowContext.lastTestResults;
 92 | 
 93 | 			const commitMessage = messageGenerator.generateMessage({
 94 | 				type: 'feat',
 95 | 				description: status.currentSubtask.title,
 96 | 				changedFiles,
 97 | 				taskId: status.taskId,
 98 | 				phase: status.tddPhase,
 99 | 				tag: (workflowContext.metadata.tag as string) || undefined,
100 | 				testsPassing: testResults?.passed,
101 | 				testsFailing: testResults?.failed,
102 | 				coveragePercent: undefined // Could be added if available
103 | 			});
104 | 
105 | 			// Create commit with metadata
106 | 			await gitAdapter.createCommit(commitMessage, {
107 | 				metadata: {
108 | 					taskId: status.taskId,
109 | 					subtaskId: status.currentSubtask.id,
110 | 					phase: 'COMMIT',
111 | 					tddCycle: 'complete'
112 | 				}
113 | 			});
114 | 
115 | 			// Get commit info
116 | 			const lastCommit = await gitAdapter.getLastCommit();
117 | 
118 | 			// Complete COMMIT phase and advance workflow
119 | 			// This handles all transitions internally with a single WorkflowStateManager
120 | 			const newStatus = await workflowService.commit();
121 | 
122 | 			const isComplete = newStatus.phase === 'COMPLETE';
123 | 
124 | 			// Output success
125 | 			formatter.success('Commit created', {
126 | 				commitHash: lastCommit.hash.substring(0, 7),
127 | 				message: commitMessage.split('\n')[0], // First line only
128 | 				subtask: {
129 | 					id: status.currentSubtask.id,
130 | 					title: status.currentSubtask.title
131 | 				},
132 | 				progress: newStatus.progress,
133 | 				nextAction: isComplete
134 | 					? 'All subtasks complete. Run: autopilot status'
135 | 					: 'Start next subtask with RED phase'
136 | 			});
137 | 		} catch (error) {
138 | 			formatter.error((error as Error).message);
139 | 			if (mergedOptions.verbose) {
140 | 				console.error((error as Error).stack);
141 | 			}
142 | 			process.exit(1);
143 | 		}
144 | 	}
145 | }
146 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/core/context-manager.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * context-manager.js
  3 |  * Context and cache management for Task Master MCP Server
  4 |  */
  5 | 
  6 | import { FastMCP } from 'fastmcp';
  7 | import { LRUCache } from 'lru-cache';
  8 | 
  9 | /**
 10 |  * Configuration options for the ContextManager
 11 |  * @typedef {Object} ContextManagerConfig
 12 |  * @property {number} maxCacheSize - Maximum number of items in the cache
 13 |  * @property {number} ttl - Time to live for cached items in milliseconds
 14 |  * @property {number} maxContextSize - Maximum size of context window in tokens
 15 |  */
 16 | 
 17 | export class ContextManager {
 18 | 	/**
 19 | 	 * Create a new ContextManager instance
 20 | 	 * @param {ContextManagerConfig} config - Configuration options
 21 | 	 */
 22 | 	constructor(config = {}) {
 23 | 		this.config = {
 24 | 			maxCacheSize: config.maxCacheSize || 1000,
 25 | 			ttl: config.ttl || 1000 * 60 * 5, // 5 minutes default
 26 | 			maxContextSize: config.maxContextSize || 4000
 27 | 		};
 28 | 
 29 | 		// Initialize LRU cache for context data
 30 | 		this.cache = new LRUCache({
 31 | 			max: this.config.maxCacheSize,
 32 | 			ttl: this.config.ttl,
 33 | 			updateAgeOnGet: true
 34 | 		});
 35 | 
 36 | 		// Cache statistics
 37 | 		this.stats = {
 38 | 			hits: 0,
 39 | 			misses: 0,
 40 | 			invalidations: 0
 41 | 		};
 42 | 	}
 43 | 
 44 | 	/**
 45 | 	 * Create a new context or retrieve from cache
 46 | 	 * @param {string} contextId - Unique identifier for the context
 47 | 	 * @param {Object} metadata - Additional metadata for the context
 48 | 	 * @returns {Object} Context object with metadata
 49 | 	 */
 50 | 	async getContext(contextId, metadata = {}) {
 51 | 		const cacheKey = this._getCacheKey(contextId, metadata);
 52 | 
 53 | 		// Try to get from cache first
 54 | 		const cached = this.cache.get(cacheKey);
 55 | 		if (cached) {
 56 | 			this.stats.hits++;
 57 | 			return cached;
 58 | 		}
 59 | 
 60 | 		this.stats.misses++;
 61 | 
 62 | 		// Create new context if not in cache
 63 | 		const context = {
 64 | 			id: contextId,
 65 | 			metadata: {
 66 | 				...metadata,
 67 | 				created: new Date().toISOString()
 68 | 			}
 69 | 		};
 70 | 
 71 | 		// Cache the new context
 72 | 		this.cache.set(cacheKey, context);
 73 | 
 74 | 		return context;
 75 | 	}
 76 | 
 77 | 	/**
 78 | 	 * Update an existing context
 79 | 	 * @param {string} contextId - Context identifier
 80 | 	 * @param {Object} updates - Updates to apply to the context
 81 | 	 * @returns {Object} Updated context
 82 | 	 */
 83 | 	async updateContext(contextId, updates) {
 84 | 		const context = await this.getContext(contextId);
 85 | 
 86 | 		// Apply updates to context
 87 | 		Object.assign(context.metadata, updates);
 88 | 
 89 | 		// Update cache
 90 | 		const cacheKey = this._getCacheKey(contextId, context.metadata);
 91 | 		this.cache.set(cacheKey, context);
 92 | 
 93 | 		return context;
 94 | 	}
 95 | 
 96 | 	/**
 97 | 	 * Invalidate a context in the cache
 98 | 	 * @param {string} contextId - Context identifier
 99 | 	 * @param {Object} metadata - Metadata used in the cache key
100 | 	 */
101 | 	invalidateContext(contextId, metadata = {}) {
102 | 		const cacheKey = this._getCacheKey(contextId, metadata);
103 | 		this.cache.delete(cacheKey);
104 | 		this.stats.invalidations++;
105 | 	}
106 | 
107 | 	/**
108 | 	 * Get cached data associated with a specific key.
109 | 	 * Increments cache hit stats if found.
110 | 	 * @param {string} key - The cache key.
111 | 	 * @returns {any | undefined} The cached data or undefined if not found/expired.
112 | 	 */
113 | 	getCachedData(key) {
114 | 		const cached = this.cache.get(key);
115 | 		if (cached !== undefined) {
116 | 			// Check for undefined specifically, as null/false might be valid cached values
117 | 			this.stats.hits++;
118 | 			return cached;
119 | 		}
120 | 		this.stats.misses++;
121 | 		return undefined;
122 | 	}
123 | 
124 | 	/**
125 | 	 * Set data in the cache with a specific key.
126 | 	 * @param {string} key - The cache key.
127 | 	 * @param {any} data - The data to cache.
128 | 	 */
129 | 	setCachedData(key, data) {
130 | 		this.cache.set(key, data);
131 | 	}
132 | 
133 | 	/**
134 | 	 * Invalidate a specific cache key.
135 | 	 * Increments invalidation stats.
136 | 	 * @param {string} key - The cache key to invalidate.
137 | 	 */
138 | 	invalidateCacheKey(key) {
139 | 		this.cache.delete(key);
140 | 		this.stats.invalidations++;
141 | 	}
142 | 
143 | 	/**
144 | 	 * Get cache statistics
145 | 	 * @returns {Object} Cache statistics
146 | 	 */
147 | 	getStats() {
148 | 		return {
149 | 			hits: this.stats.hits,
150 | 			misses: this.stats.misses,
151 | 			invalidations: this.stats.invalidations,
152 | 			size: this.cache.size,
153 | 			maxSize: this.config.maxCacheSize,
154 | 			ttl: this.config.ttl
155 | 		};
156 | 	}
157 | 
158 | 	/**
159 | 	 * Generate a cache key from context ID and metadata
160 | 	 * @private
161 | 	 * @deprecated No longer used for direct cache key generation outside the manager.
162 | 	 *             Prefer generating specific keys in calling functions.
163 | 	 */
164 | 	_getCacheKey(contextId, metadata) {
165 | 		// Kept for potential backward compatibility or internal use if needed later.
166 | 		return `${contextId}:${JSON.stringify(metadata)}`;
167 | 	}
168 | }
169 | 
170 | // Export a singleton instance with default config
171 | export const contextManager = new ContextManager();
172 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/services/commit-message-generator.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * CommitMessageGenerator - Generate conventional commit messages with metadata
  3 |  *
  4 |  * Combines TemplateEngine and ScopeDetector to create structured commit messages
  5 |  * that follow conventional commits specification and include task metadata.
  6 |  */
  7 | 
  8 | import { ScopeDetector } from './scope-detector.js';
  9 | import { TemplateEngine } from './template-engine.js';
 10 | 
 11 | export interface CommitMessageOptions {
 12 | 	type: string;
 13 | 	description: string;
 14 | 	changedFiles: string[];
 15 | 	scope?: string;
 16 | 	body?: string;
 17 | 	breaking?: boolean;
 18 | 	taskId?: string;
 19 | 	phase?: string;
 20 | 	tag?: string;
 21 | 	testsPassing?: number;
 22 | 	testsFailing?: number;
 23 | 	coveragePercent?: number;
 24 | }
 25 | 
 26 | export interface ValidationResult {
 27 | 	isValid: boolean;
 28 | 	errors: string[];
 29 | }
 30 | 
 31 | export interface ParsedCommitMessage {
 32 | 	type: string;
 33 | 	scope?: string;
 34 | 	breaking: boolean;
 35 | 	description: string;
 36 | 	body?: string;
 37 | }
 38 | 
 39 | const CONVENTIONAL_COMMIT_TYPES = [
 40 | 	'feat',
 41 | 	'fix',
 42 | 	'docs',
 43 | 	'style',
 44 | 	'refactor',
 45 | 	'perf',
 46 | 	'test',
 47 | 	'build',
 48 | 	'ci',
 49 | 	'chore',
 50 | 	'revert'
 51 | ];
 52 | 
 53 | export class CommitMessageGenerator {
 54 | 	private templateEngine: TemplateEngine;
 55 | 	private scopeDetector: ScopeDetector;
 56 | 
 57 | 	constructor(
 58 | 		customTemplates?: Record<string, string>,
 59 | 		customScopeMappings?: Record<string, string>,
 60 | 		customScopePriorities?: Record<string, number>
 61 | 	) {
 62 | 		this.templateEngine = new TemplateEngine(customTemplates);
 63 | 		this.scopeDetector = new ScopeDetector(
 64 | 			customScopeMappings,
 65 | 			customScopePriorities
 66 | 		);
 67 | 	}
 68 | 
 69 | 	/**
 70 | 	 * Generate a conventional commit message with metadata
 71 | 	 */
 72 | 	generateMessage(options: CommitMessageOptions): string {
 73 | 		const {
 74 | 			type,
 75 | 			description,
 76 | 			changedFiles,
 77 | 			scope: manualScope,
 78 | 			body,
 79 | 			breaking = false,
 80 | 			taskId,
 81 | 			phase,
 82 | 			tag,
 83 | 			testsPassing,
 84 | 			testsFailing,
 85 | 			coveragePercent
 86 | 		} = options;
 87 | 
 88 | 		// Determine scope (manual override or auto-detect)
 89 | 		const scope = manualScope ?? this.scopeDetector.detectScope(changedFiles);
 90 | 
 91 | 		// Build template variables
 92 | 		const variables = {
 93 | 			type,
 94 | 			scope,
 95 | 			breaking: breaking ? '!' : '',
 96 | 			description,
 97 | 			body,
 98 | 			taskId,
 99 | 			phase,
100 | 			tag,
101 | 			testsPassing,
102 | 			testsFailing,
103 | 			coveragePercent
104 | 		};
105 | 
106 | 		// Generate message from template
107 | 		return this.templateEngine.render('commitMessage', variables);
108 | 	}
109 | 
110 | 	/**
111 | 	 * Validate that a commit message follows conventional commits format
112 | 	 */
113 | 	validateConventionalCommit(message: string): ValidationResult {
114 | 		const errors: string[] = [];
115 | 
116 | 		// Parse first line (header)
117 | 		const lines = message.split('\n');
118 | 		const header = lines[0];
119 | 
120 | 		if (!header) {
121 | 			errors.push('Missing commit message');
122 | 			return { isValid: false, errors };
123 | 		}
124 | 
125 | 		// Check format: type(scope)?: description
126 | 		const headerRegex = /^(\w+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/;
127 | 		const match = header.match(headerRegex);
128 | 
129 | 		if (!match) {
130 | 			errors.push(
131 | 				'Invalid conventional commit format. Expected: type(scope): description'
132 | 			);
133 | 			return { isValid: false, errors };
134 | 		}
135 | 
136 | 		const [, type, , , description] = match;
137 | 
138 | 		// Validate type
139 | 		if (!CONVENTIONAL_COMMIT_TYPES.includes(type)) {
140 | 			errors.push(
141 | 				`Invalid commit type "${type}". Must be one of: ${CONVENTIONAL_COMMIT_TYPES.join(', ')}`
142 | 			);
143 | 		}
144 | 
145 | 		// Validate description
146 | 		if (!description || description.trim().length === 0) {
147 | 			errors.push('Missing description');
148 | 		}
149 | 
150 | 		return {
151 | 			isValid: errors.length === 0,
152 | 			errors
153 | 		};
154 | 	}
155 | 
156 | 	/**
157 | 	 * Parse a conventional commit message into its components
158 | 	 */
159 | 	parseCommitMessage(message: string): ParsedCommitMessage {
160 | 		const lines = message.split('\n');
161 | 		const header = lines[0];
162 | 
163 | 		// Parse header: type(scope)!: description
164 | 		const headerRegex = /^(\w+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/;
165 | 		const match = header.match(headerRegex);
166 | 
167 | 		if (!match) {
168 | 			throw new Error('Invalid conventional commit format');
169 | 		}
170 | 
171 | 		const [, type, scope, breaking, description] = match;
172 | 
173 | 		// Body is everything after the first blank line
174 | 		const bodyStartIndex = lines.findIndex((line, i) => i > 0 && line === '');
175 | 		const body =
176 | 			bodyStartIndex !== -1
177 | 				? lines
178 | 						.slice(bodyStartIndex + 1)
179 | 						.join('\n')
180 | 						.trim()
181 | 				: undefined;
182 | 
183 | 		return {
184 | 			type,
185 | 			scope,
186 | 			breaking: breaking === '!',
187 | 			description,
188 | 			body
189 | 		};
190 | 	}
191 | 
192 | 	/**
193 | 	 * Get the scope detector instance (for testing/customization)
194 | 	 */
195 | 	getScopeDetector(): ScopeDetector {
196 | 		return this.scopeDetector;
197 | 	}
198 | 
199 | 	/**
200 | 	 * Get the template engine instance (for testing/customization)
201 | 	 */
202 | 	getTemplateEngine(): TemplateEngine {
203 | 		return this.templateEngine;
204 | 	}
205 | }
206 | 
```

--------------------------------------------------------------------------------
/.github/scripts/parse-metrics.mjs:
--------------------------------------------------------------------------------

```
  1 | #!/usr/bin/env node
  2 | 
  3 | import { readFileSync, existsSync, writeFileSync } from 'fs';
  4 | 
  5 | function parseMetricsTable(content, metricName) {
  6 | 	const lines = content.split('\n');
  7 | 
  8 | 	for (let i = 0; i < lines.length; i++) {
  9 | 		const line = lines[i].trim();
 10 | 		// Match a markdown table row like: | Metric Name | value | ...
 11 | 		const safeName = metricName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
 12 | 		const re = new RegExp(`^\\|\\s*${safeName}\\s*\\|\\s*([^|]+)\\|?`);
 13 | 		const match = line.match(re);
 14 | 		if (match) {
 15 | 			return match[1].trim() || 'N/A';
 16 | 		}
 17 | 	}
 18 | 	return 'N/A';
 19 | }
 20 | 
 21 | function parseCountMetric(content, metricName) {
 22 | 	const result = parseMetricsTable(content, metricName);
 23 | 	// Extract number from string, handling commas and spaces
 24 | 	const numberMatch = result.toString().match(/[\d,]+/);
 25 | 	if (numberMatch) {
 26 | 		const number = parseInt(numberMatch[0].replace(/,/g, ''));
 27 | 		return isNaN(number) ? 0 : number;
 28 | 	}
 29 | 	return 0;
 30 | }
 31 | 
 32 | function main() {
 33 | 	const metrics = {
 34 | 		issues_created: 0,
 35 | 		issues_closed: 0,
 36 | 		prs_created: 0,
 37 | 		prs_merged: 0,
 38 | 		issue_avg_first_response: 'N/A',
 39 | 		issue_avg_time_to_close: 'N/A',
 40 | 		pr_avg_first_response: 'N/A',
 41 | 		pr_avg_merge_time: 'N/A'
 42 | 	};
 43 | 
 44 | 	// Parse issue metrics
 45 | 	if (existsSync('issue_metrics.md')) {
 46 | 		console.log('📄 Found issue_metrics.md, parsing...');
 47 | 		const issueContent = readFileSync('issue_metrics.md', 'utf8');
 48 | 
 49 | 		metrics.issues_created = parseCountMetric(
 50 | 			issueContent,
 51 | 			'Total number of items created'
 52 | 		);
 53 | 		metrics.issues_closed = parseCountMetric(
 54 | 			issueContent,
 55 | 			'Number of items closed'
 56 | 		);
 57 | 		metrics.issue_avg_first_response = parseMetricsTable(
 58 | 			issueContent,
 59 | 			'Time to first response'
 60 | 		);
 61 | 		metrics.issue_avg_time_to_close = parseMetricsTable(
 62 | 			issueContent,
 63 | 			'Time to close'
 64 | 		);
 65 | 	} else {
 66 | 		console.warn('[parse-metrics] issue_metrics.md not found; using defaults.');
 67 | 	}
 68 | 
 69 | 	// Parse PR created metrics
 70 | 	if (existsSync('pr_created_metrics.md')) {
 71 | 		console.log('📄 Found pr_created_metrics.md, parsing...');
 72 | 		const prCreatedContent = readFileSync('pr_created_metrics.md', 'utf8');
 73 | 
 74 | 		metrics.prs_created = parseCountMetric(
 75 | 			prCreatedContent,
 76 | 			'Total number of items created'
 77 | 		);
 78 | 		metrics.pr_avg_first_response = parseMetricsTable(
 79 | 			prCreatedContent,
 80 | 			'Time to first response'
 81 | 		);
 82 | 	} else {
 83 | 		console.warn(
 84 | 			'[parse-metrics] pr_created_metrics.md not found; using defaults.'
 85 | 		);
 86 | 	}
 87 | 
 88 | 	// Parse PR merged metrics (for more accurate merge data)
 89 | 	if (existsSync('pr_merged_metrics.md')) {
 90 | 		console.log('📄 Found pr_merged_metrics.md, parsing...');
 91 | 		const prMergedContent = readFileSync('pr_merged_metrics.md', 'utf8');
 92 | 
 93 | 		metrics.prs_merged = parseCountMetric(
 94 | 			prMergedContent,
 95 | 			'Total number of items created'
 96 | 		);
 97 | 		// For merged PRs, "Time to close" is actually time to merge
 98 | 		metrics.pr_avg_merge_time = parseMetricsTable(
 99 | 			prMergedContent,
100 | 			'Time to close'
101 | 		);
102 | 	} else {
103 | 		console.warn(
104 | 			'[parse-metrics] pr_merged_metrics.md not found; falling back to pr_metrics.md.'
105 | 		);
106 | 		// Fallback: try old pr_metrics.md if it exists
107 | 		if (existsSync('pr_metrics.md')) {
108 | 			console.log('📄 Falling back to pr_metrics.md...');
109 | 			const prContent = readFileSync('pr_metrics.md', 'utf8');
110 | 
111 | 			const mergedCount = parseCountMetric(prContent, 'Number of items merged');
112 | 			metrics.prs_merged =
113 | 				mergedCount || parseCountMetric(prContent, 'Number of items closed');
114 | 
115 | 			const maybeMergeTime = parseMetricsTable(
116 | 				prContent,
117 | 				'Average time to merge'
118 | 			);
119 | 			metrics.pr_avg_merge_time =
120 | 				maybeMergeTime !== 'N/A'
121 | 					? maybeMergeTime
122 | 					: parseMetricsTable(prContent, 'Time to close');
123 | 		} else {
124 | 			console.warn('[parse-metrics] pr_metrics.md not found; using defaults.');
125 | 		}
126 | 	}
127 | 
128 | 	// Output for GitHub Actions
129 | 	const output = Object.entries(metrics)
130 | 		.map(([key, value]) => `${key}=${value}`)
131 | 		.join('\n');
132 | 
133 | 	// Always output to stdout for debugging
134 | 	console.log('\n=== FINAL METRICS ===');
135 | 	Object.entries(metrics).forEach(([key, value]) => {
136 | 		console.log(`${key}: ${value}`);
137 | 	});
138 | 
139 | 	// Write to GITHUB_OUTPUT if in GitHub Actions
140 | 	if (process.env.GITHUB_OUTPUT) {
141 | 		try {
142 | 			writeFileSync(process.env.GITHUB_OUTPUT, output + '\n', { flag: 'a' });
143 | 			console.log(
144 | 				`\nSuccessfully wrote metrics to ${process.env.GITHUB_OUTPUT}`
145 | 			);
146 | 		} catch (error) {
147 | 			console.error(`Failed to write to GITHUB_OUTPUT: ${error.message}`);
148 | 			process.exit(1);
149 | 		}
150 | 	} else {
151 | 		console.log(
152 | 			'\nNo GITHUB_OUTPUT environment variable found, skipping file write'
153 | 		);
154 | 	}
155 | }
156 | 
157 | main();
158 | 
```

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

```typescript
  1 | /**
  2 |  * Toast Notification Component
  3 |  */
  4 | 
  5 | import React, { useState, useEffect } from 'react';
  6 | import type { ToastNotification as ToastType } from '../types';
  7 | 
  8 | interface ToastNotificationProps {
  9 | 	notification: ToastType;
 10 | 	onDismiss: (id: string) => void;
 11 | }
 12 | 
 13 | export const ToastNotification: React.FC<ToastNotificationProps> = ({
 14 | 	notification,
 15 | 	onDismiss
 16 | }) => {
 17 | 	const [isVisible, setIsVisible] = useState(true);
 18 | 	const [progress, setProgress] = useState(100);
 19 | 	const duration = notification.duration || 5000; // 5 seconds default
 20 | 
 21 | 	useEffect(() => {
 22 | 		const progressInterval = setInterval(() => {
 23 | 			setProgress((prev) => {
 24 | 				const decrease = (100 / duration) * 100; // Update every 100ms
 25 | 				return Math.max(0, prev - decrease);
 26 | 			});
 27 | 		}, 100);
 28 | 
 29 | 		const timeoutId = setTimeout(() => {
 30 | 			setIsVisible(false);
 31 | 			setTimeout(() => onDismiss(notification.id), 300); // Wait for animation
 32 | 		}, duration);
 33 | 
 34 | 		return () => {
 35 | 			clearInterval(progressInterval);
 36 | 			clearTimeout(timeoutId);
 37 | 		};
 38 | 	}, [notification.id, duration, onDismiss]);
 39 | 
 40 | 	const getIcon = () => {
 41 | 		switch (notification.type) {
 42 | 			case 'success':
 43 | 				return (
 44 | 					<svg
 45 | 						className="w-5 h-5 text-green-400"
 46 | 						fill="none"
 47 | 						stroke="currentColor"
 48 | 						viewBox="0 0 24 24"
 49 | 					>
 50 | 						<path
 51 | 							strokeLinecap="round"
 52 | 							strokeLinejoin="round"
 53 | 							strokeWidth={2}
 54 | 							d="M5 13l4 4L19 7"
 55 | 						/>
 56 | 					</svg>
 57 | 				);
 58 | 			case 'info':
 59 | 				return (
 60 | 					<svg
 61 | 						className="w-5 h-5 text-blue-400"
 62 | 						fill="none"
 63 | 						stroke="currentColor"
 64 | 						viewBox="0 0 24 24"
 65 | 					>
 66 | 						<path
 67 | 							strokeLinecap="round"
 68 | 							strokeLinejoin="round"
 69 | 							strokeWidth={2}
 70 | 							d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
 71 | 						/>
 72 | 					</svg>
 73 | 				);
 74 | 			case 'warning':
 75 | 				return (
 76 | 					<svg
 77 | 						className="w-5 h-5 text-yellow-400"
 78 | 						fill="none"
 79 | 						stroke="currentColor"
 80 | 						viewBox="0 0 24 24"
 81 | 					>
 82 | 						<path
 83 | 							strokeLinecap="round"
 84 | 							strokeLinejoin="round"
 85 | 							strokeWidth={2}
 86 | 							d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.667-2.308-1.667-3.08 0L3.34 19c-.77 1.333.192 3 1.732 3z"
 87 | 						/>
 88 | 					</svg>
 89 | 				);
 90 | 			case 'error':
 91 | 				return (
 92 | 					<svg
 93 | 						className="w-5 h-5 text-red-400"
 94 | 						fill="none"
 95 | 						stroke="currentColor"
 96 | 						viewBox="0 0 24 24"
 97 | 					>
 98 | 						<path
 99 | 							strokeLinecap="round"
100 | 							strokeLinejoin="round"
101 | 							strokeWidth={2}
102 | 							d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
103 | 						/>
104 | 					</svg>
105 | 				);
106 | 		}
107 | 	};
108 | 
109 | 	const bgColor = {
110 | 		success: 'bg-green-900/90',
111 | 		info: 'bg-blue-900/90',
112 | 		warning: 'bg-yellow-900/90',
113 | 		error: 'bg-red-900/90'
114 | 	}[notification.type];
115 | 
116 | 	const borderColor = {
117 | 		success: 'border-green-600',
118 | 		info: 'border-blue-600',
119 | 		warning: 'border-yellow-600',
120 | 		error: 'border-red-600'
121 | 	}[notification.type];
122 | 
123 | 	const progressColor = {
124 | 		success: 'bg-green-400',
125 | 		info: 'bg-blue-400',
126 | 		warning: 'bg-yellow-400',
127 | 		error: 'bg-red-400'
128 | 	}[notification.type];
129 | 
130 | 	return (
131 | 		<div
132 | 			className={`${bgColor} ${borderColor} border rounded-lg shadow-lg p-4 mb-2 transition-all duration-300 ${
133 | 				isVisible ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-full'
134 | 			} max-w-sm w-full relative overflow-hidden`}
135 | 		>
136 | 			<div className="flex items-start">
137 | 				<div className="flex-shrink-0">{getIcon()}</div>
138 | 				<div className="ml-3 flex-1">
139 | 					<h3 className="text-sm font-medium text-white">
140 | 						{notification.title}
141 | 					</h3>
142 | 					<p className="mt-1 text-sm text-gray-300">{notification.message}</p>
143 | 				</div>
144 | 				<button
145 | 					onClick={() => onDismiss(notification.id)}
146 | 					className="ml-4 flex-shrink-0 inline-flex text-gray-400 hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
147 | 				>
148 | 					<span className="sr-only">Close</span>
149 | 					<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
150 | 						<path
151 | 							fillRule="evenodd"
152 | 							d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
153 | 							clipRule="evenodd"
154 | 						/>
155 | 					</svg>
156 | 				</button>
157 | 			</div>
158 | 			{/* Progress bar */}
159 | 			<div className="absolute bottom-0 left-0 w-full h-1 bg-gray-700">
160 | 				<div
161 | 					className={`h-full ${progressColor} transition-all duration-100 ease-linear`}
162 | 					style={{ width: `${progress}%` }}
163 | 				/>
164 | 			</div>
165 | 		</div>
166 | 	);
167 | };
168 | 
```

--------------------------------------------------------------------------------
/apps/extension/esbuild.js:
--------------------------------------------------------------------------------

```javascript
  1 | const esbuild = require('esbuild');
  2 | const path = require('path');
  3 | 
  4 | const production = process.argv.includes('--production');
  5 | const watch = process.argv.includes('--watch');
  6 | 
  7 | /**
  8 |  * @type {import('esbuild').Plugin}
  9 |  */
 10 | const esbuildProblemMatcherPlugin = {
 11 | 	name: 'esbuild-problem-matcher',
 12 | 
 13 | 	setup(build) {
 14 | 		build.onStart(() => {
 15 | 			console.log('[watch] build started');
 16 | 		});
 17 | 		build.onEnd((result) => {
 18 | 			result.errors.forEach(({ text, location }) => {
 19 | 				console.error(`✘ [ERROR] ${text}`);
 20 | 				console.error(
 21 | 					`    ${location.file}:${location.line}:${location.column}:`
 22 | 				);
 23 | 			});
 24 | 			console.log('[watch] build finished');
 25 | 		});
 26 | 	}
 27 | };
 28 | 
 29 | /**
 30 |  * @type {import('esbuild').Plugin}
 31 |  */
 32 | const aliasPlugin = {
 33 | 	name: 'alias',
 34 | 	setup(build) {
 35 | 		// Handle @/ aliases for shadcn/ui
 36 | 		build.onResolve({ filter: /^@\// }, (args) => {
 37 | 			const resolvedPath = path.resolve(__dirname, 'src', args.path.slice(2));
 38 | 
 39 | 			// Try to resolve with common TypeScript extensions
 40 | 			const fs = require('fs');
 41 | 			const extensions = ['.tsx', '.ts', '.jsx', '.js'];
 42 | 
 43 | 			// Check if it's a file first
 44 | 			for (const ext of extensions) {
 45 | 				const fullPath = resolvedPath + ext;
 46 | 				if (fs.existsSync(fullPath)) {
 47 | 					return { path: fullPath };
 48 | 				}
 49 | 			}
 50 | 
 51 | 			// Check if it's a directory with index file
 52 | 			for (const ext of extensions) {
 53 | 				const indexPath = path.join(resolvedPath, 'index' + ext);
 54 | 				if (fs.existsSync(indexPath)) {
 55 | 					return { path: indexPath };
 56 | 				}
 57 | 			}
 58 | 
 59 | 			// Fallback to original behavior
 60 | 			return { path: resolvedPath };
 61 | 		});
 62 | 	}
 63 | };
 64 | 
 65 | async function main() {
 66 | 	// Build configuration for the VS Code extension
 67 | 	const extensionCtx = await esbuild.context({
 68 | 		entryPoints: ['src/extension.ts'],
 69 | 		bundle: true,
 70 | 		format: 'cjs',
 71 | 		minify: production,
 72 | 		sourcemap: !production ? 'inline' : false,
 73 | 		sourcesContent: !production,
 74 | 		platform: 'node',
 75 | 		outdir: 'dist',
 76 | 		external: ['vscode'],
 77 | 		logLevel: 'silent',
 78 | 		// Add production optimizations
 79 | 		...(production && {
 80 | 			drop: ['debugger'],
 81 | 			pure: ['console.log', 'console.debug', 'console.trace']
 82 | 		}),
 83 | 		plugins: [esbuildProblemMatcherPlugin, aliasPlugin]
 84 | 	});
 85 | 
 86 | 	// Build configuration for the React webview
 87 | 	const webviewCtx = await esbuild.context({
 88 | 		entryPoints: ['src/webview/index.tsx'],
 89 | 		bundle: true,
 90 | 		format: 'iife',
 91 | 		globalName: 'App',
 92 | 		minify: production,
 93 | 		sourcemap: !production ? 'inline' : false,
 94 | 		sourcesContent: !production,
 95 | 		platform: 'browser',
 96 | 		outdir: 'dist',
 97 | 		logLevel: 'silent',
 98 | 		target: ['es2020'],
 99 | 		jsx: 'automatic',
100 | 		jsxImportSource: 'react',
101 | 		external: ['*.css'],
102 | 		// Bundle React with webview since it's not available in the runtime
103 | 		// This prevents the multiple React instances issue
104 | 		// Ensure React is resolved from the workspace root to avoid duplicates
105 | 		alias: {
106 | 			react: path.resolve(__dirname, '../../node_modules/react'),
107 | 			'react-dom': path.resolve(__dirname, '../../node_modules/react-dom')
108 | 		},
109 | 		define: {
110 | 			'process.env.NODE_ENV': production ? '"production"' : '"development"',
111 | 			global: 'globalThis'
112 | 		},
113 | 		// Add production optimizations for webview too
114 | 		...(production && {
115 | 			drop: ['debugger'],
116 | 			pure: ['console.log', 'console.debug', 'console.trace']
117 | 		}),
118 | 		plugins: [esbuildProblemMatcherPlugin, aliasPlugin]
119 | 	});
120 | 
121 | 	// Build configuration for the React sidebar
122 | 	const sidebarCtx = await esbuild.context({
123 | 		entryPoints: ['src/webview/sidebar.tsx'],
124 | 		bundle: true,
125 | 		format: 'iife',
126 | 		globalName: 'SidebarApp',
127 | 		minify: production,
128 | 		sourcemap: !production ? 'inline' : false,
129 | 		sourcesContent: !production,
130 | 		platform: 'browser',
131 | 		outdir: 'dist',
132 | 		logLevel: 'silent',
133 | 		target: ['es2020'],
134 | 		jsx: 'automatic',
135 | 		jsxImportSource: 'react',
136 | 		external: ['*.css'],
137 | 		alias: {
138 | 			react: path.resolve(__dirname, '../../node_modules/react'),
139 | 			'react-dom': path.resolve(__dirname, '../../node_modules/react-dom')
140 | 		},
141 | 		define: {
142 | 			'process.env.NODE_ENV': production ? '"production"' : '"development"',
143 | 			global: 'globalThis'
144 | 		},
145 | 		...(production && {
146 | 			drop: ['debugger'],
147 | 			pure: ['console.log', 'console.debug', 'console.trace']
148 | 		}),
149 | 		plugins: [esbuildProblemMatcherPlugin, aliasPlugin]
150 | 	});
151 | 
152 | 	if (watch) {
153 | 		await Promise.all([
154 | 			extensionCtx.watch(),
155 | 			webviewCtx.watch(),
156 | 			sidebarCtx.watch()
157 | 		]);
158 | 	} else {
159 | 		await Promise.all([
160 | 			extensionCtx.rebuild(),
161 | 			webviewCtx.rebuild(),
162 | 			sidebarCtx.rebuild()
163 | 		]);
164 | 		await extensionCtx.dispose();
165 | 		await webviewCtx.dispose();
166 | 		await sidebarCtx.dispose();
167 | 	}
168 | }
169 | 
170 | main().catch((e) => {
171 | 	console.error(e);
172 | 	process.exit(1);
173 | });
174 | 
```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/setup.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Common setup for task-manager module tests
  3 |  */
  4 | import { jest } from '@jest/globals';
  5 | 
  6 | // Sample test data
  7 | export const sampleTasks = {
  8 | 	meta: { projectName: 'Test Project' },
  9 | 	tasks: [
 10 | 		{
 11 | 			id: 1,
 12 | 			title: 'Task 1',
 13 | 			description: 'First task description',
 14 | 			status: 'pending',
 15 | 			dependencies: [],
 16 | 			priority: 'high',
 17 | 			details: 'Detailed information for task 1',
 18 | 			testStrategy: 'Test strategy for task 1'
 19 | 		},
 20 | 		{
 21 | 			id: 2,
 22 | 			title: 'Task 2',
 23 | 			description: 'Second task description',
 24 | 			status: 'pending',
 25 | 			dependencies: [1],
 26 | 			priority: 'medium',
 27 | 			details: 'Detailed information for task 2',
 28 | 			testStrategy: 'Test strategy for task 2'
 29 | 		},
 30 | 		{
 31 | 			id: 3,
 32 | 			title: 'Task with Subtasks',
 33 | 			description: 'Task with subtasks description',
 34 | 			status: 'pending',
 35 | 			dependencies: [1, 2],
 36 | 			priority: 'high',
 37 | 			details: 'Detailed information for task 3',
 38 | 			testStrategy: 'Test strategy for task 3',
 39 | 			subtasks: [
 40 | 				{
 41 | 					id: 1,
 42 | 					title: 'Subtask 1',
 43 | 					description: 'First subtask',
 44 | 					status: 'pending',
 45 | 					dependencies: [],
 46 | 					details: 'Details for subtask 1'
 47 | 				},
 48 | 				{
 49 | 					id: 2,
 50 | 					title: 'Subtask 2',
 51 | 					description: 'Second subtask',
 52 | 					status: 'pending',
 53 | 					dependencies: [1],
 54 | 					details: 'Details for subtask 2'
 55 | 				}
 56 | 			]
 57 | 		}
 58 | 	]
 59 | };
 60 | 
 61 | export const emptySampleTasks = {
 62 | 	meta: { projectName: 'Empty Project' },
 63 | 	tasks: []
 64 | };
 65 | 
 66 | export const sampleClaudeResponse = {
 67 | 	tasks: [
 68 | 		{
 69 | 			id: 1,
 70 | 			title: 'Setup Project',
 71 | 			description: 'Initialize the project structure',
 72 | 			status: 'pending',
 73 | 			dependencies: [],
 74 | 			priority: 'high',
 75 | 			details:
 76 | 				'Create repository, configure build system, and setup dev environment',
 77 | 			testStrategy: 'Verify project builds and tests run'
 78 | 		},
 79 | 		{
 80 | 			id: 2,
 81 | 			title: 'Implement Core Feature',
 82 | 			description: 'Create the main functionality',
 83 | 			status: 'pending',
 84 | 			dependencies: [1],
 85 | 			priority: 'high',
 86 | 			details: 'Implement the core business logic for the application',
 87 | 			testStrategy:
 88 | 				'Unit tests for core functions, integration tests for workflows'
 89 | 		}
 90 | 	]
 91 | };
 92 | 
 93 | // Common mock setup function
 94 | export const setupCommonMocks = () => {
 95 | 	// Clear mocks before setup
 96 | 	jest.clearAllMocks();
 97 | 
 98 | 	// Mock implementations
 99 | 	const mocks = {
100 | 		readFileSync: jest.fn(),
101 | 		existsSync: jest.fn(),
102 | 		mkdirSync: jest.fn(),
103 | 		writeFileSync: jest.fn(),
104 | 		readJSON: jest.fn(),
105 | 		writeJSON: jest.fn(),
106 | 		log: jest.fn(),
107 | 		isTaskDependentOn: jest.fn().mockReturnValue(false),
108 | 		formatDependenciesWithStatus: jest.fn(),
109 | 		displayTaskList: jest.fn(),
110 | 		validateAndFixDependencies: jest.fn(),
111 | 		generateObjectService: jest.fn().mockResolvedValue({
112 | 			mainResult: { tasks: [] },
113 | 			telemetryData: {}
114 | 		})
115 | 	};
116 | 
117 | 	return mocks;
118 | };
119 | 
120 | // Helper to create a deep copy of objects to avoid test pollution
121 | export const cloneData = (data) => JSON.parse(JSON.stringify(data));
122 | 
123 | /**
124 |  * Shared mock implementation for getTagAwareFilePath that matches the actual implementation
125 |  * This ensures consistent behavior across all test files, particularly regarding projectRoot handling.
126 |  *
127 |  * The key difference from previous inconsistent implementations was that some tests were not
128 |  * properly handling the projectRoot parameter, leading to different behaviors between test files.
129 |  *
130 |  * @param {string} basePath - The base file path
131 |  * @param {string|null} tag - The tag name (null, undefined, or 'master' uses base path)
132 |  * @param {string} [projectRoot='.'] - The project root directory
133 |  * @returns {string} The resolved file path
134 |  */
135 | export const createGetTagAwareFilePathMock = () => {
136 | 	return jest.fn((basePath, tag, projectRoot = '.') => {
137 | 		// Handle projectRoot consistently - this was the key fix
138 | 		const fullPath = projectRoot ? `${projectRoot}/${basePath}` : basePath;
139 | 
140 | 		if (!tag || tag === 'master') {
141 | 			return fullPath;
142 | 		}
143 | 
144 | 		// Mock the slugification behavior (matches actual implementation)
145 | 		const slugifiedTag = tag.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
146 | 		const idx = fullPath.lastIndexOf('.');
147 | 		return `${fullPath.slice(0, idx)}_${slugifiedTag}${fullPath.slice(idx)}`;
148 | 	});
149 | };
150 | 
151 | /**
152 |  * Shared mock implementation for slugifyTagForFilePath that matches the actual implementation
153 |  * @param {string} tagName - The tag name to slugify
154 |  * @returns {string} Slugified tag name safe for filesystem use
155 |  */
156 | export const createSlugifyTagForFilePathMock = () => {
157 | 	return jest.fn((tagName) => {
158 | 		if (!tagName || typeof tagName !== 'string') {
159 | 			return 'unknown-tag';
160 | 		}
161 | 		return tagName.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
162 | 	});
163 | };
164 | 
```

--------------------------------------------------------------------------------
/tests/unit/profiles/gemini-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('Gemini Profile 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('AGENTS.md')) {
 33 | 				return 'Sample AGENTS.md content for Gemini integration';
 34 | 			}
 35 | 			return '{}';
 36 | 		});
 37 | 		jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
 38 | 		jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
 39 | 	});
 40 | 
 41 | 	afterEach(() => {
 42 | 		// Clean up the temporary directory
 43 | 		try {
 44 | 			fs.rmSync(tempDir, { recursive: true, force: true });
 45 | 		} catch (err) {
 46 | 			console.error(`Error cleaning up: ${err.message}`);
 47 | 		}
 48 | 	});
 49 | 
 50 | 	// Test function that simulates the Gemini profile file copying behavior
 51 | 	function mockCreateGeminiStructure() {
 52 | 		// Gemini profile copies AGENTS.md to GEMINI.md in project root
 53 | 		const sourceContent = 'Sample AGENTS.md content for Gemini integration';
 54 | 		fs.writeFileSync(path.join(tempDir, 'GEMINI.md'), sourceContent);
 55 | 
 56 | 		// Gemini profile creates .gemini directory
 57 | 		fs.mkdirSync(path.join(tempDir, '.gemini'), { recursive: true });
 58 | 
 59 | 		// Gemini profile creates settings.json in .gemini directory
 60 | 		const settingsContent = JSON.stringify(
 61 | 			{
 62 | 				mcpServers: {
 63 | 					'task-master-ai': {
 64 | 						command: 'npx',
 65 | 						args: ['-y', 'task-master-ai'],
 66 | 						env: {
 67 | 							YOUR_ANTHROPIC_API_KEY: 'your-api-key-here',
 68 | 							YOUR_PERPLEXITY_API_KEY: 'your-api-key-here',
 69 | 							YOUR_OPENAI_API_KEY: 'your-api-key-here',
 70 | 							YOUR_GOOGLE_API_KEY: 'your-api-key-here',
 71 | 							YOUR_MISTRAL_API_KEY: 'your-api-key-here',
 72 | 							YOUR_AZURE_OPENAI_API_KEY: 'your-api-key-here',
 73 | 							YOUR_AZURE_OPENAI_ENDPOINT: 'your-endpoint-here',
 74 | 							YOUR_OPENROUTER_API_KEY: 'your-api-key-here',
 75 | 							YOUR_XAI_API_KEY: 'your-api-key-here',
 76 | 							YOUR_OLLAMA_API_KEY: 'your-api-key-here',
 77 | 							YOUR_OLLAMA_BASE_URL: 'http://localhost:11434/api',
 78 | 							YOUR_AWS_ACCESS_KEY_ID: 'your-access-key-id',
 79 | 							YOUR_AWS_SECRET_ACCESS_KEY: 'your-secret-access-key',
 80 | 							YOUR_AWS_REGION: 'us-east-1'
 81 | 						}
 82 | 					}
 83 | 				}
 84 | 			},
 85 | 			null,
 86 | 			2
 87 | 		);
 88 | 		fs.writeFileSync(
 89 | 			path.join(tempDir, '.gemini', 'settings.json'),
 90 | 			settingsContent
 91 | 		);
 92 | 	}
 93 | 
 94 | 	test('creates GEMINI.md file in project root', () => {
 95 | 		// Act
 96 | 		mockCreateGeminiStructure();
 97 | 
 98 | 		// Assert
 99 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
100 | 			path.join(tempDir, 'GEMINI.md'),
101 | 			'Sample AGENTS.md content for Gemini integration'
102 | 		);
103 | 	});
104 | 
105 | 	test('creates .gemini profile directory', () => {
106 | 		// Act
107 | 		mockCreateGeminiStructure();
108 | 
109 | 		// Assert
110 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.gemini'), {
111 | 			recursive: true
112 | 		});
113 | 	});
114 | 
115 | 	test('creates MCP configuration as settings.json', () => {
116 | 		// Act
117 | 		mockCreateGeminiStructure();
118 | 
119 | 		// Assert - Gemini profile should create settings.json instead of mcp.json
120 | 		const writeFileCalls = fs.writeFileSync.mock.calls;
121 | 		const settingsJsonCall = writeFileCalls.find((call) =>
122 | 			call[0].toString().includes('.gemini/settings.json')
123 | 		);
124 | 		expect(settingsJsonCall).toBeDefined();
125 | 	});
126 | 
127 | 	test('uses settings.json instead of mcp.json', () => {
128 | 		// Act
129 | 		mockCreateGeminiStructure();
130 | 
131 | 		// Assert - Should use settings.json, not mcp.json
132 | 		const writeFileCalls = fs.writeFileSync.mock.calls;
133 | 		const mcpJsonCalls = writeFileCalls.filter((call) =>
134 | 			call[0].toString().includes('mcp.json')
135 | 		);
136 | 		expect(mcpJsonCalls).toHaveLength(0);
137 | 
138 | 		const settingsJsonCalls = writeFileCalls.filter((call) =>
139 | 			call[0].toString().includes('settings.json')
140 | 		);
141 | 		expect(settingsJsonCalls).toHaveLength(1);
142 | 	});
143 | 
144 | 	test('renames AGENTS.md to GEMINI.md', () => {
145 | 		// Act
146 | 		mockCreateGeminiStructure();
147 | 
148 | 		// Assert - Gemini should rename AGENTS.md to GEMINI.md
149 | 		const writeFileCalls = fs.writeFileSync.mock.calls;
150 | 		const geminiMdCall = writeFileCalls.find((call) =>
151 | 			call[0].toString().includes('GEMINI.md')
152 | 		);
153 | 		expect(geminiMdCall).toBeDefined();
154 | 		expect(geminiMdCall[0]).toBe(path.join(tempDir, 'GEMINI.md'));
155 | 	});
156 | });
157 | 
```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/update-single-task-status.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Tests for the updateSingleTaskStatus function
  3 |  */
  4 | import { jest } from '@jest/globals';
  5 | 
  6 | // Import test fixtures
  7 | import {
  8 | 	isValidTaskStatus,
  9 | 	TASK_STATUS_OPTIONS
 10 | } from '../../../../../src/constants/task-status.js';
 11 | 
 12 | // Sample tasks data for testing
 13 | const sampleTasks = {
 14 | 	tasks: [
 15 | 		{
 16 | 			id: 1,
 17 | 			title: 'Task 1',
 18 | 			description: 'First task',
 19 | 			status: 'pending',
 20 | 			dependencies: []
 21 | 		},
 22 | 		{
 23 | 			id: 2,
 24 | 			title: 'Task 2',
 25 | 			description: 'Second task',
 26 | 			status: 'pending',
 27 | 			dependencies: []
 28 | 		},
 29 | 		{
 30 | 			id: 3,
 31 | 			title: 'Task 3',
 32 | 			description: 'Third task with subtasks',
 33 | 			status: 'pending',
 34 | 			dependencies: [],
 35 | 			subtasks: [
 36 | 				{
 37 | 					id: 1,
 38 | 					title: 'Subtask 3.1',
 39 | 					description: 'First subtask',
 40 | 					status: 'pending',
 41 | 					dependencies: []
 42 | 				},
 43 | 				{
 44 | 					id: 2,
 45 | 					title: 'Subtask 3.2',
 46 | 					description: 'Second subtask',
 47 | 					status: 'pending',
 48 | 					dependencies: []
 49 | 				}
 50 | 			]
 51 | 		}
 52 | 	]
 53 | };
 54 | 
 55 | // Simplified version of updateSingleTaskStatus for testing
 56 | const testUpdateSingleTaskStatus = (tasksData, taskIdInput, newStatus) => {
 57 | 	if (!isValidTaskStatus(newStatus)) {
 58 | 		throw new Error(
 59 | 			`Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}`
 60 | 		);
 61 | 	}
 62 | 
 63 | 	// Check if it's a subtask (e.g., "1.2")
 64 | 	if (taskIdInput.includes('.')) {
 65 | 		const [parentId, subtaskId] = taskIdInput
 66 | 			.split('.')
 67 | 			.map((id) => parseInt(id, 10));
 68 | 
 69 | 		// Find the parent task
 70 | 		const parentTask = tasksData.tasks.find((t) => t.id === parentId);
 71 | 		if (!parentTask) {
 72 | 			throw new Error(`Parent task ${parentId} not found`);
 73 | 		}
 74 | 
 75 | 		// Find the subtask
 76 | 		if (!parentTask.subtasks) {
 77 | 			throw new Error(`Parent task ${parentId} has no subtasks`);
 78 | 		}
 79 | 
 80 | 		const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
 81 | 		if (!subtask) {
 82 | 			throw new Error(
 83 | 				`Subtask ${subtaskId} not found in parent task ${parentId}`
 84 | 			);
 85 | 		}
 86 | 
 87 | 		// Update the subtask status
 88 | 		subtask.status = newStatus;
 89 | 
 90 | 		// Check if all subtasks are done (if setting to 'done')
 91 | 		if (
 92 | 			newStatus.toLowerCase() === 'done' ||
 93 | 			newStatus.toLowerCase() === 'completed'
 94 | 		) {
 95 | 			const allSubtasksDone = parentTask.subtasks.every(
 96 | 				(st) => st.status === 'done' || st.status === 'completed'
 97 | 			);
 98 | 
 99 | 			// For testing, we don't need to output suggestions
100 | 		}
101 | 	} else {
102 | 		// Handle regular task
103 | 		const taskId = parseInt(taskIdInput, 10);
104 | 		const task = tasksData.tasks.find((t) => t.id === taskId);
105 | 
106 | 		if (!task) {
107 | 			throw new Error(`Task ${taskId} not found`);
108 | 		}
109 | 
110 | 		// Update the task status
111 | 		task.status = newStatus;
112 | 
113 | 		// If marking as done, also mark all subtasks as done
114 | 		if (
115 | 			(newStatus.toLowerCase() === 'done' ||
116 | 				newStatus.toLowerCase() === 'completed') &&
117 | 			task.subtasks &&
118 | 			task.subtasks.length > 0
119 | 		) {
120 | 			task.subtasks.forEach((subtask) => {
121 | 				subtask.status = newStatus;
122 | 			});
123 | 		}
124 | 	}
125 | 
126 | 	return true;
127 | };
128 | 
129 | describe('updateSingleTaskStatus function', () => {
130 | 	test('should update regular task status', async () => {
131 | 		// Arrange
132 | 		const testTasksData = JSON.parse(JSON.stringify(sampleTasks));
133 | 
134 | 		// Act
135 | 		const result = testUpdateSingleTaskStatus(testTasksData, '2', 'done');
136 | 
137 | 		// Assert
138 | 		expect(result).toBe(true);
139 | 		expect(testTasksData.tasks[1].status).toBe('done');
140 | 	});
141 | 
142 | 	test('should throw error for invalid status', async () => {
143 | 		// Arrange
144 | 		const testTasksData = JSON.parse(JSON.stringify(sampleTasks));
145 | 
146 | 		// Assert
147 | 		expect(() =>
148 | 			testUpdateSingleTaskStatus(testTasksData, '2', 'Done')
149 | 		).toThrow(/Error: Invalid status value: Done./);
150 | 	});
151 | 
152 | 	test('should update subtask status', async () => {
153 | 		// Arrange
154 | 		const testTasksData = JSON.parse(JSON.stringify(sampleTasks));
155 | 
156 | 		// Act
157 | 		const result = testUpdateSingleTaskStatus(testTasksData, '3.1', 'done');
158 | 
159 | 		// Assert
160 | 		expect(result).toBe(true);
161 | 		expect(testTasksData.tasks[2].subtasks[0].status).toBe('done');
162 | 	});
163 | 
164 | 	test('should handle parent tasks without subtasks', async () => {
165 | 		// Arrange
166 | 		const testTasksData = JSON.parse(JSON.stringify(sampleTasks));
167 | 
168 | 		// Remove subtasks from task 3
169 | 		const taskWithoutSubtasks = { ...testTasksData.tasks[2] };
170 | 		delete taskWithoutSubtasks.subtasks;
171 | 		testTasksData.tasks[2] = taskWithoutSubtasks;
172 | 
173 | 		// Assert
174 | 		expect(() =>
175 | 			testUpdateSingleTaskStatus(testTasksData, '3.1', 'done')
176 | 		).toThrow('has no subtasks');
177 | 	});
178 | 
179 | 	test('should handle non-existent subtask ID', async () => {
180 | 		// Arrange
181 | 		const testTasksData = JSON.parse(JSON.stringify(sampleTasks));
182 | 
183 | 		// Assert
184 | 		expect(() =>
185 | 			testUpdateSingleTaskStatus(testTasksData, '3.99', 'done')
186 | 		).toThrow('Subtask 99 not found');
187 | 	});
188 | });
189 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/tests/auth/auth-refresh.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import fs from 'fs';
  2 | import os from 'os';
  3 | import path from 'path';
  4 | import type { Session } from '@supabase/supabase-js';
  5 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  6 | import { AuthManager } from '../../src/auth/auth-manager';
  7 | import { CredentialStore } from '../../src/auth/credential-store';
  8 | import type { AuthCredentials } from '../../src/auth/types';
  9 | 
 10 | describe('AuthManager Token Refresh', () => {
 11 | 	let authManager: AuthManager;
 12 | 	let credentialStore: CredentialStore;
 13 | 	let tmpDir: string;
 14 | 	let authFile: string;
 15 | 
 16 | 	beforeEach(() => {
 17 | 		// Reset singletons
 18 | 		AuthManager.resetInstance();
 19 | 		CredentialStore.resetInstance();
 20 | 
 21 | 		// Create temporary directory for test isolation
 22 | 		tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tm-auth-refresh-'));
 23 | 		authFile = path.join(tmpDir, 'auth.json');
 24 | 
 25 | 		// Initialize AuthManager with test config (this will create CredentialStore internally)
 26 | 		authManager = AuthManager.getInstance({
 27 | 			configDir: tmpDir,
 28 | 			configFile: authFile
 29 | 		});
 30 | 
 31 | 		// Get the CredentialStore instance that AuthManager created
 32 | 		credentialStore = CredentialStore.getInstance();
 33 | 		credentialStore.clearCredentials();
 34 | 	});
 35 | 
 36 | 	afterEach(() => {
 37 | 		// Clean up
 38 | 		try {
 39 | 			credentialStore.clearCredentials();
 40 | 		} catch {
 41 | 			// Ignore cleanup errors
 42 | 		}
 43 | 		AuthManager.resetInstance();
 44 | 		CredentialStore.resetInstance();
 45 | 		vi.restoreAllMocks();
 46 | 
 47 | 		// Remove temporary directory
 48 | 		if (tmpDir && fs.existsSync(tmpDir)) {
 49 | 			fs.rmSync(tmpDir, { recursive: true, force: true });
 50 | 		}
 51 | 	});
 52 | 
 53 | 	it('should return expired credentials to enable refresh flows', () => {
 54 | 		// Set up expired credentials with refresh token
 55 | 		const expiredCredentials: AuthCredentials = {
 56 | 			token: 'expired_access_token',
 57 | 			refreshToken: 'valid_refresh_token',
 58 | 			userId: 'test-user-id',
 59 | 			email: '[email protected]',
 60 | 			expiresAt: new Date(Date.now() - 1000).toISOString(), // Expired 1 second ago
 61 | 			savedAt: new Date().toISOString()
 62 | 		};
 63 | 
 64 | 		credentialStore.saveCredentials(expiredCredentials);
 65 | 
 66 | 		// Get credentials should return them even if expired
 67 | 		// Refresh will be handled by explicit calls or client operations
 68 | 		const credentials = authManager.getCredentials();
 69 | 
 70 | 		expect(credentials).not.toBeNull();
 71 | 		expect(credentials?.token).toBe('expired_access_token');
 72 | 		expect(credentials?.refreshToken).toBe('valid_refresh_token');
 73 | 	});
 74 | 
 75 | 	it('should return valid credentials', () => {
 76 | 		// Set up valid (non-expired) credentials
 77 | 		const validCredentials: AuthCredentials = {
 78 | 			token: 'valid_access_token',
 79 | 			refreshToken: 'valid_refresh_token',
 80 | 			userId: 'test-user-id',
 81 | 			email: '[email protected]',
 82 | 			expiresAt: new Date(Date.now() + 3600000).toISOString(), // Expires in 1 hour
 83 | 			savedAt: new Date().toISOString()
 84 | 		};
 85 | 
 86 | 		credentialStore.saveCredentials(validCredentials);
 87 | 
 88 | 		const credentials = authManager.getCredentials();
 89 | 
 90 | 		expect(credentials?.token).toBe('valid_access_token');
 91 | 	});
 92 | 
 93 | 	it('should return expired credentials even without refresh token', () => {
 94 | 		// Set up expired credentials WITHOUT refresh token
 95 | 		// We still return them - it's up to the caller to handle
 96 | 		const expiredCredentials: AuthCredentials = {
 97 | 			token: 'expired_access_token',
 98 | 			refreshToken: undefined,
 99 | 			userId: 'test-user-id',
100 | 			email: '[email protected]',
101 | 			expiresAt: new Date(Date.now() - 1000).toISOString(), // Expired 1 second ago
102 | 			savedAt: new Date().toISOString()
103 | 		};
104 | 
105 | 		credentialStore.saveCredentials(expiredCredentials);
106 | 
107 | 		const credentials = authManager.getCredentials();
108 | 
109 | 		// Returns credentials even if expired
110 | 		expect(credentials).not.toBeNull();
111 | 		expect(credentials?.token).toBe('expired_access_token');
112 | 	});
113 | 
114 | 	it('should return null if no credentials exist', () => {
115 | 		const credentials = authManager.getCredentials();
116 | 		expect(credentials).toBeNull();
117 | 	});
118 | 
119 | 	it('should return credentials regardless of refresh token validity', () => {
120 | 		// Set up expired credentials with refresh token
121 | 		const expiredCredentials: AuthCredentials = {
122 | 			token: 'expired_access_token',
123 | 			refreshToken: 'invalid_refresh_token',
124 | 			userId: 'test-user-id',
125 | 			email: '[email protected]',
126 | 			expiresAt: new Date(Date.now() - 1000).toISOString(),
127 | 			savedAt: new Date().toISOString()
128 | 		};
129 | 
130 | 		credentialStore.saveCredentials(expiredCredentials);
131 | 
132 | 		const credentials = authManager.getCredentials();
133 | 
134 | 		// Returns credentials - refresh will be attempted by the client which will handle failure
135 | 		expect(credentials).not.toBeNull();
136 | 		expect(credentials?.token).toBe('expired_access_token');
137 | 		expect(credentials?.refreshToken).toBe('invalid_refresh_token');
138 | 	});
139 | });
140 | 
```

--------------------------------------------------------------------------------
/src/ai-providers/google-vertex.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * google-vertex.js
  3 |  * AI provider implementation for Google Vertex AI models using Vercel AI SDK.
  4 |  */
  5 | 
  6 | import { createVertex } from '@ai-sdk/google-vertex';
  7 | import { BaseAIProvider } from './base-provider.js';
  8 | import { resolveEnvVariable } from '../../scripts/modules/utils.js';
  9 | import { log } from '../../scripts/modules/utils.js';
 10 | 
 11 | // Vertex-specific error classes
 12 | class VertexAuthError extends Error {
 13 | 	constructor(message) {
 14 | 		super(message);
 15 | 		this.name = 'VertexAuthError';
 16 | 		this.code = 'vertex_auth_error';
 17 | 	}
 18 | }
 19 | 
 20 | class VertexConfigError extends Error {
 21 | 	constructor(message) {
 22 | 		super(message);
 23 | 		this.name = 'VertexConfigError';
 24 | 		this.code = 'vertex_config_error';
 25 | 	}
 26 | }
 27 | 
 28 | class VertexApiError extends Error {
 29 | 	constructor(message, statusCode) {
 30 | 		super(message);
 31 | 		this.name = 'VertexApiError';
 32 | 		this.code = 'vertex_api_error';
 33 | 		this.statusCode = statusCode;
 34 | 	}
 35 | }
 36 | 
 37 | export class VertexAIProvider extends BaseAIProvider {
 38 | 	constructor() {
 39 | 		super();
 40 | 		this.name = 'Google Vertex AI';
 41 | 	}
 42 | 
 43 | 	/**
 44 | 	 * Returns the required API key environment variable name for Google Vertex AI.
 45 | 	 * @returns {string} The environment variable name
 46 | 	 */
 47 | 	getRequiredApiKeyName() {
 48 | 		return 'GOOGLE_API_KEY';
 49 | 	}
 50 | 
 51 | 	/**
 52 | 	 * Validates Vertex AI-specific authentication parameters
 53 | 	 * @param {object} params - Parameters to validate
 54 | 	 * @throws {Error} If required parameters are missing
 55 | 	 */
 56 | 	validateAuth(params) {
 57 | 		const { apiKey, projectId, location, credentials } = params;
 58 | 
 59 | 		// Check for API key OR service account credentials
 60 | 		if (!apiKey && !credentials) {
 61 | 			throw new VertexAuthError(
 62 | 				'Either Google API key (GOOGLE_API_KEY) or service account credentials (GOOGLE_APPLICATION_CREDENTIALS) is required for Vertex AI'
 63 | 			);
 64 | 		}
 65 | 
 66 | 		// Project ID is required for Vertex AI
 67 | 		if (!projectId) {
 68 | 			throw new VertexConfigError(
 69 | 				'Google Cloud project ID is required for Vertex AI. Set VERTEX_PROJECT_ID environment variable.'
 70 | 			);
 71 | 		}
 72 | 
 73 | 		// Location is required for Vertex AI
 74 | 		if (!location) {
 75 | 			throw new VertexConfigError(
 76 | 				'Google Cloud location is required for Vertex AI. Set VERTEX_LOCATION environment variable (e.g., "us-central1").'
 77 | 			);
 78 | 		}
 79 | 	}
 80 | 
 81 | 	/**
 82 | 	 * Creates and returns a Google Vertex AI client instance.
 83 | 	 * @param {object} params - Parameters for client initialization
 84 | 	 * @param {string} [params.apiKey] - Google API key
 85 | 	 * @param {string} params.projectId - Google Cloud project ID
 86 | 	 * @param {string} params.location - Google Cloud location (e.g., "us-central1")
 87 | 	 * @param {object} [params.credentials] - Service account credentials object
 88 | 	 * @param {string} [params.baseURL] - Optional custom API endpoint
 89 | 	 * @returns {Function} Google Vertex AI client function
 90 | 	 * @throws {Error} If required parameters are missing or initialization fails
 91 | 	 */
 92 | 	getClient(params) {
 93 | 		try {
 94 | 			const { apiKey, projectId, location, credentials, baseURL } = params;
 95 | 			const fetchImpl = this.createProxyFetch();
 96 | 
 97 | 			// Configure auth options - either API key or service account
 98 | 			const authOptions = {};
 99 | 			if (apiKey) {
100 | 				authOptions.apiKey = apiKey;
101 | 			} else if (credentials) {
102 | 				authOptions.googleAuthOptions = credentials;
103 | 			}
104 | 
105 | 			// Return Vertex AI client
106 | 			return createVertex({
107 | 				...authOptions,
108 | 				projectId,
109 | 				location,
110 | 				...(baseURL && { baseURL }),
111 | 				...(fetchImpl && { fetch: fetchImpl })
112 | 			});
113 | 		} catch (error) {
114 | 			this.handleError('client initialization', error);
115 | 		}
116 | 	}
117 | 
118 | 	/**
119 | 	 * Handle errors from Vertex AI
120 | 	 * @param {string} operation - Description of the operation that failed
121 | 	 * @param {Error} error - The error object
122 | 	 * @throws {Error} Rethrows the error with additional context
123 | 	 */
124 | 	handleError(operation, error) {
125 | 		log('error', `Vertex AI ${operation} error:`, error);
126 | 
127 | 		// Handle known error types
128 | 		if (
129 | 			error.name === 'VertexAuthError' ||
130 | 			error.name === 'VertexConfigError' ||
131 | 			error.name === 'VertexApiError'
132 | 		) {
133 | 			throw error;
134 | 		}
135 | 
136 | 		// Handle network/API errors
137 | 		if (error.response) {
138 | 			const statusCode = error.response.status;
139 | 			const errorMessage = error.response.data?.error?.message || error.message;
140 | 
141 | 			// Categorize by status code
142 | 			if (statusCode === 401 || statusCode === 403) {
143 | 				throw new VertexAuthError(`Authentication failed: ${errorMessage}`);
144 | 			} else if (statusCode === 400) {
145 | 				throw new VertexConfigError(`Invalid request: ${errorMessage}`);
146 | 			} else {
147 | 				throw new VertexApiError(
148 | 					`API error (${statusCode}): ${errorMessage}`,
149 | 					statusCode
150 | 				);
151 | 			}
152 | 		}
153 | 
154 | 		// Generic error handling
155 | 		throw new Error(`Vertex AI ${operation} failed: ${error.message}`);
156 | 	}
157 | }
158 | 
```

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

```typescript
  1 | /**
  2 |  * @fileoverview Start Command - Initialize and start TDD workflow
  3 |  */
  4 | 
  5 | import { type WorkflowContext, createTmCore } from '@tm/core';
  6 | import { Command } from 'commander';
  7 | import {
  8 | 	AutopilotBaseOptions,
  9 | 	OutputFormatter,
 10 | 	createGitAdapter,
 11 | 	createOrchestrator,
 12 | 	hasWorkflowState,
 13 | 	parseSubtasks,
 14 | 	validateTaskId
 15 | } from './shared.js';
 16 | import { getProjectRoot } from '../../utils/project-root.js';
 17 | 
 18 | interface StartOptions extends AutopilotBaseOptions {
 19 | 	force?: boolean;
 20 | 	maxAttempts?: string;
 21 | }
 22 | 
 23 | /**
 24 |  * Start Command - Initialize new TDD workflow
 25 |  */
 26 | export class StartCommand extends Command {
 27 | 	constructor() {
 28 | 		super('start');
 29 | 
 30 | 		this.description('Initialize and start a new TDD workflow for a task')
 31 | 			.argument('<taskId>', 'Task ID to start workflow for')
 32 | 			.option('-f, --force', 'Force start even if workflow state exists')
 33 | 			.option('--max-attempts <number>', 'Maximum attempts per subtask', '3')
 34 | 			.action(async (taskId: string, options: StartOptions) => {
 35 | 				await this.execute(taskId, options);
 36 | 			});
 37 | 	}
 38 | 
 39 | 	private async execute(taskId: string, options: StartOptions): Promise<void> {
 40 | 		// Inherit parent options
 41 | 		const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
 42 | 		const mergedOptions: StartOptions = {
 43 | 			...parentOpts,
 44 | 			...options,
 45 | 			projectRoot: getProjectRoot(
 46 | 				options.projectRoot || parentOpts?.projectRoot
 47 | 			)
 48 | 		};
 49 | 
 50 | 		const formatter = new OutputFormatter(mergedOptions.json || false);
 51 | 
 52 | 		try {
 53 | 			// Validate task ID
 54 | 			if (!validateTaskId(taskId)) {
 55 | 				formatter.error('Invalid task ID format', {
 56 | 					taskId,
 57 | 					expected: 'Format: number or number.number (e.g., "1" or "1.2")'
 58 | 				});
 59 | 				process.exit(1);
 60 | 			}
 61 | 
 62 | 			// Check for existing workflow state
 63 | 			const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
 64 | 			if (hasState && !mergedOptions.force) {
 65 | 				formatter.error(
 66 | 					'Workflow state already exists. Use --force to overwrite or resume with "autopilot resume"'
 67 | 				);
 68 | 				process.exit(1);
 69 | 			}
 70 | 
 71 | 			// Initialize Task Master Core
 72 | 			const tmCore = await createTmCore({
 73 | 				projectPath: mergedOptions.projectRoot!
 74 | 			});
 75 | 
 76 | 			// Get current tag from ConfigManager
 77 | 			const currentTag = tmCore.config.getActiveTag();
 78 | 
 79 | 			// Load task
 80 | 			formatter.info(`Loading task ${taskId}...`);
 81 | 			const { task } = await tmCore.tasks.get(taskId);
 82 | 
 83 | 			if (!task) {
 84 | 				formatter.error('Task not found', { taskId });
 85 | 				process.exit(1);
 86 | 			}
 87 | 
 88 | 			// Validate task has subtasks
 89 | 			if (!task.subtasks || task.subtasks.length === 0) {
 90 | 				formatter.error('Task has no subtasks. Expand task first.', {
 91 | 					taskId,
 92 | 					suggestion: `Run: task-master expand --id=${taskId}`
 93 | 				});
 94 | 				process.exit(1);
 95 | 			}
 96 | 
 97 | 			// Initialize Git adapter
 98 | 			const gitAdapter = createGitAdapter(mergedOptions.projectRoot!);
 99 | 			await gitAdapter.ensureGitRepository();
100 | 			await gitAdapter.ensureCleanWorkingTree();
101 | 
102 | 			// Parse subtasks
103 | 			const maxAttempts = parseInt(mergedOptions.maxAttempts || '3', 10);
104 | 			const subtasks = parseSubtasks(task, maxAttempts);
105 | 
106 | 			// Create workflow context
107 | 			const context: WorkflowContext = {
108 | 				taskId: task.id,
109 | 				subtasks,
110 | 				currentSubtaskIndex: 0,
111 | 				errors: [],
112 | 				metadata: {
113 | 					startedAt: new Date().toISOString(),
114 | 					tags: task.tags || []
115 | 				}
116 | 			};
117 | 
118 | 			// Create orchestrator with persistence
119 | 			const orchestrator = createOrchestrator(
120 | 				context,
121 | 				mergedOptions.projectRoot!
122 | 			);
123 | 
124 | 			// Complete PREFLIGHT phase
125 | 			orchestrator.transition({ type: 'PREFLIGHT_COMPLETE' });
126 | 
127 | 			// Generate descriptive branch name
128 | 			const sanitizedTitle = task.title
129 | 				.toLowerCase()
130 | 				.replace(/[^a-z0-9]+/g, '-')
131 | 				.replace(/^-+|-+$/g, '')
132 | 				.substring(0, 50);
133 | 			const formattedTaskId = taskId.replace(/\./g, '-');
134 | 			const tagPrefix = currentTag ? `${currentTag}/` : '';
135 | 			const branchName = `${tagPrefix}task-${formattedTaskId}-${sanitizedTitle}`;
136 | 
137 | 			// Create and checkout branch
138 | 			formatter.info(`Creating branch: ${branchName}`);
139 | 			await gitAdapter.createAndCheckoutBranch(branchName);
140 | 
141 | 			// Transition to SUBTASK_LOOP
142 | 			orchestrator.transition({
143 | 				type: 'BRANCH_CREATED',
144 | 				branchName
145 | 			});
146 | 
147 | 			// Output success
148 | 			formatter.success('TDD workflow started', {
149 | 				taskId: task.id,
150 | 				title: task.title,
151 | 				phase: orchestrator.getCurrentPhase(),
152 | 				tddPhase: orchestrator.getCurrentTDDPhase(),
153 | 				branchName,
154 | 				subtasks: subtasks.length,
155 | 				currentSubtask: subtasks[0]?.title
156 | 			});
157 | 
158 | 			// Clean up
159 | 		} catch (error) {
160 | 			formatter.error((error as Error).message);
161 | 			if (mergedOptions.verbose) {
162 | 				console.error((error as Error).stack);
163 | 			}
164 | 			process.exit(1);
165 | 		}
166 | 	}
167 | }
168 | 
```

--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Task Master
  5 |  * Copyright (c) 2025 Eyal Toledano, Ralph Khreish
  6 |  *
  7 |  * This software is licensed under the MIT License with Commons Clause.
  8 |  * You may use this software for any purpose, including commercial applications,
  9 |  * and modify and redistribute it freely, subject to the following restrictions:
 10 |  *
 11 |  * 1. You may not sell this software or offer it as a service.
 12 |  * 2. The origin of this software must not be misrepresented.
 13 |  * 3. Altered source versions must be plainly marked as such.
 14 |  *
 15 |  * For the full license text, see the LICENSE file in the root directory.
 16 |  */
 17 | 
 18 | /**
 19 |  * Claude Task Master
 20 |  * A task management system for AI-driven development with Claude
 21 |  */
 22 | 
 23 | // This file serves as the main entry point for the package
 24 | // The primary functionality is provided through the CLI commands
 25 | 
 26 | import { fileURLToPath } from 'url';
 27 | import { dirname, resolve } from 'path';
 28 | import { createRequire } from 'module';
 29 | import { spawn } from 'child_process';
 30 | import { Command } from 'commander';
 31 | 
 32 | const __filename = fileURLToPath(import.meta.url);
 33 | const __dirname = dirname(__filename);
 34 | const require = createRequire(import.meta.url);
 35 | 
 36 | // Get package information
 37 | const packageJson = require('./package.json');
 38 | 
 39 | // Export the path to the dev.js script for programmatic usage
 40 | export const devScriptPath = resolve(__dirname, './scripts/dev.js');
 41 | 
 42 | // Export a function to initialize a new project programmatically
 43 | export const initProject = async (options = {}) => {
 44 | 	const init = await import('./scripts/init.js');
 45 | 	return init.initializeProject(options);
 46 | };
 47 | 
 48 | // Export a function to run init as a CLI command
 49 | export const runInitCLI = async (options = {}) => {
 50 | 	try {
 51 | 		const init = await import('./scripts/init.js');
 52 | 		const result = await init.initializeProject(options);
 53 | 		return result;
 54 | 	} catch (error) {
 55 | 		console.error('Initialization failed:', error.message);
 56 | 		if (process.env.DEBUG === 'true') {
 57 | 			console.error('Debug stack trace:', error.stack);
 58 | 		}
 59 | 		throw error; // Re-throw to be handled by the command handler
 60 | 	}
 61 | };
 62 | 
 63 | // Export version information
 64 | export const version = packageJson.version;
 65 | 
 66 | // CLI implementation
 67 | if (import.meta.url === `file://${process.argv[1]}`) {
 68 | 	const program = new Command();
 69 | 
 70 | 	program
 71 | 		.name('task-master')
 72 | 		.description('Claude Task Master CLI')
 73 | 		.version(version);
 74 | 
 75 | 	program
 76 | 		.command('init')
 77 | 		.description('Initialize a new project')
 78 | 		.option('-y, --yes', 'Skip prompts and use default values')
 79 | 		.option('-n, --name <n>', 'Project name')
 80 | 		.option('-d, --description <description>', 'Project description')
 81 | 		.option('-v, --version <version>', 'Project version', '0.1.0')
 82 | 		.option('-a, --author <author>', 'Author name')
 83 | 		.option('--skip-install', 'Skip installing dependencies')
 84 | 		.option('--dry-run', 'Show what would be done without making changes')
 85 | 		.option('--aliases', 'Add shell aliases (tm, taskmaster)')
 86 | 		.option('--no-aliases', 'Skip shell aliases (tm, taskmaster)')
 87 | 		.option('--git', 'Initialize Git repository')
 88 | 		.option('--no-git', 'Skip Git repository initialization')
 89 | 		.option('--git-tasks', 'Store tasks in Git')
 90 | 		.option('--no-git-tasks', 'No Git storage of tasks')
 91 | 		.action(async (cmdOptions) => {
 92 | 			try {
 93 | 				await runInitCLI(cmdOptions);
 94 | 			} catch (err) {
 95 | 				console.error('Init failed:', err.message);
 96 | 				process.exit(1);
 97 | 			}
 98 | 		});
 99 | 
100 | 	program
101 | 		.command('dev')
102 | 		.description('Run the dev.js script')
103 | 		.allowUnknownOption(true)
104 | 		.action(() => {
105 | 			const args = process.argv.slice(process.argv.indexOf('dev') + 1);
106 | 			const child = spawn('node', [devScriptPath, ...args], {
107 | 				stdio: 'inherit',
108 | 				cwd: process.cwd()
109 | 			});
110 | 
111 | 			child.on('close', (code) => {
112 | 				process.exit(code);
113 | 			});
114 | 		});
115 | 
116 | 	// Add shortcuts for common dev.js commands
117 | 	program
118 | 		.command('list')
119 | 		.description('List all tasks')
120 | 		.action(() => {
121 | 			const child = spawn('node', [devScriptPath, 'list'], {
122 | 				stdio: 'inherit',
123 | 				cwd: process.cwd()
124 | 			});
125 | 
126 | 			child.on('close', (code) => {
127 | 				process.exit(code);
128 | 			});
129 | 		});
130 | 
131 | 	program
132 | 		.command('next')
133 | 		.description('Show the next task to work on')
134 | 		.action(() => {
135 | 			const child = spawn('node', [devScriptPath, 'next'], {
136 | 				stdio: 'inherit',
137 | 				cwd: process.cwd()
138 | 			});
139 | 
140 | 			child.on('close', (code) => {
141 | 				process.exit(code);
142 | 			});
143 | 		});
144 | 
145 | 	program
146 | 		.command('generate')
147 | 		.description('Generate task files')
148 | 		.action(() => {
149 | 			const child = spawn('node', [devScriptPath, 'generate'], {
150 | 				stdio: 'inherit',
151 | 				cwd: process.cwd()
152 | 			});
153 | 
154 | 			child.on('close', (code) => {
155 | 				process.exit(code);
156 | 			});
157 | 		});
158 | 
159 | 	program.parse(process.argv);
160 | }
161 | 
```

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

```typescript
  1 | /**
  2 |  * @fileoverview get-tasks MCP tool
  3 |  * Get all tasks from Task Master with optional filtering
  4 |  */
  5 | 
  6 | import { z } from 'zod';
  7 | import {
  8 | 	handleApiResult,
  9 | 	withNormalizedProjectRoot
 10 | } from '../../shared/utils.js';
 11 | import type { MCPContext } from '../../shared/types.js';
 12 | import { createTmCore, type TaskStatus, type Task } from '@tm/core';
 13 | import type { FastMCP } from 'fastmcp';
 14 | 
 15 | const GetTasksSchema = z.object({
 16 | 	projectRoot: z
 17 | 		.string()
 18 | 		.describe('The directory of the project. Must be an absolute path.'),
 19 | 	status: z
 20 | 		.string()
 21 | 		.optional()
 22 | 		.describe(
 23 | 			"Filter tasks by status (e.g., 'pending', 'done') or multiple statuses separated by commas (e.g., 'blocked,deferred')"
 24 | 		),
 25 | 	withSubtasks: z
 26 | 		.boolean()
 27 | 		.optional()
 28 | 		.describe('Include subtasks nested within their parent tasks in the response'),
 29 | 	tag: z.string().optional().describe('Tag context to operate on')
 30 | });
 31 | 
 32 | type GetTasksArgs = z.infer<typeof GetTasksSchema>;
 33 | 
 34 | /**
 35 |  * Register the get_tasks tool with the MCP server
 36 |  */
 37 | export function registerGetTasksTool(server: FastMCP) {
 38 | 	server.addTool({
 39 | 		name: 'get_tasks',
 40 | 		description:
 41 | 			'Get all tasks from Task Master, optionally filtering by status and including subtasks.',
 42 | 		parameters: GetTasksSchema,
 43 | 		execute: withNormalizedProjectRoot(
 44 | 			async (args: GetTasksArgs, context: MCPContext) => {
 45 | 				const { projectRoot, status, withSubtasks, tag } = args;
 46 | 
 47 | 				try {
 48 | 					context.log.info(
 49 | 						`Getting tasks from ${projectRoot}${status ? ` with status filter: ${status}` : ''}${tag ? ` for tag: ${tag}` : ''}`
 50 | 					);
 51 | 
 52 | 					// Create tm-core with logging callback
 53 | 					const tmCore = await createTmCore({
 54 | 						projectPath: projectRoot,
 55 | 						loggerConfig: {
 56 | 							mcpMode: true,
 57 | 							logCallback: context.log
 58 | 						}
 59 | 					});
 60 | 
 61 | 					// Build filter
 62 | 					const filter =
 63 | 						status && status !== 'all'
 64 | 							? {
 65 | 									status: status
 66 | 										.split(',')
 67 | 										.map((s: string) => s.trim() as TaskStatus)
 68 | 								}
 69 | 							: undefined;
 70 | 
 71 | 					// Call tm-core tasks.list()
 72 | 					const result = await tmCore.tasks.list({
 73 | 						tag,
 74 | 						filter,
 75 | 						includeSubtasks: withSubtasks
 76 | 					});
 77 | 
 78 | 					context.log.info(
 79 | 						`Retrieved ${result.tasks?.length || 0} tasks (${result.filtered} filtered, ${result.total} total)`
 80 | 					);
 81 | 
 82 | 					// Calculate stats using reduce for cleaner code
 83 | 					const totalTasks = result.total;
 84 | 					const taskCounts = result.tasks.reduce(
 85 | 						(acc, task) => {
 86 | 							acc[task.status] = (acc[task.status] || 0) + 1;
 87 | 							return acc;
 88 | 						},
 89 | 						{} as Record<string, number>
 90 | 					);
 91 | 
 92 | 					const completionPercentage =
 93 | 						totalTasks > 0 ? ((taskCounts.done || 0) / totalTasks) * 100 : 0;
 94 | 
 95 | 					// Count subtasks using reduce
 96 | 					const subtaskCounts = result.tasks.reduce(
 97 | 						(acc, task) => {
 98 | 							task.subtasks?.forEach((st) => {
 99 | 								acc.total++;
100 | 								acc[st.status] = (acc[st.status] || 0) + 1;
101 | 							});
102 | 							return acc;
103 | 						},
104 | 						{ total: 0 } as Record<string, number>
105 | 					);
106 | 
107 | 					const subtaskCompletionPercentage =
108 | 						subtaskCounts.total > 0
109 | 							? ((subtaskCounts.done || 0) / subtaskCounts.total) * 100
110 | 							: 0;
111 | 
112 | 					return handleApiResult({
113 | 						result: {
114 | 							success: true,
115 | 							data: {
116 | 								tasks: result.tasks as Task[],
117 | 								filter: status || 'all',
118 | 								stats: {
119 | 									total: totalTasks,
120 | 									completed: taskCounts.done || 0,
121 | 									inProgress: taskCounts['in-progress'] || 0,
122 | 									pending: taskCounts.pending || 0,
123 | 									blocked: taskCounts.blocked || 0,
124 | 									deferred: taskCounts.deferred || 0,
125 | 									cancelled: taskCounts.cancelled || 0,
126 | 									review: taskCounts.review || 0,
127 | 									completionPercentage,
128 | 									subtasks: {
129 | 										total: subtaskCounts.total,
130 | 										completed: subtaskCounts.done || 0,
131 | 										inProgress: subtaskCounts['in-progress'] || 0,
132 | 										pending: subtaskCounts.pending || 0,
133 | 										blocked: subtaskCounts.blocked || 0,
134 | 										deferred: subtaskCounts.deferred || 0,
135 | 										cancelled: subtaskCounts.cancelled || 0,
136 | 										completionPercentage: subtaskCompletionPercentage
137 | 									}
138 | 								}
139 | 							}
140 | 						},
141 | 						log: context.log,
142 | 						projectRoot,
143 | 						tag: result.tag
144 | 					});
145 | 				} catch (error: any) {
146 | 					context.log.error(`Error in get-tasks: ${error.message}`);
147 | 					if (error.stack) {
148 | 						context.log.debug(error.stack);
149 | 					}
150 | 					return handleApiResult({
151 | 						result: {
152 | 							success: false,
153 | 							error: {
154 | 								message: `Failed to get tasks: ${error.message}`
155 | 							}
156 | 						},
157 | 						log: context.log,
158 | 						projectRoot
159 | 					});
160 | 				}
161 | 			}
162 | 		)
163 | 	});
164 | }
165 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/storage/adapters/activity-logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Activity.jsonl append-only logging system for workflow tracking.
  3 |  * Uses newline-delimited JSON (JSONL) format for structured event logging.
  4 |  *
  5 |  * @module activity-logger
  6 |  */
  7 | 
  8 | import path from 'path';
  9 | import fs from 'fs-extra';
 10 | 
 11 | /**
 12 |  * Activity log entry structure
 13 |  */
 14 | export interface ActivityEvent {
 15 | 	timestamp: string;
 16 | 	type: string;
 17 | 	[key: string]: any;
 18 | }
 19 | 
 20 | /**
 21 |  * Filter criteria for activity log queries
 22 |  */
 23 | export interface ActivityFilter {
 24 | 	type?: string;
 25 | 	timestampFrom?: string;
 26 | 	timestampTo?: string;
 27 | 	predicate?: (event: ActivityEvent) => boolean;
 28 | }
 29 | 
 30 | /**
 31 |  * Appends an activity event to the log file.
 32 |  * Uses atomic append operations to ensure data integrity.
 33 |  *
 34 |  * @param {string} activityPath - Path to the activity.jsonl file
 35 |  * @param {Omit<ActivityEvent, 'timestamp'>} event - Event data to log (timestamp added automatically)
 36 |  * @returns {Promise<void>}
 37 |  *
 38 |  * @example
 39 |  * await logActivity('/path/to/activity.jsonl', {
 40 |  *   type: 'phase-start',
 41 |  *   phase: 'red'
 42 |  * });
 43 |  */
 44 | export async function logActivity(
 45 | 	activityPath: string,
 46 | 	event: Omit<ActivityEvent, 'timestamp'>
 47 | ): Promise<void> {
 48 | 	// Add timestamp to event
 49 | 	const logEntry = {
 50 | 		...event,
 51 | 		timestamp: new Date().toISOString()
 52 | 	} as ActivityEvent;
 53 | 
 54 | 	// Ensure directory exists
 55 | 	await fs.ensureDir(path.dirname(activityPath));
 56 | 
 57 | 	// Convert to JSONL format (single line with newline)
 58 | 	const line = JSON.stringify(logEntry) + '\n';
 59 | 
 60 | 	// Append to file atomically
 61 | 	// Using 'a' flag ensures atomic append on most systems
 62 | 	await fs.appendFile(activityPath, line, 'utf-8');
 63 | }
 64 | 
 65 | /**
 66 |  * Reads and parses all events from an activity log file.
 67 |  * Returns events in chronological order.
 68 |  *
 69 |  * @param {string} activityPath - Path to the activity.jsonl file
 70 |  * @returns {Promise<ActivityEvent[]>} Array of activity events
 71 |  * @throws {Error} If file contains invalid JSON
 72 |  *
 73 |  * @example
 74 |  * const events = await readActivityLog('/path/to/activity.jsonl');
 75 |  * console.log(`Found ${events.length} events`);
 76 |  */
 77 | export async function readActivityLog(
 78 | 	activityPath: string
 79 | ): Promise<ActivityEvent[]> {
 80 | 	// Return empty array if file doesn't exist
 81 | 	if (!(await fs.pathExists(activityPath))) {
 82 | 		return [];
 83 | 	}
 84 | 
 85 | 	// Read file content
 86 | 	const content = await fs.readFile(activityPath, 'utf-8');
 87 | 
 88 | 	// Parse JSONL (newline-delimited JSON)
 89 | 	const lines = content.trim().split('\n');
 90 | 	const events: ActivityEvent[] = [];
 91 | 
 92 | 	for (let i = 0; i < lines.length; i++) {
 93 | 		const line = lines[i].trim();
 94 | 
 95 | 		// Skip empty lines
 96 | 		if (!line) {
 97 | 			continue;
 98 | 		}
 99 | 
100 | 		// Parse JSON
101 | 		try {
102 | 			const event = JSON.parse(line);
103 | 			events.push(event);
104 | 		} catch (error) {
105 | 			const errorMessage =
106 | 				error instanceof Error ? error.message : String(error);
107 | 			throw new Error(`Invalid JSON at line ${i + 1}: ${errorMessage}`);
108 | 		}
109 | 	}
110 | 
111 | 	return events;
112 | }
113 | 
114 | /**
115 |  * Filters activity log events based on criteria.
116 |  * Supports filtering by event type, timestamp range, and custom predicates.
117 |  *
118 |  * @param {string} activityPath - Path to the activity.jsonl file
119 |  * @param {ActivityFilter} filter - Filter criteria
120 |  * @returns {Promise<ActivityEvent[]>} Filtered array of events
121 |  *
122 |  * @example
123 |  * // Filter by event type
124 |  * const phaseEvents = await filterActivityLog('/path/to/activity.jsonl', {
125 |  *   type: 'phase-start'
126 |  * });
127 |  *
128 |  * // Filter by timestamp range
129 |  * const recentEvents = await filterActivityLog('/path/to/activity.jsonl', {
130 |  *   timestampFrom: '2024-01-15T10:00:00.000Z'
131 |  * });
132 |  *
133 |  * // Filter with custom predicate
134 |  * const failedTests = await filterActivityLog('/path/to/activity.jsonl', {
135 |  *   predicate: (event) => event.type === 'test-run' && event.result === 'fail'
136 |  * });
137 |  */
138 | export async function filterActivityLog(
139 | 	activityPath: string,
140 | 	filter: ActivityFilter & Record<string, any>
141 | ): Promise<ActivityEvent[]> {
142 | 	const events = await readActivityLog(activityPath);
143 | 
144 | 	return events.filter((event) => {
145 | 		// Filter by type
146 | 		if (filter.type && event.type !== filter.type) {
147 | 			return false;
148 | 		}
149 | 
150 | 		// Filter by timestamp range
151 | 		if (filter.timestampFrom && event.timestamp < filter.timestampFrom) {
152 | 			return false;
153 | 		}
154 | 
155 | 		if (filter.timestampTo && event.timestamp > filter.timestampTo) {
156 | 			return false;
157 | 		}
158 | 
159 | 		// Filter by custom predicate
160 | 		if (filter.predicate && !filter.predicate(event)) {
161 | 			return false;
162 | 		}
163 | 
164 | 		// Filter by other fields (exact match)
165 | 		for (const [key, value] of Object.entries(filter)) {
166 | 			if (
167 | 				key === 'type' ||
168 | 				key === 'timestampFrom' ||
169 | 				key === 'timestampTo' ||
170 | 				key === 'predicate'
171 | 			) {
172 | 				continue;
173 | 			}
174 | 
175 | 			if (event[key] !== value) {
176 | 				return false;
177 | 			}
178 | 		}
179 | 
180 | 		return true;
181 | 	});
182 | }
183 | 
```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/find-next-task.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { log } from '../utils.js';
  2 | import { addComplexityToTask } from '../utils.js';
  3 | 
  4 | /**
  5 |  * Return the next work item:
  6 |  *   •  Prefer an eligible SUBTASK that belongs to any parent task
  7 |  *      whose own status is `in-progress`.
  8 |  *   •  If no such subtask exists, fall back to the best top-level task
  9 |  *      (previous behaviour).
 10 |  *
 11 |  * The function still exports the same name (`findNextTask`) so callers
 12 |  * don't need to change.  It now always returns an object with
 13 |  *  ─ id            →  number  (task)  or  "parentId.subId"  (subtask)
 14 |  *  ─ title         →  string
 15 |  *  ─ status        →  string
 16 |  *  ─ priority      →  string  ("high" | "medium" | "low")
 17 |  *  ─ dependencies  →  array   (all IDs expressed in the same dotted form)
 18 |  *  ─ parentId      →  number  (present only when it's a subtask)
 19 |  *
 20 |  * @param {Object[]} tasks  – full array of top-level tasks, each may contain .subtasks[]
 21 |  * @param {Object} [complexityReport=null] - Optional complexity report object
 22 |  * @returns {Object|null}   – next work item or null if nothing is eligible
 23 |  */
 24 | function findNextTask(tasks, complexityReport = null) {
 25 | 	// ---------- helpers ----------------------------------------------------
 26 | 	const priorityValues = { high: 3, medium: 2, low: 1 };
 27 | 
 28 | 	const toFullSubId = (parentId, maybeDotId) => {
 29 | 		//  "12.3"  ->  "12.3"
 30 | 		//        4 ->  "12.4"   (numeric / short form)
 31 | 		if (typeof maybeDotId === 'string' && maybeDotId.includes('.')) {
 32 | 			return maybeDotId;
 33 | 		}
 34 | 		return `${parentId}.${maybeDotId}`;
 35 | 	};
 36 | 
 37 | 	// ---------- build completed-ID set (tasks *and* subtasks) --------------
 38 | 	const completedIds = new Set();
 39 | 	tasks.forEach((t) => {
 40 | 		if (t.status === 'done' || t.status === 'completed') {
 41 | 			completedIds.add(String(t.id));
 42 | 		}
 43 | 		if (Array.isArray(t.subtasks)) {
 44 | 			t.subtasks.forEach((st) => {
 45 | 				if (st.status === 'done' || st.status === 'completed') {
 46 | 					completedIds.add(`${t.id}.${st.id}`);
 47 | 				}
 48 | 			});
 49 | 		}
 50 | 	});
 51 | 
 52 | 	// ---------- 1) look for eligible subtasks ------------------------------
 53 | 	const candidateSubtasks = [];
 54 | 
 55 | 	tasks
 56 | 		.filter((t) => t.status === 'in-progress' && Array.isArray(t.subtasks))
 57 | 		.forEach((parent) => {
 58 | 			parent.subtasks.forEach((st) => {
 59 | 				const stStatus = (st.status || 'pending').toLowerCase();
 60 | 				if (stStatus !== 'pending' && stStatus !== 'in-progress') return;
 61 | 
 62 | 				const fullDeps =
 63 | 					st.dependencies?.map((d) => toFullSubId(parent.id, d)) ?? [];
 64 | 
 65 | 				const depsSatisfied =
 66 | 					fullDeps.length === 0 ||
 67 | 					fullDeps.every((depId) => completedIds.has(String(depId)));
 68 | 
 69 | 				if (depsSatisfied) {
 70 | 					candidateSubtasks.push({
 71 | 						id: `${parent.id}.${st.id}`,
 72 | 						title: st.title || `Subtask ${st.id}`,
 73 | 						status: st.status || 'pending',
 74 | 						priority: st.priority || parent.priority || 'medium',
 75 | 						dependencies: fullDeps,
 76 | 						parentId: parent.id
 77 | 					});
 78 | 				}
 79 | 			});
 80 | 		});
 81 | 
 82 | 	if (candidateSubtasks.length > 0) {
 83 | 		// sort by priority → dep-count → parent-id → sub-id
 84 | 		candidateSubtasks.sort((a, b) => {
 85 | 			const pa = priorityValues[a.priority] ?? 2;
 86 | 			const pb = priorityValues[b.priority] ?? 2;
 87 | 			if (pb !== pa) return pb - pa;
 88 | 
 89 | 			if (a.dependencies.length !== b.dependencies.length)
 90 | 				return a.dependencies.length - b.dependencies.length;
 91 | 
 92 | 			// compare parent then sub-id numerically
 93 | 			const [aPar, aSub] = a.id.split('.').map(Number);
 94 | 			const [bPar, bSub] = b.id.split('.').map(Number);
 95 | 			if (aPar !== bPar) return aPar - bPar;
 96 | 			return aSub - bSub;
 97 | 		});
 98 | 		const nextTask = candidateSubtasks[0];
 99 | 
100 | 		// Add complexity to the task before returning
101 | 		if (nextTask && complexityReport) {
102 | 			addComplexityToTask(nextTask, complexityReport);
103 | 		}
104 | 
105 | 		return nextTask;
106 | 	}
107 | 
108 | 	// ---------- 2) fall back to top-level tasks (original logic) ------------
109 | 	const eligibleTasks = tasks.filter((task) => {
110 | 		const status = (task.status || 'pending').toLowerCase();
111 | 		if (status !== 'pending' && status !== 'in-progress') return false;
112 | 		const deps = task.dependencies ?? [];
113 | 		return deps.every((depId) => completedIds.has(String(depId)));
114 | 	});
115 | 
116 | 	if (eligibleTasks.length === 0) return null;
117 | 
118 | 	const nextTask = eligibleTasks.sort((a, b) => {
119 | 		const pa = priorityValues[a.priority || 'medium'] ?? 2;
120 | 		const pb = priorityValues[b.priority || 'medium'] ?? 2;
121 | 		if (pb !== pa) return pb - pa;
122 | 
123 | 		const da = (a.dependencies ?? []).length;
124 | 		const db = (b.dependencies ?? []).length;
125 | 		if (da !== db) return da - db;
126 | 
127 | 		return a.id - b.id;
128 | 	})[0];
129 | 
130 | 	// Add complexity to the task before returning
131 | 	if (nextTask && complexityReport) {
132 | 		addComplexityToTask(nextTask, complexityReport);
133 | 	}
134 | 
135 | 	return nextTask;
136 | }
137 | 
138 | export default findNextTask;
139 | 
```

--------------------------------------------------------------------------------
/tests/unit/ai-providers/openai.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Tests for OpenAI Provider
  3 |  *
  4 |  * This test suite covers:
  5 |  * 1. Validation of maxTokens parameter
  6 |  * 2. Client creation and configuration
  7 |  * 3. Model handling
  8 |  */
  9 | 
 10 | import { jest } from '@jest/globals';
 11 | 
 12 | // Mock the utils module to prevent logging during tests
 13 | jest.mock('../../../scripts/modules/utils.js', () => ({
 14 | 	log: jest.fn()
 15 | }));
 16 | 
 17 | // Import the provider
 18 | import { OpenAIProvider } from '../../../src/ai-providers/openai.js';
 19 | 
 20 | describe('OpenAIProvider', () => {
 21 | 	let provider;
 22 | 
 23 | 	beforeEach(() => {
 24 | 		provider = new OpenAIProvider();
 25 | 		jest.clearAllMocks();
 26 | 	});
 27 | 
 28 | 	describe('validateOptionalParams', () => {
 29 | 		it('should accept valid maxTokens values', () => {
 30 | 			expect(() =>
 31 | 				provider.validateOptionalParams({ maxTokens: 1000 })
 32 | 			).not.toThrow();
 33 | 			expect(() =>
 34 | 				provider.validateOptionalParams({ maxTokens: 1 })
 35 | 			).not.toThrow();
 36 | 			expect(() =>
 37 | 				provider.validateOptionalParams({ maxTokens: '1000' })
 38 | 			).not.toThrow();
 39 | 		});
 40 | 
 41 | 		it('should reject invalid maxTokens values', () => {
 42 | 			expect(() => provider.validateOptionalParams({ maxTokens: 0 })).toThrow(
 43 | 				Error
 44 | 			);
 45 | 			expect(() => provider.validateOptionalParams({ maxTokens: -1 })).toThrow(
 46 | 				Error
 47 | 			);
 48 | 			expect(() => provider.validateOptionalParams({ maxTokens: NaN })).toThrow(
 49 | 				Error
 50 | 			);
 51 | 			expect(() =>
 52 | 				provider.validateOptionalParams({ maxTokens: Infinity })
 53 | 			).toThrow(Error);
 54 | 			expect(() =>
 55 | 				provider.validateOptionalParams({ maxTokens: 'invalid' })
 56 | 			).toThrow(Error);
 57 | 		});
 58 | 
 59 | 		it('should accept valid temperature values', () => {
 60 | 			expect(() =>
 61 | 				provider.validateOptionalParams({ temperature: 0 })
 62 | 			).not.toThrow();
 63 | 			expect(() =>
 64 | 				provider.validateOptionalParams({ temperature: 0.5 })
 65 | 			).not.toThrow();
 66 | 			expect(() =>
 67 | 				provider.validateOptionalParams({ temperature: 1 })
 68 | 			).not.toThrow();
 69 | 		});
 70 | 
 71 | 		it('should reject invalid temperature values', () => {
 72 | 			expect(() =>
 73 | 				provider.validateOptionalParams({ temperature: -0.1 })
 74 | 			).toThrow(Error);
 75 | 			expect(() =>
 76 | 				provider.validateOptionalParams({ temperature: 1.1 })
 77 | 			).toThrow(Error);
 78 | 		});
 79 | 	});
 80 | 
 81 | 	describe('getRequiredApiKeyName', () => {
 82 | 		it('should return OPENAI_API_KEY', () => {
 83 | 			expect(provider.getRequiredApiKeyName()).toBe('OPENAI_API_KEY');
 84 | 		});
 85 | 	});
 86 | 
 87 | 	describe('getClient', () => {
 88 | 		it('should create client even without API key (validation deferred to SDK)', () => {
 89 | 			// getClient() no longer validates API key - validation is deferred to SDK initialization
 90 | 			const client = provider.getClient({});
 91 | 			expect(typeof client).toBe('function');
 92 | 		});
 93 | 
 94 | 		it('should create client with apiKey only', () => {
 95 | 			const params = {
 96 | 				apiKey: 'sk-test-123'
 97 | 			};
 98 | 
 99 | 			// The getClient method should return a function
100 | 			const client = provider.getClient(params);
101 | 			expect(typeof client).toBe('function');
102 | 
103 | 			// The client function should be callable and return a model object
104 | 			const model = client('gpt-4');
105 | 			expect(model).toBeDefined();
106 | 			expect(model.modelId).toBe('gpt-4');
107 | 		});
108 | 
109 | 		it('should create client with apiKey and baseURL', () => {
110 | 			const params = {
111 | 				apiKey: 'sk-test-456',
112 | 				baseURL: 'https://api.openai.example'
113 | 			};
114 | 
115 | 			// Should not throw when baseURL is provided
116 | 			const client = provider.getClient(params);
117 | 			expect(typeof client).toBe('function');
118 | 
119 | 			// The client function should be callable and return a model object
120 | 			const model = client('gpt-5');
121 | 			expect(model).toBeDefined();
122 | 			expect(model.modelId).toBe('gpt-5');
123 | 		});
124 | 
125 | 		it('should return the same client instance for the same parameters', () => {
126 | 			const params = {
127 | 				apiKey: 'sk-test-789'
128 | 			};
129 | 
130 | 			// Multiple calls with same params should work
131 | 			const client1 = provider.getClient(params);
132 | 			const client2 = provider.getClient(params);
133 | 
134 | 			expect(typeof client1).toBe('function');
135 | 			expect(typeof client2).toBe('function');
136 | 
137 | 			// Both clients should be able to create models
138 | 			const model1 = client1('gpt-4');
139 | 			const model2 = client2('gpt-4');
140 | 			expect(model1.modelId).toBe('gpt-4');
141 | 			expect(model2.modelId).toBe('gpt-4');
142 | 		});
143 | 
144 | 		it('should handle different model IDs correctly', () => {
145 | 			const client = provider.getClient({ apiKey: 'sk-test-models' });
146 | 
147 | 			// Test with different models
148 | 			const gpt4 = client('gpt-4');
149 | 			expect(gpt4.modelId).toBe('gpt-4');
150 | 
151 | 			const gpt5 = client('gpt-5');
152 | 			expect(gpt5.modelId).toBe('gpt-5');
153 | 
154 | 			const gpt35 = client('gpt-3.5-turbo');
155 | 			expect(gpt35.modelId).toBe('gpt-3.5-turbo');
156 | 		});
157 | 	});
158 | 
159 | 	describe('name property', () => {
160 | 		it('should have OpenAI as the provider name', () => {
161 | 			expect(provider.name).toBe('OpenAI');
162 | 		});
163 | 	});
164 | });
165 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/services/config-loader.service.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Configuration Loader Service
  3 |  * Responsible for loading configuration from various file sources
  4 |  */
  5 | 
  6 | import fs from 'node:fs/promises';
  7 | import path from 'node:path';
  8 | import {
  9 | 	ERROR_CODES,
 10 | 	TaskMasterError
 11 | } from '../../../common/errors/task-master-error.js';
 12 | import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
 13 | import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
 14 | 
 15 | /**
 16 |  * ConfigLoader handles loading configuration from files
 17 |  * Single responsibility: File-based configuration loading
 18 |  */
 19 | export class ConfigLoader {
 20 | 	private localConfigPath: string;
 21 | 	private globalConfigPath: string;
 22 | 
 23 | 	constructor(projectRoot: string) {
 24 | 		this.localConfigPath = path.join(projectRoot, '.taskmaster', 'config.json');
 25 | 		this.globalConfigPath = path.join(
 26 | 			process.env.HOME || '',
 27 | 			'.taskmaster',
 28 | 			'config.json'
 29 | 		);
 30 | 	}
 31 | 
 32 | 	/**
 33 | 	 * Get default configuration values
 34 | 	 */
 35 | 	getDefaultConfig(): PartialConfiguration {
 36 | 		return {
 37 | 			models: {
 38 | 				main: DEFAULT_CONFIG_VALUES.MODELS.MAIN,
 39 | 				fallback: DEFAULT_CONFIG_VALUES.MODELS.FALLBACK
 40 | 			},
 41 | 			workflow: {
 42 | 				enableAutopilot: DEFAULT_CONFIG_VALUES.WORKFLOW.ENABLE_AUTOPILOT,
 43 | 				maxPhaseAttempts: DEFAULT_CONFIG_VALUES.WORKFLOW.MAX_PHASE_ATTEMPTS,
 44 | 				branchPattern: DEFAULT_CONFIG_VALUES.WORKFLOW.BRANCH_PATTERN,
 45 | 				requireCleanWorkingTree:
 46 | 					DEFAULT_CONFIG_VALUES.WORKFLOW.REQUIRE_CLEAN_WORKING_TREE,
 47 | 				autoStageChanges: DEFAULT_CONFIG_VALUES.WORKFLOW.AUTO_STAGE_CHANGES,
 48 | 				includeCoAuthor: DEFAULT_CONFIG_VALUES.WORKFLOW.INCLUDE_CO_AUTHOR,
 49 | 				coAuthorName: DEFAULT_CONFIG_VALUES.WORKFLOW.CO_AUTHOR_NAME,
 50 | 				coAuthorEmail: DEFAULT_CONFIG_VALUES.WORKFLOW.CO_AUTHOR_EMAIL,
 51 | 				testThresholds: {
 52 | 					minTests: DEFAULT_CONFIG_VALUES.WORKFLOW.MIN_TESTS,
 53 | 					maxFailuresInGreen:
 54 | 						DEFAULT_CONFIG_VALUES.WORKFLOW.MAX_FAILURES_IN_GREEN
 55 | 				},
 56 | 				commitMessageTemplate:
 57 | 					DEFAULT_CONFIG_VALUES.WORKFLOW.COMMIT_MESSAGE_TEMPLATE,
 58 | 				allowedCommitTypes: [
 59 | 					...DEFAULT_CONFIG_VALUES.WORKFLOW.ALLOWED_COMMIT_TYPES
 60 | 				],
 61 | 				defaultCommitType: DEFAULT_CONFIG_VALUES.WORKFLOW.DEFAULT_COMMIT_TYPE,
 62 | 				operationTimeout: DEFAULT_CONFIG_VALUES.WORKFLOW.OPERATION_TIMEOUT,
 63 | 				enableActivityLogging:
 64 | 					DEFAULT_CONFIG_VALUES.WORKFLOW.ENABLE_ACTIVITY_LOGGING,
 65 | 				activityLogPath: DEFAULT_CONFIG_VALUES.WORKFLOW.ACTIVITY_LOG_PATH,
 66 | 				enableStateBackup: DEFAULT_CONFIG_VALUES.WORKFLOW.ENABLE_STATE_BACKUP,
 67 | 				maxStateBackups: DEFAULT_CONFIG_VALUES.WORKFLOW.MAX_STATE_BACKUPS,
 68 | 				abortOnMaxAttempts: DEFAULT_CONFIG_VALUES.WORKFLOW.ABORT_ON_MAX_ATTEMPTS
 69 | 			},
 70 | 			storage: {
 71 | 				type: DEFAULT_CONFIG_VALUES.STORAGE.TYPE,
 72 | 				encoding: DEFAULT_CONFIG_VALUES.STORAGE.ENCODING,
 73 | 				enableBackup: false,
 74 | 				maxBackups: DEFAULT_CONFIG_VALUES.STORAGE.MAX_BACKUPS,
 75 | 				enableCompression: false,
 76 | 				atomicOperations: true
 77 | 			},
 78 | 			version: DEFAULT_CONFIG_VALUES.VERSION
 79 | 		};
 80 | 	}
 81 | 
 82 | 	/**
 83 | 	 * Load local project configuration
 84 | 	 */
 85 | 	async loadLocalConfig(): Promise<PartialConfiguration | null> {
 86 | 		try {
 87 | 			const configData = await fs.readFile(this.localConfigPath, 'utf-8');
 88 | 			return JSON.parse(configData);
 89 | 		} catch (error: any) {
 90 | 			if (error.code === 'ENOENT') {
 91 | 				// File doesn't exist, return null
 92 | 				console.debug('No local config.json found, using defaults');
 93 | 				return null;
 94 | 			}
 95 | 			throw new TaskMasterError(
 96 | 				'Failed to load local configuration',
 97 | 				ERROR_CODES.CONFIG_ERROR,
 98 | 				{ configPath: this.localConfigPath },
 99 | 				error
100 | 			);
101 | 		}
102 | 	}
103 | 
104 | 	/**
105 | 	 * Load global user configuration
106 | 	 * @future-implementation Full implementation pending
107 | 	 */
108 | 	async loadGlobalConfig(): Promise<PartialConfiguration | null> {
109 | 		// TODO: Implement in future PR
110 | 		// For now, return null to indicate no global config
111 | 		return null;
112 | 
113 | 		// Future implementation:
114 | 		// try {
115 | 		//   const configData = await fs.readFile(this.globalConfigPath, 'utf-8');
116 | 		//   return JSON.parse(configData);
117 | 		// } catch (error: any) {
118 | 		//   if (error.code === 'ENOENT') {
119 | 		//     return null;
120 | 		//   }
121 | 		//   throw new TaskMasterError(
122 | 		//     'Failed to load global configuration',
123 | 		//     ERROR_CODES.CONFIG_ERROR,
124 | 		//     { configPath: this.globalConfigPath },
125 | 		//     error
126 | 		//   );
127 | 		// }
128 | 	}
129 | 
130 | 	/**
131 | 	 * Check if local config exists
132 | 	 */
133 | 	async hasLocalConfig(): Promise<boolean> {
134 | 		try {
135 | 			await fs.access(this.localConfigPath);
136 | 			return true;
137 | 		} catch {
138 | 			return false;
139 | 		}
140 | 	}
141 | 
142 | 	/**
143 | 	 * Check if global config exists
144 | 	 */
145 | 	async hasGlobalConfig(): Promise<boolean> {
146 | 		try {
147 | 			await fs.access(this.globalConfigPath);
148 | 			return true;
149 | 		} catch {
150 | 			return false;
151 | 		}
152 | 	}
153 | }
154 | 
```
Page 14/69FirstPrevNextLast