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

# Directory Structure

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

# Files

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

```javascript
  1 | import { jest } from '@jest/globals';
  2 | import fs from 'fs';
  3 | import path from 'path';
  4 | import { fileURLToPath } from 'url';
  5 | import { getRulesProfile } from '../../../src/utils/rule-transformer.js';
  6 | 
  7 | const __filename = fileURLToPath(import.meta.url);
  8 | const __dirname = path.dirname(__filename);
  9 | 
 10 | describe('Amp Profile Integration', () => {
 11 | 	let tempDir;
 12 | 	let ampProfile;
 13 | 
 14 | 	beforeEach(() => {
 15 | 		// Create temporary directory for testing
 16 | 		tempDir = fs.mkdtempSync(path.join(__dirname, 'temp-amp-unit-'));
 17 | 
 18 | 		// Get the Amp profile
 19 | 		ampProfile = getRulesProfile('amp');
 20 | 	});
 21 | 
 22 | 	afterEach(() => {
 23 | 		// Clean up temporary directory
 24 | 		if (fs.existsSync(tempDir)) {
 25 | 			fs.rmSync(tempDir, { recursive: true, force: true });
 26 | 		}
 27 | 	});
 28 | 
 29 | 	describe('Profile Structure', () => {
 30 | 		test('should have expected profile structure', () => {
 31 | 			expect(ampProfile).toBeDefined();
 32 | 			expect(ampProfile.profileName).toBe('amp');
 33 | 			expect(ampProfile.displayName).toBe('Amp');
 34 | 			expect(ampProfile.profileDir).toBe('.vscode');
 35 | 			expect(ampProfile.rulesDir).toBe('.');
 36 | 			expect(ampProfile.mcpConfig).toBe(true);
 37 | 			expect(ampProfile.mcpConfigName).toBe('settings.json');
 38 | 			expect(ampProfile.mcpConfigPath).toBe('.vscode/settings.json');
 39 | 			expect(ampProfile.includeDefaultRules).toBe(false);
 40 | 		});
 41 | 
 42 | 		test('should have correct file mapping', () => {
 43 | 			expect(ampProfile.fileMap).toEqual({
 44 | 				'AGENTS.md': '.taskmaster/AGENT.md'
 45 | 			});
 46 | 		});
 47 | 
 48 | 		test('should not create unnecessary directories', () => {
 49 | 			// Unlike profiles that copy entire directories, Amp should only create what's needed
 50 | 			const assetsDir = path.join(tempDir, 'assets');
 51 | 			fs.mkdirSync(assetsDir, { recursive: true });
 52 | 			fs.writeFileSync(
 53 | 				path.join(assetsDir, 'AGENTS.md'),
 54 | 				'Task Master instructions'
 55 | 			);
 56 | 
 57 | 			// Call onAddRulesProfile
 58 | 			ampProfile.onAddRulesProfile(tempDir, assetsDir);
 59 | 
 60 | 			// Should only have created .taskmaster directory and AGENT.md
 61 | 			expect(fs.existsSync(path.join(tempDir, '.taskmaster'))).toBe(true);
 62 | 			expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(true);
 63 | 
 64 | 			// Should not have created any other directories (like .claude)
 65 | 			expect(fs.existsSync(path.join(tempDir, '.amp'))).toBe(false);
 66 | 			expect(fs.existsSync(path.join(tempDir, '.claude'))).toBe(false);
 67 | 		});
 68 | 	});
 69 | 
 70 | 	describe('AGENT.md Import Logic', () => {
 71 | 		test('should handle missing source file gracefully', () => {
 72 | 			// Call onAddRulesProfile without creating source file
 73 | 			const assetsDir = path.join(tempDir, 'assets');
 74 | 			fs.mkdirSync(assetsDir, { recursive: true });
 75 | 
 76 | 			// Should not throw error
 77 | 			expect(() => {
 78 | 				ampProfile.onAddRulesProfile(tempDir, assetsDir);
 79 | 			}).not.toThrow();
 80 | 
 81 | 			// Should not create any files
 82 | 			expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(false);
 83 | 			expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
 84 | 				false
 85 | 			);
 86 | 		});
 87 | 
 88 | 		test('should preserve existing content when adding import', () => {
 89 | 			// Create existing AGENT.md with specific content
 90 | 			const existingContent =
 91 | 				'# My Custom Amp Setup\n\nThis is my custom configuration.\n\n## Custom Section\n\nSome custom rules here.';
 92 | 			fs.writeFileSync(path.join(tempDir, 'AGENT.md'), existingContent);
 93 | 
 94 | 			// Create mock source
 95 | 			const assetsDir = path.join(tempDir, 'assets');
 96 | 			fs.mkdirSync(assetsDir, { recursive: true });
 97 | 			fs.writeFileSync(
 98 | 				path.join(assetsDir, 'AGENTS.md'),
 99 | 				'Task Master instructions'
100 | 			);
101 | 
102 | 			// Call onAddRulesProfile
103 | 			ampProfile.onAddRulesProfile(tempDir, assetsDir);
104 | 
105 | 			// Check that existing content is preserved
106 | 			const updatedContent = fs.readFileSync(
107 | 				path.join(tempDir, 'AGENT.md'),
108 | 				'utf8'
109 | 			);
110 | 			expect(updatedContent).toContain('# My Custom Amp Setup');
111 | 			expect(updatedContent).toContain('This is my custom configuration.');
112 | 			expect(updatedContent).toContain('## Custom Section');
113 | 			expect(updatedContent).toContain('Some custom rules here.');
114 | 			expect(updatedContent).toContain('@./.taskmaster/AGENT.md');
115 | 		});
116 | 	});
117 | 
118 | 	describe('MCP Configuration Handling', () => {
119 | 		test('should handle missing .vscode directory gracefully', () => {
120 | 			// Call onAddRulesProfile without .vscode directory
121 | 			const assetsDir = path.join(tempDir, 'assets');
122 | 			fs.mkdirSync(assetsDir, { recursive: true });
123 | 
124 | 			// Should not throw error
125 | 			expect(() => {
126 | 				ampProfile.onAddRulesProfile(tempDir, assetsDir);
127 | 			}).not.toThrow();
128 | 		});
129 | 
130 | 		test('should handle malformed JSON gracefully', () => {
131 | 			// Create .vscode directory with malformed JSON
132 | 			const vscodeDirPath = path.join(tempDir, '.vscode');
133 | 			fs.mkdirSync(vscodeDirPath, { recursive: true });
134 | 			fs.writeFileSync(
135 | 				path.join(vscodeDirPath, 'settings.json'),
136 | 				'{ malformed json'
137 | 			);
138 | 
139 | 			// Should not throw error
140 | 			expect(() => {
141 | 				ampProfile.onAddRulesProfile(tempDir, path.join(tempDir, 'assets'));
142 | 			}).not.toThrow();
143 | 		});
144 | 
145 | 		test('should preserve other VS Code settings when renaming', () => {
146 | 			// Create .vscode/settings.json with various settings
147 | 			const vscodeDirPath = path.join(tempDir, '.vscode');
148 | 			fs.mkdirSync(vscodeDirPath, { recursive: true });
149 | 
150 | 			const initialConfig = {
151 | 				'editor.fontSize': 14,
152 | 				'editor.tabSize': 2,
153 | 				mcpServers: {
154 | 					'task-master-ai': {
155 | 						command: 'npx',
156 | 						args: ['-y', 'task-master-ai']
157 | 					}
158 | 				},
159 | 				'workbench.colorTheme': 'Dark+'
160 | 			};
161 | 
162 | 			fs.writeFileSync(
163 | 				path.join(vscodeDirPath, 'settings.json'),
164 | 				JSON.stringify(initialConfig, null, '\t')
165 | 			);
166 | 
167 | 			// Call onPostConvertRulesProfile (which handles MCP transformation)
168 | 			ampProfile.onPostConvertRulesProfile(
169 | 				tempDir,
170 | 				path.join(tempDir, 'assets')
171 | 			);
172 | 
173 | 			// Check that other settings are preserved
174 | 			const settingsFile = path.join(vscodeDirPath, 'settings.json');
175 | 			const content = fs.readFileSync(settingsFile, 'utf8');
176 | 			const config = JSON.parse(content);
177 | 
178 | 			expect(config['editor.fontSize']).toBe(14);
179 | 			expect(config['editor.tabSize']).toBe(2);
180 | 			expect(config['workbench.colorTheme']).toBe('Dark+');
181 | 			expect(config['amp.mcpServers']).toBeDefined();
182 | 			expect(config.mcpServers).toBeUndefined();
183 | 		});
184 | 	});
185 | 
186 | 	describe('Removal Logic', () => {
187 | 		test('should handle missing files gracefully during removal', () => {
188 | 			// Should not throw error when removing non-existent files
189 | 			expect(() => {
190 | 				ampProfile.onRemoveRulesProfile(tempDir);
191 | 			}).not.toThrow();
192 | 		});
193 | 
194 | 		test('should handle malformed JSON gracefully during removal', () => {
195 | 			// Create .vscode directory with malformed JSON
196 | 			const vscodeDirPath = path.join(tempDir, '.vscode');
197 | 			fs.mkdirSync(vscodeDirPath, { recursive: true });
198 | 			fs.writeFileSync(
199 | 				path.join(vscodeDirPath, 'settings.json'),
200 | 				'{ malformed json'
201 | 			);
202 | 
203 | 			// Should not throw error
204 | 			expect(() => {
205 | 				ampProfile.onRemoveRulesProfile(tempDir);
206 | 			}).not.toThrow();
207 | 		});
208 | 
209 | 		test('should preserve .vscode directory if it contains other files', () => {
210 | 			// Create .vscode directory with amp.mcpServers and other files
211 | 			const vscodeDirPath = path.join(tempDir, '.vscode');
212 | 			fs.mkdirSync(vscodeDirPath, { recursive: true });
213 | 
214 | 			const initialConfig = {
215 | 				'amp.mcpServers': {
216 | 					'task-master-ai': {
217 | 						command: 'npx',
218 | 						args: ['-y', 'task-master-ai']
219 | 					}
220 | 				}
221 | 			};
222 | 
223 | 			fs.writeFileSync(
224 | 				path.join(vscodeDirPath, 'settings.json'),
225 | 				JSON.stringify(initialConfig, null, '\t')
226 | 			);
227 | 
228 | 			// Create another file in .vscode
229 | 			fs.writeFileSync(path.join(vscodeDirPath, 'launch.json'), '{}');
230 | 
231 | 			// Call onRemoveRulesProfile
232 | 			ampProfile.onRemoveRulesProfile(tempDir);
233 | 
234 | 			// Check that .vscode directory is preserved
235 | 			expect(fs.existsSync(vscodeDirPath)).toBe(true);
236 | 			expect(fs.existsSync(path.join(vscodeDirPath, 'launch.json'))).toBe(true);
237 | 		});
238 | 	});
239 | 
240 | 	describe('Lifecycle Function Integration', () => {
241 | 		test('should have all required lifecycle functions', () => {
242 | 			expect(typeof ampProfile.onAddRulesProfile).toBe('function');
243 | 			expect(typeof ampProfile.onRemoveRulesProfile).toBe('function');
244 | 			expect(typeof ampProfile.onPostConvertRulesProfile).toBe('function');
245 | 		});
246 | 
247 | 		test('onPostConvertRulesProfile should behave like onAddRulesProfile', () => {
248 | 			// Create mock source
249 | 			const assetsDir = path.join(tempDir, 'assets');
250 | 			fs.mkdirSync(assetsDir, { recursive: true });
251 | 			fs.writeFileSync(
252 | 				path.join(assetsDir, 'AGENTS.md'),
253 | 				'Task Master instructions'
254 | 			);
255 | 
256 | 			// Call onPostConvertRulesProfile
257 | 			ampProfile.onPostConvertRulesProfile(tempDir, assetsDir);
258 | 
259 | 			// Should have same result as onAddRulesProfile
260 | 			expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
261 | 				true
262 | 			);
263 | 			expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(true);
264 | 
265 | 			const agentContent = fs.readFileSync(
266 | 				path.join(tempDir, 'AGENT.md'),
267 | 				'utf8'
268 | 			);
269 | 			expect(agentContent).toContain('@./.taskmaster/AGENT.md');
270 | 		});
271 | 	});
272 | 
273 | 	describe('Error Handling', () => {
274 | 		test('should handle file system errors gracefully', () => {
275 | 			// Mock fs.writeFileSync to throw an error
276 | 			const originalWriteFileSync = fs.writeFileSync;
277 | 			fs.writeFileSync = jest.fn().mockImplementation(() => {
278 | 				throw new Error('Permission denied');
279 | 			});
280 | 
281 | 			// Create mock source
282 | 			const assetsDir = path.join(tempDir, 'assets');
283 | 			fs.mkdirSync(assetsDir, { recursive: true });
284 | 			originalWriteFileSync.call(
285 | 				fs,
286 | 				path.join(assetsDir, 'AGENTS.md'),
287 | 				'Task Master instructions'
288 | 			);
289 | 
290 | 			// Should not throw error
291 | 			expect(() => {
292 | 				ampProfile.onAddRulesProfile(tempDir, assetsDir);
293 | 			}).not.toThrow();
294 | 
295 | 			// Restore original function
296 | 			fs.writeFileSync = originalWriteFileSync;
297 | 		});
298 | 	});
299 | });
300 | 
```

--------------------------------------------------------------------------------
/docs/mcp-provider.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Provider Implementation
  2 | 
  3 | ## Overview
  4 | 
  5 | The MCP Provider creates a modern AI SDK-compliant custom provider that integrates with the existing Task Master MCP server infrastructure. This provider enables AI operations through MCP session sampling while following modern AI SDK patterns and **includes full support for structured object generation (generateObject)** for schema-driven features like PRD parsing and task creation.
  6 | 
  7 | ## Architecture
  8 | 
  9 | ### Components
 10 | 
 11 | 1. **MCPProvider** (`mcp-server/src/providers/mcp-provider.js`)
 12 |    - Main provider class following Claude Code pattern
 13 |    - Session-based provider (no API key required)
 14 |    - Registers with provider registry on MCP server connect
 15 | 
 16 | 2. **AI SDK Implementation** (`mcp-server/src/custom-sdk/`)
 17 |    - `index.js` - Provider factory function
 18 |    - `language-model.js` - LanguageModelV1 implementation with **doGenerateObject support**
 19 |    - `message-converter.js` - Format conversion utilities
 20 |    - `json-extractor.js` - **NEW**: Robust JSON extraction from AI responses
 21 |    - `schema-converter.js` - **NEW**: Schema-to-instructions conversion utility
 22 |    - `errors.js` - Error handling and mapping
 23 | 
 24 | 3. **Integration Points**
 25 |    - MCP Server registration (`mcp-server/src/index.js`)
 26 |    - AI Services integration (`scripts/modules/ai-services-unified.js`)
 27 |    - Model configuration (`scripts/modules/supported-models.json`)
 28 | 
 29 | ### Session Flow
 30 | 
 31 | ```
 32 | MCP Client Connect → MCP Server → registerRemoteProvider()
 33 |                                         ↓
 34 |                            MCPRemoteProvider (existing)
 35 |                            MCPProvider 
 36 |                                         ↓
 37 |                                Provider Registry
 38 |                                         ↓
 39 |                                AI Services Layer
 40 |                                         ↓
 41 |                         Text Generation + Object Generation
 42 | ```
 43 | 
 44 | ## Implementation Details
 45 | 
 46 | ### Provider Registration
 47 | 
 48 | The MCP server registers **both** providers when a client connects:
 49 | 
 50 | ```javascript
 51 | // mcp-server/src/index.js
 52 | registerRemoteProvider(session) {
 53 |   if (session?.clientCapabilities?.sampling) {
 54 |     // Register existing provider
 55 |     // Register unified MCP provider
 56 |     const mcpProvider = new MCPProvider();
 57 |     mcpProvider.setSession(session);
 58 |     
 59 |     const providerRegistry = ProviderRegistry.getInstance();
 60 |     providerRegistry.registerProvider('mcp', mcpProvider);
 61 |   }
 62 | }
 63 | ```
 64 | 
 65 | ### AI Services Integration
 66 | 
 67 | The AI services layer includes the new provider:
 68 | 
 69 | ```javascript
 70 | // scripts/modules/ai-services-unified.js
 71 | const PROVIDERS = {
 72 |   // ... other providers
 73 |   'mcp': () => {
 74 |     const providerRegistry = ProviderRegistry.getInstance();
 75 |     return providerRegistry.getProvider('mcp');
 76 |   }
 77 | };
 78 | ```
 79 | 
 80 | ### Message Conversion
 81 | 
 82 | The provider converts between AI SDK and MCP formats:
 83 | 
 84 | ```javascript
 85 | // AI SDK prompt → MCP sampling format
 86 | const { messages, systemPrompt } = convertToMCPFormat(options.prompt);
 87 | 
 88 | // MCP response → AI SDK format
 89 | const result = convertFromMCPFormat(response);
 90 | ```
 91 | 
 92 | ## Structured Object Generation (generateObject)
 93 | 
 94 | ### Overview
 95 | 
 96 | The MCP Provider includes full support for structured object generation, enabling schema-driven features like PRD parsing, task creation, and any operations requiring validated JSON outputs.
 97 | 
 98 | ### Architecture
 99 | 
