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

--------------------------------------------------------------------------------
/README-task-master.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Task Master
  2 | 
  3 | ### by [@eyaltoledano](https://x.com/eyaltoledano)
  4 | 
  5 | A task management system for AI-driven development with Claude, designed to work seamlessly with Cursor AI.
  6 | 
  7 | ## Requirements
  8 | 
  9 | - Node.js 14.0.0 or higher
 10 | - Anthropic API key (Claude API)
 11 | - Anthropic SDK version 0.39.0 or higher
 12 | - OpenAI SDK (for Perplexity API integration, optional)
 13 | 
 14 | ## Configuration
 15 | 
 16 | Taskmaster uses two primary configuration methods:
 17 | 
 18 | 1.  **`.taskmasterconfig` File (Project Root)**
 19 | 
 20 |     - Stores most settings: AI model selections (main, research, fallback), parameters (max tokens, temperature), logging level, default priority/subtasks, project name.
 21 |     - **Created and managed using `task-master models --setup` CLI command or the `models` MCP tool.**
 22 |     - Do not edit manually unless you know what you are doing.
 23 | 
 24 | 2.  **Environment Variables (`.env` file or MCP `env` block)**
 25 |     - Used **only** for sensitive **API Keys** (e.g., `ANTHROPIC_API_KEY`, `PERPLEXITY_API_KEY`, etc.) and specific endpoints (like `OLLAMA_BASE_URL`).
 26 |     - **For CLI:** Place keys in a `.env` file in your project root.
 27 |     - **For MCP/Cursor:** Place keys in the `env` section of your `.cursor/mcp.json` (or other MCP config according to the AI IDE or client you use) file under the `taskmaster-ai` server definition.
 28 | 
 29 | **Important:** Settings like model choices, max tokens, temperature, and log level are **no longer configured via environment variables.** Use the `task-master models` command or tool.
 30 | 
 31 | See the [Configuration Guide](docs/configuration.md) for full details.
 32 | 
 33 | ## Installation
 34 | 
 35 | ```bash
 36 | # Install globally
 37 | npm install -g task-master-ai
 38 | 
 39 | # OR install locally within your project
 40 | npm install task-master-ai
 41 | ```
 42 | 
 43 | ### Initialize a new project
 44 | 
 45 | ```bash
 46 | # If installed globally
 47 | task-master init
 48 | 
 49 | # If installed locally
 50 | npx task-master init
 51 | ```
 52 | 
 53 | This will prompt you for project details and set up a new project with the necessary files and structure.
 54 | 
 55 | ### Important Notes
 56 | 
 57 | 1. **ES Modules Configuration:**
 58 | 
 59 |    - This project uses ES Modules (ESM) instead of CommonJS.
 60 |    - This is set via `"type": "module"` in your package.json.
 61 |    - Use `import/export` syntax instead of `require()`.
 62 |    - Files should use `.js` or `.mjs` extensions.
 63 |    - To use a CommonJS module, either:
 64 |      - Rename it with `.cjs` extension
 65 |      - Use `await import()` for dynamic imports
 66 |    - If you need CommonJS throughout your project, remove `"type": "module"` from package.json, but Task Master scripts expect ESM.
 67 | 
 68 | 2. The Anthropic SDK version should be 0.39.0 or higher.
 69 | 
 70 | ## Quick Start with Global Commands
 71 | 
 72 | After installing the package globally, you can use these CLI commands from any directory:
 73 | 
 74 | ```bash
 75 | # Initialize a new project
 76 | task-master init
 77 | 
 78 | # Parse a PRD and generate tasks
 79 | task-master parse-prd your-prd.txt
 80 | 
 81 | # List all tasks
 82 | task-master list
 83 | 
 84 | # Show the next task to work on
 85 | task-master next
 86 | 
 87 | # Generate task files
 88 | task-master generate
 89 | ```
 90 | 
 91 | ## Troubleshooting
 92 | 
 93 | ### If `task-master init` doesn't respond:
 94 | 
 95 | Try running it with Node directly:
 96 | 
 97 | ```bash
 98 | node node_modules/claude-task-master/scripts/init.js
 99 | ```