100 | The generateObject implementation includes:
101 | 
102 | 1. **Schema-to-Instructions Conversion** (`schema-converter.js`)
103 |    - Converts Zod schemas to natural language instructions
104 |    - Generates example outputs to guide AI responses
105 |    - Handles complex nested schemas and validation requirements
106 | 
107 | 2. **JSON Extraction Pipeline** (`json-extractor.js`)
108 |    - Multiple extraction strategies for robust JSON parsing
109 |    - Handles code blocks, malformed JSON, and various response formats
110 |    - Fallback mechanisms for maximum reliability
111 | 
112 | 3. **Validation System**
113 |    - Complete schema validation using Zod
114 |    - Detailed error reporting for failed validations
115 |    - Type-safe object generation
116 | 
117 | ### Implementation Details
118 | 
119 | #### doGenerateObject Method
120 | 
121 | The `MCPLanguageModel` class implements the AI SDK's `doGenerateObject` method:
122 | 
123 | ```javascript
124 | async doGenerateObject({ schema, objectName, prompt, ...options }) {
125 |   // Convert schema to instructions
126 |   const instructions = convertSchemaToInstructions(schema, objectName);
127 |   
128 |   // Enhance prompt with structured output requirements
129 |   const enhancedPrompt = enhancePromptForObjectGeneration(prompt, instructions);
130 |   
131 |   // Generate response via MCP sampling
132 |   const response = await this.doGenerate({ prompt: enhancedPrompt, ...options });
133 |   
134 |   // Extract and validate JSON
135 |   const extractedJson = extractJsonFromResponse(response.text);
136 |   const validatedObject = schema.parse(extractedJson);
137 |   
138 |   return {
139 |     object: validatedObject,
140 |     usage: response.usage,
141 |     finishReason: response.finishReason
142 |   };
143 | }
144 | ```
145 | 
146 | #### AI SDK Compatibility
147 | 
148 | The provider includes required properties for AI SDK object generation:
149 | 
150 | ```javascript
151 | class MCPLanguageModel {
152 |   get defaultObjectGenerationMode() {
153 |     return 'tool';
154 |   }
155 |   
156 |   get supportsStructuredOutputs() {
157 |     return true;
158 |   }
159 |   
160 |   // ... doGenerateObject implementation
161 | }
162 | ```
163 | 
164 | ### Usage Examples
165 | 
166 | #### PRD Parsing
167 | 
168 | ```javascript
169 | import { z } from 'zod';
170 | 
171 | const taskSchema = z.object({
172 |   title: z.string(),
173 |   description: z.string(),
174 |   priority: z.enum(['high', 'medium', 'low']),
175 |   dependencies: z.array(z.number()).optional()
176 | });
177 | 
178 | const result = await generateObject({
179 |   model: mcpModel,
180 |   schema: taskSchema,
181 |   prompt: 'Parse this PRD section into a task: [PRD content]'
182 | });
183 | 
184 | console.log(result.object); // Validated task object
185 | ```
186 | 
187 | #### Task Creation
188 | 
189 | ```javascript
190 | const taskCreationSchema = z.object({
191 |   task: z.object({
192 |     title: z.string(),
193 |     description: z.string(),
194 |     details: z.string(),
195 |     testStrategy: z.string(),
196 |     priority: z.enum(['high', 'medium', 'low']),
197 |     dependencies: z.array(z.number()).optional()
198 |   })
199 | });
200 | 
201 | const result = await generateObject({
202 |   model: mcpModel,
203 |   schema: taskCreationSchema,
204 |   prompt: 'Create a comprehensive task for implementing user authentication'
205 | });
206 | ```
207 | 
208 | ### Error Handling
209 | 
210 | The implementation provides comprehensive error handling:
211 | 
212 | - **Schema Validation Errors**: Detailed Zod validation messages
213 | - **JSON Extraction Failures**: Fallback strategies and clear error reporting
214 | - **MCP Communication Errors**: Proper error mapping and recovery
215 | - **Timeout Handling**: Configurable timeouts for long-running operations
216 | 
217 | ### Testing
218 | 
219 | The generateObject functionality is fully tested:
220 | 
221 | ```bash
222 | # Test object generation
223 | npm test -- --grep "generateObject"
224 | 
225 | # Test with actual MCP session
226 | node test-object-generation.js
227 | ```
228 | 
229 | ### Supported Features
230 | 
231 | ✅ **Schema Conversion**: Zod schemas → Natural language instructions  
232 | ✅ **JSON Extraction**: Multiple strategies for robust parsing  
233 | ✅ **Validation**: Complete schema validation with error reporting  
234 | ✅ **Error Recovery**: Fallback mechanisms for failed extractions  
235 | ✅ **Type Safety**: Full TypeScript support with inferred types  
236 | ✅ **AI SDK Compliance**: Complete LanguageModelV1 interface implementation  
237 | 
238 | ## Usage
239 | 
240 | ### Configuration
241 | 
242 | Add to supported models configuration:
243 | 
244 | ```json
245 | {
246 |   "mcp": [
247 |     {
248 |       "id": "claude-3-5-sonnet-20241022",
249 |       "swe_score": 0.623,
250 |       "cost_per_1m_tokens": { "input": 0, "output": 0 },
251 |       "allowed_roles": ["main", "fallback", "research"],
252 |       "max_tokens": 200000
253 |     }
254 |   ]
255 | }
256 | ```
257 | 
258 | ### CLI Usage
259 | 
260 | ```bash
261 | # Set provider for main role
262 | tm models set-main --provider mcp --model claude-3-5-sonnet-20241022
263 | 
264 | # Use in task operations
265 | tm add-task "Create user authentication system"
266 | ```
267 | 
268 | ### Programmatic Usage
269 | 
270 | ```javascript
271 | const provider = registry.getProvider('mcp');
272 | if (provider && provider.hasValidSession()) {
273 |   const client = provider.getClient({ temperature: 0.7 });
274 |   const model = client({ modelId: 'claude-3-5-sonnet-20241022' });
275 |   
276 |   const result = await model.doGenerate({
277 |     prompt: [
278 |       { role: 'user', content: 'Hello!' }
279 |     ]
280 |   });
281 | }
282 | ```
283 | 
284 | ## Testing
285 | 
286 | ### Component Tests
287 | 
288 | ```bash
289 | # Test individual components
290 | node test-mcp-components.js
291 | ```
292 | 
293 | ### Integration Testing
294 | 
295 | 1. Start MCP server
296 | 2. Connect Claude client
297 | 3. Verify both providers are registered
298 | 4. Test AI operations through mcp provider
299 | 
300 | ### Validation Checklist
301 | 
302 | - ✅ Provider creation and initialization
303 | - ✅ Registry integration
304 | - ✅ Session management
305 | - ✅ Message conversion
306 | - ✅ Error handling
307 | - ✅ AI Services integration
308 | - ✅ Model configuration
309 | 
310 | ## Key Benefits
311 | 
312 | 1. **AI SDK Compliance** - Full LanguageModelV1 implementation
313 | 2. **Session Integration** - Leverages existing MCP session infrastructure
314 | 3. **Registry Pattern** - Uses provider registry for discovery
315 | 4. **Backward Compatibility** - Coexists with existing MCPRemoteProvider
316 | 5. **Future Ready** - Supports AI SDK features and patterns
317 | 
318 | ## Troubleshooting
319 | 
320 | ### Provider Not Found
321 | 
322 | ```
323 | Error: Provider "mcp" not found in registry
324 | ```
325 | 
326 | **Solution**: Ensure MCP server is running and client is connected
327 | 
328 | ### Session Errors
329 | 
330 | ```
331 | Error: MCP Provider requires active MCP session
332 | ```
333 | 
334 | **Solution**: Check MCP client connection and session capabilities
335 | 
336 | ### Sampling Errors
337 | 
338 | ```
339 | Error: MCP session must have client sampling capabilities
340 | ```
341 | 
342 | **Solution**: Verify MCP client supports sampling operations
343 | 
344 | ## Next Steps
345 | 
346 | 1. **Performance Optimization** - Add caching and connection pooling
347 | 2. **Enhanced Streaming** - Implement native streaming if MCP supports it
348 | 3. **Tool Integration** - Add support for function calling through MCP tools
349 | 4. **Monitoring** - Add metrics and logging for provider usage
350 | 5. **Documentation** - Update user guides and API documentation
351 | 
```

--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-vscode.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | 
  3 | // Mock fs module before importing anything that uses it
  4 | jest.mock('fs', () => ({
  5 | 	readFileSync: jest.fn(),
  6 | 	writeFileSync: jest.fn(),
  7 | 	existsSync: jest.fn(),
  8 | 	mkdirSync: jest.fn()
  9 | }));
 10 | 
 11 | // Import modules after mocking
 12 | import fs from 'fs';
 13 | import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
 14 | import { vscodeProfile } from '../../../src/profiles/vscode.js';
 15 | 
 16 | describe('VS Code Rule Transformer', () => {
 17 | 	// Set up spies on the mocked modules
 18 | 	const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
 19 | 	const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
 20 | 	const mockExistsSync = jest.spyOn(fs, 'existsSync');
 21 | 	const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
 22 | 	const mockConsoleError = jest
 23 | 		.spyOn(console, 'error')
 24 | 		.mockImplementation(() => {});
 25 | 
 26 | 	beforeEach(() => {
 27 | 		jest.clearAllMocks();
 28 | 		// Setup default mocks
 29 | 		mockReadFileSync.mockReturnValue('');
 30 | 		mockWriteFileSync.mockImplementation(() => {});
 31 | 		mockExistsSync.mockReturnValue(true);
 32 | 		mockMkdirSync.mockImplementation(() => {});
 33 | 	});
 34 | 
 35 | 	afterAll(() => {
 36 | 		jest.restoreAllMocks();
 37 | 	});
 38 | 
 39 | 	it('should correctly convert basic terms', () => {
 40 | 		const testContent = `---
 41 | description: Test Cursor rule for basic terms
 42 | globs: **/*
 43 | alwaysApply: true
 44 | ---
 45 | 
 46 | This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
 47 | Also has references to .mdc files and cursor rules.`;
 48 | 
 49 | 		// Mock file read to return our test content
 50 | 		mockReadFileSync.mockReturnValue(testContent);
 51 | 
 52 | 		// Call the actual function
 53 | 		const result = convertRuleToProfileRule(
 54 | 			'source.mdc',
 55 | 			'target.md',
 56 | 			vscodeProfile
 57 | 		);
 58 | 
 59 | 		// Verify the function succeeded
 60 | 		expect(result).toBe(true);
 61 | 
 62 | 		// Verify file operations were called correctly
 63 | 		expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8');
 64 | 		expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
 65 | 
 66 | 		// Get the transformed content that was written
 67 | 		const writeCall = mockWriteFileSync.mock.calls[0];
 68 | 		const transformedContent = writeCall[1];
 69 | 
 70 | 		// Verify transformations
 71 | 		expect(transformedContent).toContain('VS Code');
 72 | 		expect(transformedContent).toContain('code.visualstudio.com');
 73 | 		expect(transformedContent).toContain('.md');
 74 | 		expect(transformedContent).toContain('vscode rules'); // "cursor rules" -> "vscode rules"
 75 | 		expect(transformedContent).toContain('applyTo: "**/*"'); // globs -> applyTo transformation
 76 | 		expect(transformedContent).not.toContain('cursor.so');
 77 | 		expect(transformedContent).not.toContain('Cursor rule');
 78 | 		expect(transformedContent).not.toContain('globs:');
 79 | 		expect(transformedContent).not.toContain('alwaysApply:');
 80 | 	});
 81 | 
 82 | 	it('should correctly convert tool references', () => {
 83 | 		const testContent = `---
 84 | description: Test Cursor rule for tool references
 85 | globs: **/*
 86 | alwaysApply: true
 87 | ---
 88 | 
 89 | - Use the search tool to find code
 90 | - The edit_file tool lets you modify files
 91 | - run_command executes terminal commands
 92 | - use_mcp connects to external services`;
 93 | 
 94 | 		// Mock file read to return our test content
 95 | 		mockReadFileSync.mockReturnValue(testContent);
 96 | 
 97 | 		// Call the actual function
 98 | 		const result = convertRuleToProfileRule(
 99 | 			'source.mdc',
100 | 			'target.md',
101 | 			vscodeProfile
102 | 		);
103 | 
104 | 		// Verify the function succeeded
105 | 		expect(result).toBe(true);
106 | 
107 | 		// Get the transformed content that was written
108 | 		const writeCall = mockWriteFileSync.mock.calls[0];
109 | 		const transformedContent = writeCall[1];
110 | 
111 | 		// Verify transformations (VS Code uses standard tool names, so no transformation)
112 | 		expect(transformedContent).toContain('search tool');
113 | 		expect(transformedContent).toContain('edit_file tool');
114 | 		expect(transformedContent).toContain('run_command');
115 | 		expect(transformedContent).toContain('use_mcp');
116 | 		expect(transformedContent).toContain('applyTo: "**/*"'); // globs -> applyTo transformation
117 | 	});
118 | 
119 | 	it('should correctly update file references and directory paths', () => {
120 | 		const testContent = `---
121 | description: Test Cursor rule for file references
122 | globs: .cursor/rules/*.md
123 | alwaysApply: true
124 | ---
125 | 
126 | This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and 
127 | [taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).
128 | Files are in the .cursor/rules directory and we should reference the rules directory.`;
129 | 
130 | 		// Mock file read to return our test content
131 | 		mockReadFileSync.mockReturnValue(testContent);
132 | 
133 | 		// Call the actual function
134 | 		const result = convertRuleToProfileRule(
135 | 			'source.mdc',
136 | 			'target.instructions.md',
137 | 			vscodeProfile
138 | 		);
139 | 
140 | 		// Verify the function succeeded
141 | 		expect(result).toBe(true);
142 | 
143 | 		// Get the transformed content that was written
144 | 		const writeCall = mockWriteFileSync.mock.calls[0];
145 | 		const transformedContent = writeCall[1];
146 | 
147 | 		// Verify transformations specific to VS Code
148 | 		expect(transformedContent).toContain(
149 | 			'applyTo: ".github/instructions/*.md"'
150 | 		); // globs -> applyTo with path transformation
151 | 		expect(transformedContent).toContain(
152 | 			'(.github/instructions/dev_workflow.instructions.md)'
153 | 		); // File path transformation - no taskmaster subdirectory for VS Code
154 | 		expect(transformedContent).toContain(
155 | 			'(.github/instructions/taskmaster.instructions.md)'
156 | 		); // File path transformation - no taskmaster subdirectory for VS Code
157 | 		expect(transformedContent).toContain('instructions directory'); // "rules directory" -> "instructions directory"
158 | 		expect(transformedContent).not.toContain('(mdc:.cursor/rules/');
159 | 		expect(transformedContent).not.toContain('.cursor/rules');
160 | 		expect(transformedContent).not.toContain('globs:');
161 | 		expect(transformedContent).not.toContain('rules directory');
162 | 	});
163 | 
164 | 	it('should transform globs to applyTo with various patterns', () => {
165 | 		const testContent = `---
166 | description: Test VS Code applyTo transformation
167 | globs: .cursor/rules/*.md
168 | alwaysApply: true
169 | ---
170 | 
171 | Another section:
172 | globs: **/*.ts
173 | final: true
174 | 
175 | Last one:
176 | globs: src/**/*
177 | ---`;
178 | 
179 | 		// Mock file read to return our test content
180 | 		mockReadFileSync.mockReturnValue(testContent);
181 | 
182 | 		// Call the actual function
183 | 		const result = convertRuleToProfileRule(
184 | 			'source.mdc',
185 | 			'target.md',
186 | 			vscodeProfile
187 | 		);
188 | 
189 | 		// Verify the function succeeded
190 | 		expect(result).toBe(true);
191 | 
192 | 		// Get the transformed content that was written
193 | 		const writeCall = mockWriteFileSync.mock.calls[0];
194 | 		const transformedContent = writeCall[1];
195 | 
196 | 		// Verify all globs transformations
197 | 		expect(transformedContent).toContain(
198 | 			'applyTo: ".github/instructions/*.md"'
199 | 		); // Path transformation applied
200 | 		expect(transformedContent).toContain('applyTo: "**/*.ts"'); // Pattern with quotes
201 | 		expect(transformedContent).toContain('applyTo: "src/**/*"'); // Complex pattern with quotes
202 | 		expect(transformedContent).not.toContain('globs:'); // No globs should remain
203 | 	});
204 | 
205 | 	it('should handle VS Code MCP configuration paths correctly', () => {
206 | 		const testContent = `---
207 | description: Test MCP configuration paths
208 | globs: **/*
209 | alwaysApply: true
210 | ---
211 | 
212 | MCP configuration is at .cursor/mcp.json for Cursor.
213 | The .cursor/rules directory contains rules.
214 | Update your .cursor/mcp.json file accordingly.`;
215 | 
216 | 		// Mock file read to return our test content
217 | 		mockReadFileSync.mockReturnValue(testContent);
218 | 
219 | 		// Call the actual function
220 | 		const result = convertRuleToProfileRule(
221 | 			'source.mdc',
222 | 			'target.md',
223 | 			vscodeProfile
224 | 		);
225 | 
226 | 		// Verify the function succeeded
227 | 		expect(result).toBe(true);
228 | 
229 | 		// Get the transformed content that was written
230 | 		const writeCall = mockWriteFileSync.mock.calls[0];
231 | 		const transformedContent = writeCall[1];
232 | 
233 | 		// Verify MCP paths are correctly transformed
234 | 		expect(transformedContent).toContain('.vscode/mcp.json'); // MCP config in .vscode
235 | 		expect(transformedContent).toContain('.github/instructions'); // Rules/instructions in .github/instructions
236 | 		expect(transformedContent).not.toContain('.cursor/mcp.json');
237 | 		expect(transformedContent).not.toContain('.cursor/rules');
238 | 	});
239 | 
240 | 	it('should handle file read errors', () => {
241 | 		// Mock file read to throw an error
242 | 		mockReadFileSync.mockImplementation(() => {
243 | 			throw new Error('File not found');
244 | 		});
245 | 
246 | 		// Call the actual function
247 | 		const result = convertRuleToProfileRule(
248 | 			'nonexistent.mdc',
249 | 			'target.md',
250 | 			vscodeProfile
251 | 		);
252 | 
253 | 		// Verify the function failed gracefully
254 | 		expect(result).toBe(false);
255 | 
256 | 		// Verify writeFileSync was not called
257 | 		expect(mockWriteFileSync).not.toHaveBeenCalled();
258 | 
259 | 		// Verify error was logged
260 | 		expect(mockConsoleError).toHaveBeenCalledWith(
261 | 			'Error converting rule file: File not found'
262 | 		);
263 | 	});
264 | 
265 | 	it('should handle file write errors', () => {
266 | 		const testContent = 'test content';
267 | 		mockReadFileSync.mockReturnValue(testContent);
268 | 
269 | 		// Mock file write to throw an error
270 | 		mockWriteFileSync.mockImplementation(() => {
271 | 			throw new Error('Permission denied');
272 | 		});
273 | 
274 | 		// Call the actual function
275 | 		const result = convertRuleToProfileRule(
276 | 			'source.mdc',
277 | 			'target.md',
278 | 			vscodeProfile
279 | 		);
280 | 
281 | 		// Verify the function failed gracefully
282 | 		expect(result).toBe(false);
283 | 
284 | 		// Verify error was logged
285 | 		expect(mockConsoleError).toHaveBeenCalledWith(
286 | 			'Error converting rule file: Permission denied'
287 | 		);
288 | 	});
289 | 
290 | 	it('should create target directory if it does not exist', () => {
291 | 		const testContent = 'test content';
292 | 		mockReadFileSync.mockReturnValue(testContent);
293 | 
294 | 		// Mock directory doesn't exist initially
295 | 		mockExistsSync.mockReturnValue(false);
296 | 
297 | 		// Call the actual function
298 | 		convertRuleToProfileRule(
299 | 			'source.mdc',
300 | 			'.github/instructions/deep/path/target.md',
301 | 			vscodeProfile
302 | 		);
303 | 
304 | 		// Verify directory creation was called
305 | 		expect(mockMkdirSync).toHaveBeenCalledWith(
306 | 			'.github/instructions/deep/path',
307 | 			{
308 | 				recursive: true
309 | 			}
310 | 		);
311 | 	});
312 | });
313 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/commands/briefs.command.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Briefs Command - Friendly alias for tag management in API storage
  3 |  * Provides brief-specific commands that only work with API storage
  4 |  */
  5 | 
  6 | import {
  7 | 	type LogLevel,
  8 | 	type TagInfo,
  9 | 	tryAddTagViaRemote,
 10 | 	tryListTagsViaRemote
 11 | } from '@tm/bridge';
 12 | import type { TmCore } from '@tm/core';
 13 | import { AuthManager, createTmCore } from '@tm/core';
 14 | import { Command } from 'commander';
 15 | import { checkAuthentication } from '../utils/auth-helpers.js';
 16 | import {
 17 | 	selectBriefFromInput,
 18 | 	selectBriefInteractive
 19 | } from '../utils/brief-selection.js';
 20 | import * as ui from '../utils/ui.js';
 21 | 
 22 | /**
 23 |  * Result type from briefs command
 24 |  */
 25 | export interface BriefsResult {
 26 | 	success: boolean;
 27 | 	action: 'list' | 'select' | 'create';
 28 | 	briefs?: TagInfo[];
 29 | 	currentBrief?: string | null;
 30 | 	message?: string;
 31 | }
 32 | 
 33 | /**
 34 |  * BriefsCommand - Manage briefs for API storage (friendly alias)
 35 |  * Only works when using API storage (tryhamster.com)
 36 |  */
 37 | export class BriefsCommand extends Command {
 38 | 	private tmCore?: TmCore;
 39 | 	private authManager: AuthManager;
 40 | 	private lastResult?: BriefsResult;
 41 | 
 42 | 	constructor(name?: string) {
 43 | 		super(name || 'briefs');
 44 | 
 45 | 		// Initialize auth manager
 46 | 		this.authManager = AuthManager.getInstance();
 47 | 
 48 | 		// Configure the command
 49 | 		this.description('Manage briefs (API storage only)');
 50 | 		this.alias('brief');
 51 | 
 52 | 		// Add subcommands
 53 | 		this.addListCommand();
 54 | 		this.addSelectCommand();
 55 | 		this.addCreateCommand();
 56 | 
 57 | 		// Accept optional positional argument for brief URL/ID
 58 | 		this.argument('[briefOrUrl]', 'Brief ID or Hamster brief URL');
 59 | 
 60 | 		// Default action: if argument provided, select brief; else list briefs
 61 | 		this.action(async (briefOrUrl?: string) => {
 62 | 			if (briefOrUrl && briefOrUrl.trim().length > 0) {
 63 | 				await this.executeSelectFromUrl(briefOrUrl.trim());
 64 | 				return;
 65 | 			}
 66 | 			await this.executeList();
 67 | 		});
 68 | 	}
 69 | 
 70 | 	/**
 71 | 	 * Check if user is authenticated (required for briefs)
 72 | 	 */
 73 | 	private async checkAuth(): Promise<boolean> {
 74 | 		return checkAuthentication(this.authManager, {
 75 | 			message:
 76 | 				'The "briefs" command requires you to be logged in to your Hamster account.',
 77 | 			footer:
 78 | 				'Working locally instead?\n' +
 79 | 				'  → Use "task-master tags" for local tag management.',
 80 | 			authCommand: 'task-master auth login'
 81 | 		});
 82 | 	}
 83 | 
 84 | 	/**
 85 | 	 * Add list subcommand
 86 | 	 */
 87 | 	private addListCommand(): void {
 88 | 		this.command('list')
 89 | 			.description('List all briefs (default action)')
 90 | 			.option('--show-metadata', 'Show additional brief metadata')
 91 | 			.addHelpText(
 92 | 				'after',
 93 | 				`
 94 | Examples:
 95 |   $ tm briefs            # List all briefs (default)
 96 |   $ tm briefs list       # List all briefs (explicit)
 97 |   $ tm briefs list --show-metadata  # List with metadata
 98 | 
 99 | Note: This command only works with API storage (tryhamster.com).
100 | `
101 | 			)
102 | 			.action(async (options) => {
103 | 				await this.executeList(options);
104 | 			});
105 | 	}
106 | 
107 | 	/**
108 | 	 * Add select subcommand
109 | 	 */
110 | 	private addSelectCommand(): void {
111 | 		this.command('select')
112 | 			.description('Select a brief to work with')
113 | 			.argument(
114 | 				'[briefOrUrl]',
115 | 				'Brief ID or Hamster URL (optional, interactive if omitted)'
116 | 			)
117 | 			.addHelpText(
118 | 				'after',
119 | 				`
120 | Examples:
121 |   $ tm brief select                                    # Interactive selection
122 |   $ tm brief select abc12345                           # Select by ID
123 |   $ tm brief select https://app.tryhamster.com/...     # Select by URL
124 | 
125 | Shortcuts:
126 |   $ tm brief <brief-url>                               # Same as "select"
127 |   $ tm brief                                           # List all briefs
128 | 
129 | Note: Works exactly like "tm context brief" - reuses the same interactive interface.
130 | `
131 | 			)
132 | 			.action(async (briefOrUrl) => {
133 | 				await this.executeSelect(briefOrUrl);
134 | 			});
135 | 	}
136 | 
137 | 	/**
138 | 	 * Add create subcommand
139 | 	 */
140 | 	private addCreateCommand(): void {
141 | 		this.command('create')
142 | 			.description('Create a new brief (redirects to web UI)')
143 | 			.argument('[name]', 'Brief name (optional)')
144 | 			.addHelpText(
145 | 				'after',
146 | 				`
147 | Examples:
148 |   $ tm briefs create              # Redirect to web UI to create brief
149 |   $ tm briefs create my-new-brief # Redirect with suggested name
150 | 
151 | Note: Briefs must be created through the Hamster Studio web interface.
152 | `
153 | 			)
154 | 			.action(async (name) => {
155 | 				await this.executeCreate(name);
156 | 			});
157 | 	}
158 | 
159 | 	/**
160 | 	 * Initialize TmCore if not already initialized
161 | 	 */
162 | 	private async initTmCore(): Promise<void> {
163 | 		if (!this.tmCore) {
164 | 			this.tmCore = await createTmCore({
165 | 				projectPath: process.cwd()
166 | 			});
167 | 		}
168 | 	}
169 | 
170 | 	/**
171 | 	 * Execute list briefs
172 | 	 */
173 | 	private async executeList(options?: {
174 | 		showMetadata?: boolean;
175 | 	}): Promise<void> {
176 | 		try {
177 | 			// Check authentication
178 | 			if (!(await this.checkAuth())) {
179 | 				process.exit(1);
180 | 			}
181 | 
182 | 			// Use the bridge to list briefs
183 | 			const remoteResult = await tryListTagsViaRemote({
184 | 				projectRoot: process.cwd(),
185 | 				showMetadata: options?.showMetadata || false,
186 | 				report: (level: LogLevel, ...args: unknown[]) => {
187 | 					const message = args[0] as string;
188 | 					if (level === 'error') ui.displayError(message);
189 | 					else if (level === 'warn') ui.displayWarning(message);
190 | 					else if (level === 'info') ui.displayInfo(message);
191 | 				}
192 | 			});
193 | 
194 | 			if (!remoteResult) {
195 | 				throw new Error('Failed to fetch briefs from API');
196 | 			}
197 | 
198 | 			this.setLastResult({
199 | 				success: remoteResult.success,
200 | 				action: 'list',
201 | 				briefs: remoteResult.tags,
202 | 				currentBrief: remoteResult.currentTag,
203 | 				message: remoteResult.message
204 | 			});
205 | 		} catch (error) {
206 | 			ui.displayError(`Failed to list briefs: ${(error as Error).message}`);
207 | 			this.setLastResult({
208 | 				success: false,
209 | 				action: 'list',
210 | 				message: (error as Error).message
211 | 			});
212 | 			process.exit(1);
213 | 		}
214 | 	}
215 | 
216 | 	/**
217 | 	 * Execute select brief interactively or by name/ID
218 | 	 */
219 | 	private async executeSelect(nameOrId?: string): Promise<void> {
220 | 		try {
221 | 			// Check authentication
222 | 			const hasSession = await this.authManager.hasValidSession();
223 | 			if (!hasSession) {
224 | 				ui.displayError('Not authenticated. Run "tm auth login" first.');
225 | 				process.exit(1);
226 | 			}
227 | 
228 | 			// If name/ID provided, treat it as URL/ID selection
229 | 			if (nameOrId && nameOrId.trim().length > 0) {
230 | 				await this.executeSelectFromUrl(nameOrId.trim());
231 | 				return;
232 | 			}
233 | 
234 | 			// Check if org is selected for interactive selection
235 | 			const context = this.authManager.getContext();
236 | 			if (!context?.orgId) {
237 | 				ui.displayErrorBox(
238 | 					'No organization selected. Run "tm context org" first.'
239 | 				);
240 | 				process.exit(1);
241 | 			}
242 | 
243 | 			// Use shared utility for interactive selection
244 | 			const result = await selectBriefInteractive(
245 | 				this.authManager,
246 | 				context.orgId
247 | 			);
248 | 
249 | 			this.setLastResult({
250 | 				success: result.success,
251 | 				action: 'select',
252 | 				currentBrief: result.briefId,
253 | 				message: result.message
254 | 			});
255 | 
256 | 			if (!result.success) {
257 | 				process.exit(1);
258 | 			}
259 | 		} catch (error) {
260 | 			ui.displayErrorBox(`Failed to select brief: ${(error as Error).message}`);
261 | 			this.setLastResult({
262 | 				success: false,
263 | 				action: 'select',
264 | 				message: (error as Error).message
265 | 			});
266 | 			process.exit(1);
267 | 		}
268 | 	}
269 | 
270 | 	/**
271 | 	 * Execute select brief from any input (URL, ID, or name)
272 | 	 * All parsing logic is in tm-core
273 | 	 */
274 | 	private async executeSelectFromUrl(input: string): Promise<void> {
275 | 		try {
276 | 			// Check authentication
277 | 			const hasSession = await this.authManager.hasValidSession();
278 | 			if (!hasSession) {
279 | 				ui.displayError('Not authenticated. Run "tm auth login" first.');
280 | 				process.exit(1);
281 | 			}
282 | 
283 | 			// Initialize tmCore to access business logic
284 | 			await this.initTmCore();
285 | 
286 | 			// Use shared utility - tm-core handles ALL parsing
287 | 			const result = await selectBriefFromInput(
288 | 				this.authManager,
289 | 				input,
290 | 				this.tmCore
291 | 			);
292 | 
293 | 			this.setLastResult({
294 | 				success: result.success,
295 | 				action: 'select',
296 | 				currentBrief: result.briefId,
297 | 				message: result.message
298 | 			});
299 | 
300 | 			if (!result.success) {
301 | 				process.exit(1);
302 | 			}
303 | 		} catch (error) {
304 | 			ui.displayErrorBox(`Failed to select brief: ${(error as Error).message}`);
305 | 			this.setLastResult({
306 | 				success: false,
307 | 				action: 'select',
308 | 				message: (error as Error).message
309 | 			});
310 | 			process.exit(1);
311 | 		}
312 | 	}
313 | 
314 | 	/**
315 | 	 * Execute create brief (redirect to web UI)
316 | 	 */
317 | 	private async executeCreate(name?: string): Promise<void> {
318 | 		try {
319 | 			// Check authentication
320 | 			if (!(await this.checkAuth())) {
321 | 				process.exit(1);
322 | 			}
323 | 
324 | 			// Use the bridge to redirect to web UI
325 | 			const remoteResult = await tryAddTagViaRemote({
326 | 				tagName: name || 'new-brief',
327 | 				projectRoot: process.cwd(),
328 | 				report: (level: LogLevel, ...args: unknown[]) => {
329 | 					const message = args[0] as string;
330 | 					if (level === 'error') ui.displayError(message);
331 | 					else if (level === 'warn') ui.displayWarning(message);
332 | 					else if (level === 'info') ui.displayInfo(message);
333 | 				}
334 | 			});
335 | 
336 | 			if (!remoteResult) {
337 | 				throw new Error('Failed to get brief creation URL');
338 | 			}
339 | 
340 | 			this.setLastResult({
341 | 				success: remoteResult.success,
342 | 				action: 'create',
343 | 				message: remoteResult.message
344 | 			});
345 | 
346 | 			if (!remoteResult.success) {
347 | 				process.exit(1);
348 | 			}
349 | 		} catch (error) {
350 | 			ui.displayErrorBox(`Failed to create brief: ${(error as Error).message}`);
351 | 			this.setLastResult({
352 | 				success: false,
353 | 				action: 'create',
354 | 				message: (error as Error).message
355 | 			});
356 | 			process.exit(1);
357 | 		}
358 | 	}
359 | 
360 | 	/**
361 | 	 * Set the last result for programmatic access
362 | 	 */
363 | 	private setLastResult(result: BriefsResult): void {
364 | 		this.lastResult = result;
365 | 	}
366 | 
367 | 	/**
368 | 	 * Get the last result (for programmatic usage)
369 | 	 */
370 | 	getLastResult(): BriefsResult | undefined {
371 | 		return this.lastResult;
372 | 	}
373 | 
374 | 	/**
375 | 	 * Register this command on an existing program
376 | 	 */
377 | 	static register(program: Command, name?: string): BriefsCommand {
378 | 		const briefsCommand = new BriefsCommand(name);
379 | 		program.addCommand(briefsCommand);
380 | 		return briefsCommand;
381 | 	}
382 | }
383 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/tasks/services/preflight-checker.service.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Preflight Checker Service
  3 |  * Validates environment and prerequisites for autopilot execution
  4 |  */
  5 | 
  6 | import { readFileSync, existsSync, readdirSync } from 'node:fs';
  7 | import { join } from 'path';
  8 | import { execSync } from 'child_process';
  9 | import { getLogger } from '../../../common/logger/factory.js';
 10 | import {
 11 | 	isGitRepository,
 12 | 	isGhCliAvailable,
 13 | 	getDefaultBranch
 14 | } from '../../../common/utils/git-utils.js';
 15 | 
 16 | const logger = getLogger('PreflightChecker');
 17 | 
 18 | /**
 19 |  * Result of a single preflight check
 20 |  */
 21 | export interface CheckResult {
 22 | 	/** Whether the check passed */
 23 | 	success: boolean;
 24 | 	/** The value detected/validated */
 25 | 	value?: any;
 26 | 	/** Error or warning message */
 27 | 	message?: string;
 28 | }
 29 | 
 30 | /**
 31 |  * Complete preflight validation results
 32 |  */
 33 | export interface PreflightResult {
 34 | 	/** Overall success - all checks passed */
 35 | 	success: boolean;
 36 | 	/** Test command detection result */
 37 | 	testCommand: CheckResult;
 38 | 	/** Git working tree status */
 39 | 	gitWorkingTree: CheckResult;
 40 | 	/** Required tools availability */
 41 | 	requiredTools: CheckResult;
 42 | 	/** Default branch detection */
 43 | 	defaultBranch: CheckResult;
 44 | 	/** Summary message */
 45 | 	summary: string;
 46 | }
 47 | 
 48 | /**
 49 |  * Tool validation result
 50 |  */
 51 | interface ToolCheck {
 52 | 	name: string;
 53 | 	available: boolean;
 54 | 	version?: string;
 55 | 	message?: string;
 56 | }
 57 | 
 58 | /**
 59 |  * PreflightChecker validates environment for autopilot execution
 60 |  */
 61 | export class PreflightChecker {
 62 | 	private projectRoot: string;
 63 | 
 64 | 	constructor(projectRoot: string) {
 65 | 		if (!projectRoot) {
 66 | 			throw new Error('projectRoot is required for PreflightChecker');
 67 | 		}
 68 | 		this.projectRoot = projectRoot;
 69 | 	}
 70 | 
 71 | 	/**
 72 | 	 * Detect test command from package.json
 73 | 	 */
 74 | 	async detectTestCommand(): Promise<CheckResult> {
 75 | 		try {
 76 | 			const packageJsonPath = join(this.projectRoot, 'package.json');
 77 | 			const packageJsonContent = readFileSync(packageJsonPath, 'utf-8');
 78 | 			const packageJson = JSON.parse(packageJsonContent);
 79 | 
 80 | 			if (!packageJson.scripts || !packageJson.scripts.test) {
 81 | 				return {
 82 | 					success: false,
 83 | 					message:
 84 | 						'No test script found in package.json. Please add a "test" script.'
 85 | 				};
 86 | 			}
 87 | 
 88 | 			const testCommand = packageJson.scripts.test;
 89 | 
 90 | 			return {
 91 | 				success: true,
 92 | 				value: testCommand,
 93 | 				message: `Test command: ${testCommand}`
 94 | 			};
 95 | 		} catch (error: any) {
 96 | 			if (error.code === 'ENOENT') {
 97 | 				return {
 98 | 					success: false,
 99 | 					message: 'package.json not found in project root'
100 | 				};
101 | 			}
102 | 
103 | 			return {
104 | 				success: false,
105 | 				message: `Failed to read package.json: ${error.message}`
106 | 			};
107 | 		}
108 | 	}
109 | 
110 | 	/**
111 | 	 * Check git working tree status
112 | 	 */
113 | 	async checkGitWorkingTree(): Promise<CheckResult> {
114 | 		try {
115 | 			// Check if it's a git repository
116 | 			const isRepo = await isGitRepository(this.projectRoot);
117 | 			if (!isRepo) {
118 | 				return {
119 | 					success: false,
120 | 					message: 'Not a git repository. Initialize git first.'
121 | 				};
122 | 			}
123 | 
124 | 			// Check for changes (staged/unstaged/untracked) without requiring HEAD
125 | 			const status = execSync('git status --porcelain', {
126 | 				cwd: this.projectRoot,
127 | 				encoding: 'utf-8',
128 | 				timeout: 5000
129 | 			});
130 | 			if (status.trim().length > 0) {
131 | 				return {
132 | 					success: false,
133 | 					value: 'dirty',
134 | 					message:
135 | 						'Working tree has uncommitted or untracked changes. Please commit or stash them.'
136 | 				};
137 | 			}
138 | 			return {
139 | 				success: true,
140 | 				value: 'clean',
141 | 				message: 'Working tree is clean'
142 | 			};
143 | 		} catch (error: any) {
144 | 			return {
145 | 				success: false,
146 | 				message: `Git check failed: ${error.message}`
147 | 			};
148 | 		}
149 | 	}
150 | 
151 | 	/**
152 | 	 * Detect project types based on common configuration files
153 | 	 */
154 | 	private detectProjectTypes(): string[] {
155 | 		const types: string[] = [];
156 | 
157 | 		if (existsSync(join(this.projectRoot, 'package.json'))) types.push('node');
158 | 		if (
159 | 			existsSync(join(this.projectRoot, 'requirements.txt')) ||
160 | 			existsSync(join(this.projectRoot, 'setup.py')) ||
161 | 			existsSync(join(this.projectRoot, 'pyproject.toml'))
162 | 		)
163 | 			types.push('python');
164 | 		if (
165 | 			existsSync(join(this.projectRoot, 'pom.xml')) ||
166 | 			existsSync(join(this.projectRoot, 'build.gradle'))
167 | 		)
168 | 			types.push('java');
169 | 		if (existsSync(join(this.projectRoot, 'go.mod'))) types.push('go');
170 | 		if (existsSync(join(this.projectRoot, 'Cargo.toml'))) types.push('rust');
171 | 		if (existsSync(join(this.projectRoot, 'composer.json'))) types.push('php');
172 | 		if (existsSync(join(this.projectRoot, 'Gemfile'))) types.push('ruby');
173 | 		const files = readdirSync(this.projectRoot);
174 | 		if (files.some((f) => f.endsWith('.csproj') || f.endsWith('.sln')))
175 | 			types.push('dotnet');
176 | 
177 | 		return types;
178 | 	}
179 | 
180 | 	/**
181 | 	 * Get required tools for a project type
182 | 	 */
183 | 	private getToolsForProjectType(
184 | 		type: string
185 | 	): Array<{ command: string; args: string[] }> {
186 | 		const toolMap: Record<
187 | 			string,
188 | 			Array<{ command: string; args: string[] }>
189 | 		> = {
190 | 			node: [
191 | 				{ command: 'node', args: ['--version'] },
192 | 				{ command: 'npm', args: ['--version'] }
193 | 			],
194 | 			python: [
195 | 				{ command: 'python3', args: ['--version'] },
196 | 				{ command: 'pip3', args: ['--version'] }
197 | 			],
198 | 			java: [{ command: 'java', args: ['--version'] }],
199 | 			go: [{ command: 'go', args: ['version'] }],
200 | 			rust: [{ command: 'cargo', args: ['--version'] }],
201 | 			php: [
202 | 				{ command: 'php', args: ['--version'] },
203 | 				{ command: 'composer', args: ['--version'] }
204 | 			],
205 | 			ruby: [
206 | 				{ command: 'ruby', args: ['--version'] },
207 | 				{ command: 'bundle', args: ['--version'] }
208 | 			],
209 | 			dotnet: [{ command: 'dotnet', args: ['--version'] }]
210 | 		};
211 | 
212 | 		return toolMap[type] || [];
213 | 	}
214 | 
215 | 	/**
216 | 	 * Validate required tools availability
217 | 	 */
218 | 	async validateRequiredTools(): Promise<CheckResult> {
219 | 		const tools: ToolCheck[] = [];
220 | 
221 | 		// Always check git and gh CLI
222 | 		tools.push(this.checkTool('git', ['--version']));
223 | 		tools.push(await this.checkGhCli());
224 | 
225 | 		// Detect project types and check their tools
226 | 		const projectTypes = this.detectProjectTypes();
227 | 
228 | 		if (projectTypes.length === 0) {
229 | 			logger.warn('No recognized project type detected');
230 | 		} else {
231 | 			logger.info(`Detected project types: ${projectTypes.join(', ')}`);
232 | 		}
233 | 
234 | 		for (const type of projectTypes) {
235 | 			const typeTools = this.getToolsForProjectType(type);
236 | 			for (const tool of typeTools) {
237 | 				tools.push(this.checkTool(tool.command, tool.args));
238 | 			}
239 | 		}
240 | 
241 | 		// Determine overall success
242 | 		const allAvailable = tools.every((tool) => tool.available);
243 | 		const missingTools = tools
244 | 			.filter((tool) => !tool.available)
245 | 			.map((tool) => tool.name);
246 | 
247 | 		if (!allAvailable) {
248 | 			return {
249 | 				success: false,
250 | 				value: tools,
251 | 				message: `Missing required tools: ${missingTools.join(', ')}`
252 | 			};
253 | 		}
254 | 
255 | 		return {
256 | 			success: true,
257 | 			value: tools,
258 | 			message: 'All required tools are available'
259 | 		};
260 | 	}
261 | 
262 | 	/**
263 | 	 * Check if a command-line tool is available
264 | 	 */
265 | 	private checkTool(command: string, versionArgs: string[]): ToolCheck {
266 | 		try {
267 | 			const version = execSync(`${command} ${versionArgs.join(' ')}`, {
268 | 				cwd: this.projectRoot,
269 | 				encoding: 'utf-8',
270 | 				stdio: 'pipe',
271 | 				timeout: 5000
272 | 			})
273 | 				.trim()
274 | 				.split('\n')[0];
275 | 
276 | 			return {
277 | 				name: command,
278 | 				available: true,
279 | 				version,
280 | 				message: `${command} ${version}`
281 | 			};
282 | 		} catch (error) {
283 | 			return {
284 | 				name: command,
285 | 				available: false,
286 | 				message: `${command} not found`
287 | 			};
288 | 		}
289 | 	}
290 | 
291 | 	/**
292 | 	 * Check GitHub CLI installation and authentication status
293 | 	 */
294 | 	private async checkGhCli(): Promise<ToolCheck> {
295 | 		try {
296 | 			const version = execSync('gh --version', {
297 | 				cwd: this.projectRoot,
298 | 				encoding: 'utf-8',
299 | 				stdio: 'pipe',
300 | 				timeout: 5000
301 | 			})
302 | 				.trim()
303 | 				.split('\n')[0];
304 | 			const authed = await isGhCliAvailable(this.projectRoot);
305 | 			return {
306 | 				name: 'gh',
307 | 				available: true,
308 | 				version,
309 | 				message: authed
310 | 					? 'GitHub CLI installed (authenticated)'
311 | 					: 'GitHub CLI installed (not authenticated)'
312 | 			};
313 | 		} catch {
314 | 			return { name: 'gh', available: false, message: 'GitHub CLI not found' };
315 | 		}
316 | 	}
317 | 
318 | 	/**
319 | 	 * Detect default branch
320 | 	 */
321 | 	async detectDefaultBranch(): Promise<CheckResult> {
322 | 		try {
323 | 			const defaultBranch = await getDefaultBranch(this.projectRoot);
324 | 
325 | 			if (!defaultBranch) {
326 | 				return {
327 | 					success: false,
328 | 					message:
329 | 						'Could not determine default branch. Make sure remote is configured.'
330 | 				};
331 | 			}
332 | 
333 | 			return {
334 | 				success: true,
335 | 				value: defaultBranch,
336 | 				message: `Default branch: ${defaultBranch}`
337 | 			};
338 | 		} catch (error: any) {
339 | 			return {
340 | 				success: false,
341 | 				message: `Failed to detect default branch: ${error.message}`
342 | 			};
343 | 		}
344 | 	}
345 | 
346 | 	/**
347 | 	 * Run all preflight checks
348 | 	 */
349 | 	async runAllChecks(): Promise<PreflightResult> {
350 | 		logger.info('Running preflight checks...');
351 | 
352 | 		const testCommand = await this.detectTestCommand();
353 | 		const gitWorkingTree = await this.checkGitWorkingTree();
354 | 		const requiredTools = await this.validateRequiredTools();
355 | 		const defaultBranch = await this.detectDefaultBranch();
356 | 
357 | 		const allSuccess =
358 | 			testCommand.success &&
359 | 			gitWorkingTree.success &&
360 | 			requiredTools.success &&
361 | 			defaultBranch.success;
362 | 
363 | 		// Build summary
364 | 		const passed: string[] = [];
365 | 		const failed: string[] = [];
366 | 
367 | 		if (testCommand.success) passed.push('Test command');
368 | 		else failed.push('Test command');
369 | 
370 | 		if (gitWorkingTree.success) passed.push('Git working tree');
371 | 		else failed.push('Git working tree');
372 | 
373 | 		if (requiredTools.success) passed.push('Required tools');
374 | 		else failed.push('Required tools');
375 | 
376 | 		if (defaultBranch.success) passed.push('Default branch');
377 | 		else failed.push('Default branch');
378 | 
379 | 		const total = passed.length + failed.length;
380 | 		const summary = allSuccess
381 | 			? `All preflight checks passed (${passed.length}/${total})`
382 | 			: `Preflight checks failed: ${failed.join(', ')} (${passed.length}/${total} passed)`;
383 | 
384 | 		logger.info(summary);
385 | 
386 | 		return {
387 | 			success: allSuccess,
388 | 			testCommand,
389 | 			gitWorkingTree,
390 | 			requiredTools,
391 | 			defaultBranch,
392 | 			summary
393 | 		};
394 | 	}
395 | }
396 | 
```

--------------------------------------------------------------------------------
/docs/examples/codex-cli-usage.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Codex CLI Provider Usage Examples
  2 | 
  3 | This guide provides practical examples of using Task Master with the Codex CLI provider.
  4 | 
  5 | ## Prerequisites
  6 | 
  7 | Before using these examples, ensure you have:
  8 | 
  9 | ```bash
 10 | # 1. Codex CLI installed
 11 | npm install -g @openai/codex
 12 | 
 13 | # 2. Authenticated with ChatGPT
 14 | codex login
 15 | 
 16 | # 3. Codex CLI configured as your provider
 17 | task-master models --set-main gpt-5-codex --codex-cli
 18 | ```
 19 | 
 20 | ## Example 1: Basic Task Creation
 21 | 
 22 | Use Codex CLI to create tasks from a simple description:
 23 | 
 24 | ```bash
 25 | # Add a task with AI-powered enhancement
 26 | task-master add-task --prompt="Implement user authentication with JWT" --research
 27 | ```
 28 | 
 29 | **What happens**:
 30 | 1. Task Master sends your prompt to GPT-5-Codex via the CLI
 31 | 2. The AI analyzes your request and generates a detailed task
 32 | 3. The task is added to your `.taskmaster/tasks/tasks.json`
 33 | 4. OAuth credentials are automatically used (no API key needed)
 34 | 
 35 | ## Example 2: Parsing a Product Requirements Document
 36 | 
 37 | Create a comprehensive task list from a PRD:
 38 | 
 39 | ```bash
 40 | # Create your PRD
 41 | cat > my-feature.txt <<EOF
 42 | # User Profile Feature
 43 | 
 44 | ## Requirements
 45 | 1. Users can view their profile
 46 | 2. Users can edit their information
 47 | 3. Profile pictures can be uploaded
 48 | 4. Email verification required
 49 | 
 50 | ## Technical Constraints
 51 | - Use React for frontend
 52 | - Node.js/Express backend
 53 | - PostgreSQL database
 54 | EOF
 55 | 
 56 | # Parse with Codex CLI
 57 | task-master parse-prd my-feature.txt --num-tasks 12
 58 | ```
 59 | 
 60 | **What happens**:
 61 | 1. GPT-5-Codex reads and analyzes your PRD
 62 | 2. Generates structured tasks with dependencies
 63 | 3. Creates subtasks for complex items
 64 | 4. Saves everything to `.taskmaster/tasks/`
 65 | 
 66 | ## Example 3: Expanding Tasks with Research
 67 | 
 68 | Break down a complex task into detailed subtasks:
 69 | 
 70 | ```bash
 71 | # First, show your current tasks
 72 | task-master list
 73 | 
 74 | # Expand a specific task (e.g., task 1.2)
 75 | task-master expand --id=1.2 --research --force
 76 | ```
 77 | 
 78 | **What happens**:
 79 | 1. Codex CLI uses GPT-5 for research-level analysis
 80 | 2. Breaks down the task into logical subtasks
 81 | 3. Adds implementation details and test strategies
 82 | 4. Updates the task with dependency information
 83 | 
 84 | ## Example 4: Analyzing Project Complexity
 85 | 
 86 | Get AI-powered insights into your project's task complexity:
 87 | 
 88 | ```bash
 89 | # Analyze all tasks
 90 | task-master analyze-complexity --research
 91 | 
 92 | # View the complexity report
 93 | task-master complexity-report
 94 | ```
 95 | 
 96 | **What happens**:
 97 | 1. GPT-5 analyzes each task's scope and requirements
 98 | 2. Assigns complexity scores and estimates subtask counts
 99 | 3. Generates a detailed report
100 | 4. Saves to `.taskmaster/reports/task-complexity-report.json`
101 | 
102 | ## Example 5: Using Custom Codex CLI Settings
103 | 
104 | Configure Codex CLI behavior for different commands:
105 | 
106 | ```json
107 | // In .taskmaster/config.json
108 | {
109 |   "models": {
110 |     "main": {
111 |       "provider": "codex-cli",
112 |       "modelId": "gpt-5-codex",
113 |       "maxTokens": 128000,
114 |       "temperature": 0.2
115 |     }
116 |   },
117 |   "codexCli": {
118 |     "allowNpx": true,
119 |     "approvalMode": "on-failure",
120 |     "sandboxMode": "workspace-write",
121 |     "commandSpecific": {
122 |       "parse-prd": {
123 |         "verbose": true,
124 |         "approvalMode": "never"
125 |       },
126 |       "expand": {
127 |         "sandboxMode": "read-only",
128 |         "verbose": true
129 |       }
130 |     }
131 |   }
132 | }
133 | ```
134 | 
135 | ```bash
136 | # Now parse-prd runs with verbose output and no approvals
137 | task-master parse-prd requirements.txt
138 | 
139 | # Expand runs with read-only mode
140 | task-master expand --id=2.1
141 | ```
142 | 
143 | ## Example 6: Workflow - Building a Feature End-to-End
144 | 
145 | Complete workflow from PRD to implementation tracking:
146 | 
147 | ```bash
148 | # Step 1: Initialize project
149 | task-master init
150 | 
151 | # Step 2: Set up Codex CLI
152 | task-master models --set-main gpt-5-codex --codex-cli
153 | task-master models --set-fallback gpt-5 --codex-cli
154 | 
155 | # Step 3: Create PRD
156 | cat > feature-prd.txt <<EOF
157 | # Authentication System
158 | 
159 | Implement a complete authentication system with:
160 | - User registration
161 | - Email verification
162 | - Password reset
163 | - Two-factor authentication
164 | - Session management
165 | EOF
166 | 
167 | # Step 4: Parse PRD into tasks
168 | task-master parse-prd feature-prd.txt --num-tasks 8
169 | 
170 | # Step 5: Analyze complexity
171 | task-master analyze-complexity --research
172 | 
173 | # Step 6: Expand complex tasks
174 | task-master expand --all --research
175 | 
176 | # Step 7: Start working
177 | task-master next
178 | # Shows: Task 1.1: User registration database schema
179 | 
180 | # Step 8: Mark completed as you work
181 | task-master set-status --id=1.1 --status=done
182 | 
183 | # Step 9: Continue to next task
184 | task-master next
185 | ```
186 | 
187 | ## Example 7: Multi-Role Configuration
188 | 
189 | Use Codex CLI for main tasks, Perplexity for research:
190 | 
191 | ```json
192 | // In .taskmaster/config.json
193 | {
194 |   "models": {
195 |     "main": {
196 |       "provider": "codex-cli",
197 |       "modelId": "gpt-5-codex",
198 |       "maxTokens": 128000,
199 |       "temperature": 0.2
200 |     },
201 |     "research": {
202 |       "provider": "perplexity",
203 |       "modelId": "sonar-pro",
204 |       "maxTokens": 8700,
205 |       "temperature": 0.1
206 |     },
207 |     "fallback": {
208 |       "provider": "codex-cli",
209 |       "modelId": "gpt-5",
210 |       "maxTokens": 128000,
211 |       "temperature": 0.2
212 |     }
213 |   }
214 | }
215 | ```
216 | 
217 | ```bash
218 | # Main task operations use GPT-5-Codex
219 | task-master add-task --prompt="Build REST API endpoint"
220 | 
221 | # Research operations use Perplexity
222 | task-master analyze-complexity --research
223 | 
224 | # Fallback to GPT-5 if needed
225 | task-master expand --id=3.2 --force
226 | ```
227 | 
228 | ## Example 8: Troubleshooting Common Issues
229 | 
230 | ### Issue: Codex CLI not found
231 | 
232 | ```bash
233 | # Check if Codex is installed
234 | codex --version
235 | 
236 | # If not found, install globally
237 | npm install -g @openai/codex
238 | 
239 | # Or enable npx fallback in config
240 | cat >> .taskmaster/config.json <<EOF
241 | {
242 |   "codexCli": {
243 |     "allowNpx": true
244 |   }
245 | }
246 | EOF
247 | ```
248 | 
249 | ### Issue: Not authenticated
250 | 
251 | ```bash
252 | # Check auth status
253 | codex
254 | # Use /about command to see auth info
255 | 
256 | # Re-authenticate if needed
257 | codex login
258 | ```
259 | 
260 | ### Issue: Want more verbose output
261 | 
262 | ```bash
263 | # Enable verbose mode in config
264 | cat >> .taskmaster/config.json <<EOF
265 | {
266 |   "codexCli": {
267 |     "verbose": true
268 |   }
269 | }
270 | EOF
271 | 
272 | # Or for specific commands
273 | task-master parse-prd my-prd.txt
274 | # (verbose output shows detailed Codex CLI interactions)
275 | ```
276 | 
277 | ## Example 9: CI/CD Integration
278 | 
279 | Use Codex CLI in automated workflows:
280 | 
281 | ```yaml
282 | # .github/workflows/task-analysis.yml
283 | name: Analyze Task Complexity
284 | 
285 | on:
286 |   push:
287 |     paths:
288 |       - '.taskmaster/**'
289 | 
290 | jobs:
291 |   analyze:
292 |     runs-on: ubuntu-latest
293 |     steps:
294 |       - uses: actions/checkout@v4
295 | 
296 |       - name: Setup Node.js
297 |         uses: actions/setup-node@v4
298 |         with:
299 |           node-version: '20'
300 | 
301 |       - name: Install Task Master
302 |         run: npm install -g task-master-ai
303 | 
304 |       - name: Configure Codex CLI
305 |         run: |
306 |           npm install -g @openai/codex
307 |           echo "${{ secrets.OPENAI_CODEX_API_KEY }}" > ~/.codex-auth
308 |         env:
309 |           OPENAI_CODEX_API_KEY: ${{ secrets.OPENAI_CODEX_API_KEY }}
310 | 
311 |       - name: Configure Task Master
312 |         run: |
313 |           cat > .taskmaster/config.json <<EOF
314 |           {
315 |             "models": {
316 |               "main": {
317 |                 "provider": "codex-cli",
318 |                 "modelId": "gpt-5"
319 |               }
320 |             },
321 |             "codexCli": {
322 |               "allowNpx": true,
323 |               "skipGitRepoCheck": true,
324 |               "approvalMode": "never",
325 |               "fullAuto": true
326 |             }
327 |           }
328 |           EOF
329 | 
330 |       - name: Analyze Complexity
331 |         run: task-master analyze-complexity --research
332 | 
333 |       - name: Upload Report
334 |         uses: actions/upload-artifact@v3
335 |         with:
336 |           name: complexity-report
337 |           path: .taskmaster/reports/task-complexity-report.json
338 | ```
339 | 
340 | ## Best Practices
341 | 
342 | ### 1. Use OAuth for Development
343 | 
344 | ```bash
345 | # For local development, use OAuth (no API key needed)
346 | codex login
347 | task-master models --set-main gpt-5-codex --codex-cli
348 | ```
349 | 
350 | ### 2. Configure Approval Modes Appropriately
351 | 
352 | ```json
353 | {
354 |   "codexCli": {
355 |     "approvalMode": "on-failure",  // Safe default
356 |     "sandboxMode": "workspace-write"  // Restricts to project directory
357 |   }
358 | }
359 | ```
360 | 
361 | ### 3. Use Command-Specific Settings
362 | 
363 | ```json
364 | {
365 |   "codexCli": {
366 |     "commandSpecific": {
367 |       "parse-prd": {
368 |         "approvalMode": "never",  // PRD parsing is safe
369 |         "verbose": true
370 |       },
371 |       "expand": {
372 |         "approvalMode": "on-request",  // More cautious for task expansion
373 |         "verbose": false
374 |       }
375 |     }
376 |   }
377 | }
378 | ```
379 | 
380 | ### 4. Leverage Codebase Analysis
381 | 
382 | ```json
383 | {
384 |   "global": {
385 |     "enableCodebaseAnalysis": true  // Let Codex analyze your code
386 |   }
387 | }
388 | ```
389 | 
390 | ### 5. Handle Errors Gracefully
391 | 
392 | ```bash
393 | # Always configure a fallback model
394 | task-master models --set-fallback gpt-5 --codex-cli
395 | 
396 | # Or use a different provider as fallback
397 | task-master models --set-fallback claude-3-5-sonnet
398 | ```
399 | 
400 | ## Next Steps
401 | 
402 | - Read the [Codex CLI Provider Documentation](../providers/codex-cli.md)
403 | - Explore [Configuration Options](../configuration.md#codex-cli-provider)
404 | - Check out [Command Reference](../command-reference.md)
405 | - Learn about [Task Structure](../task-structure.md)
406 | 
407 | ## Common Patterns
408 | 
409 | ### Pattern: Daily Development Workflow
410 | 
411 | ```bash
412 | # Morning: Review tasks
413 | task-master list
414 | 
415 | # Get next task
416 | task-master next
417 | 
418 | # Work on task...
419 | 
420 | # Update task with notes
421 | task-master update-subtask --id=2.3 --prompt="Implemented authentication middleware"
422 | 
423 | # Mark complete
424 | task-master set-status --id=2.3 --status=done
425 | 
426 | # Repeat
427 | ```
428 | 
429 | ### Pattern: Feature Planning
430 | 
431 | ```bash
432 | # Write feature spec
433 | vim new-feature.txt
434 | 
435 | # Generate tasks
436 | task-master parse-prd new-feature.txt --num-tasks 10
437 | 
438 | # Analyze and expand
439 | task-master analyze-complexity --research
440 | task-master expand --all --research --force
441 | 
442 | # Review and adjust
443 | task-master list
444 | ```
445 | 
446 | ### Pattern: Sprint Planning
447 | 
448 | ```bash
449 | # Parse sprint requirements
450 | task-master parse-prd sprint-requirements.txt
451 | 
452 | # Analyze complexity
453 | task-master analyze-complexity --research
454 | 
455 | # View report
456 | task-master complexity-report
457 | 
458 | # Adjust task estimates based on complexity scores
459 | ```
460 | 
461 | ---
462 | 
463 | For more examples and advanced usage, see the [full documentation](https://docs.task-master.dev).
464 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/utils/auto-update.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Auto-update utilities for task-master-ai CLI
  3 |  */
  4 | 
  5 | import { spawn } from 'child_process';
  6 | import https from 'https';
  7 | import boxen from 'boxen';
  8 | import chalk from 'chalk';
  9 | import ora from 'ora';
 10 | import process from 'process';
 11 | 
 12 | export interface UpdateInfo {
 13 | 	currentVersion: string;
 14 | 	latestVersion: string;
 15 | 	needsUpdate: boolean;
 16 | 	highlights?: string[];
 17 | }
 18 | 
 19 | /**
 20 |  * Get current version from build-time injected environment variable
 21 |  */
 22 | function getCurrentVersion(): string {
 23 | 	// Version is injected at build time via TM_PUBLIC_VERSION
 24 | 	const version = process.env.TM_PUBLIC_VERSION;
 25 | 	if (version && version !== 'unknown') {
 26 | 		return version;
 27 | 	}
 28 | 
 29 | 	// Fallback for development or if injection failed
 30 | 	console.warn('Could not read version from TM_PUBLIC_VERSION, using fallback');
 31 | 	return '0.0.0';
 32 | }
 33 | 
 34 | /**
 35 |  * Compare semantic versions with proper pre-release handling
 36 |  * @param v1 - First version
 37 |  * @param v2 - Second version
 38 |  * @returns -1 if v1 < v2, 0 if v1 = v2, 1 if v1 > v2
 39 |  */
 40 | export function compareVersions(v1: string, v2: string): number {
 41 | 	const toParts = (v: string) => {
 42 | 		const [core, pre = ''] = v.split('-', 2);
 43 | 		const nums = core.split('.').map((n) => Number.parseInt(n, 10) || 0);
 44 | 		return { nums, pre };
 45 | 	};
 46 | 
 47 | 	const a = toParts(v1);
 48 | 	const b = toParts(v2);
 49 | 	const len = Math.max(a.nums.length, b.nums.length);
 50 | 
 51 | 	// Compare numeric parts
 52 | 	for (let i = 0; i < len; i++) {
 53 | 		const d = (a.nums[i] || 0) - (b.nums[i] || 0);
 54 | 		if (d !== 0) return d < 0 ? -1 : 1;
 55 | 	}
 56 | 
 57 | 	// Handle pre-release comparison
 58 | 	if (a.pre && !b.pre) return -1; // prerelease < release
 59 | 	if (!a.pre && b.pre) return 1; // release > prerelease
 60 | 	if (a.pre === b.pre) return 0; // same or both empty
 61 | 	return a.pre < b.pre ? -1 : 1; // basic prerelease tie-break
 62 | }
 63 | 
 64 | /**
 65 |  * Fetch CHANGELOG.md from GitHub and extract highlights for a specific version
 66 |  */
 67 | async function fetchChangelogHighlights(version: string): Promise<string[]> {
 68 | 	return new Promise((resolve) => {
 69 | 		const options = {
 70 | 			hostname: 'raw.githubusercontent.com',
 71 | 			path: '/eyaltoledano/claude-task-master/main/CHANGELOG.md',
 72 | 			method: 'GET',
 73 | 			headers: {
 74 | 				'User-Agent': `task-master-ai/${version}`
 75 | 			}
 76 | 		};
 77 | 
 78 | 		const req = https.request(options, (res) => {
 79 | 			let data = '';
 80 | 
 81 | 			res.on('data', (chunk) => {
 82 | 				data += chunk;
 83 | 			});
 84 | 
 85 | 			res.on('end', () => {
 86 | 				try {
 87 | 					if (res.statusCode !== 200) {
 88 | 						resolve([]);
 89 | 						return;
 90 | 					}
 91 | 
 92 | 					const highlights = parseChangelogHighlights(data, version);
 93 | 					resolve(highlights);
 94 | 				} catch (error) {
 95 | 					resolve([]);
 96 | 				}
 97 | 			});
 98 | 		});
 99 | 
100 | 		req.on('error', () => {
101 | 			resolve([]);
102 | 		});
103 | 
104 | 		req.setTimeout(3000, () => {
105 | 			req.destroy();
106 | 			resolve([]);
107 | 		});
108 | 
109 | 		req.end();
110 | 	});
111 | }
112 | 
113 | /**
114 |  * Parse changelog markdown to extract Minor Changes for a specific version
115 |  * @internal - Exported for testing purposes only
116 |  */
117 | export function parseChangelogHighlights(
118 | 	changelog: string,
119 | 	version: string
120 | ): string[] {
121 | 	try {
122 | 		// Validate version format (basic semver pattern) to prevent ReDoS
123 | 		if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(version)) {
124 | 			return [];
125 | 		}
126 | 
127 | 		// Find the version section
128 | 		const versionRegex = new RegExp(
129 | 			`## ${version.replace(/\./g, '\\.')}\\s*\\n`,
130 | 			'i'
131 | 		);
132 | 		const versionMatch = changelog.match(versionRegex);
133 | 
134 | 		if (!versionMatch) {
135 | 			return [];
136 | 		}
137 | 
138 | 		// Extract content from this version to the next version heading
139 | 		const startIdx = versionMatch.index! + versionMatch[0].length;
140 | 		const nextVersionIdx = changelog.indexOf('\n## ', startIdx);
141 | 		const versionContent =
142 | 			nextVersionIdx > 0
143 | 				? changelog.slice(startIdx, nextVersionIdx)
144 | 				: changelog.slice(startIdx);
145 | 
146 | 		// Find Minor Changes section
147 | 		const minorChangesMatch = versionContent.match(
148 | 			/### Minor Changes\s*\n([\s\S]*?)(?=\n###|\n##|$)/i
149 | 		);
150 | 
151 | 		if (!minorChangesMatch) {
152 | 			return [];
153 | 		}
154 | 
155 | 		const minorChangesContent = minorChangesMatch[1];
156 | 		const highlights: string[] = [];
157 | 
158 | 		// Extract all bullet points (lines starting with -)
159 | 		// Format: - [#PR](...) Thanks [@author]! - Description
160 | 		const bulletRegex = /^-\s+\[#\d+\][^\n]*?!\s+-\s+(.+?)$/gm;
161 | 		let match;
162 | 
163 | 		while ((match = bulletRegex.exec(minorChangesContent)) !== null) {
164 | 			const desc = match[1].trim();
165 | 			highlights.push(desc);
166 | 		}
167 | 
168 | 		return highlights;
169 | 	} catch (error) {
170 | 		return [];
171 | 	}
172 | }
173 | 
174 | /**
175 |  * Check for newer version of task-master-ai
176 |  */
177 | export async function checkForUpdate(
178 | 	currentVersionOverride?: string
179 | ): Promise<UpdateInfo> {
180 | 	const currentVersion = currentVersionOverride || getCurrentVersion();
181 | 
182 | 	return new Promise((resolve) => {
183 | 		const options = {
184 | 			hostname: 'registry.npmjs.org',
185 | 			path: '/task-master-ai',
186 | 			method: 'GET',
187 | 			headers: {
188 | 				Accept: 'application/vnd.npm.install-v1+json',
189 | 				'User-Agent': `task-master-ai/${currentVersion}`
190 | 			}
191 | 		};
192 | 
193 | 		const req = https.request(options, (res) => {
194 | 			let data = '';
195 | 
196 | 			res.on('data', (chunk) => {
197 | 				data += chunk;
198 | 			});
199 | 
200 | 			res.on('end', async () => {
201 | 				try {
202 | 					if (res.statusCode !== 200)
203 | 						throw new Error(`npm registry status ${res.statusCode}`);
204 | 					const npmData = JSON.parse(data);
205 | 					const latestVersion = npmData['dist-tags']?.latest || currentVersion;
206 | 
207 | 					const needsUpdate =
208 | 						compareVersions(currentVersion, latestVersion) < 0;
209 | 
210 | 					// Fetch highlights if update is needed
211 | 					let highlights: string[] | undefined;
212 | 					if (needsUpdate) {
213 | 						highlights = await fetchChangelogHighlights(latestVersion);
214 | 					}
215 | 
216 | 					resolve({
217 | 						currentVersion,
218 | 						latestVersion,
219 | 						needsUpdate,
220 | 						highlights
221 | 					});
222 | 				} catch (error) {
223 | 					resolve({
224 | 						currentVersion,
225 | 						latestVersion: currentVersion,
226 | 						needsUpdate: false
227 | 					});
228 | 				}
229 | 			});
230 | 		});
231 | 
232 | 		req.on('error', () => {
233 | 			resolve({
234 | 				currentVersion,
235 | 				latestVersion: currentVersion,
236 | 				needsUpdate: false
237 | 			});
238 | 		});
239 | 
240 | 		req.setTimeout(3000, () => {
241 | 			req.destroy();
242 | 			resolve({
243 | 				currentVersion,
244 | 				latestVersion: currentVersion,
245 | 				needsUpdate: false
246 | 			});
247 | 		});
248 | 
249 | 		req.end();
250 | 	});
251 | }
252 | 
253 | /**
254 |  * Display upgrade notification message
255 |  */
256 | export function displayUpgradeNotification(
257 | 	currentVersion: string,
258 | 	latestVersion: string,
259 | 	highlights?: string[]
260 | ) {
261 | 	let content = `${chalk.blue.bold('Update Available!')} ${chalk.dim(currentVersion)} → ${chalk.green(latestVersion)}`;
262 | 
263 | 	if (highlights && highlights.length > 0) {
264 | 		content += '\n\n' + chalk.bold("What's New:");
265 | 		for (const highlight of highlights) {
266 | 			content += '\n' + chalk.cyan('• ') + highlight;
267 | 		}
268 | 		content += '\n\n' + 'Auto-updating to the latest version...';
269 | 	} else {
270 | 		content +=
271 | 			'\n\n' +
272 | 			'Auto-updating to the latest version with new features and bug fixes...';
273 | 	}
274 | 
275 | 	const message = boxen(content, {
276 | 		padding: 1,
277 | 		margin: { top: 1, bottom: 1 },
278 | 		borderColor: 'yellow',
279 | 		borderStyle: 'round'
280 | 	});
281 | 
282 | 	console.log(message);
283 | }
284 | 
285 | /**
286 |  * Automatically update task-master-ai to the latest version
287 |  */
288 | export async function performAutoUpdate(
289 | 	latestVersion: string
290 | ): Promise<boolean> {
291 | 	if (
292 | 		process.env.TASKMASTER_SKIP_AUTO_UPDATE === '1' ||
293 | 		process.env.CI ||
294 | 		process.env.NODE_ENV === 'test'
295 | 	) {
296 | 		const reason =
297 | 			process.env.TASKMASTER_SKIP_AUTO_UPDATE === '1'
298 | 				? 'TASKMASTER_SKIP_AUTO_UPDATE=1'
299 | 				: process.env.CI
300 | 					? 'CI environment'
301 | 					: 'NODE_ENV=test';
302 | 		console.log(chalk.dim(`Skipping auto-update (${reason})`));
303 | 		return false;
304 | 	}
305 | 	const spinner = ora({
306 | 		text: chalk.blue(
307 | 			`Updating task-master-ai to version ${chalk.green(latestVersion)}`
308 | 		),
309 | 		spinner: 'dots',
310 | 		color: 'blue'
311 | 	}).start();
312 | 
313 | 	return new Promise((resolve) => {
314 | 		const updateProcess = spawn(
315 | 			'npm',
316 | 			[
317 | 				'install',
318 | 				'-g',
319 | 				`task-master-ai@${latestVersion}`,
320 | 				'--no-fund',
321 | 				'--no-audit',
322 | 				'--loglevel=warn'
323 | 			],
324 | 			{
325 | 				stdio: ['ignore', 'pipe', 'pipe']
326 | 			}
327 | 		);
328 | 
329 | 		let errorOutput = '';
330 | 
331 | 		updateProcess.stdout.on('data', () => {
332 | 			// Update spinner text with progress
333 | 			spinner.text = chalk.blue(
334 | 				`Installing task-master-ai@${latestVersion}...`
335 | 			);
336 | 		});
337 | 
338 | 		updateProcess.stderr.on('data', (data) => {
339 | 			errorOutput += data.toString();
340 | 		});
341 | 
342 | 		updateProcess.on('close', (code) => {
343 | 			if (code === 0) {
344 | 				spinner.succeed(
345 | 					chalk.green(
346 | 						`Successfully updated to version ${chalk.bold(latestVersion)}`
347 | 					)
348 | 				);
349 | 				resolve(true);
350 | 			} else {
351 | 				spinner.fail(chalk.red('Auto-update failed'));
352 | 				console.log(
353 | 					chalk.cyan(
354 | 						`Please run manually: npm install -g task-master-ai@${latestVersion}`
355 | 					)
356 | 				);
357 | 				if (errorOutput) {
358 | 					console.log(chalk.dim(`Error: ${errorOutput.trim()}`));
359 | 				}
360 | 				resolve(false);
361 | 			}
362 | 		});
363 | 
364 | 		updateProcess.on('error', (error) => {
365 | 			spinner.fail(chalk.red('Auto-update failed'));
366 | 			console.log(chalk.red('Error:'), error.message);
367 | 			console.log(
368 | 				chalk.cyan(
369 | 					`Please run manually: npm install -g task-master-ai@${latestVersion}`
370 | 				)
371 | 			);
372 | 			resolve(false);
373 | 		});
374 | 	});
375 | }
376 | 
377 | /**
378 |  * Restart the CLI with the newly installed version
379 |  * @param argv - Original command-line arguments (process.argv)
380 |  */
381 | export function restartWithNewVersion(argv: string[]): void {
382 | 	const args = argv.slice(2); // Remove 'node' and script path
383 | 
384 | 	console.log(chalk.dim('Restarting with updated version...\n'));
385 | 
386 | 	// Spawn the updated task-master command
387 | 	const child = spawn('task-master', args, {
388 | 		stdio: 'inherit', // Inherit stdin/stdout/stderr so it looks seamless
389 | 		detached: false,
390 | 		shell: process.platform === 'win32' // Windows compatibility
391 | 	});
392 | 
393 | 	child.on('exit', (code, signal) => {
394 | 		if (signal) {
395 | 			process.kill(process.pid, signal);
396 | 			return;
397 | 		}
398 | 		process.exit(code ?? 0);
399 | 	});
400 | 
401 | 	child.on('error', (error) => {
402 | 		console.error(
403 | 			chalk.red('Failed to restart with new version:'),
404 | 			error.message
405 | 		);
406 | 		console.log(chalk.yellow('Please run your command again manually.'));
407 | 		process.exit(1);
408 | 	});
409 | }
410 | 
```

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

```typescript
  1 | /**
  2 |  * @fileoverview Task Loader Service
  3 |  * Loads and validates tasks for autopilot execution
  4 |  */
  5 | 
  6 | import type { Task, Subtask } from '../../../common/types/index.js';
  7 | import type { TaskService } from './task-service.js';
  8 | import { getLogger } from '../../../common/logger/factory.js';
  9 | import { isTaskComplete } from '../../../common/constants/index.js';
 10 | 
 11 | const logger = getLogger('TaskLoader');
 12 | 
 13 | /**
 14 |  * Validation error types
 15 |  */
 16 | export type ValidationErrorType =
 17 | 	| 'task_not_found'
 18 | 	| 'task_completed'
 19 | 	| 'no_subtasks'
 20 | 	| 'circular_dependencies'
 21 | 	| 'missing_dependencies'
 22 | 	| 'invalid_structure';
 23 | 
 24 | /**
 25 |  * Validation result for task loading
 26 |  */
 27 | export interface TaskValidationResult {
 28 | 	/** Whether validation passed */
 29 | 	success: boolean;
 30 | 	/** Loaded task (only present if validation succeeded) */
 31 | 	task?: Task;
 32 | 	/** Error type */
 33 | 	errorType?: ValidationErrorType;
 34 | 	/** Human-readable error message */
 35 | 	errorMessage?: string;
 36 | 	/** Actionable suggestion for fixing the error */
 37 | 	suggestion?: string;
 38 | 	/** Dependency analysis (only for dependency errors) */
 39 | 	dependencyIssues?: DependencyIssue[];
 40 | }
 41 | 
 42 | /**
 43 |  * Dependency issue details
 44 |  */
 45 | export interface DependencyIssue {
 46 | 	/** Subtask ID with the issue */
 47 | 	subtaskId: string;
 48 | 	/** Type of dependency issue */
 49 | 	issueType: 'circular' | 'missing' | 'invalid';
 50 | 	/** Description of the issue */
 51 | 	message: string;
 52 | 	/** The problematic dependency reference */
 53 | 	dependencyRef?: string;
 54 | }
 55 | 
 56 | /**
 57 |  * TaskLoaderService loads and validates tasks for autopilot execution
 58 |  */
 59 | export class TaskLoaderService {
 60 | 	private taskService: TaskService;
 61 | 
 62 | 	constructor(taskService: TaskService) {
 63 | 		if (!taskService) {
 64 | 			throw new Error('taskService is required for TaskLoaderService');
 65 | 		}
 66 | 		this.taskService = taskService;
 67 | 	}
 68 | 
 69 | 	/**
 70 | 	 * Load and validate a task for autopilot execution
 71 | 	 */
 72 | 	async loadAndValidateTask(taskId: string): Promise<TaskValidationResult> {
 73 | 		logger.info(`Loading task ${taskId}...`);
 74 | 
 75 | 		// Step 1: Load task
 76 | 		const task = await this.loadTask(taskId);
 77 | 		if (!task) {
 78 | 			return {
 79 | 				success: false,
 80 | 				errorType: 'task_not_found',
 81 | 				errorMessage: `Task with ID "${taskId}" not found`,
 82 | 				suggestion:
 83 | 					'Use "task-master list" to see available tasks or verify the task ID is correct.'
 84 | 			};
 85 | 		}
 86 | 
 87 | 		// Step 2: Validate task status
 88 | 		const statusValidation = this.validateTaskStatus(task);
 89 | 		if (!statusValidation.success) {
 90 | 			return statusValidation;
 91 | 		}
 92 | 
 93 | 		// Step 3: Check for subtasks
 94 | 		const subtaskValidation = this.validateSubtasksExist(task);
 95 | 		if (!subtaskValidation.success) {
 96 | 			return subtaskValidation;
 97 | 		}
 98 | 
 99 | 		// Step 4: Validate subtask structure
100 | 		const structureValidation = this.validateSubtaskStructure(task);
101 | 		if (!structureValidation.success) {
102 | 			return structureValidation;
103 | 		}
104 | 
105 | 		// Step 5: Analyze dependencies
106 | 		const dependencyValidation = this.validateDependencies(task);
107 | 		if (!dependencyValidation.success) {
108 | 			return dependencyValidation;
109 | 		}
110 | 
111 | 		logger.info(`Task ${taskId} validated successfully`);
112 | 
113 | 		return {
114 | 			success: true,
115 | 			task
116 | 		};
117 | 	}
118 | 
119 | 	/**
120 | 	 * Load task using TaskService
121 | 	 */
122 | 	private async loadTask(taskId: string): Promise<Task | null> {
123 | 		try {
124 | 			return await this.taskService.getTask(taskId);
125 | 		} catch (error) {
126 | 			logger.error(`Failed to load task ${taskId}:`, error);
127 | 			return null;
128 | 		}
129 | 	}
130 | 
131 | 	/**
132 | 	 * Validate task status is appropriate for autopilot
133 | 	 */
134 | 	private validateTaskStatus(task: Task): TaskValidationResult {
135 | 		if (isTaskComplete(task.status)) {
136 | 			return {
137 | 				success: false,
138 | 				errorType: 'task_completed',
139 | 				errorMessage: `Task "${task.title}" is already ${task.status}`,
140 | 				suggestion:
141 | 					'Autopilot can only execute tasks that are pending or in-progress. Use a different task.'
142 | 			};
143 | 		}
144 | 
145 | 		return { success: true };
146 | 	}
147 | 
148 | 	/**
149 | 	 * Validate task has subtasks
150 | 	 */
151 | 	private validateSubtasksExist(task: Task): TaskValidationResult {
152 | 		if (!task.subtasks || task.subtasks.length === 0) {
153 | 			return {
154 | 				success: false,
155 | 				errorType: 'no_subtasks',
156 | 				errorMessage: `Task "${task.title}" has no subtasks`,
157 | 				suggestion: this.buildExpansionSuggestion(task)
158 | 			};
159 | 		}
160 | 
161 | 		return { success: true };
162 | 	}
163 | 
164 | 	/**
165 | 	 * Build helpful suggestion for expanding tasks
166 | 	 */
167 | 	private buildExpansionSuggestion(task: Task): string {
168 | 		const suggestions: string[] = [
169 | 			`Autopilot requires tasks to be broken down into subtasks for execution.`
170 | 		];
171 | 
172 | 		// Add expansion command suggestion
173 | 		suggestions.push(`\nExpand this task using:`);
174 | 		suggestions.push(`  task-master expand --id=${task.id}`);
175 | 
176 | 		// If task has complexity analysis, mention it
177 | 		if (task.complexity || task.recommendedSubtasks) {
178 | 			suggestions.push(
179 | 				`\nThis task has complexity analysis available. Consider reviewing it first:`
180 | 			);
181 | 			suggestions.push(`  task-master show ${task.id}`);
182 | 		} else {
183 | 			suggestions.push(
184 | 				`\nOr analyze task complexity first to determine optimal subtask count:`
185 | 			);
186 | 			suggestions.push(`  task-master analyze-complexity --from=${task.id}`);
187 | 		}
188 | 
189 | 		return suggestions.join('\n');
190 | 	}
191 | 
192 | 	/**
193 | 	 * Validate subtask structure
194 | 	 */
195 | 	private validateSubtaskStructure(task: Task): TaskValidationResult {
196 | 		for (const subtask of task.subtasks) {
197 | 			// Check required fields
198 | 			if (!subtask.title || !subtask.description) {
199 | 				return {
200 | 					success: false,
201 | 					errorType: 'invalid_structure',
202 | 					errorMessage: `Subtask ${task.id}.${subtask.id} is missing required fields`,
203 | 					suggestion:
204 | 						'Subtasks must have title and description. Re-expand the task or manually fix the subtask structure.'
205 | 				};
206 | 			}
207 | 
208 | 			// Validate dependencies are arrays
209 | 			if (subtask.dependencies && !Array.isArray(subtask.dependencies)) {
210 | 				return {
211 | 					success: false,
212 | 					errorType: 'invalid_structure',
213 | 					errorMessage: `Subtask ${task.id}.${subtask.id} has invalid dependencies format`,
214 | 					suggestion:
215 | 						'Dependencies must be an array. Fix the task structure manually.'
216 | 				};
217 | 			}
218 | 		}
219 | 
220 | 		return { success: true };
221 | 	}
222 | 
223 | 	/**
224 | 	 * Validate subtask dependencies
225 | 	 */
226 | 	private validateDependencies(task: Task): TaskValidationResult {
227 | 		const issues: DependencyIssue[] = [];
228 | 		const subtaskIds = new Set(task.subtasks.map((st) => String(st.id)));
229 | 
230 | 		for (const subtask of task.subtasks) {
231 | 			const subtaskId = `${task.id}.${subtask.id}`;
232 | 
233 | 			// Check for missing dependencies
234 | 			if (subtask.dependencies && subtask.dependencies.length > 0) {
235 | 				for (const depId of subtask.dependencies) {
236 | 					const depIdStr = String(depId);
237 | 
238 | 					if (!subtaskIds.has(depIdStr)) {
239 | 						issues.push({
240 | 							subtaskId,
241 | 							issueType: 'missing',
242 | 							message: `References non-existent subtask ${depIdStr}`,
243 | 							dependencyRef: depIdStr
244 | 						});
245 | 					}
246 | 				}
247 | 			}
248 | 
249 | 			// Check for circular dependencies
250 | 			const circularCheck = this.detectCircularDependency(
251 | 				subtask,
252 | 				task.subtasks,
253 | 				new Set()
254 | 			);
255 | 
256 | 			if (circularCheck) {
257 | 				issues.push({
258 | 					subtaskId,
259 | 					issueType: 'circular',
260 | 					message: `Circular dependency detected: ${circularCheck.join(' -> ')}`
261 | 				});
262 | 			}
263 | 		}
264 | 
265 | 		if (issues.length > 0) {
266 | 			const errorType =
267 | 				issues[0].issueType === 'circular'
268 | 					? 'circular_dependencies'
269 | 					: 'missing_dependencies';
270 | 
271 | 			return {
272 | 				success: false,
273 | 				errorType,
274 | 				errorMessage: `Task "${task.title}" has dependency issues`,
275 | 				suggestion:
276 | 					'Fix dependency issues manually or re-expand the task:\n' +
277 | 					issues
278 | 						.map((issue) => `  - ${issue.subtaskId}: ${issue.message}`)
279 | 						.join('\n'),
280 | 				dependencyIssues: issues
281 | 			};
282 | 		}
283 | 
284 | 		return { success: true };
285 | 	}
286 | 
287 | 	/**
288 | 	 * Detect circular dependencies using depth-first search
289 | 	 */
290 | 	private detectCircularDependency(
291 | 		subtask: Subtask,
292 | 		allSubtasks: Subtask[],
293 | 		visited: Set<string>
294 | 	): string[] | null {
295 | 		const subtaskId = String(subtask.id);
296 | 
297 | 		if (visited.has(subtaskId)) {
298 | 			return [subtaskId];
299 | 		}
300 | 
301 | 		visited.add(subtaskId);
302 | 
303 | 		if (subtask.dependencies && subtask.dependencies.length > 0) {
304 | 			for (const depId of subtask.dependencies) {
305 | 				const depIdStr = String(depId);
306 | 				const dependency = allSubtasks.find((st) => String(st.id) === depIdStr);
307 | 
308 | 				if (dependency) {
309 | 					const circular = this.detectCircularDependency(
310 | 						dependency,
311 | 						allSubtasks,
312 | 						new Set(visited)
313 | 					);
314 | 
315 | 					if (circular) {
316 | 						return [subtaskId, ...circular];
317 | 					}
318 | 				}
319 | 			}
320 | 		}
321 | 
322 | 		return null;
323 | 	}
324 | 
325 | 	/**
326 | 	 * Get ordered subtask execution sequence
327 | 	 * Returns subtasks in dependency order (tasks with no deps first)
328 | 	 */
329 | 	getExecutionOrder(task: Task): Subtask[] {
330 | 		const ordered: Subtask[] = [];
331 | 		const completed = new Set<string>();
332 | 
333 | 		// Keep adding subtasks whose dependencies are all completed
334 | 		while (ordered.length < task.subtasks.length) {
335 | 			let added = false;
336 | 
337 | 			for (const subtask of task.subtasks) {
338 | 				const subtaskId = String(subtask.id);
339 | 
340 | 				if (completed.has(subtaskId)) {
341 | 					continue;
342 | 				}
343 | 
344 | 				// Check if all dependencies are completed
345 | 				const allDepsCompleted =
346 | 					!subtask.dependencies ||
347 | 					subtask.dependencies.length === 0 ||
348 | 					subtask.dependencies.every((depId) => completed.has(String(depId)));
349 | 
350 | 				if (allDepsCompleted) {
351 | 					ordered.push(subtask);
352 | 					completed.add(subtaskId);
353 | 					added = true;
354 | 					break;
355 | 				}
356 | 			}
357 | 
358 | 			// Safety check to prevent infinite loop
359 | 			if (!added && ordered.length < task.subtasks.length) {
360 | 				logger.warn(
361 | 					`Could not determine complete execution order for task ${task.id}`
362 | 				);
363 | 				// Add remaining subtasks in original order
364 | 				for (const subtask of task.subtasks) {
365 | 					if (!completed.has(String(subtask.id))) {
366 | 						ordered.push(subtask);
367 | 					}
368 | 				}
369 | 				break;
370 | 			}
371 | 		}
372 | 
373 | 		return ordered;
374 | 	}
375 | 
376 | 	/**
377 | 	 * Clean up resources
378 | 	 */
379 | 	async cleanup(): Promise<void> {
380 | 		// TaskService doesn't require explicit cleanup
381 | 		// Resources are automatically released when instance is garbage collected
382 | 	}
383 | }
384 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/logger/logger.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Tests for MCP logging integration
  3 |  */
  4 | 
  5 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  6 | import { type LogCallback, LogLevel, Logger } from './logger.js';
  7 | 
  8 | describe('Logger - MCP Integration', () => {
  9 | 	// Store original environment
 10 | 	let originalEnv: Record<string, string | undefined>;
 11 | 
 12 | 	beforeEach(() => {
 13 | 		// Save original environment
 14 | 		originalEnv = {
 15 | 			MCP_MODE: process.env.MCP_MODE,
 16 | 			TASK_MASTER_MCP: process.env.TASK_MASTER_MCP,
 17 | 			TASK_MASTER_SILENT: process.env.TASK_MASTER_SILENT,
 18 | 			TM_SILENT: process.env.TM_SILENT,
 19 | 			TASK_MASTER_LOG_LEVEL: process.env.TASK_MASTER_LOG_LEVEL,
 20 | 			TM_LOG_LEVEL: process.env.TM_LOG_LEVEL,
 21 | 			NO_COLOR: process.env.NO_COLOR,
 22 | 			TASK_MASTER_NO_COLOR: process.env.TASK_MASTER_NO_COLOR
 23 | 		};
 24 | 
 25 | 		// Clear environment variables for clean tests
 26 | 		delete process.env.MCP_MODE;
 27 | 		delete process.env.TASK_MASTER_MCP;
 28 | 		delete process.env.TASK_MASTER_SILENT;
 29 | 		delete process.env.TM_SILENT;
 30 | 		delete process.env.TASK_MASTER_LOG_LEVEL;
 31 | 		delete process.env.TM_LOG_LEVEL;
 32 | 		delete process.env.NO_COLOR;
 33 | 		delete process.env.TASK_MASTER_NO_COLOR;
 34 | 	});
 35 | 
 36 | 	afterEach(() => {
 37 | 		// Restore original environment
 38 | 		for (const [key, value] of Object.entries(originalEnv)) {
 39 | 			if (value === undefined) {
 40 | 				delete process.env[key];
 41 | 			} else {
 42 | 				process.env[key] = value;
 43 | 			}
 44 | 		}
 45 | 	});
 46 | 	describe('Callback-based logging', () => {
 47 | 		it('should call callback instead of console when logCallback is provided', () => {
 48 | 			const mockCallback = vi.fn();
 49 | 			const logger = new Logger({
 50 | 				level: LogLevel.INFO,
 51 | 				logCallback: mockCallback
 52 | 			});
 53 | 
 54 | 			logger.info('Test message');
 55 | 
 56 | 			expect(mockCallback).toHaveBeenCalledWith(
 57 | 				'info',
 58 | 				expect.stringContaining('Test message')
 59 | 			);
 60 | 		});
 61 | 
 62 | 		it('should call callback for all log levels', () => {
 63 | 			const mockCallback = vi.fn();
 64 | 			const logger = new Logger({
 65 | 				level: LogLevel.DEBUG,
 66 | 				logCallback: mockCallback
 67 | 			});
 68 | 
 69 | 			logger.error('Error message');
 70 | 			logger.warn('Warning message');
 71 | 			logger.info('Info message');
 72 | 			logger.debug('Debug message');
 73 | 
 74 | 			expect(mockCallback).toHaveBeenNthCalledWith(
 75 | 				1,
 76 | 				'error',
 77 | 				expect.stringContaining('Error message')
 78 | 			);
 79 | 			expect(mockCallback).toHaveBeenNthCalledWith(
 80 | 				2,
 81 | 				'warn',
 82 | 				expect.stringContaining('Warning message')
 83 | 			);
 84 | 			expect(mockCallback).toHaveBeenNthCalledWith(
 85 | 				3,
 86 | 				'info',
 87 | 				expect.stringContaining('Info message')
 88 | 			);
 89 | 			expect(mockCallback).toHaveBeenNthCalledWith(
 90 | 				4,
 91 | 				'debug',
 92 | 				expect.stringContaining('Debug message')
 93 | 			);
 94 | 		});
 95 | 
 96 | 		it('should respect log level with callback', () => {
 97 | 			const mockCallback = vi.fn();
 98 | 			const logger = new Logger({
 99 | 				level: LogLevel.WARN,
100 | 				logCallback: mockCallback
101 | 			});
102 | 
103 | 			logger.debug('Debug message');
104 | 			logger.info('Info message');
105 | 			logger.warn('Warning message');
106 | 			logger.error('Error message');
107 | 
108 | 			// Only warn and error should be logged
109 | 			expect(mockCallback).toHaveBeenCalledTimes(2);
110 | 			expect(mockCallback).toHaveBeenNthCalledWith(
111 | 				1,
112 | 				'warn',
113 | 				expect.stringContaining('Warning message')
114 | 			);
115 | 			expect(mockCallback).toHaveBeenNthCalledWith(
116 | 				2,
117 | 				'error',
118 | 				expect.stringContaining('Error message')
119 | 			);
120 | 		});
121 | 
122 | 		it('should handle raw log() calls with callback', () => {
123 | 			const mockCallback = vi.fn();
124 | 			const logger = new Logger({
125 | 				level: LogLevel.INFO,
126 | 				logCallback: mockCallback
127 | 			});
128 | 
129 | 			logger.log('Raw message', 'with args');
130 | 
131 | 			expect(mockCallback).toHaveBeenCalledWith('log', 'Raw message with args');
132 | 		});
133 | 	});
134 | 
135 | 	describe('MCP mode with callback', () => {
136 | 		it('should not silence logs when mcpMode=true and callback is provided', () => {
137 | 			const mockCallback = vi.fn();
138 | 			const logger = new Logger({
139 | 				level: LogLevel.INFO,
140 | 				mcpMode: true,
141 | 				logCallback: mockCallback
142 | 			});
143 | 
144 | 			logger.info('Test message');
145 | 
146 | 			expect(mockCallback).toHaveBeenCalledWith(
147 | 				'info',
148 | 				expect.stringContaining('Test message')
149 | 			);
150 | 		});
151 | 
152 | 		it('should silence logs when mcpMode=true and no callback', () => {
153 | 			const consoleSpy = vi.spyOn(console, 'log');
154 | 			const logger = new Logger({
155 | 				level: LogLevel.INFO,
156 | 				mcpMode: true
157 | 				// No callback
158 | 			});
159 | 
160 | 			logger.info('Test message');
161 | 
162 | 			expect(consoleSpy).not.toHaveBeenCalled();
163 | 			consoleSpy.mockRestore();
164 | 		});
165 | 	});
166 | 
167 | 	describe('Child loggers', () => {
168 | 		it('should inherit callback from parent', () => {
169 | 			const mockCallback = vi.fn();
170 | 			const parent = new Logger({
171 | 				level: LogLevel.INFO,
172 | 				logCallback: mockCallback
173 | 			});
174 | 
175 | 			const child = parent.child('child');
176 | 			child.info('Child message');
177 | 
178 | 			expect(mockCallback).toHaveBeenCalledWith(
179 | 				'info',
180 | 				expect.stringContaining('[child]')
181 | 			);
182 | 			expect(mockCallback).toHaveBeenCalledWith(
183 | 				'info',
184 | 				expect.stringContaining('Child message')
185 | 			);
186 | 		});
187 | 
188 | 		it('should allow child to override callback', () => {
189 | 			const parentCallback = vi.fn();
190 | 			const childCallback = vi.fn();
191 | 
192 | 			const parent = new Logger({
193 | 				level: LogLevel.INFO,
194 | 				logCallback: parentCallback
195 | 			});
196 | 
197 | 			const child = parent.child('child', {
198 | 				logCallback: childCallback
199 | 			});
200 | 
201 | 			parent.info('Parent message');
202 | 			child.info('Child message');
203 | 
204 | 			expect(parentCallback).toHaveBeenCalledTimes(1);
205 | 			expect(childCallback).toHaveBeenCalledTimes(1);
206 | 		});
207 | 	});
208 | 
209 | 	describe('Configuration updates', () => {
210 | 		it('should allow updating logCallback via setConfig', () => {
211 | 			const callback1 = vi.fn();
212 | 			const callback2 = vi.fn();
213 | 
214 | 			const logger = new Logger({
215 | 				level: LogLevel.INFO,
216 | 				logCallback: callback1
217 | 			});
218 | 
219 | 			logger.info('Message 1');
220 | 			expect(callback1).toHaveBeenCalledTimes(1);
221 | 
222 | 			logger.setConfig({ logCallback: callback2 });
223 | 			logger.info('Message 2');
224 | 
225 | 			expect(callback1).toHaveBeenCalledTimes(1);
226 | 			expect(callback2).toHaveBeenCalledTimes(1);
227 | 		});
228 | 
229 | 		it('should maintain mcpMode behavior when updating config', () => {
230 | 			const callback = vi.fn();
231 | 			const logger = new Logger({
232 | 				level: LogLevel.INFO,
233 | 				mcpMode: true
234 | 			});
235 | 
236 | 			// Initially silent (no callback)
237 | 			logger.info('Message 1');
238 | 			expect(callback).not.toHaveBeenCalled();
239 | 
240 | 			// Add callback - should start logging
241 | 			logger.setConfig({ logCallback: callback });
242 | 			logger.info('Message 2');
243 | 			expect(callback).toHaveBeenCalledTimes(1);
244 | 		});
245 | 	});
246 | 
247 | 	describe('Formatting with callback', () => {
248 | 		it('should include prefix in callback messages', () => {
249 | 			const mockCallback = vi.fn();
250 | 			const logger = new Logger({
251 | 				level: LogLevel.INFO,
252 | 				prefix: 'test-prefix',
253 | 				logCallback: mockCallback
254 | 			});
255 | 
256 | 			logger.info('Test message');
257 | 
258 | 			expect(mockCallback).toHaveBeenCalledWith(
259 | 				'info',
260 | 				expect.stringContaining('[test-prefix]')
261 | 			);
262 | 		});
263 | 
264 | 		it('should include timestamp when enabled', () => {
265 | 			const mockCallback = vi.fn();
266 | 			const logger = new Logger({
267 | 				level: LogLevel.INFO,
268 | 				timestamp: true,
269 | 				logCallback: mockCallback
270 | 			});
271 | 
272 | 			logger.info('Test message');
273 | 
274 | 			const [[, message]] = mockCallback.mock.calls;
275 | 			// Message should contain ISO timestamp pattern
276 | 			expect(message).toMatch(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
277 | 		});
278 | 
279 | 		it('should format additional arguments', () => {
280 | 			const mockCallback = vi.fn();
281 | 			const logger = new Logger({
282 | 				level: LogLevel.INFO,
283 | 				logCallback: mockCallback
284 | 			});
285 | 
286 | 			const data = { key: 'value' };
287 | 			logger.info('Test message', data, 'string arg');
288 | 
289 | 			expect(mockCallback).toHaveBeenCalledWith(
290 | 				'info',
291 | 				expect.stringContaining('Test message')
292 | 			);
293 | 			expect(mockCallback).toHaveBeenCalledWith(
294 | 				'info',
295 | 				expect.stringContaining('"key"')
296 | 			);
297 | 			expect(mockCallback).toHaveBeenCalledWith(
298 | 				'info',
299 | 				expect.stringContaining('string arg')
300 | 			);
301 | 		});
302 | 	});
303 | 
304 | 	describe('Edge cases', () => {
305 | 		it('should handle null/undefined callback gracefully', () => {
306 | 			const logger = new Logger({
307 | 				level: LogLevel.INFO,
308 | 				logCallback: undefined
309 | 			});
310 | 
311 | 			const consoleSpy = vi.spyOn(console, 'log');
312 | 
313 | 			// Should fallback to console
314 | 			logger.info('Test message');
315 | 
316 | 			expect(consoleSpy).toHaveBeenCalled();
317 | 			consoleSpy.mockRestore();
318 | 		});
319 | 
320 | 		it('should not call callback when level is SILENT', () => {
321 | 			const mockCallback = vi.fn();
322 | 			const logger = new Logger({
323 | 				level: LogLevel.SILENT,
324 | 				logCallback: mockCallback
325 | 			});
326 | 
327 | 			logger.error('Error');
328 | 			logger.warn('Warning');
329 | 			logger.info('Info');
330 | 			logger.debug('Debug');
331 | 
332 | 			expect(mockCallback).not.toHaveBeenCalled();
333 | 		});
334 | 
335 | 		it('should propagate callback errors', () => {
336 | 			const errorCallback: LogCallback = () => {
337 | 				throw new Error('Callback error');
338 | 			};
339 | 
340 | 			const logger = new Logger({
341 | 				level: LogLevel.INFO,
342 | 				logCallback: errorCallback
343 | 			});
344 | 
345 | 			// Should throw
346 | 			expect(() => {
347 | 				logger.info('Test message');
348 | 			}).toThrow('Callback error');
349 | 		});
350 | 	});
351 | 
352 | 	describe('Environment variable detection', () => {
353 | 		it('should detect MCP mode from environment', () => {
354 | 			const originalEnv = process.env.MCP_MODE;
355 | 			process.env.MCP_MODE = 'true';
356 | 
357 | 			const logger = new Logger({
358 | 				level: LogLevel.INFO
359 | 			});
360 | 
361 | 			const config = logger.getConfig();
362 | 			expect(config.mcpMode).toBe(true);
363 | 			expect(config.silent).toBe(true); // Should be silent without callback
364 | 
365 | 			// Cleanup
366 | 			if (originalEnv === undefined) {
367 | 				delete process.env.MCP_MODE;
368 | 			} else {
369 | 				process.env.MCP_MODE = originalEnv;
370 | 			}
371 | 		});
372 | 
373 | 		it('should detect log level from environment', () => {
374 | 			const originalEnv = process.env.TASK_MASTER_LOG_LEVEL;
375 | 			process.env.TASK_MASTER_LOG_LEVEL = 'DEBUG';
376 | 
377 | 			const logger = new Logger();
378 | 			const config = logger.getConfig();
379 | 			expect(config.level).toBe(LogLevel.DEBUG);
380 | 
381 | 			// Cleanup
382 | 			if (originalEnv === undefined) {
383 | 				delete process.env.TASK_MASTER_LOG_LEVEL;
384 | 			} else {
385 | 				process.env.TASK_MASTER_LOG_LEVEL = originalEnv;
386 | 			}
387 | 		});
388 | 	});
389 | });
390 | 
```

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

```typescript
  1 | /**
  2 |  * @fileoverview Unit tests for ConfigPersistence service
  3 |  */
  4 | 
  5 | import fs from 'node:fs/promises';
  6 | import type { PartialConfiguration } from '@tm/core/common/interfaces/configuration.interface.js';
  7 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  8 | import { ConfigPersistence } from './config-persistence.service.js';
  9 | 
 10 | vi.mock('node:fs', () => ({
 11 | 	promises: {
 12 | 		readFile: vi.fn(),
 13 | 		writeFile: vi.fn(),
 14 | 		mkdir: vi.fn(),
 15 | 		unlink: vi.fn(),
 16 | 		access: vi.fn(),
 17 | 		readdir: vi.fn(),
 18 | 		rename: vi.fn()
 19 | 	}
 20 | }));
 21 | 
 22 | describe('ConfigPersistence', () => {
 23 | 	let persistence: ConfigPersistence;
 24 | 	const testProjectRoot = '/test/project';
 25 | 
 26 | 	beforeEach(() => {
 27 | 		persistence = new ConfigPersistence(testProjectRoot);
 28 | 		vi.clearAllMocks();
 29 | 	});
 30 | 
 31 | 	afterEach(() => {
 32 | 		vi.restoreAllMocks();
 33 | 	});
 34 | 
 35 | 	describe('saveConfig', () => {
 36 | 		const mockConfig: PartialConfiguration = {
 37 | 			models: { main: 'test-model', fallback: 'test-fallback' },
 38 | 			storage: {
 39 | 				type: 'file' as const,
 40 | 				enableBackup: true,
 41 | 				maxBackups: 5,
 42 | 				enableCompression: true,
 43 | 				encoding: 'utf-8',
 44 | 				atomicOperations: true
 45 | 			}
 46 | 		};
 47 | 
 48 | 		it('should save configuration to file', async () => {
 49 | 			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
 50 | 			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
 51 | 
 52 | 			await persistence.saveConfig(mockConfig);
 53 | 
 54 | 			expect(fs.mkdir).toHaveBeenCalledWith('/test/project/.taskmaster', {
 55 | 				recursive: true
 56 | 			});
 57 | 
 58 | 			expect(fs.writeFile).toHaveBeenCalledWith(
 59 | 				'/test/project/.taskmaster/config.json',
 60 | 				JSON.stringify(mockConfig, null, 2),
 61 | 				'utf-8'
 62 | 			);
 63 | 		});
 64 | 
 65 | 		it('should use atomic write when specified', async () => {
 66 | 			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
 67 | 			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
 68 | 			vi.mocked(fs.rename).mockResolvedValue(undefined);
 69 | 
 70 | 			await persistence.saveConfig(mockConfig, { atomic: true });
 71 | 
 72 | 			// Should write to temp file first
 73 | 			expect(fs.writeFile).toHaveBeenCalledWith(
 74 | 				'/test/project/.taskmaster/config.json.tmp',
 75 | 				JSON.stringify(mockConfig, null, 2),
 76 | 				'utf-8'
 77 | 			);
 78 | 
 79 | 			// Then rename to final location
 80 | 			expect(fs.rename).toHaveBeenCalledWith(
 81 | 				'/test/project/.taskmaster/config.json.tmp',
 82 | 				'/test/project/.taskmaster/config.json'
 83 | 			);
 84 | 		});
 85 | 
 86 | 		it('should create backup when requested', async () => {
 87 | 			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
 88 | 			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
 89 | 			vi.mocked(fs.access).mockResolvedValue(undefined); // Config exists
 90 | 			vi.mocked(fs.readFile).mockResolvedValue('{"old": "config"}');
 91 | 			vi.mocked(fs.readdir).mockResolvedValue([]);
 92 | 
 93 | 			await persistence.saveConfig(mockConfig, { createBackup: true });
 94 | 
 95 | 			// Should create backup directory
 96 | 			expect(fs.mkdir).toHaveBeenCalledWith(
 97 | 				'/test/project/.taskmaster/backups',
 98 | 				{ recursive: true }
 99 | 			);
100 | 
101 | 			// Should read existing config for backup
102 | 			expect(fs.readFile).toHaveBeenCalledWith(
103 | 				'/test/project/.taskmaster/config.json',
104 | 				'utf-8'
105 | 			);
106 | 
107 | 			// Should write backup file
108 | 			expect(fs.writeFile).toHaveBeenCalledWith(
109 | 				expect.stringContaining('/test/project/.taskmaster/backups/config-'),
110 | 				'{"old": "config"}',
111 | 				'utf-8'
112 | 			);
113 | 		});
114 | 
115 | 		it('should not create backup if config does not exist', async () => {
116 | 			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
117 | 			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
118 | 			vi.mocked(fs.access).mockRejectedValue(new Error('Not found'));
119 | 
120 | 			await persistence.saveConfig(mockConfig, { createBackup: true });
121 | 
122 | 			// Should not read or create backup
123 | 			expect(fs.readFile).not.toHaveBeenCalled();
124 | 			expect(fs.writeFile).toHaveBeenCalledTimes(1); // Only the main config
125 | 		});
126 | 
127 | 		it('should throw TaskMasterError on save failure', async () => {
128 | 			vi.mocked(fs.mkdir).mockRejectedValue(new Error('Disk full'));
129 | 
130 | 			await expect(persistence.saveConfig(mockConfig)).rejects.toThrow(
131 | 				'Failed to save configuration'
132 | 			);
133 | 		});
134 | 	});
135 | 
136 | 	describe('configExists', () => {
137 | 		it('should return true when config exists', async () => {
138 | 			vi.mocked(fs.access).mockResolvedValue(undefined);
139 | 
140 | 			const exists = await persistence.configExists();
141 | 
142 | 			expect(fs.access).toHaveBeenCalledWith(
143 | 				'/test/project/.taskmaster/config.json'
144 | 			);
145 | 			expect(exists).toBe(true);
146 | 		});
147 | 
148 | 		it('should return false when config does not exist', async () => {
149 | 			vi.mocked(fs.access).mockRejectedValue(new Error('Not found'));
150 | 
151 | 			const exists = await persistence.configExists();
152 | 
153 | 			expect(exists).toBe(false);
154 | 		});
155 | 	});
156 | 
157 | 	describe('deleteConfig', () => {
158 | 		it('should delete configuration file', async () => {
159 | 			vi.mocked(fs.unlink).mockResolvedValue(undefined);
160 | 
161 | 			await persistence.deleteConfig();
162 | 
163 | 			expect(fs.unlink).toHaveBeenCalledWith(
164 | 				'/test/project/.taskmaster/config.json'
165 | 			);
166 | 		});
167 | 
168 | 		it('should not throw when file does not exist', async () => {
169 | 			const error = new Error('File not found') as any;
170 | 			error.code = 'ENOENT';
171 | 			vi.mocked(fs.unlink).mockRejectedValue(error);
172 | 
173 | 			await expect(persistence.deleteConfig()).resolves.not.toThrow();
174 | 		});
175 | 
176 | 		it('should throw TaskMasterError for other errors', async () => {
177 | 			vi.mocked(fs.unlink).mockRejectedValue(new Error('Permission denied'));
178 | 
179 | 			await expect(persistence.deleteConfig()).rejects.toThrow(
180 | 				'Failed to delete configuration'
181 | 			);
182 | 		});
183 | 	});
184 | 
185 | 	describe('getBackups', () => {
186 | 		it('should return list of backup files sorted newest first', async () => {
187 | 			vi.mocked(fs.readdir).mockResolvedValue([
188 | 				'config-2024-01-01T10-00-00-000Z.json',
189 | 				'config-2024-01-02T10-00-00-000Z.json',
190 | 				'config-2024-01-03T10-00-00-000Z.json',
191 | 				'other-file.txt'
192 | 			] as any);
193 | 
194 | 			const backups = await persistence.getBackups();
195 | 
196 | 			expect(fs.readdir).toHaveBeenCalledWith(
197 | 				'/test/project/.taskmaster/backups'
198 | 			);
199 | 
200 | 			expect(backups).toEqual([
201 | 				'config-2024-01-03T10-00-00-000Z.json',
202 | 				'config-2024-01-02T10-00-00-000Z.json',
203 | 				'config-2024-01-01T10-00-00-000Z.json'
204 | 			]);
205 | 		});
206 | 
207 | 		it('should return empty array when backup directory does not exist', async () => {
208 | 			vi.mocked(fs.readdir).mockRejectedValue(new Error('Not found'));
209 | 
210 | 			const backups = await persistence.getBackups();
211 | 
212 | 			expect(backups).toEqual([]);
213 | 		});
214 | 
215 | 		it('should filter out non-backup files', async () => {
216 | 			vi.mocked(fs.readdir).mockResolvedValue([
217 | 				'config-2024-01-01T10-00-00-000Z.json',
218 | 				'README.md',
219 | 				'.DS_Store',
220 | 				'config.json',
221 | 				'config-backup.json' // Wrong format
222 | 			] as any);
223 | 
224 | 			const backups = await persistence.getBackups();
225 | 
226 | 			expect(backups).toEqual(['config-2024-01-01T10-00-00-000Z.json']);
227 | 		});
228 | 	});
229 | 
230 | 	describe('restoreFromBackup', () => {
231 | 		const backupFile = 'config-2024-01-01T10-00-00-000Z.json';
232 | 		const backupContent = '{"restored": "config"}';
233 | 
234 | 		it('should restore configuration from backup', async () => {
235 | 			vi.mocked(fs.readFile).mockResolvedValue(backupContent);
236 | 			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
237 | 
238 | 			await persistence.restoreFromBackup(backupFile);
239 | 
240 | 			expect(fs.readFile).toHaveBeenCalledWith(
241 | 				`/test/project/.taskmaster/backups/${backupFile}`,
242 | 				'utf-8'
243 | 			);
244 | 
245 | 			expect(fs.writeFile).toHaveBeenCalledWith(
246 | 				'/test/project/.taskmaster/config.json',
247 | 				backupContent,
248 | 				'utf-8'
249 | 			);
250 | 		});
251 | 
252 | 		it('should throw TaskMasterError when backup file not found', async () => {
253 | 			vi.mocked(fs.readFile).mockRejectedValue(new Error('File not found'));
254 | 
255 | 			await expect(
256 | 				persistence.restoreFromBackup('nonexistent.json')
257 | 			).rejects.toThrow('Failed to restore from backup');
258 | 		});
259 | 
260 | 		it('should throw TaskMasterError on write failure', async () => {
261 | 			vi.mocked(fs.readFile).mockResolvedValue(backupContent);
262 | 			vi.mocked(fs.writeFile).mockRejectedValue(new Error('Disk full'));
263 | 
264 | 			await expect(persistence.restoreFromBackup(backupFile)).rejects.toThrow(
265 | 				'Failed to restore from backup'
266 | 			);
267 | 		});
268 | 	});
269 | 
270 | 	describe('backup management', () => {
271 | 		it('should clean old backups when limit exceeded', async () => {
272 | 			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
273 | 			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
274 | 			vi.mocked(fs.access).mockResolvedValue(undefined);
275 | 			vi.mocked(fs.readFile).mockResolvedValue('{"old": "config"}');
276 | 			vi.mocked(fs.unlink).mockResolvedValue(undefined);
277 | 
278 | 			// Mock 7 existing backups
279 | 			vi.mocked(fs.readdir).mockResolvedValue([
280 | 				'config-2024-01-01T10-00-00-000Z.json',
281 | 				'config-2024-01-02T10-00-00-000Z.json',
282 | 				'config-2024-01-03T10-00-00-000Z.json',
283 | 				'config-2024-01-04T10-00-00-000Z.json',
284 | 				'config-2024-01-05T10-00-00-000Z.json',
285 | 				'config-2024-01-06T10-00-00-000Z.json',
286 | 				'config-2024-01-07T10-00-00-000Z.json'
287 | 			] as any);
288 | 
289 | 			await persistence.saveConfig({}, { createBackup: true });
290 | 
291 | 			// Should delete oldest backups (keeping 5)
292 | 			expect(fs.unlink).toHaveBeenCalledWith(
293 | 				'/test/project/.taskmaster/backups/config-2024-01-01T10-00-00-000Z.json'
294 | 			);
295 | 			expect(fs.unlink).toHaveBeenCalledWith(
296 | 				'/test/project/.taskmaster/backups/config-2024-01-02T10-00-00-000Z.json'
297 | 			);
298 | 		});
299 | 
300 | 		it('should handle backup cleanup errors gracefully', async () => {
301 | 			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
302 | 			vi.mocked(fs.writeFile).mockResolvedValue(undefined);
303 | 			vi.mocked(fs.access).mockResolvedValue(undefined);
304 | 			vi.mocked(fs.readFile).mockResolvedValue('{"old": "config"}');
305 | 			vi.mocked(fs.readdir).mockResolvedValue(['config-old.json'] as any);
306 | 			vi.mocked(fs.unlink).mockRejectedValue(new Error('Permission denied'));
307 | 
308 | 			// Mock console.warn to verify it's called
309 | 			const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
310 | 
311 | 			// Should not throw even if cleanup fails
312 | 			await expect(
313 | 				persistence.saveConfig({}, { createBackup: true })
314 | 			).resolves.not.toThrow();
315 | 
316 | 			expect(warnSpy).toHaveBeenCalledWith(
317 | 				'Failed to clean old backups:',
318 | 				expect.any(Error)
319 | 			);
320 | 
321 | 			warnSpy.mockRestore();
322 | 		});
323 | 	});
324 | });
325 | 
```
Page 29/69FirstPrevNextLast