100 | 
101 | Or clone the repository and run:
102 | 
103 | ```bash
104 | git clone https://github.com/eyaltoledano/claude-task-master.git
105 | cd claude-task-master
106 | node scripts/init.js
107 | ```
108 | 
109 | ## Task Structure
110 | 
111 | Tasks in tasks.json have the following structure:
112 | 
113 | - `id`: Unique identifier for the task (Example: `1`)
114 | - `title`: Brief, descriptive title of the task (Example: `"Initialize Repo"`)
115 | - `description`: Concise description of what the task involves (Example: `"Create a new repository, set up initial structure."`)
116 | - `status`: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`)
117 | - `dependencies`: IDs of tasks that must be completed before this task (Example: `[1, 2]`)
118 |   - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending)
119 |   - This helps quickly identify which prerequisite tasks are blocking work
120 | - `priority`: Importance level of the task (Example: `"high"`, `"medium"`, `"low"`)
121 | - `details`: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`)
122 | - `testStrategy`: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`)
123 | - `subtasks`: List of smaller, more specific tasks that make up the main task (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`)
124 | 
125 | ## Integrating with Cursor AI
126 | 
127 | Claude Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development.
128 | 
129 | ### Setup with Cursor
130 | 
131 | 1. After initializing your project, open it in Cursor
132 | 2. The `.cursor/rules/dev_workflow.mdc` file is automatically loaded by Cursor, providing the AI with knowledge about the task management system
133 | 3. Place your PRD document in the `scripts/` directory (e.g., `scripts/prd.txt`)
134 | 4. Open Cursor's AI chat and switch to Agent mode
135 | 
136 | ### Setting up MCP in Cursor
137 | 
138 | To enable enhanced task management capabilities directly within Cursor using the Model Control Protocol (MCP):
139 | 
140 | 1. Go to Cursor settings
141 | 2. Navigate to the MCP section
142 | 3. Click on "Add New MCP Server"
143 | 4. Configure with the following details:
144 |    - Name: "Task Master"
145 |    - Type: "Command"
146 |    - Command: "npx -y task-master-ai"
147 | 5. Save the settings
148 | 
149 | Once configured, you can interact with Task Master's task management commands directly through Cursor's interface, providing a more integrated experience.
150 | 
151 | ### Initial Task Generation
152 | 
153 | In Cursor's AI chat, instruct the agent to generate tasks from your PRD:
154 | 
155 | ```
156 | Please use the task-master parse-prd command to generate tasks from my PRD. The PRD is located at scripts/prd.txt.
157 | ```
158 | 
159 | The agent will execute:
160 | 
161 | ```bash
162 | task-master parse-prd scripts/prd.txt
163 | ```
164 | 
165 | This will:
166 | 
167 | - Parse your PRD document
168 | - Generate a structured `tasks.json` file with tasks, dependencies, priorities, and test strategies
169 | - The agent will understand this process due to the Cursor rules
170 | 
171 | ### Generate Individual Task Files
172 | 
173 | Next, ask the agent to generate individual task files:
174 | 
175 | ```
176 | Please generate individual task files from tasks.json
177 | ```
178 | 
179 | The agent will execute:
180 | 
181 | ```bash
182 | task-master generate
183 | ```
184 | 
185 | This creates individual task files in the `tasks/` directory (e.g., `task_001.txt`, `task_002.txt`), making it easier to reference specific tasks.
186 | 
187 | ## AI-Driven Development Workflow
188 | 
189 | The Cursor agent is pre-configured (via the rules file) to follow this workflow:
190 | 
191 | ### 1. Task Discovery and Selection
192 | 
193 | Ask the agent to list available tasks:
194 | 
195 | ```
196 | What tasks are available to work on next?
197 | ```
198 | 
199 | The agent will:
200 | 
201 | - Run `task-master list` to see all tasks
202 | - Run `task-master next` to determine the next task to work on
203 | - Analyze dependencies to determine which tasks are ready to be worked on
204 | - Prioritize tasks based on priority level and ID order
205 | - Suggest the next task(s) to implement
206 | 
207 | ### 2. Task Implementation
208 | 
209 | When implementing a task, the agent will:
210 | 
211 | - Reference the task's details section for implementation specifics
212 | - Consider dependencies on previous tasks
213 | - Follow the project's coding standards
214 | - Create appropriate tests based on the task's testStrategy
215 | 
216 | You can ask:
217 | 
218 | ```
219 | Let's implement task 3. What does it involve?
220 | ```
221 | 
222 | ### 3. Task Verification
223 | 
224 | Before marking a task as complete, verify it according to:
225 | 
226 | - The task's specified testStrategy
227 | - Any automated tests in the codebase
228 | - Manual verification if required
229 | 
230 | ### 4. Task Completion
231 | 
232 | When a task is completed, tell the agent:
233 | 
234 | ```
235 | Task 3 is now complete. Please update its status.
236 | ```
237 | 
238 | The agent will execute:
239 | 
240 | ```bash
241 | task-master set-status --id=3 --status=done
242 | ```
243 | 
244 | ### 5. Handling Implementation Drift
245 | 
246 | If during implementation, you discover that:
247 | 
248 | - The current approach differs significantly from what was planned
249 | - Future tasks need to be modified due to current implementation choices
250 | - New dependencies or requirements have emerged
251 | 
252 | Tell the agent:
253 | 
254 | ```
255 | We've changed our approach. We're now using Express instead of Fastify. Please update all future tasks to reflect this change.
256 | ```
257 | 
258 | The agent will execute:
259 | 
260 | ```bash
261 | task-master update --from=4 --prompt="Now we are using Express instead of Fastify."
262 | ```
263 | 
264 | This will rewrite or re-scope subsequent tasks in tasks.json while preserving completed work.
265 | 
266 | ### 6. Breaking Down Complex Tasks
267 | 
268 | For complex tasks that need more granularity:
269 | 
270 | ```
271 | Task 5 seems complex. Can you break it down into subtasks?
272 | ```
273 | 
274 | The agent will execute:
275 | 
276 | ```bash
277 | task-master expand --id=5 --num=3
278 | ```
279 | 
280 | You can provide additional context:
281 | 
282 | ```
283 | Please break down task 5 with a focus on security considerations.
284 | ```
285 | 
286 | The agent will execute:
287 | 
288 | ```bash
289 | task-master expand --id=5 --prompt="Focus on security aspects"
290 | ```
291 | 
292 | You can also expand all pending tasks:
293 | 
294 | ```
295 | Please break down all pending tasks into subtasks.
296 | ```
297 | 
298 | The agent will execute:
299 | 
300 | ```bash
301 | task-master expand --all
302 | ```
303 | 
304 | For research-backed subtask generation using Perplexity AI:
305 | 
306 | ```
307 | Please break down task 5 using research-backed generation.
308 | ```
309 | 
310 | The agent will execute:
311 | 
312 | ```bash
313 | task-master expand --id=5 --research
314 | ```
315 | 
316 | ## Command Reference
317 | 
318 | Here's a comprehensive reference of all available commands:
319 | 
320 | ### Parse PRD
321 | 
322 | ```bash
323 | # Parse a PRD file and generate tasks
324 | task-master parse-prd <prd-file.txt>
325 | 
326 | # Limit the number of tasks generated (default is 10)
327 | task-master parse-prd <prd-file.txt> --num-tasks=5
328 | 
329 | # Allow task master to determine the number of tasks based on complexity
330 | task-master parse-prd <prd-file.txt> --num-tasks=0
331 | ```
332 | 
333 | ### List Tasks
334 | 
335 | ```bash
336 | # List all tasks
337 | task-master list
338 | 
339 | # List tasks with a specific status
340 | task-master list --status=<status>
341 | 
342 | # List tasks with subtasks
343 | task-master list --with-subtasks
344 | 
345 | # List tasks with a specific status and include subtasks
346 | task-master list --status=<status> --with-subtasks
347 | ```
348 | 
349 | ### Show Next Task
350 | 
351 | ```bash
352 | # Show the next task to work on based on dependencies and status
353 | task-master next
354 | ```
355 | 
356 | ### Show Specific Task
357 | 
358 | ```bash
359 | # Show details of a specific task
360 | task-master show <id>
361 | # or
362 | task-master show --id=<id>
363 | 
364 | # View a specific subtask (e.g., subtask 2 of task 1)
365 | task-master show 1.2
366 | ```
367 | 
368 | ### Update Tasks
369 | 
370 | ```bash
371 | # Update tasks from a specific ID and provide context
372 | task-master update --from=<id> --prompt="<prompt>"
373 | ```
374 | 
375 | ### Generate Task Files
376 | 
377 | ```bash
378 | # Generate individual task files from tasks.json
379 | task-master generate
380 | ```
381 | 
382 | ### Set Task Status
383 | 
384 | ```bash
385 | # Set status of a single task
386 | task-master set-status --id=<id> --status=<status>
387 | 
388 | # Set status for multiple tasks
389 | task-master set-status --id=1,2,3 --status=<status>
390 | 
391 | # Set status for subtasks
392 | task-master set-status --id=1.1,1.2 --status=<status>
393 | ```
394 | 
395 | When marking a task as "done", all of its subtasks will automatically be marked as "done" as well.
396 | 
397 | ### Expand Tasks
398 | 
399 | ```bash
400 | # Expand a specific task with subtasks
401 | task-master expand --id=<id> --num=<number>
402 | 
403 | # Expand a task with a dynamic number of subtasks (ignoring complexity report)
404 | task-master expand --id=<id> --num=0
405 | 
406 | # Expand with additional context
407 | task-master expand --id=<id> --prompt="<context>"
408 | 
409 | # Expand all pending tasks
410 | task-master expand --all
411 | 
412 | # Force regeneration of subtasks for tasks that already have them
413 | task-master expand --all --force
414 | 
415 | # Research-backed subtask generation for a specific task
416 | task-master expand --id=<id> --research
417 | 
418 | # Research-backed generation for all tasks
419 | task-master expand --all --research
420 | ```
421 | 
422 | ### Clear Subtasks
423 | 
424 | ```bash
425 | # Clear subtasks from a specific task
426 | task-master clear-subtasks --id=<id>
427 | 
428 | # Clear subtasks from multiple tasks
429 | task-master clear-subtasks --id=1,2,3
430 | 
431 | # Clear subtasks from all tasks
432 | task-master clear-subtasks --all
433 | ```
434 | 
435 | ### Analyze Task Complexity
436 | 
437 | ```bash
438 | # Analyze complexity of all tasks
439 | task-master analyze-complexity
440 | 
441 | # Save report to a custom location
442 | task-master analyze-complexity --output=my-report.json
443 | 
444 | # Use a specific LLM model
445 | task-master analyze-complexity --model=claude-3-opus-20240229
446 | 
447 | # Set a custom complexity threshold (1-10)
448 | task-master analyze-complexity --threshold=6
449 | 
450 | # Use an alternative tasks file
451 | task-master analyze-complexity --file=custom-tasks.json
452 | 
453 | # Use Perplexity AI for research-backed complexity analysis
454 | task-master analyze-complexity --research
455 | ```
456 | 
457 | ### View Complexity Report
458 | 
459 | ```bash
460 | # Display the task complexity analysis report
461 | task-master complexity-report
462 | 
463 | # View a report at a custom location
464 | task-master complexity-report --file=my-report.json
465 | ```
466 | 
467 | ### Managing Task Dependencies
468 | 
469 | ```bash
470 | # Add a dependency to a task
471 | task-master add-dependency --id=<id> --depends-on=<id>
472 | 
473 | # Remove a dependency from a task
474 | task-master remove-dependency --id=<id> --depends-on=<id>
475 | 
476 | # Validate dependencies without fixing them
477 | task-master validate-dependencies
478 | 
479 | # Find and fix invalid dependencies automatically
480 | task-master fix-dependencies
481 | ```
482 | 
483 | ### Add a New Task
484 | 
485 | ```bash
486 | # Add a new task using AI
487 | task-master add-task --prompt="Description of the new task"
488 | 
489 | # Add a task with dependencies
490 | task-master add-task --prompt="Description" --dependencies=1,2,3
491 | 
492 | # Add a task with priority
493 | task-master add-task --prompt="Description" --priority=high
494 | ```
495 | 
496 | ## Feature Details
497 | 
498 | ### Analyzing Task Complexity
499 | 
500 | The `analyze-complexity` command:
501 | 
502 | - Analyzes each task using AI to assess its complexity on a scale of 1-10
503 | - Recommends optimal number of subtasks based on configured DEFAULT_SUBTASKS
504 | - Generates tailored prompts for expanding each task
505 | - Creates a comprehensive JSON report with ready-to-use commands
506 | - Saves the report to scripts/task-complexity-report.json by default
507 | 
508 | The generated report contains:
509 | 
510 | - Complexity analysis for each task (scored 1-10)
511 | - Recommended number of subtasks based on complexity
512 | - AI-generated expansion prompts customized for each task
513 | - Ready-to-run expansion commands directly within each task analysis
514 | 
515 | ### Viewing Complexity Report
516 | 
517 | The `complexity-report` command:
518 | 
519 | - Displays a formatted, easy-to-read version of the complexity analysis report
520 | - Shows tasks organized by complexity score (highest to lowest)
521 | - Provides complexity distribution statistics (low, medium, high)
522 | - Highlights tasks recommended for expansion based on threshold score
523 | - Includes ready-to-use expansion commands for each complex task
524 | - If no report exists, offers to generate one on the spot
525 | 
526 | ### Smart Task Expansion
527 | 
528 | The `expand` command automatically checks for and uses the complexity report:
529 | 
530 | When a complexity report exists:
531 | 
532 | - Tasks are automatically expanded using the recommended subtask count and prompts
533 | - When expanding all tasks, they're processed in order of complexity (highest first)
534 | - Research-backed generation is preserved from the complexity analysis
535 | - You can still override recommendations with explicit command-line options
536 | 
537 | Example workflow:
538 | 
539 | ```bash
540 | # Generate the complexity analysis report with research capabilities
541 | task-master analyze-complexity --research
542 | 
543 | # Review the report in a readable format
544 | task-master complexity-report
545 | 
546 | # Expand tasks using the optimized recommendations
547 | task-master expand --id=8
548 | # or expand all tasks
549 | task-master expand --all
550 | ```
551 | 
552 | ### Finding the Next Task
553 | 
554 | The `next` command:
555 | 
556 | - Identifies tasks that are pending/in-progress and have all dependencies satisfied
557 | - Prioritizes tasks by priority level, dependency count, and task ID
558 | - Displays comprehensive information about the selected task:
559 |   - Basic task details (ID, title, priority, dependencies)
560 |   - Implementation details
561 |   - Subtasks (if they exist)
562 | - Provides contextual suggested actions:
563 |   - Command to mark the task as in-progress
564 |   - Command to mark the task as done
565 |   - Commands for working with subtasks
566 | 
567 | ### Viewing Specific Task Details
568 | 
569 | The `show` command:
570 | 
571 | - Displays comprehensive details about a specific task or subtask
572 | - Shows task status, priority, dependencies, and detailed implementation notes
573 | - For parent tasks, displays all subtasks and their status
574 | - For subtasks, shows parent task relationship
575 | - Provides contextual action suggestions based on the task's state
576 | - Works with both regular tasks and subtasks (using the format taskId.subtaskId)
577 | 
578 | ## Best Practices for AI-Driven Development
579 | 
580 | 1. **Start with a detailed PRD**: The more detailed your PRD, the better the generated tasks will be.
581 | 
582 | 2. **Review generated tasks**: After parsing the PRD, review the tasks to ensure they make sense and have appropriate dependencies.
583 | 
584 | 3. **Analyze task complexity**: Use the complexity analysis feature to identify which tasks should be broken down further.
585 | 
586 | 4. **Follow the dependency chain**: Always respect task dependencies - the Cursor agent will help with this.
587 | 
588 | 5. **Update as you go**: If your implementation diverges from the plan, use the update command to keep future tasks aligned with your current approach.
589 | 
590 | 6. **Break down complex tasks**: Use the expand command to break down complex tasks into manageable subtasks.
591 | 
592 | 7. **Regenerate task files**: After any updates to tasks.json, regenerate the task files to keep them in sync.
593 | 
594 | 8. **Communicate context to the agent**: When asking the Cursor agent to help with a task, provide context about what you're trying to achieve.
595 | 
596 | 9. **Validate dependencies**: Periodically run the validate-dependencies command to check for invalid or circular dependencies.
597 | 
598 | ## Example Cursor AI Interactions
599 | 
600 | ### Starting a new project
601 | 
602 | ```
603 | I've just initialized a new project with Claude Task Master. I have a PRD at scripts/prd.txt.
604 | Can you help me parse it and set up the initial tasks?
605 | ```
606 | 
607 | ### Working on tasks
608 | 
609 | ```
610 | What's the next task I should work on? Please consider dependencies and priorities.
611 | ```
612 | 
613 | ### Implementing a specific task
614 | 
615 | ```
616 | I'd like to implement task 4. Can you help me understand what needs to be done and how to approach it?
617 | ```
618 | 
619 | ### Managing subtasks
620 | 
621 | ```
622 | I need to regenerate the subtasks for task 3 with a different approach. Can you help me clear and regenerate them?
623 | ```
624 | 
625 | ### Handling changes
626 | 
627 | ```
628 | We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change?
629 | ```
630 | 
631 | ### Completing work
632 | 
633 | ```
634 | I've finished implementing the authentication system described in task 2. All tests are passing.
635 | Please mark it as complete and tell me what I should work on next.
636 | ```
637 | 
638 | ### Analyzing complexity
639 | 
640 | ```
641 | Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further?
642 | ```
643 | 
644 | ### Viewing complexity report
645 | 
646 | ```
647 | Can you show me the complexity report in a more readable format?
648 | ```
649 | 
```

--------------------------------------------------------------------------------
/.taskmaster/docs/task-template-importing-prd.txt:
--------------------------------------------------------------------------------

```
  1 | # Task Template Importing System - Product Requirements Document
  2 | 
  3 | <context>
  4 | # Overview  
  5 | The Task Template Importing system enables seamless integration of external task templates into the Task Master CLI through automatic file discovery. This system allows users to drop task template files into the tasks directory and immediately access them as new tag contexts without manual import commands or configuration. The solution addresses the need for multi-project task management, team collaboration through shared templates, and clean separation between permanent tasks and temporary project contexts.
  6 | 
  7 | # Core Features  
  8 | ## Silent Task Template Discovery
  9 | - **What it does**: Automatically scans for `tasks_*.json` files in the tasks directory during tag operations
 10 | - **Why it's important**: Eliminates friction in adding new task contexts and enables zero-configuration workflow
 11 | - **How it works**: File pattern matching extracts tag names from filenames and validates against internal tag keys
 12 | 
 13 | ## External Tag Resolution System
 14 | - **What it does**: Provides fallback mechanism to external files when tags are not found in main tasks.json
 15 | - **Why it's important**: Maintains clean separation between core tasks and project-specific templates
 16 | - **How it works**: Tag resolution logic checks external files as secondary source while preserving main file precedence
 17 | 
 18 | ## Read-Only External Tag Access
 19 | - **What it does**: Allows viewing and switching to external tags while preventing modifications
 20 | - **Why it's important**: Protects template integrity and prevents accidental changes to shared templates
 21 | - **How it works**: All task modifications route to main tasks.json regardless of current tag context
 22 | 
 23 | ## Tag Precedence Management
 24 | - **What it does**: Ensures main tasks.json tags override external files with same tag names
 25 | - **Why it's important**: Prevents conflicts and maintains data integrity
 26 | - **How it works**: Priority system where main file tags take precedence over external file tags
 27 | 
 28 | # User Experience  
 29 | ## User Personas
 30 | - **Solo Developer**: Manages multiple projects with different task contexts
 31 | - **Team Lead**: Shares standardized task templates across team members
 32 | - **Project Manager**: Organizes tasks by project phases or feature branches
 33 | 
 34 | ## Key User Flows
 35 | ### Template Addition Flow
 36 | 1. User receives or creates a `tasks_projectname.json` file
 37 | 2. User drops file into `.taskmaster/tasks/` directory
 38 | 3. Tag becomes immediately available via `task-master use-tag projectname`
 39 | 4. User can list, view, and switch to external tag without configuration
 40 | 
 41 | ### Template Usage Flow
 42 | 1. User runs `task-master tags` to see available tags including external ones
 43 | 2. External tags display with `(imported)` indicator
 44 | 3. User switches to external tag with `task-master use-tag projectname`
 45 | 4. User can view tasks but modifications are routed to main tasks.json
 46 | 
 47 | ## UI/UX Considerations
 48 | - External tags clearly marked with `(imported)` suffix in listings
 49 | - Visual indicators distinguish between main and external tags
 50 | - Error messages guide users when external files are malformed
 51 | - Read-only warnings when attempting to modify external tag contexts
 52 | </context>
 53 | 
 54 | <PRD>
 55 | # Technical Architecture  
 56 | ## System Components
 57 | 1. **External File Discovery Engine**
 58 |    - File pattern scanner for `tasks_*.json` files
 59 |    - Tag name extraction from filenames using regex
 60 |    - Dynamic tag registry combining main and external sources
 61 |    - Error handling for malformed external files
 62 | 
 63 | 2. **Enhanced Tag Resolution System**
 64 |    - Fallback mechanism to external files when tags not found in main tasks.json
 65 |    - Precedence management ensuring main file tags override external files
 66 |    - Read-only access enforcement for external tags
 67 |    - Tag metadata preservation during discovery operations
 68 | 
 69 | 3. **Silent Discovery Integration**
 70 |    - Automatic scanning during tag-related operations
 71 |    - Seamless integration with existing tag management functions
 72 |    - Zero-configuration workflow requiring no manual import commands
 73 |    - Dynamic tag availability without restart requirements
 74 | 
 75 | ## Data Models
 76 | 
 77 | ### External Task File Structure
 78 | ```json
 79 | {
 80 |   "meta": {
 81 |     "projectName": "External Project Name",
 82 |     "version": "1.0.0",
 83 |     "templateSource": "external",
 84 |     "createdAt": "ISO-8601 timestamp"
 85 |   },
 86 |   "tags": {
 87 |     "projectname": {
 88 |       "meta": {
 89 |         "name": "Project Name",
 90 |         "description": "Project description",
 91 |         "createdAt": "ISO-8601 timestamp"
 92 |       },
 93 |       "tasks": [
 94 |         // Array of task objects
 95 |       ]
 96 |     },
 97 |     "master": {
 98 |       // This section is ignored to prevent conflicts
 99 |     }
100 |   }
101 | }
102 | ```
103 | 
104 | ### Enhanced Tag Registry Model
105 | ```json
106 | {
107 |   "mainTags": [
108 |     {
109 |       "name": "master",
110 |       "source": "main",
111 |       "taskCount": 150,
112 |       "isActive": true
113 |     }
114 |   ],
115 |   "externalTags": [
116 |     {
117 |       "name": "projectname",
118 |       "source": "external",
119 |       "filename": "tasks_projectname.json",
120 |       "taskCount": 25,
121 |       "isReadOnly": true
122 |     }
123 |   ]
124 | }
125 | ```
126 | 
127 | ## APIs and Integrations
128 | 1. **File System Discovery API**
129 |    - Directory scanning with pattern matching
130 |    - JSON file validation and parsing
131 |    - Error handling for corrupted or malformed files
132 |    - File modification time tracking for cache invalidation
133 | 
134 | 2. **Enhanced Tag Management API**
135 |    - `scanForExternalTaskFiles(projectRoot)` - Discover external template files
136 |    - `getExternalTagsFromFiles(projectRoot)` - Extract tag names from external files
137 |    - `readExternalTagData(projectRoot, tagName)` - Read specific external tag data
138 |    - `getAvailableTags(projectRoot)` - Combined main and external tag listing
139 | 
140 | 3. **Tag Resolution Enhancement**
141 |    - Modified `readJSON()` with external file fallback
142 |    - Enhanced `tags()` function with external tag display
143 |    - Updated `useTag()` function supporting external tag switching
144 |    - Read-only enforcement for external tag operations
145 | 
146 | ## Infrastructure Requirements
147 | 1. **File System Access**
148 |    - Read permissions for tasks directory
149 |    - JSON parsing capabilities
150 |    - Pattern matching and regex support
151 |    - Error handling for file system operations
152 | 
153 | 2. **Backward Compatibility**
154 |    - Existing tag operations continue unchanged
155 |    - Main tasks.json structure preserved
156 |    - No breaking changes to current workflows
157 |    - Graceful degradation when external files unavailable
158 | 
159 | # Development Roadmap  
160 | ## Phase 1: Core External File Discovery (Foundation)
161 | 1. **External File Scanner Implementation**
162 |    - Create `scanForExternalTaskFiles()` function in utils.js
163 |    - Implement file pattern matching for `tasks_*.json` files
164 |    - Add error handling for file system access issues
165 |    - Test with various filename patterns and edge cases
166 | 
167 | 2. **Tag Name Extraction System**
168 |    - Implement `getExternalTagsFromFiles()` function
169 |    - Create regex pattern for extracting tag names from filenames
170 |    - Add validation to ensure tag names match internal tag key format
171 |    - Handle special characters and invalid filename patterns
172 | 
173 | 3. **External Tag Data Reader**
174 |    - Create `readExternalTagData()` function
175 |    - Implement JSON parsing with error handling
176 |    - Add validation for required tag structure
177 |    - Ignore 'master' key in external files to prevent conflicts
178 | 
179 | ## Phase 2: Tag Resolution Enhancement (Core Integration)
180 | 1. **Enhanced Tag Registry**
181 |    - Implement `getAvailableTags()` function combining main and external sources
182 |    - Create tag metadata structure including source information
183 |    - Add deduplication logic prioritizing main tags over external
184 |    - Implement caching mechanism for performance optimization
185 | 
186 | 2. **Modified readJSON Function**
187 |    - Add external file fallback when tag not found in main tasks.json
188 |    - Maintain precedence rule: main tasks.json overrides external files
189 |    - Preserve existing error handling and validation patterns
190 |    - Ensure read-only access for external tags
191 | 
192 | 3. **Tag Listing Enhancement**
193 |    - Update `tags()` function to display external tags with `(imported)` indicator
194 |    - Show external tag metadata and task counts
195 |    - Maintain current tag highlighting and sorting functionality
196 |    - Add visual distinction between main and external tags
197 | 
198 | ## Phase 3: User Interface Integration (User Experience)
199 | 1. **Tag Switching Enhancement**
200 |    - Update `useTag()` function to support external tag switching
201 |    - Add read-only warnings when switching to external tags
202 |    - Update state.json with external tag context information
203 |    - Maintain current tag switching behavior for main tags
204 | 
205 | 2. **Error Handling and User Feedback**
206 |    - Implement comprehensive error messages for malformed external files
207 |    - Add user guidance for proper external file structure
208 |    - Create warnings for read-only operations on external tags
209 |    - Ensure graceful degradation when external files are corrupted
210 | 
211 | 3. **Documentation and Help Integration**
212 |    - Update command help text to include external tag information
213 |    - Add examples of external file structure and usage
214 |    - Create troubleshooting guide for common external file issues
215 |    - Document file naming conventions and best practices
216 | 
217 | ## Phase 4: Advanced Features and Optimization (Enhancement)
218 | 1. **Performance Optimization**
219 |    - Implement file modification time caching
220 |    - Add lazy loading for external tag data
221 |    - Optimize file scanning for directories with many files
222 |    - Create efficient tag resolution caching mechanism
223 | 
224 | 2. **Advanced External File Features**
225 |    - Support for nested external file directories
226 |    - Batch external file validation and reporting
227 |    - External file metadata display and management
228 |    - Integration with version control ignore patterns
229 | 
230 | 3. **Team Collaboration Features**
231 |    - Shared external file validation
232 |    - External file conflict detection and resolution
233 |    - Team template sharing guidelines and documentation
234 |    - Integration with git workflows for template management
235 | 
236 | # Logical Dependency Chain
237 | ## Foundation Layer (Must Be Built First)
238 | 1. **External File Scanner** 
239 |    - Core requirement for all other functionality
240 |    - Provides the discovery mechanism for external template files
241 |    - Must handle file system access and pattern matching reliably
242 | 
243 | 2. **Tag Name Extraction**
244 |    - Depends on file scanner functionality
245 |    - Required for identifying available external tags
246 |    - Must validate tag names against internal format requirements
247 | 
248 | 3. **External Tag Data Reader**
249 |    - Depends on tag name extraction
250 |    - Provides access to external tag content
251 |    - Must handle JSON parsing and validation safely
252 | 
253 | ## Integration Layer (Builds on Foundation)
254 | 4. **Enhanced Tag Registry**
255 |    - Depends on all foundation components
256 |    - Combines main and external tag sources
257 |    - Required for unified tag management across the system
258 | 
259 | 5. **Modified readJSON Function**
260 |    - Depends on enhanced tag registry
261 |    - Provides fallback mechanism for tag resolution
262 |    - Critical for maintaining backward compatibility
263 | 
264 | 6. **Tag Listing Enhancement**
265 |    - Depends on enhanced tag registry
266 |    - Provides user visibility into external tags
267 |    - Required for user discovery of available templates
268 | 
269 | ## User Experience Layer (Completes the Feature)
270 | 7. **Tag Switching Enhancement**
271 |    - Depends on modified readJSON and tag listing
272 |    - Enables user interaction with external tags
273 |    - Must enforce read-only access properly
274 | 
275 | 8. **Error Handling and User Feedback**
276 |    - Can be developed in parallel with other UX components
277 |    - Enhances reliability and user experience
278 |    - Should be integrated throughout development process
279 | 
280 | 9. **Documentation and Help Integration**
281 |    - Should be developed alongside implementation
282 |    - Required for user adoption and proper usage
283 |    - Can be completed in parallel with advanced features
284 | 
285 | ## Optimization Layer (Performance and Advanced Features)
286 | 10. **Performance Optimization**
287 |     - Can be developed after core functionality is stable
288 |     - Improves user experience with large numbers of external files
289 |     - Not blocking for initial release
290 | 
291 | 11. **Advanced External File Features**
292 |     - Can be developed independently after core features
293 |     - Enhances power user workflows
294 |     - Optional for initial release
295 | 
296 | 12. **Team Collaboration Features**
297 |     - Depends on stable core functionality
298 |     - Enhances team workflows and template sharing
299 |     - Can be prioritized based on user feedback
300 | 
301 | # Risks and Mitigations  
302 | ## Technical Challenges
303 | 
304 | ### File System Performance
305 | **Risk**: Scanning for external files on every tag operation could impact performance with large directories.
306 | **Mitigation**: 
307 | - Implement file modification time caching to avoid unnecessary rescans
308 | - Use lazy loading for external tag data - only read when accessed
309 | - Add configurable limits on number of external files to scan
310 | - Optimize file pattern matching with efficient regex patterns
311 | 
312 | ### External File Corruption
313 | **Risk**: Malformed or corrupted external JSON files could break tag operations.
314 | **Mitigation**:
315 | - Implement robust JSON parsing with comprehensive error handling
316 | - Add file validation before attempting to parse external files
317 | - Gracefully skip corrupted files and continue with valid ones
318 | - Provide clear error messages guiding users to fix malformed files
319 | 
320 | ### Tag Name Conflicts
321 | **Risk**: External files might contain tag names that conflict with main tasks.json tags.
322 | **Mitigation**:
323 | - Implement strict precedence rule: main tasks.json always overrides external files
324 | - Add warnings when external tags are ignored due to conflicts
325 | - Document naming conventions to avoid common conflicts
326 | - Provide validation tools to check for potential conflicts
327 | 
328 | ## MVP Definition
329 | 
330 | ### Core Feature Scope
331 | **Risk**: Including too many advanced features could delay the core functionality.
332 | **Mitigation**:
333 | - Define MVP as basic external file discovery + tag switching
334 | - Focus on the silent discovery mechanism as the primary value proposition
335 | - Defer advanced features like nested directories and batch operations
336 | - Ensure each phase delivers complete, usable functionality
337 | 
338 | ### User Experience Complexity
339 | **Risk**: The read-only nature of external tags might confuse users.
340 | **Mitigation**:
341 | - Provide clear visual indicators for external tags in all interfaces
342 | - Add explicit warnings when users attempt to modify external tag contexts
343 | - Document the read-only behavior and its rationale clearly
344 | - Consider future enhancement for external tag modification workflows
345 | 
346 | ### Backward Compatibility
347 | **Risk**: Changes to tag resolution logic might break existing workflows.
348 | **Mitigation**:
349 | - Maintain existing tag operations unchanged for main tasks.json
350 | - Add external file support as enhancement, not replacement
351 | - Test thoroughly with existing task structures and workflows
352 | - Provide migration path if any breaking changes are necessary
353 | 
354 | ## Resource Constraints
355 | 
356 | ### Development Complexity
357 | **Risk**: Integration with existing tag management system could be complex.
358 | **Mitigation**:
359 | - Phase implementation to minimize risk of breaking existing functionality
360 | - Create comprehensive test suite covering both main and external tag scenarios
361 | - Use feature flags to enable/disable external file support during development
362 | - Implement thorough error handling to prevent system failures
363 | 
364 | ### File System Dependencies
365 | **Risk**: Different operating systems might handle file operations differently.
366 | **Mitigation**:
367 | - Use Node.js built-in file system APIs for cross-platform compatibility
368 | - Test on multiple operating systems (Windows, macOS, Linux)
369 | - Handle file path separators and naming conventions properly
370 | - Add fallback mechanisms for file system access issues
371 | 
372 | ### User Adoption
373 | **Risk**: Users might not understand or adopt the external file template system.
374 | **Mitigation**:
375 | - Create clear documentation with practical examples
376 | - Provide sample external template files for common use cases
377 | - Integrate help and guidance directly into the CLI interface
378 | - Gather user feedback early and iterate on the user experience
379 | 
380 | # Appendix  
381 | ## External File Naming Convention
382 | 
383 | ### Filename Pattern
384 | - **Format**: `tasks_[tagname].json`
385 | - **Examples**: `tasks_feature-auth.json`, `tasks_v2-migration.json`, `tasks_project-alpha.json`
386 | - **Validation**: Tag name must match internal tag key format (alphanumeric, hyphens, underscores)
387 | 
388 | ### File Structure Requirements
389 | ```json
390 | {
391 |   "meta": {
392 |     "projectName": "Required: Human-readable project name",
393 |     "version": "Optional: Template version",
394 |     "templateSource": "Optional: Source identifier",
395 |     "createdAt": "Optional: ISO-8601 timestamp"
396 |   },
397 |   "tags": {
398 |     "[tagname]": {
399 |       "meta": {
400 |         "name": "Required: Tag display name",
401 |         "description": "Optional: Tag description",
402 |         "createdAt": "Optional: ISO-8601 timestamp"
403 |       },
404 |       "tasks": [
405 |         // Required: Array of task objects following standard task structure
406 |       ]
407 |     }
408 |   }
409 | }
410 | ```
411 | 
412 | ## Implementation Functions Specification
413 | 
414 | ### Core Discovery Functions
415 | ```javascript
416 | // Scan tasks directory for external template files
417 | function scanForExternalTaskFiles(projectRoot) {
418 |   // Returns: Array of external file paths
419 | }
420 | 
421 | // Extract tag names from external filenames
422 | function getExternalTagsFromFiles(projectRoot) {
423 |   // Returns: Array of external tag names
424 | }
425 | 
426 | // Read specific external tag data
427 | function readExternalTagData(projectRoot, tagName) {
428 |   // Returns: Tag data object or null if not found
429 | }
430 | 
431 | // Get combined main and external tags
432 | function getAvailableTags(projectRoot) {
433 |   // Returns: Combined tag registry with metadata
434 | }
435 | ```
436 | 
437 | ### Integration Points
438 | ```javascript
439 | // Enhanced readJSON with external fallback
440 | function readJSON(projectRoot, tag = null) {
441 |   // Modified to check external files when tag not found in main
442 | }
443 | 
444 | // Enhanced tags listing with external indicators
445 | function tags(projectRoot, options = {}) {
446 |   // Modified to display external tags with (imported) suffix
447 | }
448 | 
449 | // Enhanced tag switching with external support
450 | function useTag(projectRoot, tagName) {
451 |   // Modified to support switching to external tags (read-only)
452 | }
453 | ```
454 | 
455 | ## Error Handling Specifications
456 | 
457 | ### File System Errors
458 | - **ENOENT**: External file not found - gracefully skip and continue
459 | - **EACCES**: Permission denied - warn user and continue with available files
460 | - **EISDIR**: Directory instead of file - skip and continue scanning
461 | 
462 | ### JSON Parsing Errors
463 | - **SyntaxError**: Malformed JSON - skip file and log warning with filename
464 | - **Missing required fields**: Skip file and provide specific error message
465 | - **Invalid tag structure**: Skip file and guide user to correct format
466 | 
467 | ### Tag Conflict Resolution
468 | - **Duplicate tag names**: Main tasks.json takes precedence, log warning
469 | - **Invalid tag names**: Skip external file and provide naming guidance
470 | - **Master key in external**: Ignore master key, process other tags normally
471 | </PRD> 
```

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

```javascript
  1 | import { jest } from '@jest/globals';
  2 | 
  3 | // Mock the 'ai' SDK
  4 | const mockGenerateText = jest.fn();
  5 | const mockGenerateObject = jest.fn();
  6 | const mockNoObjectGeneratedError = class NoObjectGeneratedError extends Error {
  7 | 	static isInstance(error) {
  8 | 		return error instanceof mockNoObjectGeneratedError;
  9 | 	}
 10 | 	constructor(cause) {
 11 | 		super('No object generated');
 12 | 		this.cause = cause;
 13 | 		this.usage = cause.usage;
 14 | 	}
 15 | };
 16 | const mockJSONParseError = class JSONParseError extends Error {
 17 | 	constructor(text) {
 18 | 		super('JSON parse error');
 19 | 		this.text = text;
 20 | 	}
 21 | };
 22 | 
 23 | jest.unstable_mockModule('ai', () => ({
 24 | 	generateText: mockGenerateText,
 25 | 	streamText: jest.fn(),
 26 | 	generateObject: mockGenerateObject,
 27 | 	streamObject: jest.fn(),
 28 | 	zodSchema: jest.fn((schema) => schema),
 29 | 	NoObjectGeneratedError: mockNoObjectGeneratedError,
 30 | 	JSONParseError: mockJSONParseError
 31 | }));
 32 | 
 33 | // Mock jsonrepair
 34 | const mockJsonrepair = jest.fn();
 35 | jest.unstable_mockModule('jsonrepair', () => ({
 36 | 	jsonrepair: mockJsonrepair
 37 | }));
 38 | 
 39 | // Mock logging and utilities
 40 | jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
 41 | 	log: jest.fn(),
 42 | 	findProjectRoot: jest.fn(() => '/mock/project/root'),
 43 | 	isEmpty: jest.fn(
 44 | 		(val) =>
 45 | 			!val ||
 46 | 			(Array.isArray(val) && val.length === 0) ||
 47 | 			(typeof val === 'object' && Object.keys(val).length === 0)
 48 | 	),
 49 | 	resolveEnvVariable: jest.fn((key) => process.env[key])
 50 | }));
 51 | 
 52 | // Import after mocking
 53 | const { BaseAIProvider } = await import(
 54 | 	'../../../src/ai-providers/base-provider.js'
 55 | );
 56 | 
 57 | describe('BaseAIProvider', () => {
 58 | 	let testProvider;
 59 | 	let mockClient;
 60 | 
 61 | 	beforeEach(() => {
 62 | 		// Create a concrete test provider
 63 | 		class TestProvider extends BaseAIProvider {
 64 | 			constructor() {
 65 | 				super();
 66 | 				this.name = 'TestProvider';
 67 | 			}
 68 | 
 69 | 			getRequiredApiKeyName() {
 70 | 				return 'TEST_API_KEY';
 71 | 			}
 72 | 
 73 | 			async getClient() {
 74 | 				return mockClient;
 75 | 			}
 76 | 		}
 77 | 
 78 | 		mockClient = jest.fn((modelId) => ({ modelId }));
 79 | 		jest.clearAllMocks();
 80 | 		testProvider = new TestProvider();
 81 | 	});
 82 | 
 83 | 	describe('1. Parameter Validation - Catches Invalid Inputs', () => {
 84 | 		describe('validateAuth', () => {
 85 | 			it('should throw when API key is missing', () => {
 86 | 				expect(() => testProvider.validateAuth({})).toThrow(
 87 | 					'TestProvider API key is required'
 88 | 				);
 89 | 			});
 90 | 
 91 | 			it('should pass when API key is provided', () => {
 92 | 				expect(() =>
 93 | 					testProvider.validateAuth({ apiKey: 'test-key' })
 94 | 				).not.toThrow();
 95 | 			});
 96 | 		});
 97 | 
 98 | 		describe('validateParams', () => {
 99 | 			it('should throw when model ID is missing', () => {
100 | 				expect(() => testProvider.validateParams({ apiKey: 'key' })).toThrow(
101 | 					'TestProvider Model ID is required'
102 | 				);
103 | 			});
104 | 
105 | 			it('should throw when both API key and model ID are missing', () => {
106 | 				expect(() => testProvider.validateParams({})).toThrow(
107 | 					'TestProvider API key is required'
108 | 				);
109 | 			});
110 | 		});
111 | 
112 | 		describe('validateOptionalParams', () => {
113 | 			it('should throw for temperature below 0', () => {
114 | 				expect(() =>
115 | 					testProvider.validateOptionalParams({ temperature: -0.1 })
116 | 				).toThrow('Temperature must be between 0 and 1');
117 | 			});
118 | 
119 | 			it('should throw for temperature above 1', () => {
120 | 				expect(() =>
121 | 					testProvider.validateOptionalParams({ temperature: 1.1 })
122 | 				).toThrow('Temperature must be between 0 and 1');
123 | 			});
124 | 
125 | 			it('should accept temperature at boundaries', () => {
126 | 				expect(() =>
127 | 					testProvider.validateOptionalParams({ temperature: 0 })
128 | 				).not.toThrow();
129 | 				expect(() =>
130 | 					testProvider.validateOptionalParams({ temperature: 1 })
131 | 				).not.toThrow();
132 | 			});
133 | 
134 | 			it('should throw for invalid maxTokens values', () => {
135 | 				expect(() =>
136 | 					testProvider.validateOptionalParams({ maxTokens: 0 })
137 | 				).toThrow('maxTokens must be a finite number greater than 0');
138 | 				expect(() =>
139 | 					testProvider.validateOptionalParams({ maxTokens: -100 })
140 | 				).toThrow('maxTokens must be a finite number greater than 0');
141 | 				expect(() =>
142 | 					testProvider.validateOptionalParams({ maxTokens: Infinity })
143 | 				).toThrow('maxTokens must be a finite number greater than 0');
144 | 				expect(() =>
145 | 					testProvider.validateOptionalParams({ maxTokens: 'invalid' })
146 | 				).toThrow('maxTokens must be a finite number greater than 0');
147 | 			});
148 | 		});
149 | 
150 | 		describe('validateMessages', () => {
151 | 			it('should throw for null/undefined messages', async () => {
152 | 				await expect(
153 | 					testProvider.generateText({
154 | 						apiKey: 'key',
155 | 						modelId: 'model',
156 | 						messages: null
157 | 					})
158 | 				).rejects.toThrow('Invalid or empty messages array provided');
159 | 
160 | 				await expect(
161 | 					testProvider.generateText({
162 | 						apiKey: 'key',
163 | 						modelId: 'model',
164 | 						messages: undefined
165 | 					})
166 | 				).rejects.toThrow('Invalid or empty messages array provided');
167 | 			});
168 | 
169 | 			it('should throw for empty messages array', async () => {
170 | 				await expect(
171 | 					testProvider.generateText({
172 | 						apiKey: 'key',
173 | 						modelId: 'model',
174 | 						messages: []
175 | 					})
176 | 				).rejects.toThrow('Invalid or empty messages array provided');
177 | 			});
178 | 
179 | 			it('should throw for messages without role or content', async () => {
180 | 				await expect(
181 | 					testProvider.generateText({
182 | 						apiKey: 'key',
183 | 						modelId: 'model',
184 | 						messages: [{ content: 'test' }] // missing role
185 | 					})
186 | 				).rejects.toThrow(
187 | 					'Invalid message format. Each message must have role and content'
188 | 				);
189 | 
190 | 				await expect(
191 | 					testProvider.generateText({
192 | 						apiKey: 'key',
193 | 						modelId: 'model',
194 | 						messages: [{ role: 'user' }] // missing content
195 | 					})
196 | 				).rejects.toThrow(
197 | 					'Invalid message format. Each message must have role and content'
198 | 				);
199 | 			});
200 | 		});
201 | 	});
202 | 
203 | 	describe('2. Error Handling - Proper Error Context', () => {
204 | 		it('should wrap API errors with context', async () => {
205 | 			const apiError = new Error('API rate limit exceeded');
206 | 			mockGenerateText.mockRejectedValue(apiError);
207 | 
208 | 			await expect(
209 | 				testProvider.generateText({
210 | 					apiKey: 'key',
211 | 					modelId: 'model',
212 | 					messages: [{ role: 'user', content: 'test' }]
213 | 				})
214 | 			).rejects.toThrow(
215 | 				'TestProvider API error during text generation: API rate limit exceeded'
216 | 			);
217 | 		});
218 | 
219 | 		it('should handle errors without message property', async () => {
220 | 			const apiError = { code: 'NETWORK_ERROR' };
221 | 			mockGenerateText.mockRejectedValue(apiError);
222 | 
223 | 			await expect(
224 | 				testProvider.generateText({
225 | 					apiKey: 'key',
226 | 					modelId: 'model',
227 | 					messages: [{ role: 'user', content: 'test' }]
228 | 				})
229 | 			).rejects.toThrow(
230 | 				'TestProvider API error during text generation: Unknown error occurred'
231 | 			);
232 | 		});
233 | 	});
234 | 
235 | 	describe('3. Abstract Class Protection', () => {
236 | 		it('should prevent direct instantiation of BaseAIProvider', () => {
237 | 			expect(() => new BaseAIProvider()).toThrow(
238 | 				'BaseAIProvider cannot be instantiated directly'
239 | 			);
240 | 		});
241 | 
242 | 		it('should throw when abstract methods are not implemented', () => {
243 | 			class IncompleteProvider extends BaseAIProvider {
244 | 				constructor() {
245 | 					super();
246 | 				}
247 | 			}
248 | 			const provider = new IncompleteProvider();
249 | 
250 | 			expect(() => provider.getClient()).toThrow(
251 | 				'getClient must be implemented by provider'
252 | 			);
253 | 			expect(() => provider.getRequiredApiKeyName()).toThrow(
254 | 				'getRequiredApiKeyName must be implemented by provider'
255 | 			);
256 | 		});
257 | 	});
258 | 
259 | 	describe('4. Token Parameter Preparation', () => {
260 | 		it('should convert maxTokens to maxOutputTokens as integer', () => {
261 | 			const result = testProvider.prepareTokenParam('model', 1000.7);
262 | 			expect(result).toEqual({ maxOutputTokens: 1000 });
263 | 		});
264 | 
265 | 		it('should handle string numbers', () => {
266 | 			const result = testProvider.prepareTokenParam('model', '500');
267 | 			expect(result).toEqual({ maxOutputTokens: 500 });
268 | 		});
269 | 
270 | 		it('should return empty object when maxTokens is undefined', () => {
271 | 			const result = testProvider.prepareTokenParam('model', undefined);
272 | 			expect(result).toEqual({});
273 | 		});
274 | 
275 | 		it('should floor decimal values', () => {
276 | 			const result = testProvider.prepareTokenParam('model', 999.99);
277 | 			expect(result).toEqual({ maxOutputTokens: 999 });
278 | 		});
279 | 	});
280 | 
281 | 	describe('5. JSON Repair for Malformed Responses', () => {
282 | 		it('should repair malformed JSON in generateObject errors', async () => {
283 | 			const malformedJson = '{"key": "value",,}'; // Double comma
284 | 			const repairedJson = '{"key": "value"}';
285 | 
286 | 			const parseError = new mockJSONParseError(malformedJson);
287 | 			const noObjectError = new mockNoObjectGeneratedError(parseError);
288 | 			noObjectError.usage = {
289 | 				promptTokens: 100,
290 | 				completionTokens: 50,
291 | 				totalTokens: 150
292 | 			};
293 | 
294 | 			mockGenerateObject.mockRejectedValue(noObjectError);
295 | 			mockJsonrepair.mockReturnValue(repairedJson);
296 | 
297 | 			const result = await testProvider.generateObject({
298 | 				apiKey: 'key',
299 | 				modelId: 'model',
300 | 				messages: [{ role: 'user', content: 'test' }],
301 | 				schema: { type: 'object' },
302 | 				objectName: 'TestObject'
303 | 			});
304 | 
305 | 			expect(mockJsonrepair).toHaveBeenCalledWith(malformedJson);
306 | 			expect(result).toEqual({
307 | 				object: { key: 'value' },
308 | 				usage: {
309 | 					inputTokens: 100,
310 | 					outputTokens: 50,
311 | 					totalTokens: 150
312 | 				}
313 | 			});
314 | 		});
315 | 
316 | 		it('should throw original error when JSON repair fails', async () => {
317 | 			const malformedJson = 'not even close to JSON';
318 | 			const parseError = new mockJSONParseError(malformedJson);
319 | 			const noObjectError = new mockNoObjectGeneratedError(parseError);
320 | 
321 | 			mockGenerateObject.mockRejectedValue(noObjectError);
322 | 			mockJsonrepair.mockImplementation(() => {
323 | 				throw new Error('Cannot repair this JSON');
324 | 			});
325 | 
326 | 			await expect(
327 | 				testProvider.generateObject({
328 | 					apiKey: 'key',
329 | 					modelId: 'model',
330 | 					messages: [{ role: 'user', content: 'test' }],
331 | 					schema: { type: 'object' },
332 | 					objectName: 'TestObject'
333 | 				})
334 | 			).rejects.toThrow('TestProvider API error during object generation');
335 | 		});
336 | 
337 | 		it('should handle non-JSON parse errors normally', async () => {
338 | 			const regularError = new Error('Network timeout');
339 | 			mockGenerateObject.mockRejectedValue(regularError);
340 | 
341 | 			await expect(
342 | 				testProvider.generateObject({
343 | 					apiKey: 'key',
344 | 					modelId: 'model',
345 | 					messages: [{ role: 'user', content: 'test' }],
346 | 					schema: { type: 'object' },
347 | 					objectName: 'TestObject'
348 | 				})
349 | 			).rejects.toThrow(
350 | 				'TestProvider API error during object generation: Network timeout'
351 | 			);
352 | 
353 | 			expect(mockJsonrepair).not.toHaveBeenCalled();
354 | 		});
355 | 	});
356 | 
357 | 	describe('6. Usage Token Normalization', () => {
358 | 		it('should normalize different token formats in generateText', async () => {
359 | 			// Test promptTokens/completionTokens format (older format)
360 | 			mockGenerateText.mockResolvedValue({
361 | 				text: 'response',
362 | 				usage: { promptTokens: 10, completionTokens: 5 }
363 | 			});
364 | 
365 | 			let result = await testProvider.generateText({
366 | 				apiKey: 'key',
367 | 				modelId: 'model',
368 | 				messages: [{ role: 'user', content: 'test' }]
369 | 			});
370 | 
371 | 			expect(result.usage).toEqual({
372 | 				inputTokens: 10,
373 | 				outputTokens: 5,
374 | 				totalTokens: 15
375 | 			});
376 | 
377 | 			// Test inputTokens/outputTokens format (newer format)
378 | 			mockGenerateText.mockResolvedValue({
379 | 				text: 'response',
380 | 				usage: { inputTokens: 20, outputTokens: 10, totalTokens: 30 }
381 | 			});
382 | 
383 | 			result = await testProvider.generateText({
384 | 				apiKey: 'key',
385 | 				modelId: 'model',
386 | 				messages: [{ role: 'user', content: 'test' }]
387 | 			});
388 | 
389 | 			expect(result.usage).toEqual({
390 | 				inputTokens: 20,
391 | 				outputTokens: 10,
392 | 				totalTokens: 30
393 | 			});
394 | 		});
395 | 
396 | 		it('should handle missing usage data gracefully', async () => {
397 | 			mockGenerateText.mockResolvedValue({
398 | 				text: 'response',
399 | 				usage: undefined
400 | 			});
401 | 
402 | 			const result = await testProvider.generateText({
403 | 				apiKey: 'key',
404 | 				modelId: 'model',
405 | 				messages: [{ role: 'user', content: 'test' }]
406 | 			});
407 | 
408 | 			expect(result.usage).toEqual({
409 | 				inputTokens: 0,
410 | 				outputTokens: 0,
411 | 				totalTokens: 0
412 | 			});
413 | 		});
414 | 
415 | 		it('should calculate totalTokens when missing', async () => {
416 | 			mockGenerateText.mockResolvedValue({
417 | 				text: 'response',
418 | 				usage: { inputTokens: 15, outputTokens: 25 }
419 | 			});
420 | 
421 | 			const result = await testProvider.generateText({
422 | 				apiKey: 'key',
423 | 				modelId: 'model',
424 | 				messages: [{ role: 'user', content: 'test' }]
425 | 			});
426 | 
427 | 			expect(result.usage.totalTokens).toBe(40);
428 | 		});
429 | 	});
430 | 
431 | 	describe('7. Schema Validation for Object Methods', () => {
432 | 		it('should throw when schema is missing for generateObject', async () => {
433 | 			await expect(
434 | 				testProvider.generateObject({
435 | 					apiKey: 'key',
436 | 					modelId: 'model',
437 | 					messages: [{ role: 'user', content: 'test' }],
438 | 					objectName: 'TestObject'
439 | 					// missing schema
440 | 				})
441 | 			).rejects.toThrow('Schema is required for object generation');
442 | 		});
443 | 
444 | 		it('should throw when objectName is missing for generateObject', async () => {
445 | 			await expect(
446 | 				testProvider.generateObject({
447 | 					apiKey: 'key',
448 | 					modelId: 'model',
449 | 					messages: [{ role: 'user', content: 'test' }],
450 | 					schema: { type: 'object' }
451 | 					// missing objectName
452 | 				})
453 | 			).rejects.toThrow('Object name is required for object generation');
454 | 		});
455 | 
456 | 		it('should throw when schema is missing for streamObject', async () => {
457 | 			await expect(
458 | 				testProvider.streamObject({
459 | 					apiKey: 'key',
460 | 					modelId: 'model',
461 | 					messages: [{ role: 'user', content: 'test' }]
462 | 					// missing schema
463 | 				})
464 | 			).rejects.toThrow('Schema is required for object streaming');
465 | 		});
466 | 
467 | 		it('should use json mode when needsExplicitJsonSchema is true', async () => {
468 | 			testProvider.needsExplicitJsonSchema = true;
469 | 			mockGenerateObject.mockResolvedValue({
470 | 				object: { test: 'value' },
471 | 				usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }
472 | 			});
473 | 
474 | 			await testProvider.generateObject({
475 | 				apiKey: 'key',
476 | 				modelId: 'model',
477 | 				messages: [{ role: 'user', content: 'test' }],
478 | 				schema: { type: 'object' },
479 | 				objectName: 'TestObject'
480 | 			});
481 | 
482 | 			expect(mockGenerateObject).toHaveBeenCalledWith(
483 | 				expect.objectContaining({
484 | 					mode: 'json' // Should be 'json' not 'auto'
485 | 				})
486 | 			);
487 | 		});
488 | 	});
489 | 
490 | 	describe('8. Integration Points - Client Creation', () => {
491 | 		it('should pass params to getClient method', async () => {
492 | 			const getClientSpy = jest.spyOn(testProvider, 'getClient');
493 | 			mockGenerateText.mockResolvedValue({
494 | 				text: 'response',
495 | 				usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }
496 | 			});
497 | 
498 | 			const params = {
499 | 				apiKey: 'test-key',
500 | 				modelId: 'test-model',
501 | 				messages: [{ role: 'user', content: 'test' }],
502 | 				customParam: 'custom-value'
503 | 			};
504 | 
505 | 			await testProvider.generateText(params);
506 | 
507 | 			expect(getClientSpy).toHaveBeenCalledWith(params);
508 | 		});
509 | 
510 | 		it('should use client with correct model ID', async () => {
511 | 			mockGenerateText.mockResolvedValue({
512 | 				text: 'response',
513 | 				usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }
514 | 			});
515 | 
516 | 			await testProvider.generateText({
517 | 				apiKey: 'key',
518 | 				modelId: 'gpt-4-turbo',
519 | 				messages: [{ role: 'user', content: 'test' }]
520 | 			});
521 | 
522 | 			expect(mockClient).toHaveBeenCalledWith('gpt-4-turbo');
523 | 			expect(mockGenerateText).toHaveBeenCalledWith(
524 | 				expect.objectContaining({
525 | 					model: { modelId: 'gpt-4-turbo' }
526 | 				})
527 | 			);
528 | 		});
529 | 	});
530 | 
531 | 	describe('9. Edge Cases - Boundary Conditions', () => {
532 | 		it('should handle zero maxTokens gracefully', () => {
533 | 			// This should throw in validation
534 | 			expect(() =>
535 | 				testProvider.validateOptionalParams({ maxTokens: 0 })
536 | 			).toThrow('maxTokens must be a finite number greater than 0');
537 | 		});
538 | 
539 | 		it('should handle very large maxTokens', () => {
540 | 			const result = testProvider.prepareTokenParam('model', 999999999);
541 | 			expect(result).toEqual({ maxOutputTokens: 999999999 });
542 | 		});
543 | 
544 | 		it('should handle NaN temperature gracefully', () => {
545 | 			// NaN fails the range check (NaN < 0 is false, NaN > 1 is also false)
546 | 			// But NaN is not between 0 and 1, so we need to check the actual behavior
547 | 			// The current implementation doesn't explicitly check for NaN,
548 | 			// it passes because NaN < 0 and NaN > 1 are both false
549 | 			expect(() =>
550 | 				testProvider.validateOptionalParams({ temperature: NaN })
551 | 			).not.toThrow();
552 | 			// This is actually a bug - NaN should be rejected
553 | 			// But we're testing current behavior, not desired behavior
554 | 		});
555 | 
556 | 		it('should handle concurrent calls safely', async () => {
557 | 			mockGenerateText.mockImplementation(async () => ({
558 | 				text: 'response',
559 | 				usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }
560 | 			}));
561 | 
562 | 			const promises = Array.from({ length: 10 }, (_, i) =>
563 | 				testProvider.generateText({
564 | 					apiKey: 'key',
565 | 					modelId: `model-${i}`,
566 | 					messages: [{ role: 'user', content: `test-${i}` }]
567 | 				})
568 | 			);
569 | 
570 | 			const results = await Promise.all(promises);
571 | 			expect(results).toHaveLength(10);
572 | 			expect(mockClient).toHaveBeenCalledTimes(10);
573 | 		});
574 | 	});
575 | 
576 | 	describe('10. Default Behavior - isRequiredApiKey', () => {
577 | 		it('should return true by default for isRequiredApiKey', () => {
578 | 			expect(testProvider.isRequiredApiKey()).toBe(true);
579 | 		});
580 | 
581 | 		it('should allow override of isRequiredApiKey', () => {
582 | 			class NoAuthProvider extends BaseAIProvider {
583 | 				constructor() {
584 | 					super();
585 | 				}
586 | 				isRequiredApiKey() {
587 | 					return false;
588 | 				}
589 | 				validateAuth() {
590 | 					// Override to not require API key
591 | 				}
592 | 				getClient() {
593 | 					return mockClient;
594 | 				}
595 | 				getRequiredApiKeyName() {
596 | 					return null;
597 | 				}
598 | 			}
599 | 
600 | 			const provider = new NoAuthProvider();
601 | 			expect(provider.isRequiredApiKey()).toBe(false);
602 | 		});
603 | 	});
604 | 
605 | 	describe('11. Temperature Filtering - CLI vs Standard Providers', () => {
606 | 		const mockStreamText = jest.fn();
607 | 		const mockStreamObject = jest.fn();
608 | 
609 | 		beforeEach(() => {
610 | 			mockStreamText.mockReset();
611 | 			mockStreamObject.mockReset();
612 | 		});
613 | 
614 | 		it('should include temperature in generateText when supported', async () => {
615 | 			testProvider.supportsTemperature = true;
616 | 			mockGenerateText.mockResolvedValue({
617 | 				text: 'response',
618 | 				usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }
619 | 			});
620 | 
621 | 			await testProvider.generateText({
622 | 				apiKey: 'key',
623 | 				modelId: 'model',
624 | 				messages: [{ role: 'user', content: 'test' }],
625 | 				temperature: 0.7
626 | 			});
627 | 
628 | 			expect(mockGenerateText).toHaveBeenCalledWith(
629 | 				expect.objectContaining({ temperature: 0.7 })
630 | 			);
631 | 		});
632 | 
633 | 		it('should exclude temperature in generateText when not supported', async () => {
634 | 			testProvider.supportsTemperature = false;
635 | 			mockGenerateText.mockResolvedValue({
636 | 				text: 'response',
637 | 				usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }
638 | 			});
639 | 
640 | 			await testProvider.generateText({
641 | 				apiKey: 'key',
642 | 				modelId: 'model',
643 | 				messages: [{ role: 'user', content: 'test' }],
644 | 				temperature: 0.7
645 | 			});
646 | 
647 | 			const callArgs = mockGenerateText.mock.calls[0][0];
648 | 			expect(callArgs).not.toHaveProperty('temperature');
649 | 		});
650 | 
651 | 		it('should exclude temperature when undefined even if supported', async () => {
652 | 			testProvider.supportsTemperature = true;
653 | 			mockGenerateText.mockResolvedValue({
654 | 				text: 'response',
655 | 				usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }
656 | 			});
657 | 
658 | 			await testProvider.generateText({
659 | 				apiKey: 'key',
660 | 				modelId: 'model',
661 | 				messages: [{ role: 'user', content: 'test' }],
662 | 				temperature: undefined
663 | 			});
664 | 
665 | 			const callArgs = mockGenerateText.mock.calls[0][0];
666 | 			expect(callArgs).not.toHaveProperty('temperature');
667 | 		});
668 | 	});
669 | });
670 | 
```

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

```javascript
  1 | /**
  2 |  * src/ai-providers/gemini-cli.js
  3 |  *
  4 |  * Implementation for interacting with Gemini models via Gemini CLI
  5 |  * using the ai-sdk-provider-gemini-cli package.
  6 |  */
  7 | 
  8 | import { generateObject, generateText, streamText } from 'ai';
  9 | import { parse } from 'jsonc-parser';
 10 | import { BaseAIProvider } from './base-provider.js';
 11 | import { log } from '../../scripts/modules/utils.js';
 12 | import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';
 13 | 
 14 | export class GeminiCliProvider extends BaseAIProvider {
 15 | 	constructor() {
 16 | 		super();
 17 | 		this.name = 'Gemini CLI';
 18 | 		// Gemini CLI requires explicit JSON schema mode
 19 | 		this.needsExplicitJsonSchema = true;
 20 | 		// Gemini CLI does not support temperature parameter
 21 | 		this.supportsTemperature = false;
 22 | 	}
 23 | 
 24 | 	/**
 25 | 	 * Override validateAuth to handle Gemini CLI authentication options
 26 | 	 * @param {object} params - Parameters to validate
 27 | 	 */
 28 | 	validateAuth(params) {
 29 | 		// Gemini CLI is designed to use pre-configured OAuth authentication
 30 | 		// Users choose gemini-cli specifically to leverage their existing
 31 | 		// gemini auth login credentials, not to use API keys.
 32 | 		// We support API keys for compatibility, but the expected usage
 33 | 		// is through CLI authentication (no API key required).
 34 | 		// No validation needed - the SDK will handle auth internally
 35 | 	}
 36 | 
 37 | 	/**
 38 | 	 * Creates and returns a Gemini CLI client instance.
 39 | 	 * @param {object} params - Parameters for client initialization
 40 | 	 * @param {string} [params.apiKey] - Optional Gemini API key (rarely used with gemini-cli)
 41 | 	 * @param {string} [params.baseURL] - Optional custom API endpoint
 42 | 	 * @returns {Promise<Function>} Gemini CLI client function
 43 | 	 * @throws {Error} If initialization fails
 44 | 	 */
 45 | 	async getClient(params) {
 46 | 		try {
 47 | 			// Primary use case: Use existing gemini CLI authentication
 48 | 			// Secondary use case: Direct API key (for compatibility)
 49 | 			let authOptions = {};
 50 | 
 51 | 			if (params.apiKey && params.apiKey !== 'gemini-cli-no-key-required') {
 52 | 				// API key provided - use it for compatibility
 53 | 				authOptions = {
 54 | 					authType: 'api-key',
 55 | 					apiKey: params.apiKey
 56 | 				};
 57 | 			} else {
 58 | 				// Expected case: Use gemini CLI authentication via OAuth
 59 | 				authOptions = {
 60 | 					authType: 'oauth-personal'
 61 | 				};
 62 | 			}
 63 | 
 64 | 			// Add baseURL if provided (for custom endpoints)
 65 | 			if (params.baseURL) {
 66 | 				authOptions.baseURL = params.baseURL;
 67 | 			}
 68 | 
 69 | 			// Create and return the provider
 70 | 			return createGeminiProvider(authOptions);
 71 | 		} catch (error) {
 72 | 			this.handleError('client initialization', error);
 73 | 		}
 74 | 	}
 75 | 
 76 | 	/**
 77 | 	 * Extracts system messages from the messages array and returns them separately.
 78 | 	 * This is needed because ai-sdk-provider-gemini-cli expects system prompts as a separate parameter.
 79 | 	 * @param {Array} messages - Array of message objects
 80 | 	 * @param {Object} options - Options for system prompt enhancement
 81 | 	 * @param {boolean} options.enforceJsonOutput - Whether to add JSON enforcement to system prompt
 82 | 	 * @returns {Object} - {systemPrompt: string|undefined, messages: Array}
 83 | 	 */
 84 | 	_extractSystemMessage(messages, options = {}) {
 85 | 		if (!messages || !Array.isArray(messages)) {
 86 | 			return { systemPrompt: undefined, messages: messages || [] };
 87 | 		}
 88 | 
 89 | 		const systemMessages = messages.filter((msg) => msg.role === 'system');
 90 | 		const nonSystemMessages = messages.filter((msg) => msg.role !== 'system');
 91 | 
 92 | 		// Combine multiple system messages if present
 93 | 		let systemPrompt =
 94 | 			systemMessages.length > 0
 95 | 				? systemMessages.map((msg) => msg.content).join('\n\n')
 96 | 				: undefined;
 97 | 
 98 | 		// Add Gemini CLI specific JSON enforcement if requested
 99 | 		if (options.enforceJsonOutput) {
100 | 			const jsonEnforcement = this._getJsonEnforcementPrompt();
101 | 			systemPrompt = systemPrompt
102 | 				? `${systemPrompt}\n\n${jsonEnforcement}`
103 | 				: jsonEnforcement;
104 | 		}
105 | 
106 | 		return { systemPrompt, messages: nonSystemMessages };
107 | 	}
108 | 
109 | 	/**
110 | 	 * Gets a Gemini CLI specific system prompt to enforce strict JSON output
111 | 	 * @returns {string} JSON enforcement system prompt
112 | 	 */
113 | 	_getJsonEnforcementPrompt() {
114 | 		return `CRITICAL: You MUST respond with ONLY valid JSON. Do not include any explanatory text, markdown formatting, code block markers, or conversational phrases like "Here is" or "Of course". Your entire response must be parseable JSON that starts with { or [ and ends with } or ]. No exceptions.`;
115 | 	}
116 | 
117 | 	/**
118 | 	 * Checks if a string is valid JSON
119 | 	 * @param {string} text - Text to validate
120 | 	 * @returns {boolean} True if valid JSON
121 | 	 */
122 | 	_isValidJson(text) {
123 | 		if (!text || typeof text !== 'string') {
124 | 			return false;
125 | 		}
126 | 
127 | 		try {
128 | 			JSON.parse(text.trim());
129 | 			return true;
130 | 		} catch {
131 | 			return false;
132 | 		}
133 | 	}
134 | 
135 | 	/**
136 | 	 * Detects if the user prompt is requesting JSON output
137 | 	 * @param {Array} messages - Array of message objects
138 | 	 * @returns {boolean} True if JSON output is likely expected
139 | 	 */
140 | 	_detectJsonRequest(messages) {
141 | 		const userMessages = messages.filter((msg) => msg.role === 'user');
142 | 		const combinedText = userMessages
143 | 			.map((msg) => msg.content)
144 | 			.join(' ')
145 | 			.toLowerCase();
146 | 
147 | 		// Look for indicators that JSON output is expected
148 | 		const jsonIndicators = [
149 | 			'json',
150 | 			'respond only with',
151 | 			'return only',
152 | 			'output only',
153 | 			'format:',
154 | 			'structure:',
155 | 			'schema:',
156 | 			'{"',
157 | 			'[{',
158 | 			'subtasks',
159 | 			'array',
160 | 			'object'
161 | 		];
162 | 
163 | 		return jsonIndicators.some((indicator) => combinedText.includes(indicator));
164 | 	}
165 | 
166 | 	/**
167 | 	 * Simplifies complex prompts for gemini-cli to improve JSON output compliance
168 | 	 * @param {Array} messages - Array of message objects
169 | 	 * @returns {Array} Simplified messages array
170 | 	 */
171 | 	_simplifyJsonPrompts(messages) {
172 | 		// First, check if this is an expand-task operation by looking at the system message
173 | 		const systemMsg = messages.find((m) => m.role === 'system');
174 | 		const isExpandTask =
175 | 			systemMsg &&
176 | 			systemMsg.content.includes(
177 | 				'You are an AI assistant helping with task breakdown. Generate exactly'
178 | 			);
179 | 
180 | 		if (!isExpandTask) {
181 | 			return messages; // Not an expand task, return unchanged
182 | 		}
183 | 
184 | 		// Extract subtask count from system message
185 | 		const subtaskCountMatch = systemMsg.content.match(
186 | 			/Generate exactly (\d+) subtasks/
187 | 		);
188 | 		const subtaskCount = subtaskCountMatch ? subtaskCountMatch[1] : '10';
189 | 
190 | 		log(
191 | 			'debug',
192 | 			`${this.name} detected expand-task operation, simplifying for ${subtaskCount} subtasks`
193 | 		);
194 | 
195 | 		return messages.map((msg) => {
196 | 			if (msg.role !== 'user') {
197 | 				return msg;
198 | 			}
199 | 
200 | 			// For expand-task user messages, create a much simpler, more direct prompt
201 | 			// that doesn't depend on specific task content
202 | 			const simplifiedPrompt = `Generate exactly ${subtaskCount} subtasks in the following JSON format.
203 | 
204 | CRITICAL INSTRUCTION: You must respond with ONLY valid JSON. No explanatory text, no "Here is", no "Of course", no markdown - just the JSON object.
205 | 
206 | Required JSON structure:
207 | {
208 |   "subtasks": [
209 |     {
210 |       "id": 1,
211 |       "title": "Specific actionable task title",
212 |       "description": "Clear task description",
213 |       "dependencies": [],
214 |       "details": "Implementation details and guidance",
215 |       "testStrategy": "Testing approach"
216 |     }
217 |   ]
218 | }
219 | 
220 | Generate ${subtaskCount} subtasks based on the original task context. Return ONLY the JSON object.`;
221 | 
222 | 			log(
223 | 				'debug',
224 | 				`${this.name} simplified user prompt for better JSON compliance`
225 | 			);
226 | 			return { ...msg, content: simplifiedPrompt };
227 | 		});
228 | 	}
229 | 
230 | 	/**
231 | 	 * Extract JSON from Gemini's response using a tolerant parser.
232 | 	 *
233 | 	 * Optimized approach that progressively tries different parsing strategies:
234 | 	 * 1. Direct parsing after cleanup
235 | 	 * 2. Smart boundary detection with single-pass analysis
236 | 	 * 3. Limited character-by-character fallback for edge cases
237 | 	 *
238 | 	 * @param {string} text - Raw text which may contain JSON
239 | 	 * @returns {string} A valid JSON string if extraction succeeds, otherwise the original text
240 | 	 */
241 | 	extractJson(text) {
242 | 		if (!text || typeof text !== 'string') {
243 | 			return text;
244 | 		}
245 | 
246 | 		let content = text.trim();
247 | 
248 | 		// Early exit for very short content
249 | 		if (content.length < 2) {
250 | 			return text;
251 | 		}
252 | 
253 | 		// Strip common wrappers in a single pass
254 | 		content = content
255 | 			// Remove markdown fences
256 | 			.replace(/^.*?```(?:json)?\s*([\s\S]*?)\s*```.*$/i, '$1')
257 | 			// Remove variable declarations
258 | 			.replace(/^\s*(?:const|let|var)\s+\w+\s*=\s*([\s\S]*?)(?:;|\s*)$/i, '$1')
259 | 			// Remove common prefixes
260 | 			.replace(/^(?:Here's|The)\s+(?:the\s+)?JSON.*?[:]\s*/i, '')
261 | 			.trim();
262 | 
263 | 		// Find the first JSON-like structure
264 | 		const firstObj = content.indexOf('{');
265 | 		const firstArr = content.indexOf('[');
266 | 
267 | 		if (firstObj === -1 && firstArr === -1) {
268 | 			return text;
269 | 		}
270 | 
271 | 		const start =
272 | 			firstArr === -1
273 | 				? firstObj
274 | 				: firstObj === -1
275 | 					? firstArr
276 | 					: Math.min(firstObj, firstArr);
277 | 		content = content.slice(start);
278 | 
279 | 		// Optimized parsing function with error collection
280 | 		const tryParse = (value) => {
281 | 			if (!value || value.length < 2) return undefined;
282 | 
283 | 			const errors = [];
284 | 			try {
285 | 				const result = parse(value, errors, {
286 | 					allowTrailingComma: true,
287 | 					allowEmptyContent: false
288 | 				});
289 | 				if (errors.length === 0 && result !== undefined) {
290 | 					return JSON.stringify(result, null, 2);
291 | 				}
292 | 			} catch {
293 | 				// Parsing failed completely
294 | 			}
295 | 			return undefined;
296 | 		};
297 | 
298 | 		// Try parsing the full content first
299 | 		const fullParse = tryParse(content);
300 | 		if (fullParse !== undefined) {
301 | 			return fullParse;
302 | 		}
303 | 
304 | 		// Smart boundary detection - single pass with optimizations
305 | 		const openChar = content[0];
306 | 		const closeChar = openChar === '{' ? '}' : ']';
307 | 
308 | 		let depth = 0;
309 | 		let inString = false;
310 | 		let escapeNext = false;
311 | 		let lastValidEnd = -1;
312 | 
313 | 		// Single-pass boundary detection with early termination
314 | 		for (let i = 0; i < content.length && i < 10000; i++) {
315 | 			// Limit scan for performance
316 | 			const char = content[i];
317 | 
318 | 			if (escapeNext) {
319 | 				escapeNext = false;
320 | 				continue;
321 | 			}
322 | 
323 | 			if (char === '\\') {
324 | 				escapeNext = true;
325 | 				continue;
326 | 			}
327 | 
328 | 			if (char === '"') {
329 | 				inString = !inString;
330 | 				continue;
331 | 			}
332 | 
333 | 			if (inString) continue;
334 | 
335 | 			if (char === openChar) {
336 | 				depth++;
337 | 			} else if (char === closeChar) {
338 | 				depth--;
339 | 				if (depth === 0) {
340 | 					lastValidEnd = i + 1;
341 | 					// Try parsing immediately on first valid boundary
342 | 					const candidate = content.slice(0, lastValidEnd);
343 | 					const parsed = tryParse(candidate);
344 | 					if (parsed !== undefined) {
345 | 						return parsed;
346 | 					}
347 | 				}
348 | 			}
349 | 		}
350 | 
351 | 		// If we found valid boundaries but parsing failed, try limited fallback
352 | 		if (lastValidEnd > 0) {
353 | 			const maxAttempts = Math.min(5, Math.floor(lastValidEnd / 100)); // Limit attempts
354 | 			for (let i = 0; i < maxAttempts; i++) {
355 | 				const testEnd = Math.max(
356 | 					lastValidEnd - i * 50,
357 | 					Math.floor(lastValidEnd * 0.8)
358 | 				);
359 | 				const candidate = content.slice(0, testEnd);
360 | 				const parsed = tryParse(candidate);
361 | 				if (parsed !== undefined) {
362 | 					return parsed;
363 | 				}
364 | 			}
365 | 		}
366 | 
367 | 		return text;
368 | 	}
369 | 
370 | 	/**
371 | 	 * Generates text using Gemini CLI model
372 | 	 * Overrides base implementation to properly handle system messages and enforce JSON output when needed
373 | 	 */
374 | 	async generateText(params) {
375 | 		try {
376 | 			this.validateParams(params);
377 | 			this.validateMessages(params.messages);
378 | 
379 | 			log(
380 | 				'debug',
381 | 				`Generating ${this.name} text with model: ${params.modelId}`
382 | 			);
383 | 
384 | 			// Detect if JSON output is expected and enforce it for better gemini-cli compatibility
385 | 			const enforceJsonOutput = this._detectJsonRequest(params.messages);
386 | 
387 | 			// Debug logging to understand what's happening
388 | 			log('debug', `${this.name} JSON detection analysis:`, {
389 | 				enforceJsonOutput,
390 | 				messageCount: params.messages.length,
391 | 				messages: params.messages.map((msg) => ({
392 | 					role: msg.role,
393 | 					contentPreview: msg.content
394 | 						? msg.content.substring(0, 200) + '...'
395 | 						: 'empty'
396 | 				}))
397 | 			});
398 | 
399 | 			if (enforceJsonOutput) {
400 | 				log(
401 | 					'debug',
402 | 					`${this.name} detected JSON request - applying strict JSON enforcement system prompt`
403 | 				);
404 | 			}
405 | 
406 | 			// For gemini-cli, simplify complex prompts before processing
407 | 			let processedMessages = params.messages;
408 | 			if (enforceJsonOutput) {
409 | 				processedMessages = this._simplifyJsonPrompts(params.messages);
410 | 			}
411 | 
412 | 			// Extract system messages for separate handling with optional JSON enforcement
413 | 			const { systemPrompt, messages } = this._extractSystemMessage(
414 | 				processedMessages,
415 | 				{ enforceJsonOutput }
416 | 			);
417 | 
418 | 			// Debug the final system prompt being sent
419 | 			log('debug', `${this.name} final system prompt:`, {
420 | 				systemPromptLength: systemPrompt ? systemPrompt.length : 0,
421 | 				systemPromptPreview: systemPrompt
422 | 					? systemPrompt.substring(0, 300) + '...'
423 | 					: 'none',
424 | 				finalMessageCount: messages.length
425 | 			});
426 | 
427 | 			const client = await this.getClient(params);
428 | 			const result = await generateText({
429 | 				model: client(params.modelId),
430 | 				system: systemPrompt,
431 | 				messages: messages,
432 | 				maxOutputTokens: params.maxTokens,
433 | 				temperature: params.temperature
434 | 			});
435 | 
436 | 			// If we detected a JSON request and gemini-cli returned conversational text,
437 | 			// attempt to extract JSON from the response
438 | 			let finalText = result.text;
439 | 			if (enforceJsonOutput && result.text && !this._isValidJson(result.text)) {
440 | 				log(
441 | 					'debug',
442 | 					`${this.name} response appears conversational, attempting JSON extraction`
443 | 				);
444 | 
445 | 				// Log first 1000 chars of the response to see what Gemini actually returned
446 | 				log('debug', `${this.name} raw response preview:`, {
447 | 					responseLength: result.text.length,
448 | 					responseStart: result.text.substring(0, 1000)
449 | 				});
450 | 
451 | 				const extractedJson = this.extractJson(result.text);
452 | 				if (this._isValidJson(extractedJson)) {
453 | 					log(
454 | 						'debug',
455 | 						`${this.name} successfully extracted JSON from conversational response`
456 | 					);
457 | 					finalText = extractedJson;
458 | 				} else {
459 | 					log(
460 | 						'debug',
461 | 						`${this.name} JSON extraction failed, returning original response`
462 | 					);
463 | 
464 | 					// Log what extraction returned to debug why it failed
465 | 					log('debug', `${this.name} extraction result preview:`, {
466 | 						extractedLength: extractedJson ? extractedJson.length : 0,
467 | 						extractedStart: extractedJson
468 | 							? extractedJson.substring(0, 500)
469 | 							: 'null'
470 | 					});
471 | 				}
472 | 			}
473 | 
474 | 			log(
475 | 				'debug',
476 | 				`${this.name} generateText completed successfully for model: ${params.modelId}`
477 | 			);
478 | 
479 | 			return {
480 | 				text: finalText,
481 | 				usage: {
482 | 					inputTokens: result.usage?.promptTokens,
483 | 					outputTokens: result.usage?.completionTokens,
484 | 					totalTokens: result.usage?.totalTokens
485 | 				}
486 | 			};
487 | 		} catch (error) {
488 | 			this.handleError('text generation', error);
489 | 		}
490 | 	}
491 | 
492 | 	/**
493 | 	 * Streams text using Gemini CLI model
494 | 	 * Overrides base implementation to properly handle system messages and enforce JSON output when needed
495 | 	 */
496 | 	async streamText(params) {
497 | 		try {
498 | 			this.validateParams(params);
499 | 			this.validateMessages(params.messages);
500 | 
501 | 			log('debug', `Streaming ${this.name} text with model: ${params.modelId}`);
502 | 
503 | 			// Detect if JSON output is expected and enforce it for better gemini-cli compatibility
504 | 			const enforceJsonOutput = this._detectJsonRequest(params.messages);
505 | 
506 | 			// Debug logging to understand what's happening
507 | 			log('debug', `${this.name} JSON detection analysis:`, {
508 | 				enforceJsonOutput,
509 | 				messageCount: params.messages.length,
510 | 				messages: params.messages.map((msg) => ({
511 | 					role: msg.role,
512 | 					contentPreview: msg.content
513 | 						? msg.content.substring(0, 200) + '...'
514 | 						: 'empty'
515 | 				}))
516 | 			});
517 | 
518 | 			if (enforceJsonOutput) {
519 | 				log(
520 | 					'debug',
521 | 					`${this.name} detected JSON request - applying strict JSON enforcement system prompt`
522 | 				);
523 | 			}
524 | 
525 | 			// Extract system messages for separate handling with optional JSON enforcement
526 | 			const { systemPrompt, messages } = this._extractSystemMessage(
527 | 				params.messages,
528 | 				{ enforceJsonOutput }
529 | 			);
530 | 
531 | 			const client = await this.getClient(params);
532 | 			const stream = await streamText({
533 | 				model: client(params.modelId),
534 | 				system: systemPrompt,
535 | 				messages: messages,
536 | 				maxOutputTokens: params.maxTokens,
537 | 				temperature: params.temperature
538 | 			});
539 | 
540 | 			log(
541 | 				'debug',
542 | 				`${this.name} streamText initiated successfully for model: ${params.modelId}`
543 | 			);
544 | 
545 | 			// Note: For streaming, we can't intercept and modify the response in real-time
546 | 			// The JSON extraction would need to happen on the consuming side
547 | 			return stream;
548 | 		} catch (error) {
549 | 			this.handleError('text streaming', error);
550 | 		}
551 | 	}
552 | 
553 | 	/**
554 | 	 * Generates a structured object using Gemini CLI model
555 | 	 * Overrides base implementation to handle Gemini-specific JSON formatting issues and system messages
556 | 	 */
557 | 	async generateObject(params) {
558 | 		try {
559 | 			// First try the standard generateObject from base class
560 | 			return await super.generateObject(params);
561 | 		} catch (error) {
562 | 			// If it's a JSON parsing error, try to extract and parse JSON manually
563 | 			if (error.message?.includes('JSON') || error.message?.includes('parse')) {
564 | 				log(
565 | 					'debug',
566 | 					`Gemini CLI generateObject failed with parsing error, attempting manual extraction`
567 | 				);
568 | 
569 | 				try {
570 | 					// Validate params first
571 | 					this.validateParams(params);
572 | 					this.validateMessages(params.messages);
573 | 
574 | 					if (!params.schema) {
575 | 						throw new Error('Schema is required for object generation');
576 | 					}
577 | 					if (!params.objectName) {
578 | 						throw new Error('Object name is required for object generation');
579 | 					}
580 | 
581 | 					// Extract system messages for separate handling with JSON enforcement
582 | 					const { systemPrompt, messages } = this._extractSystemMessage(
583 | 						params.messages,
584 | 						{ enforceJsonOutput: true }
585 | 					);
586 | 
587 | 					// Call generateObject directly with our client
588 | 					const client = await this.getClient(params);
589 | 					const result = await generateObject({
590 | 						model: client(params.modelId),
591 | 						system: systemPrompt,
592 | 						messages: messages,
593 | 						schema: params.schema,
594 | 						mode: this.needsExplicitJsonSchema ? 'json' : 'auto',
595 | 						maxOutputTokens: params.maxTokens,
596 | 						temperature: params.temperature
597 | 					});
598 | 
599 | 					// If we get rawResponse text, try to extract JSON from it
600 | 					if (result.rawResponse?.text && !result.object) {
601 | 						const extractedJson = this.extractJson(result.rawResponse.text);
602 | 						try {
603 | 							result.object = JSON.parse(extractedJson);
604 | 						} catch (parseError) {
605 | 							log(
606 | 								'error',
607 | 								`Failed to parse extracted JSON: ${parseError.message}`
608 | 							);
609 | 							log(
610 | 								'debug',
611 | 								`Extracted JSON: ${extractedJson.substring(0, 500)}...`
612 | 							);
613 | 							throw new Error(
614 | 								`Gemini CLI returned invalid JSON that could not be parsed: ${parseError.message}`
615 | 							);
616 | 						}
617 | 					}
618 | 
619 | 					return {
620 | 						object: result.object,
621 | 						usage: {
622 | 							inputTokens: result.usage?.promptTokens,
623 | 							outputTokens: result.usage?.completionTokens,
624 | 							totalTokens: result.usage?.totalTokens
625 | 						}
626 | 					};
627 | 				} catch (retryError) {
628 | 					log(
629 | 						'error',
630 | 						`Gemini CLI manual JSON extraction failed: ${retryError.message}`
631 | 					);
632 | 					// Re-throw the original error with more context
633 | 					throw new Error(
634 | 						`${this.name} failed to generate valid JSON object: ${error.message}`
635 | 					);
636 | 				}
637 | 			}
638 | 
639 | 			// For non-parsing errors, just re-throw
640 | 			throw error;
641 | 		}
642 | 	}
643 | 
644 | 	getRequiredApiKeyName() {
645 | 		return 'GEMINI_API_KEY';
646 | 	}
647 | 
648 | 	isRequiredApiKey() {
649 | 		return false;
650 | 	}
651 | }
652 | 
```

--------------------------------------------------------------------------------
/tests/integration/move-task-cross-tag.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 | 
  6 | const __filename = fileURLToPath(import.meta.url);
  7 | const __dirname = path.dirname(__filename);
  8 | 
  9 | // Mock dependencies before importing
 10 | const mockUtils = {
 11 | 	readJSON: jest.fn(),
 12 | 	writeJSON: jest.fn(),
 13 | 	findProjectRoot: jest.fn(() => '/test/project/root'),
 14 | 	log: jest.fn(),
 15 | 	setTasksForTag: jest.fn(),
 16 | 	traverseDependencies: jest.fn((sourceTasks, allTasks, options = {}) => {
 17 | 		// Mock realistic dependency behavior for testing
 18 | 		const { direction = 'forward' } = options;
 19 | 
 20 | 		if (direction === 'forward') {
 21 | 			// Return dependencies that tasks have
 22 | 			const result = [];
 23 | 			sourceTasks.forEach((task) => {
 24 | 				if (task.dependencies && Array.isArray(task.dependencies)) {
 25 | 					result.push(...task.dependencies);
 26 | 				}
 27 | 			});
 28 | 			return result;
 29 | 		} else if (direction === 'reverse') {
 30 | 			// Return tasks that depend on the source tasks
 31 | 			const sourceIds = sourceTasks.map((t) => t.id);
 32 | 			const normalizedSourceIds = sourceIds.map((id) => String(id));
 33 | 			const result = [];
 34 | 			allTasks.forEach((task) => {
 35 | 				if (task.dependencies && Array.isArray(task.dependencies)) {
 36 | 					const hasDependency = task.dependencies.some((depId) =>
 37 | 						normalizedSourceIds.includes(String(depId))
 38 | 					);
 39 | 					if (hasDependency) {
 40 | 						result.push(task.id);
 41 | 					}
 42 | 				}
 43 | 			});
 44 | 			return result;
 45 | 		}
 46 | 		return [];
 47 | 	})
 48 | };
 49 | 
 50 | // Mock the utils module
 51 | jest.unstable_mockModule('../../scripts/modules/utils.js', () => mockUtils);
 52 | 
 53 | // Mock other dependencies
 54 | jest.unstable_mockModule(
 55 | 	'../../scripts/modules/task-manager/is-task-dependent.js',
 56 | 	() => ({
 57 | 		default: jest.fn(() => false)
 58 | 	})
 59 | );
 60 | 
 61 | jest.unstable_mockModule('../../scripts/modules/dependency-manager.js', () => ({
 62 | 	findCrossTagDependencies: jest.fn(() => {
 63 | 		// Since dependencies can only exist within the same tag,
 64 | 		// this function should never find any cross-tag conflicts
 65 | 		return [];
 66 | 	}),
 67 | 	getDependentTaskIds: jest.fn(
 68 | 		(sourceTasks, crossTagDependencies, allTasks) => {
 69 | 			// Since we now use findAllDependenciesRecursively in the actual implementation,
 70 | 			// this mock simulates finding all dependencies recursively within the same tag
 71 | 			const dependentIds = new Set();
 72 | 			const processedIds = new Set();
 73 | 
 74 | 			function findAllDependencies(taskId) {
 75 | 				if (processedIds.has(taskId)) return;
 76 | 				processedIds.add(taskId);
 77 | 
 78 | 				const task = allTasks.find((t) => t.id === taskId);
 79 | 				if (!task || !Array.isArray(task.dependencies)) return;
 80 | 
 81 | 				task.dependencies.forEach((depId) => {
 82 | 					const normalizedDepId =
 83 | 						typeof depId === 'string' ? parseInt(depId, 10) : depId;
 84 | 					if (!isNaN(normalizedDepId) && normalizedDepId !== taskId) {
 85 | 						dependentIds.add(normalizedDepId);
 86 | 						findAllDependencies(normalizedDepId);
 87 | 					}
 88 | 				});
 89 | 			}
 90 | 
 91 | 			sourceTasks.forEach((sourceTask) => {
 92 | 				if (sourceTask && sourceTask.id) {
 93 | 					findAllDependencies(sourceTask.id);
 94 | 				}
 95 | 			});
 96 | 
 97 | 			return Array.from(dependentIds);
 98 | 		}
 99 | 	),
100 | 	validateSubtaskMove: jest.fn((taskId, sourceTag, targetTag) => {
101 | 		// Throw error for subtask IDs
102 | 		const taskIdStr = String(taskId);
103 | 		if (taskIdStr.includes('.')) {
104 | 			throw new Error('Cannot move subtasks directly between tags');
105 | 		}
106 | 	})
107 | }));
108 | 
109 | jest.unstable_mockModule(
110 | 	'../../scripts/modules/task-manager/generate-task-files.js',
111 | 	() => ({
112 | 		default: jest.fn().mockResolvedValue()
113 | 	})
114 | );
115 | 
116 | // Import the modules we'll be testing after mocking
117 | const { moveTasksBetweenTags } = await import(
118 | 	'../../scripts/modules/task-manager/move-task.js'
119 | );
120 | 
121 | describe('Cross-Tag Task Movement Integration Tests', () => {
122 | 	let testDataPath;
123 | 	let mockTasksData;
124 | 
125 | 	beforeEach(() => {
126 | 		// Setup test data path
127 | 		testDataPath = path.join(__dirname, 'temp-test-tasks.json');
128 | 
129 | 		// Initialize mock data with multiple tags
130 | 		mockTasksData = {
131 | 			backlog: {
132 | 				tasks: [
133 | 					{
134 | 						id: 1,
135 | 						title: 'Backlog Task 1',
136 | 						description: 'A task in backlog',
137 | 						status: 'pending',
138 | 						dependencies: [],
139 | 						priority: 'medium',
140 | 						tag: 'backlog'
141 | 					},
142 | 					{
143 | 						id: 2,
144 | 						title: 'Backlog Task 2',
145 | 						description: 'Another task in backlog',
146 | 						status: 'pending',
147 | 						dependencies: [1],
148 | 						priority: 'high',
149 | 						tag: 'backlog'
150 | 					},
151 | 					{
152 | 						id: 3,
153 | 						title: 'Backlog Task 3',
154 | 						description: 'Independent task',
155 | 						status: 'pending',
156 | 						dependencies: [],
157 | 						priority: 'low',
158 | 						tag: 'backlog'
159 | 					}
160 | 				]
161 | 			},
162 | 			'in-progress': {
163 | 				tasks: [
164 | 					{
165 | 						id: 4,
166 | 						title: 'In Progress Task 1',
167 | 						description: 'A task being worked on',
168 | 						status: 'in-progress',
169 | 						dependencies: [],
170 | 						priority: 'high',
171 | 						tag: 'in-progress'
172 | 					}
173 | 				]
174 | 			},
175 | 			done: {
176 | 				tasks: [
177 | 					{
178 | 						id: 5,
179 | 						title: 'Completed Task 1',
180 | 						description: 'A completed task',
181 | 						status: 'done',
182 | 						dependencies: [],
183 | 						priority: 'medium',
184 | 						tag: 'done'
185 | 					}
186 | 				]
187 | 			}
188 | 		};
189 | 
190 | 		// Setup mock utils
191 | 		mockUtils.readJSON.mockReturnValue(mockTasksData);
192 | 		mockUtils.writeJSON.mockImplementation((path, data, projectRoot, tag) => {
193 | 			// Simulate writing to file
194 | 			return Promise.resolve();
195 | 		});
196 | 	});
197 | 
198 | 	afterEach(() => {
199 | 		jest.clearAllMocks();
200 | 		// Clean up temp file if it exists
201 | 		if (fs.existsSync(testDataPath)) {
202 | 			fs.unlinkSync(testDataPath);
203 | 		}
204 | 	});
205 | 
206 | 	describe('Basic Cross-Tag Movement', () => {
207 | 		it('should move a single task between tags successfully', async () => {
208 | 			const taskIds = [1];
209 | 			const sourceTag = 'backlog';
210 | 			const targetTag = 'in-progress';
211 | 
212 | 			const result = await moveTasksBetweenTags(
213 | 				testDataPath,
214 | 				taskIds,
215 | 				sourceTag,
216 | 				targetTag,
217 | 				{},
218 | 				{ projectRoot: '/test/project' }
219 | 			);
220 | 
221 | 			// Verify readJSON was called with correct parameters
222 | 			expect(mockUtils.readJSON).toHaveBeenCalledWith(
223 | 				testDataPath,
224 | 				'/test/project',
225 | 				sourceTag
226 | 			);
227 | 
228 | 			// Verify writeJSON was called with updated data
229 | 			expect(mockUtils.writeJSON).toHaveBeenCalledWith(
230 | 				testDataPath,
231 | 				expect.objectContaining({
232 | 					backlog: expect.objectContaining({
233 | 						tasks: expect.arrayContaining([
234 | 							expect.objectContaining({ id: 2 }),
235 | 							expect.objectContaining({ id: 3 })
236 | 						])
237 | 					}),
238 | 					'in-progress': expect.objectContaining({
239 | 						tasks: expect.arrayContaining([
240 | 							expect.objectContaining({ id: 4 }),
241 | 							expect.objectContaining({
242 | 								id: 1,
243 | 								tag: 'in-progress'
244 | 							})
245 | 						])
246 | 					})
247 | 				}),
248 | 				'/test/project',
249 | 				null
250 | 			);
251 | 
252 | 			// Verify result structure
253 | 			expect(result).toEqual({
254 | 				message: 'Successfully moved 1 tasks from "backlog" to "in-progress"',
255 | 				movedTasks: [
256 | 					{
257 | 						id: 1,
258 | 						fromTag: 'backlog',
259 | 						toTag: 'in-progress'
260 | 					}
261 | 				]
262 | 			});
263 | 		});
264 | 
265 | 		it('should move multiple tasks between tags', async () => {
266 | 			const taskIds = [1, 3];
267 | 			const sourceTag = 'backlog';
268 | 			const targetTag = 'done';
269 | 
270 | 			const result = await moveTasksBetweenTags(
271 | 				testDataPath,
272 | 				taskIds,
273 | 				sourceTag,
274 | 				targetTag,
275 | 				{},
276 | 				{ projectRoot: '/test/project' }
277 | 			);
278 | 
279 | 			// Verify the moved tasks are in the target tag
280 | 			expect(mockUtils.writeJSON).toHaveBeenCalledWith(
281 | 				testDataPath,
282 | 				expect.objectContaining({
283 | 					backlog: expect.objectContaining({
284 | 						tasks: expect.arrayContaining([expect.objectContaining({ id: 2 })])
285 | 					}),
286 | 					done: expect.objectContaining({
287 | 						tasks: expect.arrayContaining([
288 | 							expect.objectContaining({ id: 5 }),
289 | 							expect.objectContaining({
290 | 								id: 1,
291 | 								tag: 'done'
292 | 							}),
293 | 							expect.objectContaining({
294 | 								id: 3,
295 | 								tag: 'done'
296 | 							})
297 | 						])
298 | 					})
299 | 				}),
300 | 				'/test/project',
301 | 				null
302 | 			);
303 | 
304 | 			// Verify result structure
305 | 			expect(result.movedTasks).toHaveLength(2);
306 | 			expect(result.movedTasks).toEqual(
307 | 				expect.arrayContaining([
308 | 					{ id: 1, fromTag: 'backlog', toTag: 'done' },
309 | 					{ id: 3, fromTag: 'backlog', toTag: 'done' }
310 | 				])
311 | 			);
312 | 		});
313 | 
314 | 		it('should create target tag if it does not exist', async () => {
315 | 			const taskIds = [1];
316 | 			const sourceTag = 'backlog';
317 | 			const targetTag = 'new-tag';
318 | 
319 | 			const result = await moveTasksBetweenTags(
320 | 				testDataPath,
321 | 				taskIds,
322 | 				sourceTag,
323 | 				targetTag,
324 | 				{},
325 | 				{ projectRoot: '/test/project' }
326 | 			);
327 | 
328 | 			// Verify new tag was created
329 | 			expect(mockUtils.writeJSON).toHaveBeenCalledWith(
330 | 				testDataPath,
331 | 				expect.objectContaining({
332 | 					'new-tag': expect.objectContaining({
333 | 						tasks: expect.arrayContaining([
334 | 							expect.objectContaining({
335 | 								id: 1,
336 | 								tag: 'new-tag'
337 | 							})
338 | 						])
339 | 					})
340 | 				}),
341 | 				'/test/project',
342 | 				null
343 | 			);
344 | 		});
345 | 	});
346 | 
347 | 	describe('Dependency Handling', () => {
348 | 		it('should move task with dependencies when withDependencies is true', async () => {
349 | 			const taskIds = [2]; // Task 2 depends on Task 1
350 | 			const sourceTag = 'backlog';
351 | 			const targetTag = 'in-progress';
352 | 
353 | 			const result = await moveTasksBetweenTags(
354 | 				testDataPath,
355 | 				taskIds,
356 | 				sourceTag,
357 | 				targetTag,
358 | 				{ withDependencies: true },
359 | 				{ projectRoot: '/test/project' }
360 | 			);
361 | 
362 | 			// Verify both task 2 and its dependency (task 1) were moved
363 | 			expect(mockUtils.writeJSON).toHaveBeenCalledWith(
364 | 				testDataPath,
365 | 				expect.objectContaining({
366 | 					backlog: expect.objectContaining({
367 | 						tasks: expect.arrayContaining([expect.objectContaining({ id: 3 })])
368 | 					}),
369 | 					'in-progress': expect.objectContaining({
370 | 						tasks: expect.arrayContaining([
371 | 							expect.objectContaining({ id: 4 }),
372 | 							expect.objectContaining({
373 | 								id: 1,
374 | 								tag: 'in-progress'
375 | 							}),
376 | 							expect.objectContaining({
377 | 								id: 2,
378 | 								tag: 'in-progress'
379 | 							})
380 | 						])
381 | 					})
382 | 				}),
383 | 				'/test/project',
384 | 				null
385 | 			);
386 | 		});
387 | 
388 | 		it('should move task normally when ignoreDependencies is true (no cross-tag conflicts to ignore)', async () => {
389 | 			const taskIds = [2]; // Task 2 depends on Task 1
390 | 			const sourceTag = 'backlog';
391 | 			const targetTag = 'in-progress';
392 | 
393 | 			const result = await moveTasksBetweenTags(
394 | 				testDataPath,
395 | 				taskIds,
396 | 				sourceTag,
397 | 				targetTag,
398 | 				{ ignoreDependencies: true },
399 | 				{ projectRoot: '/test/project' }
400 | 			);
401 | 
402 | 			// Since dependencies only exist within tags, there are no cross-tag conflicts to ignore
403 | 			// Task 2 moves with its dependencies intact
404 | 			expect(mockUtils.writeJSON).toHaveBeenCalledWith(
405 | 				testDataPath,
406 | 				expect.objectContaining({
407 | 					backlog: expect.objectContaining({
408 | 						tasks: expect.arrayContaining([
409 | 							expect.objectContaining({ id: 1 }),
410 | 							expect.objectContaining({ id: 3 })
411 | 						])
412 | 					}),
413 | 					'in-progress': expect.objectContaining({
414 | 						tasks: expect.arrayContaining([
415 | 							expect.objectContaining({ id: 4 }),
416 | 							expect.objectContaining({
417 | 								id: 2,
418 | 								tag: 'in-progress',
419 | 								dependencies: [1] // Dependencies preserved since no cross-tag conflicts
420 | 							})
421 | 						])
422 | 					})
423 | 				}),
424 | 				'/test/project',
425 | 				null
426 | 			);
427 | 		});
428 | 
429 | 		it('should provide advisory tips when ignoreDependencies breaks deps', async () => {
430 | 			// Move a task that has dependencies so cross-tag conflicts would be broken
431 | 			const taskIds = [2]; // backlog:2 depends on 1
432 | 			const sourceTag = 'backlog';
433 | 			const targetTag = 'in-progress';
434 | 
435 | 			// Override cross-tag detection to simulate conflicts for this case
436 | 			const depManager = await import(
437 | 				'../../scripts/modules/dependency-manager.js'
438 | 			);
439 | 			depManager.findCrossTagDependencies.mockReturnValueOnce([
440 | 				{ taskId: 2, dependencyId: 1, dependencyTag: sourceTag }
441 | 			]);
442 | 
443 | 			const result = await moveTasksBetweenTags(
444 | 				testDataPath,
445 | 				taskIds,
446 | 				sourceTag,
447 | 				targetTag,
448 | 				{ ignoreDependencies: true },
449 | 				{ projectRoot: '/test/project' }
450 | 			);
451 | 
452 | 			expect(Array.isArray(result.tips)).toBe(true);
453 | 			const expectedTips = [
454 | 				'Run "task-master validate-dependencies" to check for dependency issues.',
455 | 				'Run "task-master fix-dependencies" to automatically repair dangling dependencies.'
456 | 			];
457 | 			expect(result.tips).toHaveLength(expectedTips.length);
458 | 			expect(result.tips).toEqual(expect.arrayContaining(expectedTips));
459 | 		});
460 | 
461 | 		it('should move task without cross-tag dependency conflicts (since dependencies only exist within tags)', async () => {
462 | 			const taskIds = [2]; // Task 2 depends on Task 1 (both in same tag)
463 | 			const sourceTag = 'backlog';
464 | 			const targetTag = 'in-progress';
465 | 
466 | 			// Since dependencies can only exist within the same tag,
467 | 			// there should be no cross-tag conflicts
468 | 			const result = await moveTasksBetweenTags(
469 | 				testDataPath,
470 | 				taskIds,
471 | 				sourceTag,
472 | 				targetTag,
473 | 				{},
474 | 				{ projectRoot: '/test/project' }
475 | 			);
476 | 
477 | 			// Verify task was moved successfully (without dependencies)
478 | 			expect(mockUtils.writeJSON).toHaveBeenCalledWith(
479 | 				testDataPath,
480 | 				expect.objectContaining({
481 | 					backlog: expect.objectContaining({
482 | 						tasks: expect.arrayContaining([
483 | 							expect.objectContaining({ id: 1 }), // Task 1 stays in backlog
484 | 							expect.objectContaining({ id: 3 })
485 | 						])
486 | 					}),
487 | 					'in-progress': expect.objectContaining({
488 | 						tasks: expect.arrayContaining([
489 | 							expect.objectContaining({ id: 4 }),
490 | 							expect.objectContaining({
491 | 								id: 2,
492 | 								tag: 'in-progress'
493 | 							})
494 | 						])
495 | 					})
496 | 				}),
497 | 				'/test/project',
498 | 				null
499 | 			);
500 | 		});
501 | 	});
502 | 
503 | 	describe('Error Handling', () => {
504 | 		it('should throw error for invalid source tag', async () => {
505 | 			const taskIds = [1];
506 | 			const sourceTag = 'nonexistent-tag';
507 | 			const targetTag = 'in-progress';
508 | 
509 | 			// Mock readJSON to return data without the source tag
510 | 			mockUtils.readJSON.mockReturnValue({
511 | 				'in-progress': { tasks: [] }
512 | 			});
513 | 
514 | 			await expect(
515 | 				moveTasksBetweenTags(
516 | 					testDataPath,
517 | 					taskIds,
518 | 					sourceTag,
519 | 					targetTag,
520 | 					{},
521 | 					{ projectRoot: '/test/project' }
522 | 				)
523 | 			).rejects.toThrow('Source tag "nonexistent-tag" not found or invalid');
524 | 		});
525 | 
526 | 		it('should throw error for invalid task IDs', async () => {
527 | 			const taskIds = [999]; // Non-existent task ID
528 | 			const sourceTag = 'backlog';
529 | 			const targetTag = 'in-progress';
530 | 
531 | 			await expect(
532 | 				moveTasksBetweenTags(
533 | 					testDataPath,
534 | 					taskIds,
535 | 					sourceTag,
536 | 					targetTag,
537 | 					{},
538 | 					{ projectRoot: '/test/project' }
539 | 				)
540 | 			).rejects.toThrow('Task 999 not found in source tag "backlog"');
541 | 		});
542 | 
543 | 		it('should throw error for subtask movement', async () => {
544 | 			const taskIds = ['1.1']; // Subtask ID
545 | 			const sourceTag = 'backlog';
546 | 			const targetTag = 'in-progress';
547 | 
548 | 			await expect(
549 | 				moveTasksBetweenTags(
550 | 					testDataPath,
551 | 					taskIds,
552 | 					sourceTag,
553 | 					targetTag,
554 | 					{},
555 | 					{ projectRoot: '/test/project' }
556 | 				)
557 | 			).rejects.toThrow('Cannot move subtasks directly between tags');
558 | 		});
559 | 
560 | 		it('should handle ID conflicts in target tag', async () => {
561 | 			// Setup data with conflicting IDs
562 | 			const conflictingData = {
563 | 				backlog: {
564 | 					tasks: [
565 | 						{
566 | 							id: 1,
567 | 							title: 'Backlog Task',
568 | 							tag: 'backlog'
569 | 						}
570 | 					]
571 | 				},
572 | 				'in-progress': {
573 | 					tasks: [
574 | 						{
575 | 							id: 1, // Same ID as in backlog
576 | 							title: 'In Progress Task',
577 | 							tag: 'in-progress'
578 | 						}
579 | 					]
580 | 				}
581 | 			};
582 | 
583 | 			mockUtils.readJSON.mockReturnValue(conflictingData);
584 | 
585 | 			const taskIds = [1];
586 | 			const sourceTag = 'backlog';
587 | 			const targetTag = 'in-progress';
588 | 
589 | 			await expect(
590 | 				moveTasksBetweenTags(
591 | 					testDataPath,
592 | 					taskIds,
593 | 					sourceTag,
594 | 					targetTag,
595 | 					{},
596 | 					{ projectRoot: '/test/project' }
597 | 				)
598 | 			).rejects.toThrow('Task 1 already exists in target tag "in-progress"');
599 | 
600 | 			// Validate suggestions on the error payload
601 | 			try {
602 | 				await moveTasksBetweenTags(
603 | 					testDataPath,
604 | 					taskIds,
605 | 					sourceTag,
606 | 					targetTag,
607 | 					{},
608 | 					{ projectRoot: '/test/project' }
609 | 				);
610 | 			} catch (err) {
611 | 				expect(err.code).toBe('TASK_ALREADY_EXISTS');
612 | 				expect(Array.isArray(err.data?.suggestions)).toBe(true);
613 | 				const s = (err.data?.suggestions || []).join(' ');
614 | 				expect(s).toContain('different target tag');
615 | 				expect(s).toContain('different set of IDs');
616 | 				expect(s).toContain('within-tag');
617 | 			}
618 | 		});
619 | 	});
620 | 
621 | 	describe('Edge Cases', () => {
622 | 		it('should handle empty task list in source tag', async () => {
623 | 			const emptyData = {
624 | 				backlog: { tasks: [] },
625 | 				'in-progress': { tasks: [] }
626 | 			};
627 | 
628 | 			mockUtils.readJSON.mockReturnValue(emptyData);
629 | 
630 | 			const taskIds = [1];
631 | 			const sourceTag = 'backlog';
632 | 			const targetTag = 'in-progress';
633 | 
634 | 			await expect(
635 | 				moveTasksBetweenTags(
636 | 					testDataPath,
637 | 					taskIds,
638 | 					sourceTag,
639 | 					targetTag,
640 | 					{},
641 | 					{ projectRoot: '/test/project' }
642 | 				)
643 | 			).rejects.toThrow('Task 1 not found in source tag "backlog"');
644 | 		});
645 | 
646 | 		it('should preserve task metadata during move', async () => {
647 | 			const taskIds = [1];
648 | 			const sourceTag = 'backlog';
649 | 			const targetTag = 'in-progress';
650 | 
651 | 			const result = await moveTasksBetweenTags(
652 | 				testDataPath,
653 | 				taskIds,
654 | 				sourceTag,
655 | 				targetTag,
656 | 				{},
657 | 				{ projectRoot: '/test/project' }
658 | 			);
659 | 
660 | 			// Verify task metadata is preserved
661 | 			expect(mockUtils.writeJSON).toHaveBeenCalledWith(
662 | 				testDataPath,
663 | 				expect.objectContaining({
664 | 					'in-progress': expect.objectContaining({
665 | 						tasks: expect.arrayContaining([
666 | 							expect.objectContaining({
667 | 								id: 1,
668 | 								title: 'Backlog Task 1',
669 | 								description: 'A task in backlog',
670 | 								status: 'pending',
671 | 								priority: 'medium',
672 | 								tag: 'in-progress', // Tag should be updated
673 | 								metadata: expect.objectContaining({
674 | 									moveHistory: expect.arrayContaining([
675 | 										expect.objectContaining({
676 | 											fromTag: 'backlog',
677 | 											toTag: 'in-progress',
678 | 											timestamp: expect.any(String)
679 | 										})
680 | 									])
681 | 								})
682 | 							})
683 | 						])
684 | 					})
685 | 				}),
686 | 				'/test/project',
687 | 				null
688 | 			);
689 | 		});
690 | 
691 | 		// Note: force flag deprecated for cross-tag moves; covered by with/ignore dependencies tests
692 | 	});
693 | 
694 | 	describe('Complex Scenarios', () => {
695 | 		it('should handle complex moves without cross-tag conflicts (dependencies only within tags)', async () => {
696 | 			// Setup data with valid within-tag dependencies
697 | 			const validData = {
698 | 				backlog: {
699 | 					tasks: [
700 | 						{
701 | 							id: 1,
702 | 							title: 'Task 1',
703 | 							dependencies: [], // No dependencies
704 | 							tag: 'backlog'
705 | 						},
706 | 						{
707 | 							id: 3,
708 | 							title: 'Task 3',
709 | 							dependencies: [1], // Depends on Task 1 (same tag)
710 | 							tag: 'backlog'
711 | 						}
712 | 					]
713 | 				},
714 | 				'in-progress': {
715 | 					tasks: [
716 | 						{
717 | 							id: 2,
718 | 							title: 'Task 2',
719 | 							dependencies: [], // No dependencies
720 | 							tag: 'in-progress'
721 | 						}
722 | 					]
723 | 				}
724 | 			};
725 | 
726 | 			mockUtils.readJSON.mockReturnValue(validData);
727 | 
728 | 			const taskIds = [3];
729 | 			const sourceTag = 'backlog';
730 | 			const targetTag = 'in-progress';
731 | 
732 | 			// Should succeed since there are no cross-tag conflicts
733 | 			const result = await moveTasksBetweenTags(
734 | 				testDataPath,
735 | 				taskIds,
736 | 				sourceTag,
737 | 				targetTag,
738 | 				{},
739 | 				{ projectRoot: '/test/project' }
740 | 			);
741 | 
742 | 			expect(result).toEqual({
743 | 				message: 'Successfully moved 1 tasks from "backlog" to "in-progress"',
744 | 				movedTasks: [{ id: 3, fromTag: 'backlog', toTag: 'in-progress' }]
745 | 			});
746 | 		});
747 | 
748 | 		it('should handle bulk move with mixed dependency scenarios', async () => {
749 | 			const taskIds = [1, 2, 3]; // Multiple tasks with dependencies
750 | 			const sourceTag = 'backlog';
751 | 			const targetTag = 'in-progress';
752 | 
753 | 			const result = await moveTasksBetweenTags(
754 | 				testDataPath,
755 | 				taskIds,
756 | 				sourceTag,
757 | 				targetTag,
758 | 				{ withDependencies: true },
759 | 				{ projectRoot: '/test/project' }
760 | 			);
761 | 
762 | 			// Verify all tasks were moved
763 | 			expect(mockUtils.writeJSON).toHaveBeenCalledWith(
764 | 				testDataPath,
765 | 				expect.objectContaining({
766 | 					backlog: expect.objectContaining({
767 | 						tasks: [] // All tasks should be moved
768 | 					}),
769 | 					'in-progress': expect.objectContaining({
770 | 						tasks: expect.arrayContaining([
771 | 							expect.objectContaining({ id: 4 }),
772 | 							expect.objectContaining({ id: 1, tag: 'in-progress' }),
773 | 							expect.objectContaining({ id: 2, tag: 'in-progress' }),
774 | 							expect.objectContaining({ id: 3, tag: 'in-progress' })
775 | 						])
776 | 					})
777 | 				}),
778 | 				'/test/project',
779 | 				null
780 | 			);
781 | 
782 | 			// Verify result structure
783 | 			expect(result.movedTasks).toHaveLength(3);
784 | 			expect(result.movedTasks).toEqual(
785 | 				expect.arrayContaining([
786 | 					{ id: 1, fromTag: 'backlog', toTag: 'in-progress' },
787 | 					{ id: 2, fromTag: 'backlog', toTag: 'in-progress' },
788 | 					{ id: 3, fromTag: 'backlog', toTag: 'in-progress' }
789 | 				])
790 | 			);
791 | 		});
792 | 	});
793 | });
794 | 
```
Page 43/69FirstPrevNextLast