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

--------------------------------------------------------------------------------
/scripts/modules/utils.js:
--------------------------------------------------------------------------------

```javascript
   1 | /**
   2 |  * utils.js
   3 |  * Utility functions for the Task Master CLI
   4 |  */
   5 | 
   6 | import fs from 'fs';
   7 | import path from 'path';
   8 | import chalk from 'chalk';
   9 | import dotenv from 'dotenv';
  10 | // Import specific config getters needed here
  11 | import { getLogLevel, getDebugFlag } from './config-manager.js';
  12 | import * as gitUtils from './utils/git-utils.js';
  13 | import {
  14 | 	COMPLEXITY_REPORT_FILE,
  15 | 	LEGACY_COMPLEXITY_REPORT_FILE,
  16 | 	LEGACY_CONFIG_FILE
  17 | } from '../../src/constants/paths.js';
  18 | 
  19 | // Global silent mode flag
  20 | let silentMode = false;
  21 | 
  22 | // --- Environment Variable Resolution Utility ---
  23 | /**
  24 |  * Resolves an environment variable's value.
  25 |  * Precedence:
  26 |  * 1. session.env (if session provided)
  27 |  * 2. process.env
  28 |  * 3. .env file at projectRoot (if projectRoot provided)
  29 |  * @param {string} key - The environment variable key.
  30 |  * @param {object|null} [session=null] - The MCP session object.
  31 |  * @param {string|null} [projectRoot=null] - The project root directory (for .env fallback).
  32 |  * @returns {string|undefined} The value of the environment variable or undefined if not found.
  33 |  */
  34 | function resolveEnvVariable(key, session = null, projectRoot = null) {
  35 | 	// 1. Check session.env
  36 | 	if (session?.env?.[key]) {
  37 | 		return session.env[key];
  38 | 	}
  39 | 
  40 | 	// 2. Read .env file at projectRoot
  41 | 	if (projectRoot) {
  42 | 		const envPath = path.join(projectRoot, '.env');
  43 | 		if (fs.existsSync(envPath)) {
  44 | 			try {
  45 | 				const envFileContent = fs.readFileSync(envPath, 'utf-8');
  46 | 				const parsedEnv = dotenv.parse(envFileContent); // Use dotenv to parse
  47 | 				if (parsedEnv && parsedEnv[key]) {
  48 | 					// console.log(`DEBUG: Found key ${key} in ${envPath}`); // Optional debug log
  49 | 					return parsedEnv[key];
  50 | 				}
  51 | 			} catch (error) {
  52 | 				// Log error but don't crash, just proceed as if key wasn't found in file
  53 | 				log('warn', `Could not read or parse ${envPath}: ${error.message}`);
  54 | 			}
  55 | 		}
  56 | 	}
  57 | 
  58 | 	// 3. Fallback: Check process.env
  59 | 	if (process.env[key]) {
  60 | 		return process.env[key];
  61 | 	}
  62 | 
  63 | 	// Not found anywhere
  64 | 	return undefined;
  65 | }
  66 | 
  67 | // --- Tag-Aware Path Resolution Utility ---
  68 | 
  69 | /**
  70 |  * Slugifies a tag name to be filesystem-safe
  71 |  * @param {string} tagName - The tag name to slugify
  72 |  * @returns {string} Slugified tag name safe for filesystem use
  73 |  */
  74 | function slugifyTagForFilePath(tagName) {
  75 | 	if (!tagName || typeof tagName !== 'string') {
  76 | 		return 'unknown-tag';
  77 | 	}
  78 | 
  79 | 	// Replace invalid filesystem characters with hyphens and clean up
  80 | 	return tagName
  81 | 		.replace(/[^a-zA-Z0-9_-]/g, '-') // Replace invalid chars with hyphens
  82 | 		.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
  83 | 		.replace(/-+/g, '-') // Collapse multiple hyphens
  84 | 		.toLowerCase() // Convert to lowercase
  85 | 		.substring(0, 50); // Limit length to prevent overly long filenames
  86 | }
  87 | 
  88 | /**
  89 |  * Resolves a file path to be tag-aware, following the pattern used by other commands.
  90 |  * For non-master tags, appends _slugified-tagname before the file extension.
  91 |  * @param {string} basePath - The base file path (e.g., '.taskmaster/reports/task-complexity-report.json')
  92 |  * @param {string|null} tag - The tag name (null, undefined, or 'master' uses base path)
  93 |  * @param {string} [projectRoot='.'] - The project root directory
  94 |  * @returns {string} The resolved file path
  95 |  */
  96 | function getTagAwareFilePath(basePath, tag, projectRoot = '.') {
  97 | 	// Use path.parse and format for clean tag insertion
  98 | 	const parsedPath = path.parse(basePath);
  99 | 	if (!tag || tag === 'master') {
 100 | 		return path.join(projectRoot, basePath);
 101 | 	}
 102 | 
 103 | 	// Slugify the tag for filesystem safety
 104 | 	const slugifiedTag = slugifyTagForFilePath(tag);
 105 | 
 106 | 	// Append slugified tag before file extension
 107 | 	parsedPath.base = `${parsedPath.name}_${slugifiedTag}${parsedPath.ext}`;
 108 | 	const relativePath = path.format(parsedPath);
 109 | 	return path.join(projectRoot, relativePath);
 110 | }
 111 | 
 112 | // --- Project Root Finding Utility ---
 113 | /**
 114 |  * Recursively searches upwards for project root starting from a given directory.
 115 |  * @param {string} [startDir=process.cwd()] - The directory to start searching from.
 116 |  * @param {string[]} [markers=['package.json', '.git', LEGACY_CONFIG_FILE]] - Marker files/dirs to look for.
 117 |  * @returns {string|null} The path to the project root, or null if not found.
 118 |  */
 119 | function findProjectRoot(
 120 | 	startDir = process.cwd(),
 121 | 	markers = ['package.json', 'pyproject.toml', '.git', LEGACY_CONFIG_FILE]
 122 | ) {
 123 | 	let currentPath = path.resolve(startDir);
 124 | 	const rootPath = path.parse(currentPath).root;
 125 | 
 126 | 	while (currentPath !== rootPath) {
 127 | 		// Check if any marker exists in the current directory
 128 | 		const hasMarker = markers.some((marker) => {
 129 | 			const markerPath = path.join(currentPath, marker);
 130 | 			return fs.existsSync(markerPath);
 131 | 		});
 132 | 
 133 | 		if (hasMarker) {
 134 | 			return currentPath;
 135 | 		}
 136 | 
 137 | 		// Move up one directory
 138 | 		currentPath = path.dirname(currentPath);
 139 | 	}
 140 | 
 141 | 	// Check the root directory as well
 142 | 	const hasMarkerInRoot = markers.some((marker) => {
 143 | 		const markerPath = path.join(rootPath, marker);
 144 | 		return fs.existsSync(markerPath);
 145 | 	});
 146 | 
 147 | 	return hasMarkerInRoot ? rootPath : null;
 148 | }
 149 | 
 150 | // --- Dynamic Configuration Function --- (REMOVED)
 151 | 
 152 | // --- Logging and Utility Functions ---
 153 | 
 154 | // Set up logging based on log level
 155 | const LOG_LEVELS = {
 156 | 	debug: 0,
 157 | 	info: 1,
 158 | 	warn: 2,
 159 | 	error: 3,
 160 | 	success: 1 // Treat success like info level
 161 | };
 162 | 
 163 | /**
 164 |  * Returns the task manager module
 165 |  * @returns {Promise<Object>} The task manager module object
 166 |  */
 167 | async function getTaskManager() {
 168 | 	return import('./task-manager.js');
 169 | }
 170 | 
 171 | /**
 172 |  * Enable silent logging mode
 173 |  */
 174 | function enableSilentMode() {
 175 | 	silentMode = true;
 176 | }
 177 | 
 178 | /**
 179 |  * Disable silent logging mode
 180 |  */
 181 | function disableSilentMode() {
 182 | 	silentMode = false;
 183 | }
 184 | 
 185 | /**
 186 |  * Check if silent mode is enabled
 187 |  * @returns {boolean} True if silent mode is enabled
 188 |  */
 189 | function isSilentMode() {
 190 | 	return silentMode;
 191 | }
 192 | 
 193 | /**
 194 |  * Logs a message at the specified level
 195 |  * @param {string} level - The log level (debug, info, warn, error)
 196 |  * @param  {...any} args - Arguments to log
 197 |  */
 198 | function log(level, ...args) {
 199 | 	// Immediately return if silentMode is enabled
 200 | 	if (isSilentMode()) {
 201 | 		return;
 202 | 	}
 203 | 
 204 | 	// GUARD: Prevent circular dependency during config loading
 205 | 	// Use a simple fallback log level instead of calling getLogLevel()
 206 | 	let configLevel = 'info'; // Default fallback
 207 | 	try {
 208 | 		// Only try to get config level if we're not in the middle of config loading
 209 | 		configLevel = getLogLevel() || 'info';
 210 | 	} catch (error) {
 211 | 		// If getLogLevel() fails (likely due to circular dependency),
 212 | 		// use default 'info' level and continue
 213 | 		configLevel = 'info';
 214 | 	}
 215 | 
 216 | 	// Use text prefixes instead of emojis
 217 | 	const prefixes = {
 218 | 		debug: chalk.gray('[DEBUG]'),
 219 | 		info: chalk.blue('[INFO]'),
 220 | 		warn: chalk.yellow('[WARN]'),
 221 | 		error: chalk.red('[ERROR]'),
 222 | 		success: chalk.green('[SUCCESS]')
 223 | 	};
 224 | 
 225 | 	// Ensure level exists, default to info if not
 226 | 	const currentLevel = LOG_LEVELS.hasOwnProperty(level) ? level : 'info';
 227 | 
 228 | 	// Check log level configuration
 229 | 	if (
 230 | 		LOG_LEVELS[currentLevel] >= (LOG_LEVELS[configLevel] ?? LOG_LEVELS.info)
 231 | 	) {
 232 | 		const prefix = prefixes[currentLevel] || '';
 233 | 		// Use console.log for all levels, let chalk handle coloring
 234 | 		// Construct the message properly
 235 | 		const message = args
 236 | 			.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : arg))
 237 | 			.join(' ');
 238 | 		console.log(`${prefix} ${message}`);
 239 | 	}
 240 | }
 241 | 
 242 | /**
 243 |  * Checks if the data object has a tagged structure (contains tag objects with tasks arrays)
 244 |  * @param {Object} data - The data object to check
 245 |  * @returns {boolean} True if the data has a tagged structure
 246 |  */
 247 | function hasTaggedStructure(data) {
 248 | 	if (!data || typeof data !== 'object') {
 249 | 		return false;
 250 | 	}
 251 | 
 252 | 	// Check if any top-level properties are objects with tasks arrays
 253 | 	for (const key in data) {
 254 | 		if (
 255 | 			data.hasOwnProperty(key) &&
 256 | 			typeof data[key] === 'object' &&
 257 | 			Array.isArray(data[key].tasks)
 258 | 		) {
 259 | 			return true;
 260 | 		}
 261 | 	}
 262 | 	return false;
 263 | }
 264 | 
 265 | /**
 266 |  * Normalizes task IDs to ensure they are numbers instead of strings
 267 |  * @param {Array} tasks - Array of tasks to normalize
 268 |  */
 269 | function normalizeTaskIds(tasks) {
 270 | 	if (!Array.isArray(tasks)) return;
 271 | 
 272 | 	tasks.forEach((task) => {
 273 | 		// Convert task ID to number with validation
 274 | 		if (task.id !== undefined) {
 275 | 			const parsedId = parseInt(task.id, 10);
 276 | 			if (!isNaN(parsedId) && parsedId > 0) {
 277 | 				task.id = parsedId;
 278 | 			}
 279 | 		}
 280 | 
 281 | 		// Convert subtask IDs to numbers with validation
 282 | 		if (Array.isArray(task.subtasks)) {
 283 | 			task.subtasks.forEach((subtask) => {
 284 | 				if (subtask.id !== undefined) {
 285 | 					// Check for dot notation (which shouldn't exist in storage)
 286 | 					if (typeof subtask.id === 'string' && subtask.id.includes('.')) {
 287 | 						// Extract the subtask part after the dot
 288 | 						const parts = subtask.id.split('.');
 289 | 						subtask.id = parseInt(parts[parts.length - 1], 10);
 290 | 					} else {
 291 | 						const parsedSubtaskId = parseInt(subtask.id, 10);
 292 | 						if (!isNaN(parsedSubtaskId) && parsedSubtaskId > 0) {
 293 | 							subtask.id = parsedSubtaskId;
 294 | 						}
 295 | 					}
 296 | 				}
 297 | 			});
 298 | 		}
 299 | 	});
 300 | }
 301 | 
 302 | /**
 303 |  * Reads and parses a JSON file
 304 |  * @param {string} filepath - Path to the JSON file
 305 |  * @param {string} [projectRoot] - Optional project root for tag resolution (used by MCP)
 306 |  * @param {string} [tag] - Optional tag to use instead of current tag resolution
 307 |  * @returns {Object|null} The parsed JSON data or null if error
 308 |  */
 309 | function readJSON(filepath, projectRoot = null, tag = null) {
 310 | 	// GUARD: Prevent circular dependency during config loading
 311 | 	let isDebug = false; // Default fallback
 312 | 	try {
 313 | 		// Only try to get debug flag if we're not in the middle of config loading
 314 | 		isDebug = getDebugFlag();
 315 | 	} catch (error) {
 316 | 		// If getDebugFlag() fails (likely due to circular dependency),
 317 | 		// use default false and continue
 318 | 	}
 319 | 
 320 | 	if (isDebug) {
 321 | 		console.log(
 322 | 			`readJSON called with: ${filepath}, projectRoot: ${projectRoot}, tag: ${tag}`
 323 | 		);
 324 | 	}
 325 | 
 326 | 	if (!filepath) {
 327 | 		return null;
 328 | 	}
 329 | 
 330 | 	let data;
 331 | 	try {
 332 | 		data = JSON.parse(fs.readFileSync(filepath, 'utf8'));
 333 | 		if (isDebug) {
 334 | 			console.log(`Successfully read JSON from ${filepath}`);
 335 | 		}
 336 | 	} catch (err) {
 337 | 		if (isDebug) {
 338 | 			console.log(`Failed to read JSON from ${filepath}: ${err.message}`);
 339 | 		}
 340 | 		return null;
 341 | 	}
 342 | 
 343 | 	// If it's not a tasks.json file, return as-is
 344 | 	if (!filepath.includes('tasks.json') || !data) {
 345 | 		if (isDebug) {
 346 | 			console.log(`File is not tasks.json or data is null, returning as-is`);
 347 | 		}
 348 | 		return data;
 349 | 	}
 350 | 
 351 | 	// Check if this is legacy format that needs migration
 352 | 	// Only migrate if we have tasks at the ROOT level AND no tag-like structure
 353 | 	if (
 354 | 		Array.isArray(data.tasks) &&
 355 | 		!data._rawTaggedData &&
 356 | 		!hasTaggedStructure(data)
 357 | 	) {
 358 | 		if (isDebug) {
 359 | 			console.log(`File is in legacy format, performing migration...`);
 360 | 		}
 361 | 
 362 | 		normalizeTaskIds(data.tasks);
 363 | 
 364 | 		// This is legacy format - migrate it to tagged format
 365 | 		const migratedData = {
 366 | 			master: {
 367 | 				tasks: data.tasks,
 368 | 				metadata: data.metadata || {
 369 | 					created: new Date().toISOString(),
 370 | 					updated: new Date().toISOString(),
 371 | 					description: 'Tasks for master context'
 372 | 				}
 373 | 			}
 374 | 		};
 375 | 
 376 | 		// Write the migrated data back to the file
 377 | 		try {
 378 | 			writeJSON(filepath, migratedData);
 379 | 			if (isDebug) {
 380 | 				console.log(`Successfully migrated legacy format to tagged format`);
 381 | 			}
 382 | 
 383 | 			// Perform complete migration (config.json, state.json)
 384 | 			performCompleteTagMigration(filepath);
 385 | 
 386 | 			// Check and auto-switch git tags if enabled (after migration)
 387 | 			// This needs to run synchronously BEFORE tag resolution
 388 | 			if (projectRoot) {
 389 | 				try {
 390 | 					// Run git integration synchronously
 391 | 					gitUtils.checkAndAutoSwitchGitTagSync(projectRoot, filepath);
 392 | 				} catch (error) {
 393 | 					// Silent fail - don't break normal operations
 394 | 				}
 395 | 			}
 396 | 
 397 | 			// Mark for migration notice
 398 | 			markMigrationForNotice(filepath);
 399 | 		} catch (writeError) {
 400 | 			if (isDebug) {
 401 | 				console.log(`Error writing migrated data: ${writeError.message}`);
 402 | 			}
 403 | 			// If write fails, continue with the original data
 404 | 		}
 405 | 
 406 | 		// Continue processing with the migrated data structure
 407 | 		data = migratedData;
 408 | 	}
 409 | 
 410 | 	// If we have tagged data, we need to resolve which tag to use
 411 | 	if (typeof data === 'object' && !data.tasks) {
 412 | 		// This is tagged format
 413 | 		if (isDebug) {
 414 | 			console.log(`File is in tagged format, resolving tag...`);
 415 | 		}
 416 | 
 417 | 		// Ensure all tags have proper metadata before proceeding
 418 | 		for (const tagName in data) {
 419 | 			if (
 420 | 				data.hasOwnProperty(tagName) &&
 421 | 				typeof data[tagName] === 'object' &&
 422 | 				data[tagName].tasks
 423 | 			) {
 424 | 				try {
 425 | 					ensureTagMetadata(data[tagName], {
 426 | 						description: `Tasks for ${tagName} context`,
 427 | 						skipUpdate: true // Don't update timestamp during read operations
 428 | 					});
 429 | 				} catch (error) {
 430 | 					// If ensureTagMetadata fails, continue without metadata
 431 | 					if (isDebug) {
 432 | 						console.log(
 433 | 							`Failed to ensure metadata for tag ${tagName}: ${error.message}`
 434 | 						);
 435 | 					}
 436 | 				}
 437 | 			}
 438 | 		}
 439 | 
 440 | 		// Store reference to the raw tagged data for functions that need it
 441 | 		const originalTaggedData = JSON.parse(JSON.stringify(data));
 442 | 
 443 | 		// Normalize IDs in all tags before storing as originalTaggedData
 444 | 		for (const tagName in originalTaggedData) {
 445 | 			if (
 446 | 				originalTaggedData[tagName] &&
 447 | 				Array.isArray(originalTaggedData[tagName].tasks)
 448 | 			) {
 449 | 				normalizeTaskIds(originalTaggedData[tagName].tasks);
 450 | 			}
 451 | 		}
 452 | 
 453 | 		// Check and auto-switch git tags if enabled (for existing tagged format)
 454 | 		// This needs to run synchronously BEFORE tag resolution
 455 | 		if (projectRoot) {
 456 | 			try {
 457 | 				// Run git integration synchronously
 458 | 				gitUtils.checkAndAutoSwitchGitTagSync(projectRoot, filepath);
 459 | 			} catch (error) {
 460 | 				// Silent fail - don't break normal operations
 461 | 			}
 462 | 		}
 463 | 
 464 | 		try {
 465 | 			// Default to master tag if anything goes wrong
 466 | 			let resolvedTag = 'master';
 467 | 
 468 | 			// Try to resolve the correct tag, but don't fail if it doesn't work
 469 | 			try {
 470 | 				// If tag is provided, use it directly
 471 | 				if (tag) {
 472 | 					resolvedTag = tag;
 473 | 				} else if (projectRoot) {
 474 | 					// Use provided projectRoot
 475 | 					resolvedTag = resolveTag({ projectRoot });
 476 | 				} else {
 477 | 					// Try to derive projectRoot from filepath
 478 | 					const derivedProjectRoot = findProjectRoot(path.dirname(filepath));
 479 | 					if (derivedProjectRoot) {
 480 | 						resolvedTag = resolveTag({ projectRoot: derivedProjectRoot });
 481 | 					}
 482 | 					// If derivedProjectRoot is null, stick with 'master'
 483 | 				}
 484 | 			} catch (tagResolveError) {
 485 | 				if (isDebug) {
 486 | 					console.log(
 487 | 						`Tag resolution failed, using master: ${tagResolveError.message}`
 488 | 					);
 489 | 				}
 490 | 				// resolvedTag stays as 'master'
 491 | 			}
 492 | 
 493 | 			if (isDebug) {
 494 | 				console.log(`Resolved tag: ${resolvedTag}`);
 495 | 			}
 496 | 
 497 | 			// Get the data for the resolved tag
 498 | 			const tagData = data[resolvedTag];
 499 | 			if (tagData && tagData.tasks) {
 500 | 				normalizeTaskIds(tagData.tasks);
 501 | 
 502 | 				// Add the _rawTaggedData property and the resolved tag to the returned data
 503 | 				const result = {
 504 | 					...tagData,
 505 | 					tag: resolvedTag,
 506 | 					_rawTaggedData: originalTaggedData
 507 | 				};
 508 | 				if (isDebug) {
 509 | 					console.log(
 510 | 						`Returning data for tag '${resolvedTag}' with ${tagData.tasks.length} tasks`
 511 | 					);
 512 | 				}
 513 | 				return result;
 514 | 			} else {
 515 | 				// If the resolved tag doesn't exist, fall back to master
 516 | 				const masterData = data.master;
 517 | 				if (masterData && masterData.tasks) {
 518 | 					normalizeTaskIds(masterData.tasks);
 519 | 
 520 | 					if (isDebug) {
 521 | 						console.log(
 522 | 							`Tag '${resolvedTag}' not found, falling back to master with ${masterData.tasks.length} tasks`
 523 | 						);
 524 | 					}
 525 | 					return {
 526 | 						...masterData,
 527 | 						tag: 'master',
 528 | 						_rawTaggedData: originalTaggedData
 529 | 					};
 530 | 				} else {
 531 | 					if (isDebug) {
 532 | 						console.log(`No valid tag data found, returning empty structure`);
 533 | 					}
 534 | 					// Return empty structure if no valid data
 535 | 					return {
 536 | 						tasks: [],
 537 | 						tag: 'master',
 538 | 						_rawTaggedData: originalTaggedData
 539 | 					};
 540 | 				}
 541 | 			}
 542 | 		} catch (error) {
 543 | 			if (isDebug) {
 544 | 				console.log(`Error during tag resolution: ${error.message}`);
 545 | 			}
 546 | 			// If anything goes wrong, try to return master or empty
 547 | 			const masterData = data.master;
 548 | 			if (masterData && masterData.tasks) {
 549 | 				normalizeTaskIds(masterData.tasks);
 550 | 				return {
 551 | 					...masterData,
 552 | 					_rawTaggedData: originalTaggedData
 553 | 				};
 554 | 			}
 555 | 			return {
 556 | 				tasks: [],
 557 | 				_rawTaggedData: originalTaggedData
 558 | 			};
 559 | 		}
 560 | 	}
 561 | 
 562 | 	// If we reach here, it's some other format
 563 | 	if (isDebug) {
 564 | 		console.log(`File format not recognized, returning as-is`);
 565 | 	}
 566 | 	return data;
 567 | }
 568 | 
 569 | /**
 570 |  * Performs complete tag migration including config.json and state.json updates
 571 |  * @param {string} tasksJsonPath - Path to the tasks.json file that was migrated
 572 |  */
 573 | function performCompleteTagMigration(tasksJsonPath) {
 574 | 	try {
 575 | 		// Derive project root from tasks.json path
 576 | 		const projectRoot =
 577 | 			findProjectRoot(path.dirname(tasksJsonPath)) ||
 578 | 			path.dirname(tasksJsonPath);
 579 | 
 580 | 		// 1. Migrate config.json - add defaultTag and tags section
 581 | 		const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
 582 | 		if (fs.existsSync(configPath)) {
 583 | 			migrateConfigJson(configPath);
 584 | 		}
 585 | 
 586 | 		// 2. Create state.json if it doesn't exist
 587 | 		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
 588 | 		if (!fs.existsSync(statePath)) {
 589 | 			createStateJson(statePath);
 590 | 		}
 591 | 
 592 | 		if (getDebugFlag()) {
 593 | 			log(
 594 | 				'debug',
 595 | 				`Complete tag migration performed for project: ${projectRoot}`
 596 | 			);
 597 | 		}
 598 | 	} catch (error) {
 599 | 		if (getDebugFlag()) {
 600 | 			log('warn', `Error during complete tag migration: ${error.message}`);
 601 | 		}
 602 | 	}
 603 | }
 604 | 
 605 | /**
 606 |  * Migrates config.json to add tagged task system configuration
 607 |  * @param {string} configPath - Path to the config.json file
 608 |  */
 609 | function migrateConfigJson(configPath) {
 610 | 	try {
 611 | 		const rawConfig = fs.readFileSync(configPath, 'utf8');
 612 | 		const config = JSON.parse(rawConfig);
 613 | 		if (!config) return;
 614 | 
 615 | 		let modified = false;
 616 | 
 617 | 		// Add global.defaultTag if missing
 618 | 		if (!config.global) {
 619 | 			config.global = {};
 620 | 		}
 621 | 		if (!config.global.defaultTag) {
 622 | 			config.global.defaultTag = 'master';
 623 | 			modified = true;
 624 | 		}
 625 | 
 626 | 		if (modified) {
 627 | 			fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
 628 | 			if (process.env.TASKMASTER_DEBUG === 'true') {
 629 | 				console.log(
 630 | 					'[DEBUG] Updated config.json with tagged task system settings'
 631 | 				);
 632 | 			}
 633 | 		}
 634 | 	} catch (error) {
 635 | 		if (process.env.TASKMASTER_DEBUG === 'true') {
 636 | 			console.warn(`[WARN] Error migrating config.json: ${error.message}`);
 637 | 		}
 638 | 	}
 639 | }
 640 | 
 641 | /**
 642 |  * Creates initial state.json file for tagged task system
 643 |  * @param {string} statePath - Path where state.json should be created
 644 |  */
 645 | function createStateJson(statePath) {
 646 | 	try {
 647 | 		const initialState = {
 648 | 			currentTag: 'master',
 649 | 			lastSwitched: new Date().toISOString(),
 650 | 			branchTagMapping: {},
 651 | 			migrationNoticeShown: false
 652 | 		};
 653 | 
 654 | 		fs.writeFileSync(statePath, JSON.stringify(initialState, null, 2), 'utf8');
 655 | 		if (process.env.TASKMASTER_DEBUG === 'true') {
 656 | 			console.log('[DEBUG] Created initial state.json for tagged task system');
 657 | 		}
 658 | 	} catch (error) {
 659 | 		if (process.env.TASKMASTER_DEBUG === 'true') {
 660 | 			console.warn(`[WARN] Error creating state.json: ${error.message}`);
 661 | 		}
 662 | 	}
 663 | }
 664 | 
 665 | /**
 666 |  * Marks in state.json that migration occurred and notice should be shown
 667 |  * @param {string} tasksJsonPath - Path to the tasks.json file
 668 |  */
 669 | function markMigrationForNotice(tasksJsonPath) {
 670 | 	try {
 671 | 		const projectRoot = path.dirname(path.dirname(tasksJsonPath));
 672 | 		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
 673 | 
 674 | 		// Ensure state.json exists
 675 | 		if (!fs.existsSync(statePath)) {
 676 | 			createStateJson(statePath);
 677 | 		}
 678 | 
 679 | 		// Read and update state to mark migration occurred using fs directly
 680 | 		try {
 681 | 			const rawState = fs.readFileSync(statePath, 'utf8');
 682 | 			const stateData = JSON.parse(rawState) || {};
 683 | 			// Only set to false if it's not already set (i.e., first time migration)
 684 | 			if (stateData.migrationNoticeShown === undefined) {
 685 | 				stateData.migrationNoticeShown = false;
 686 | 				fs.writeFileSync(statePath, JSON.stringify(stateData, null, 2), 'utf8');
 687 | 			}
 688 | 		} catch (stateError) {
 689 | 			if (process.env.TASKMASTER_DEBUG === 'true') {
 690 | 				console.warn(
 691 | 					`[WARN] Error updating state for migration notice: ${stateError.message}`
 692 | 				);
 693 | 			}
 694 | 		}
 695 | 	} catch (error) {
 696 | 		if (process.env.TASKMASTER_DEBUG === 'true') {
 697 | 			console.warn(
 698 | 				`[WARN] Error marking migration for notice: ${error.message}`
 699 | 			);
 700 | 		}
 701 | 	}
 702 | }
 703 | 
 704 | /**
 705 |  * Writes and saves a JSON file. Handles tagged task lists properly.
 706 |  * @param {string} filepath - Path to the JSON file
 707 |  * @param {Object} data - Data to write (can be resolved tag data or raw tagged data)
 708 |  * @param {string} projectRoot - Optional project root for tag context
 709 |  * @param {string} tag - Optional tag for tag context
 710 |  */
 711 | function writeJSON(filepath, data, projectRoot = null, tag = null) {
 712 | 	const isDebug = process.env.TASKMASTER_DEBUG === 'true';
 713 | 
 714 | 	try {
 715 | 		let finalData = data;
 716 | 
 717 | 		// If data represents resolved tag data but lost _rawTaggedData (edge-case observed in MCP path)
 718 | 		if (
 719 | 			!data._rawTaggedData &&
 720 | 			projectRoot &&
 721 | 			Array.isArray(data.tasks) &&
 722 | 			!hasTaggedStructure(data)
 723 | 		) {
 724 | 			const resolvedTag = tag || getCurrentTag(projectRoot);
 725 | 
 726 | 			if (isDebug) {
 727 | 				console.log(
 728 | 					`writeJSON: Detected resolved tag data missing _rawTaggedData. Re-reading raw data to prevent data loss for tag '${resolvedTag}'.`
 729 | 				);
 730 | 			}
 731 | 
 732 | 			// Re-read the full file to get the complete tagged structure
 733 | 			const rawFullData = JSON.parse(fs.readFileSync(filepath, 'utf8'));
 734 | 
 735 | 			// Merge the updated data into the full structure
 736 | 			finalData = {
 737 | 				...rawFullData,
 738 | 				[resolvedTag]: {
 739 | 					// Preserve existing tag metadata if it exists, otherwise use what's passed
 740 | 					...(rawFullData[resolvedTag]?.metadata || {}),
 741 | 					...(data.metadata ? { metadata: data.metadata } : {}),
 742 | 					tasks: data.tasks // The updated tasks array is the source of truth here
 743 | 				}
 744 | 			};
 745 | 		}
 746 | 		// If we have _rawTaggedData, this means we're working with resolved tag data
 747 | 		// and need to merge it back into the full tagged structure
 748 | 		else if (data && data._rawTaggedData && projectRoot) {
 749 | 			const resolvedTag = tag || getCurrentTag(projectRoot);
 750 | 
 751 | 			// Get the original tagged data
 752 | 			const originalTaggedData = data._rawTaggedData;
 753 | 
 754 | 			// Create a clean copy of the current resolved data (without internal properties)
 755 | 			const { _rawTaggedData, tag: _, ...cleanResolvedData } = data;
 756 | 
 757 | 			// Update the specific tag with the resolved data
 758 | 			finalData = {
 759 | 				...originalTaggedData,
 760 | 				[resolvedTag]: cleanResolvedData
 761 | 			};
 762 | 
 763 | 			if (isDebug) {
 764 | 				console.log(
 765 | 					`writeJSON: Merging resolved data back into tag '${resolvedTag}'`
 766 | 				);
 767 | 			}
 768 | 		}
 769 | 
 770 | 		// Clean up any internal properties that shouldn't be persisted
 771 | 		let cleanData = finalData;
 772 | 		if (cleanData && typeof cleanData === 'object') {
 773 | 			// Remove any _rawTaggedData or tag properties from root level
 774 | 			const { _rawTaggedData, tag: tagProp, ...rootCleanData } = cleanData;
 775 | 			cleanData = rootCleanData;
 776 | 
 777 | 			// Additional cleanup for tag objects
 778 | 			if (typeof cleanData === 'object' && !Array.isArray(cleanData)) {
 779 | 				const finalCleanData = {};
 780 | 				for (const [key, value] of Object.entries(cleanData)) {
 781 | 					if (
 782 | 						value &&
 783 | 						typeof value === 'object' &&
 784 | 						Array.isArray(value.tasks)
 785 | 					) {
 786 | 						// This is a tag object - clean up any rogue root-level properties
 787 | 						const { created, description, ...cleanTagData } = value;
 788 | 
 789 | 						// Only keep the description if there's no metadata.description
 790 | 						if (
 791 | 							description &&
 792 | 							(!cleanTagData.metadata || !cleanTagData.metadata.description)
 793 | 						) {
 794 | 							cleanTagData.description = description;
 795 | 						}
 796 | 
 797 | 						finalCleanData[key] = cleanTagData;
 798 | 					} else {
 799 | 						finalCleanData[key] = value;
 800 | 					}
 801 | 				}
 802 | 				cleanData = finalCleanData;
 803 | 			}
 804 | 		}
 805 | 
 806 | 		fs.writeFileSync(filepath, JSON.stringify(cleanData, null, 2), 'utf8');
 807 | 
 808 | 		if (isDebug) {
 809 | 			console.log(`writeJSON: Successfully wrote to ${filepath}`);
 810 | 		}
 811 | 	} catch (error) {
 812 | 		log('error', `Error writing JSON file ${filepath}:`, error.message);
 813 | 		if (isDebug) {
 814 | 			log('error', 'Full error details:', error);
 815 | 		}
 816 | 	}
 817 | }
 818 | 
 819 | /**
 820 |  * Sanitizes a prompt string for use in a shell command
 821 |  * @param {string} prompt The prompt to sanitize
 822 |  * @returns {string} Sanitized prompt
 823 |  */
 824 | function sanitizePrompt(prompt) {
 825 | 	// Replace double quotes with escaped double quotes
 826 | 	return prompt.replace(/"/g, '\\"');
 827 | }
 828 | 
 829 | /**
 830 |  * Reads the complexity report from file
 831 |  * @param {string} customPath - Optional custom path to the report
 832 |  * @returns {Object|null} The parsed complexity report or null if not found
 833 |  */
 834 | function readComplexityReport(customPath = null) {
 835 | 	// GUARD: Prevent circular dependency during config loading
 836 | 	let isDebug = false; // Default fallback
 837 | 	try {
 838 | 		// Only try to get debug flag if we're not in the middle of config loading
 839 | 		isDebug = getDebugFlag();
 840 | 	} catch (error) {
 841 | 		// If getDebugFlag() fails (likely due to circular dependency),
 842 | 		// use default false and continue
 843 | 		isDebug = false;
 844 | 	}
 845 | 
 846 | 	try {
 847 | 		let reportPath;
 848 | 		if (customPath) {
 849 | 			reportPath = customPath;
 850 | 		} else {
 851 | 			// Try new location first, then fall back to legacy
 852 | 			const newPath = path.join(process.cwd(), COMPLEXITY_REPORT_FILE);
 853 | 			const legacyPath = path.join(
 854 | 				process.cwd(),
 855 | 				LEGACY_COMPLEXITY_REPORT_FILE
 856 | 			);
 857 | 
 858 | 			reportPath = fs.existsSync(newPath) ? newPath : legacyPath;
 859 | 		}
 860 | 
 861 | 		if (!fs.existsSync(reportPath)) {
 862 | 			if (isDebug) {
 863 | 				log('debug', `Complexity report not found at ${reportPath}`);
 864 | 			}
 865 | 			return null;
 866 | 		}
 867 | 
 868 | 		const reportData = readJSON(reportPath);
 869 | 		if (isDebug) {
 870 | 			log('debug', `Successfully read complexity report from ${reportPath}`);
 871 | 		}
 872 | 		return reportData;
 873 | 	} catch (error) {
 874 | 		if (isDebug) {
 875 | 			log('error', `Error reading complexity report: ${error.message}`);
 876 | 		}
 877 | 		return null;
 878 | 	}
 879 | }
 880 | 
 881 | /**
 882 |  * Finds a task analysis in the complexity report
 883 |  * @param {Object} report - The complexity report
 884 |  * @param {number} taskId - The task ID to find
 885 |  * @returns {Object|null} The task analysis or null if not found
 886 |  */
 887 | function findTaskInComplexityReport(report, taskId) {
 888 | 	if (
 889 | 		!report ||
 890 | 		!report.complexityAnalysis ||
 891 | 		!Array.isArray(report.complexityAnalysis)
 892 | 	) {
 893 | 		return null;
 894 | 	}
 895 | 
 896 | 	return report.complexityAnalysis.find((task) => task.taskId === taskId);
 897 | }
 898 | 
 899 | function addComplexityToTask(task, complexityReport) {
 900 | 	let taskId;
 901 | 	if (task.isSubtask) {
 902 | 		taskId = task.parentTask.id;
 903 | 	} else if (task.parentId) {
 904 | 		taskId = task.parentId;
 905 | 	} else {
 906 | 		taskId = task.id;
 907 | 	}
 908 | 
 909 | 	const taskAnalysis = findTaskInComplexityReport(complexityReport, taskId);
 910 | 	if (taskAnalysis) {
 911 | 		task.complexityScore = taskAnalysis.complexityScore;
 912 | 	}
 913 | }
 914 | 
 915 | /**
 916 |  * Checks if a task exists in the tasks array
 917 |  * @param {Array} tasks - The tasks array
 918 |  * @param {string|number} taskId - The task ID to check
 919 |  * @returns {boolean} True if the task exists, false otherwise
 920 |  */
 921 | function taskExists(tasks, taskId) {
 922 | 	if (!taskId || !tasks || !Array.isArray(tasks)) {
 923 | 		return false;
 924 | 	}
 925 | 
 926 | 	// Handle both regular task IDs and subtask IDs (e.g., "1.2")
 927 | 	if (typeof taskId === 'string' && taskId.includes('.')) {
 928 | 		const [parentId, subtaskId] = taskId
 929 | 			.split('.')
 930 | 			.map((id) => parseInt(id, 10));
 931 | 		const parentTask = tasks.find((t) => t.id === parentId);
 932 | 
 933 | 		if (!parentTask || !parentTask.subtasks) {
 934 | 			return false;
 935 | 		}
 936 | 
 937 | 		return parentTask.subtasks.some((st) => st.id === subtaskId);
 938 | 	}
 939 | 
 940 | 	const id = parseInt(taskId, 10);
 941 | 	return tasks.some((t) => t.id === id);
 942 | }
 943 | 
 944 | /**
 945 |  * Formats a task ID as a string
 946 |  * @param {string|number} id - The task ID to format
 947 |  * @returns {string} The formatted task ID
 948 |  */
 949 | function formatTaskId(id) {
 950 | 	if (typeof id === 'string' && id.includes('.')) {
 951 | 		return id; // Already formatted as a string with a dot (e.g., "1.2")
 952 | 	}
 953 | 
 954 | 	if (typeof id === 'number') {
 955 | 		return id.toString();
 956 | 	}
 957 | 
 958 | 	return id;
 959 | }
 960 | 
 961 | /**
 962 |  * Finds a task by ID in the tasks array. Optionally filters subtasks by status.
 963 |  * @param {Array} tasks - The tasks array
 964 |  * @param {string|number} taskId - The task ID to find
 965 |  * @param {Object|null} complexityReport - Optional pre-loaded complexity report
 966 |  * @param {string} [statusFilter] - Optional status to filter subtasks by
 967 |  * @returns {{task: Object|null, originalSubtaskCount: number|null, originalSubtasks: Array|null}} The task object (potentially with filtered subtasks), the original subtask count, and original subtasks array if filtered, or nulls if not found.
 968 |  */
 969 | function findTaskById(
 970 | 	tasks,
 971 | 	taskId,
 972 | 	complexityReport = null,
 973 | 	statusFilter = null
 974 | ) {
 975 | 	if (!taskId || !tasks || !Array.isArray(tasks)) {
 976 | 		return { task: null, originalSubtaskCount: null };
 977 | 	}
 978 | 
 979 | 	// Check if it's a subtask ID (e.g., "1.2")
 980 | 	if (typeof taskId === 'string' && taskId.includes('.')) {
 981 | 		// If looking for a subtask, statusFilter doesn't apply directly here.
 982 | 		const [parentId, subtaskId] = taskId
 983 | 			.split('.')
 984 | 			.map((id) => parseInt(id, 10));
 985 | 		const parentTask = tasks.find((t) => t.id === parentId);
 986 | 
 987 | 		if (!parentTask || !parentTask.subtasks) {
 988 | 			return { task: null, originalSubtaskCount: null, originalSubtasks: null };
 989 | 		}
 990 | 
 991 | 		const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
 992 | 		if (subtask) {
 993 | 			// Add reference to parent task for context
 994 | 			subtask.parentTask = {
 995 | 				id: parentTask.id,
 996 | 				title: parentTask.title,
 997 | 				status: parentTask.status
 998 | 			};
 999 | 			subtask.isSubtask = true;
1000 | 		}
1001 | 
1002 | 		// If we found a task, check for complexity data
1003 | 		if (subtask && complexityReport) {
1004 | 			addComplexityToTask(subtask, complexityReport);
1005 | 		}
1006 | 
1007 | 		return {
1008 | 			task: subtask || null,
1009 | 			originalSubtaskCount: null,
1010 | 			originalSubtasks: null
1011 | 		};
1012 | 	}
1013 | 
1014 | 	let taskResult = null;
1015 | 	let originalSubtaskCount = null;
1016 | 	let originalSubtasks = null;
1017 | 
1018 | 	// Find the main task
1019 | 	const id = parseInt(taskId, 10);
1020 | 	const task = tasks.find((t) => t.id === id) || null;
1021 | 
1022 | 	// If task not found, return nulls
1023 | 	if (!task) {
1024 | 		return { task: null, originalSubtaskCount: null, originalSubtasks: null };
1025 | 	}
1026 | 
1027 | 	taskResult = task;
1028 | 
1029 | 	// If task found and statusFilter provided, filter its subtasks
1030 | 	if (statusFilter && task.subtasks && Array.isArray(task.subtasks)) {
1031 | 		// Store original subtasks and count before filtering
1032 | 		originalSubtasks = [...task.subtasks]; // Clone the original subtasks array
1033 | 		originalSubtaskCount = task.subtasks.length;
1034 | 
1035 | 		// Clone the task to avoid modifying the original array
1036 | 		const filteredTask = { ...task };
1037 | 		filteredTask.subtasks = task.subtasks.filter(
1038 | 			(subtask) =>
1039 | 				subtask.status &&
1040 | 				subtask.status.toLowerCase() === statusFilter.toLowerCase()
1041 | 		);
1042 | 
1043 | 		taskResult = filteredTask;
1044 | 	}
1045 | 
1046 | 	// If task found and complexityReport provided, add complexity data
1047 | 	if (taskResult && complexityReport) {
1048 | 		addComplexityToTask(taskResult, complexityReport);
1049 | 	}
1050 | 
1051 | 	// Return the found task, original subtask count, and original subtasks
1052 | 	return { task: taskResult, originalSubtaskCount, originalSubtasks };
1053 | }
1054 | 
1055 | /**
1056 |  * Truncates text to a specified length
1057 |  * @param {string} text - The text to truncate
1058 |  * @param {number} maxLength - The maximum length
1059 |  * @returns {string} The truncated text
1060 |  */
1061 | function truncate(text, maxLength) {
1062 | 	if (!text || text.length <= maxLength) {
1063 | 		return text;
1064 | 	}
1065 | 
1066 | 	return `${text.slice(0, maxLength - 3)}...`;
1067 | }
1068 | 
1069 | /**
1070 |  * Checks if array or object are empty
1071 |  * @param {*} value - The value to check
1072 |  * @returns {boolean} True if empty, false otherwise
1073 |  */
1074 | function isEmpty(value) {
1075 | 	if (Array.isArray(value)) {
1076 | 		return value.length === 0;
1077 | 	} else if (typeof value === 'object' && value !== null) {
1078 | 		return Object.keys(value).length === 0;
1079 | 	}
1080 | 
1081 | 	return false; // Not an array or object, or is null
1082 | }
1083 | 
1084 | /**
1085 |  * Find cycles in a dependency graph using DFS
1086 |  * @param {string} subtaskId - Current subtask ID
1087 |  * @param {Map} dependencyMap - Map of subtask IDs to their dependencies
1088 |  * @param {Set} visited - Set of visited nodes
1089 |  * @param {Set} recursionStack - Set of nodes in current recursion stack
1090 |  * @returns {Array} - List of dependency edges that need to be removed to break cycles
1091 |  */
1092 | function findCycles(
1093 | 	subtaskId,
1094 | 	dependencyMap,
1095 | 	visited = new Set(),
1096 | 	recursionStack = new Set(),
1097 | 	path = []
1098 | ) {
1099 | 	// Mark the current node as visited and part of recursion stack
1100 | 	visited.add(subtaskId);
1101 | 	recursionStack.add(subtaskId);
1102 | 	path.push(subtaskId);
1103 | 
1104 | 	const cyclesToBreak = [];
1105 | 
1106 | 	// Get all dependencies of the current subtask
1107 | 	const dependencies = dependencyMap.get(subtaskId) || [];
1108 | 
1109 | 	// For each dependency
1110 | 	for (const depId of dependencies) {
1111 | 		// If not visited, recursively check for cycles
1112 | 		if (!visited.has(depId)) {
1113 | 			const cycles = findCycles(depId, dependencyMap, visited, recursionStack, [
1114 | 				...path
1115 | 			]);
1116 | 			cyclesToBreak.push(...cycles);
1117 | 		}
1118 | 		// If the dependency is in the recursion stack, we found a cycle
1119 | 		else if (recursionStack.has(depId)) {
1120 | 			// Find the position of the dependency in the path
1121 | 			const cycleStartIndex = path.indexOf(depId);
1122 | 			// The last edge in the cycle is what we want to remove
1123 | 			const cycleEdges = path.slice(cycleStartIndex);
1124 | 			// We'll remove the last edge in the cycle (the one that points back)
1125 | 			cyclesToBreak.push(depId);
1126 | 		}
1127 | 	}
1128 | 
1129 | 	// Remove the node from recursion stack before returning
1130 | 	recursionStack.delete(subtaskId);
1131 | 
1132 | 	return cyclesToBreak;
1133 | }
1134 | 
1135 | /**
1136 |  * Unified dependency traversal utility that supports both forward and reverse dependency traversal
1137 |  * @param {Array} sourceTasks - Array of source tasks to start traversal from
1138 |  * @param {Array} allTasks - Array of all tasks to search within
1139 |  * @param {Object} options - Configuration options
1140 |  * @param {number} options.maxDepth - Maximum recursion depth (default: 50)
1141 |  * @param {boolean} options.includeSelf - Whether to include self-references (default: false)
1142 |  * @param {'forward'|'reverse'} options.direction - Direction of traversal (default: 'forward')
1143 |  * @param {Function} options.logger - Optional logger function for warnings
1144 |  * @returns {Array} Array of all dependency task IDs found through traversal
1145 |  */
1146 | function traverseDependencies(sourceTasks, allTasks, options = {}) {
1147 | 	const {
1148 | 		maxDepth = 50,
1149 | 		includeSelf = false,
1150 | 		direction = 'forward',
1151 | 		logger = null
1152 | 	} = options;
1153 | 
1154 | 	const dependentTaskIds = new Set();
1155 | 	const processedIds = new Set();
1156 | 
1157 | 	// Helper function to normalize dependency IDs while preserving subtask format
1158 | 	function normalizeDependencyId(depId) {
1159 | 		if (typeof depId === 'string') {
1160 | 			// Preserve string format for subtask IDs like "1.2"
1161 | 			if (depId.includes('.')) {
1162 | 				return depId;
1163 | 			}
1164 | 			// Convert simple string numbers to numbers for consistency
1165 | 			const parsed = parseInt(depId, 10);
1166 | 			return isNaN(parsed) ? depId : parsed;
1167 | 		}
1168 | 		return depId;
1169 | 	}
1170 | 
1171 | 	// Helper function for forward dependency traversal
1172 | 	function findForwardDependencies(taskId, currentDepth = 0) {
1173 | 		// Check depth limit
1174 | 		if (currentDepth >= maxDepth) {
1175 | 			const warnMsg = `Maximum recursion depth (${maxDepth}) reached for task ${taskId}`;
1176 | 			if (logger && typeof logger.warn === 'function') {
1177 | 				logger.warn(warnMsg);
1178 | 			} else if (typeof log !== 'undefined' && log.warn) {
1179 | 				log.warn(warnMsg);
1180 | 			} else {
1181 | 				console.warn(warnMsg);
1182 | 			}
1183 | 			return;
1184 | 		}
1185 | 
1186 | 		if (processedIds.has(taskId)) {
1187 | 			return; // Avoid infinite loops
1188 | 		}
1189 | 		processedIds.add(taskId);
1190 | 
1191 | 		const task = allTasks.find((t) => t.id === taskId);
1192 | 		if (!task || !Array.isArray(task.dependencies)) {
1193 | 			return;
1194 | 		}
1195 | 
1196 | 		task.dependencies.forEach((depId) => {
1197 | 			const normalizedDepId = normalizeDependencyId(depId);
1198 | 
1199 | 			// Skip invalid dependencies and optionally skip self-references
1200 | 			if (
1201 | 				normalizedDepId == null ||
1202 | 				(!includeSelf && normalizedDepId === taskId)
1203 | 			) {
1204 | 				return;
1205 | 			}
1206 | 
1207 | 			dependentTaskIds.add(normalizedDepId);
1208 | 			// Recursively find dependencies of this dependency
1209 | 			findForwardDependencies(normalizedDepId, currentDepth + 1);
1210 | 		});
1211 | 	}
1212 | 
1213 | 	// Helper function for reverse dependency traversal
1214 | 	function findReverseDependencies(taskId, currentDepth = 0) {
1215 | 		// Check depth limit
1216 | 		if (currentDepth >= maxDepth) {
1217 | 			const warnMsg = `Maximum recursion depth (${maxDepth}) reached for task ${taskId}`;
1218 | 			if (logger && typeof logger.warn === 'function') {
1219 | 				logger.warn(warnMsg);
1220 | 			} else if (typeof log !== 'undefined' && log.warn) {
1221 | 				log.warn(warnMsg);
1222 | 			} else {
1223 | 				console.warn(warnMsg);
1224 | 			}
1225 | 			return;
1226 | 		}
1227 | 
1228 | 		if (processedIds.has(taskId)) {
1229 | 			return; // Avoid infinite loops
1230 | 		}
1231 | 		processedIds.add(taskId);
1232 | 
1233 | 		allTasks.forEach((task) => {
1234 | 			if (task.dependencies && Array.isArray(task.dependencies)) {
1235 | 				const dependsOnTaskId = task.dependencies.some((depId) => {
1236 | 					const normalizedDepId = normalizeDependencyId(depId);
1237 | 					return normalizedDepId === taskId;
1238 | 				});
1239 | 
1240 | 				if (dependsOnTaskId) {
1241 | 					// Skip invalid dependencies and optionally skip self-references
1242 | 					if (task.id == null || (!includeSelf && task.id === taskId)) {
1243 | 						return;
1244 | 					}
1245 | 
1246 | 					dependentTaskIds.add(task.id);
1247 | 					// Recursively find tasks that depend on this task
1248 | 					findReverseDependencies(task.id, currentDepth + 1);
1249 | 				}
1250 | 			}
1251 | 		});
1252 | 	}
1253 | 
1254 | 	// Choose traversal function based on direction
1255 | 	const traversalFunc =
1256 | 		direction === 'reverse' ? findReverseDependencies : findForwardDependencies;
1257 | 
1258 | 	// Start traversal from each source task
1259 | 	sourceTasks.forEach((sourceTask) => {
1260 | 		if (sourceTask && sourceTask.id) {
1261 | 			traversalFunc(sourceTask.id);
1262 | 		}
1263 | 	});
1264 | 
1265 | 	return Array.from(dependentTaskIds);
1266 | }
1267 | 
1268 | /**
1269 |  * Convert a string from camelCase to kebab-case
1270 |  * @param {string} str - The string to convert
1271 |  * @returns {string} The kebab-case version of the string
1272 |  */
1273 | const toKebabCase = (str) => {
1274 | 	// Special handling for common acronyms
1275 | 	const withReplacedAcronyms = str
1276 | 		.replace(/ID/g, 'Id')
1277 | 		.replace(/API/g, 'Api')
1278 | 		.replace(/UI/g, 'Ui')
1279 | 		.replace(/URL/g, 'Url')
1280 | 		.replace(/URI/g, 'Uri')
1281 | 		.replace(/JSON/g, 'Json')
1282 | 		.replace(/XML/g, 'Xml')
1283 | 		.replace(/HTML/g, 'Html')
1284 | 		.replace(/CSS/g, 'Css');
1285 | 
1286 | 	// Insert hyphens before capital letters and convert to lowercase
1287 | 	return withReplacedAcronyms
1288 | 		.replace(/([A-Z])/g, '-$1')
1289 | 		.toLowerCase()
1290 | 		.replace(/^-/, ''); // Remove leading hyphen if present
1291 | };
1292 | 
1293 | /**
1294 |  * Detect camelCase flags in command arguments
1295 |  * @param {string[]} args - Command line arguments to check
1296 |  * @returns {Array<{original: string, kebabCase: string}>} - List of flags that should be converted
1297 |  */
1298 | function detectCamelCaseFlags(args) {
1299 | 	const camelCaseFlags = [];
1300 | 	for (const arg of args) {
1301 | 		if (arg.startsWith('--')) {
1302 | 			const flagName = arg.split('=')[0].slice(2); // Remove -- and anything after =
1303 | 
1304 | 			// Skip single-word flags - they can't be camelCase
1305 | 			if (!flagName.includes('-') && !/[A-Z]/.test(flagName)) {
1306 | 				continue;
1307 | 			}
1308 | 
1309 | 			// Check for camelCase pattern (lowercase followed by uppercase)
1310 | 			if (/[a-z][A-Z]/.test(flagName)) {
1311 | 				const kebabVersion = toKebabCase(flagName);
1312 | 				if (kebabVersion !== flagName) {
1313 | 					camelCaseFlags.push({
1314 | 						original: flagName,
1315 | 						kebabCase: kebabVersion
1316 | 					});
1317 | 				}
1318 | 			}
1319 | 		}
1320 | 	}
1321 | 	return camelCaseFlags;
1322 | }
1323 | 
1324 | /**
1325 |  * Aggregates an array of telemetry objects into a single summary object.
1326 |  * @param {Array<Object>} telemetryArray - Array of telemetryData objects.
1327 |  * @param {string} overallCommandName - The name for the aggregated command.
1328 |  * @returns {Object|null} Aggregated telemetry object or null if input is empty.
1329 |  */
1330 | function aggregateTelemetry(telemetryArray, overallCommandName) {
1331 | 	if (!telemetryArray || telemetryArray.length === 0) {
1332 | 		return null;
1333 | 	}
1334 | 
1335 | 	const aggregated = {
1336 | 		timestamp: new Date().toISOString(), // Use current time for aggregation time
1337 | 		userId: telemetryArray[0].userId, // Assume userId is consistent
1338 | 		commandName: overallCommandName,
1339 | 		modelUsed: 'Multiple', // Default if models vary
1340 | 		providerName: 'Multiple', // Default if providers vary
1341 | 		inputTokens: 0,
1342 | 		outputTokens: 0,
1343 | 		totalTokens: 0,
1344 | 		totalCost: 0,
1345 | 		currency: telemetryArray[0].currency || 'USD' // Assume consistent currency or default
1346 | 	};
1347 | 
1348 | 	const uniqueModels = new Set();
1349 | 	const uniqueProviders = new Set();
1350 | 	const uniqueCurrencies = new Set();
1351 | 
1352 | 	telemetryArray.forEach((item) => {
1353 | 		aggregated.inputTokens += item.inputTokens || 0;
1354 | 		aggregated.outputTokens += item.outputTokens || 0;
1355 | 		aggregated.totalCost += item.totalCost || 0;
1356 | 		uniqueModels.add(item.modelUsed);
1357 | 		uniqueProviders.add(item.providerName);
1358 | 		uniqueCurrencies.add(item.currency || 'USD');
1359 | 	});
1360 | 
1361 | 	aggregated.totalTokens = aggregated.inputTokens + aggregated.outputTokens;
1362 | 	aggregated.totalCost = parseFloat(aggregated.totalCost.toFixed(6)); // Fix precision
1363 | 
1364 | 	if (uniqueModels.size === 1) {
1365 | 		aggregated.modelUsed = [...uniqueModels][0];
1366 | 	}
1367 | 	if (uniqueProviders.size === 1) {
1368 | 		aggregated.providerName = [...uniqueProviders][0];
1369 | 	}
1370 | 	if (uniqueCurrencies.size > 1) {
1371 | 		aggregated.currency = 'Multiple'; // Mark if currencies actually differ
1372 | 	} else if (uniqueCurrencies.size === 1) {
1373 | 		aggregated.currency = [...uniqueCurrencies][0];
1374 | 	}
1375 | 
1376 | 	return aggregated;
1377 | }
1378 | 
1379 | /**
1380 |  * @deprecated Use TaskMaster.getCurrentTag() instead
1381 |  * Gets the current tag from state.json or falls back to defaultTag from config
1382 |  * @param {string} projectRoot - The project root directory (required)
1383 |  * @returns {string} The current tag name
1384 |  */
1385 | function getCurrentTag(projectRoot) {
1386 | 	if (!projectRoot) {
1387 | 		throw new Error('projectRoot is required for getCurrentTag');
1388 | 	}
1389 | 
1390 | 	try {
1391 | 		// Try to read current tag from state.json using fs directly
1392 | 		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
1393 | 		if (fs.existsSync(statePath)) {
1394 | 			const rawState = fs.readFileSync(statePath, 'utf8');
1395 | 			const stateData = JSON.parse(rawState);
1396 | 			if (stateData && stateData.currentTag) {
1397 | 				return stateData.currentTag;
1398 | 			}
1399 | 		}
1400 | 	} catch (error) {
1401 | 		// Ignore errors, fall back to default
1402 | 	}
1403 | 
1404 | 	// Fall back to defaultTag from config using fs directly
1405 | 	try {
1406 | 		const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
1407 | 		if (fs.existsSync(configPath)) {
1408 | 			const rawConfig = fs.readFileSync(configPath, 'utf8');
1409 | 			const configData = JSON.parse(rawConfig);
1410 | 			if (configData && configData.global && configData.global.defaultTag) {
1411 | 				return configData.global.defaultTag;
1412 | 			}
1413 | 		}
1414 | 	} catch (error) {
1415 | 		// Ignore errors, use hardcoded default
1416 | 	}
1417 | 
1418 | 	// Final fallback
1419 | 	return 'master';
1420 | }
1421 | 
1422 | /**
1423 |  * Resolves the tag to use based on options
1424 |  * @param {Object} options - Options object
1425 |  * @param {string} options.projectRoot - The project root directory (required)
1426 |  * @param {string} [options.tag] - Explicit tag to use
1427 |  * @returns {string} The resolved tag name
1428 |  */
1429 | function resolveTag(options = {}) {
1430 | 	const { projectRoot, tag } = options;
1431 | 
1432 | 	if (!projectRoot) {
1433 | 		throw new Error('projectRoot is required for resolveTag');
1434 | 	}
1435 | 
1436 | 	// If explicit tag provided, use it
1437 | 	if (tag) {
1438 | 		return tag;
1439 | 	}
1440 | 
1441 | 	// Otherwise get current tag from state/config
1442 | 	return getCurrentTag(projectRoot);
1443 | }
1444 | 
1445 | /**
1446 |  * Gets the tasks array for a specific tag from tagged tasks.json data
1447 |  * @param {Object} data - The parsed tasks.json data (after migration)
1448 |  * @param {string} tagName - The tag name to get tasks for
1449 |  * @returns {Array} The tasks array for the specified tag, or empty array if not found
1450 |  */
1451 | function getTasksForTag(data, tagName) {
1452 | 	if (!data || !tagName) {
1453 | 		return [];
1454 | 	}
1455 | 
1456 | 	// Handle migrated format: { "master": { "tasks": [...] }, "otherTag": { "tasks": [...] } }
1457 | 	if (
1458 | 		data[tagName] &&
1459 | 		data[tagName].tasks &&
1460 | 		Array.isArray(data[tagName].tasks)
1461 | 	) {
1462 | 		return data[tagName].tasks;
1463 | 	}
1464 | 
1465 | 	return [];
1466 | }
1467 | 
1468 | /**
1469 |  * Sets the tasks array for a specific tag in the data structure
1470 |  * @param {Object} data - The tasks.json data object
1471 |  * @param {string} tagName - The tag name to set tasks for
1472 |  * @param {Array} tasks - The tasks array to set
1473 |  * @returns {Object} The updated data object
1474 |  */
1475 | function setTasksForTag(data, tagName, tasks) {
1476 | 	if (!data) {
1477 | 		data = {};
1478 | 	}
1479 | 
1480 | 	if (!data[tagName]) {
1481 | 		data[tagName] = {};
1482 | 	}
1483 | 
1484 | 	data[tagName].tasks = tasks || [];
1485 | 	return data;
1486 | }
1487 | 
1488 | /**
1489 |  * Flatten tasks array to include subtasks as individual searchable items
1490 |  * @param {Array} tasks - Array of task objects
1491 |  * @returns {Array} Flattened array including both tasks and subtasks
1492 |  */
1493 | function flattenTasksWithSubtasks(tasks) {
1494 | 	const flattened = [];
1495 | 
1496 | 	for (const task of tasks) {
1497 | 		// Add the main task
1498 | 		flattened.push({
1499 | 			...task,
1500 | 			searchableId: task.id.toString(), // For consistent ID handling
1501 | 			isSubtask: false
1502 | 		});
1503 | 
1504 | 		// Add subtasks if they exist
1505 | 		if (task.subtasks && task.subtasks.length > 0) {
1506 | 			for (const subtask of task.subtasks) {
1507 | 				flattened.push({
1508 | 					...subtask,
1509 | 					searchableId: `${task.id}.${subtask.id}`, // Format: "15.2"
1510 | 					isSubtask: true,
1511 | 					parentId: task.id,
1512 | 					parentTitle: task.title,
1513 | 					// Enhance subtask context with parent information
1514 | 					title: `${subtask.title} (subtask of: ${task.title})`,
1515 | 					description: `${subtask.description} [Parent: ${task.description}]`
1516 | 				});
1517 | 			}
1518 | 		}
1519 | 	}
1520 | 
1521 | 	return flattened;
1522 | }
1523 | 
1524 | /**
1525 |  * Ensures the tag object has a metadata object with created/updated timestamps.
1526 |  * @param {Object} tagObj - The tag object (e.g., data['master'])
1527 |  * @param {Object} [opts] - Optional fields (e.g., description, skipUpdate)
1528 |  * @param {string} [opts.description] - Description for the tag
1529 |  * @param {boolean} [opts.skipUpdate] - If true, don't update the 'updated' timestamp
1530 |  * @returns {Object} The updated tag object (for chaining)
1531 |  */
1532 | function ensureTagMetadata(tagObj, opts = {}) {
1533 | 	if (!tagObj || typeof tagObj !== 'object') {
1534 | 		throw new Error('tagObj must be a valid object');
1535 | 	}
1536 | 
1537 | 	const now = new Date().toISOString();
1538 | 
1539 | 	if (!tagObj.metadata) {
1540 | 		// Create new metadata object
1541 | 		tagObj.metadata = {
1542 | 			created: now,
1543 | 			updated: now,
1544 | 			...(opts.description ? { description: opts.description } : {})
1545 | 		};
1546 | 	} else {
1547 | 		// Ensure existing metadata has required fields
1548 | 		if (!tagObj.metadata.created) {
1549 | 			tagObj.metadata.created = now;
1550 | 		}
1551 | 
1552 | 		// Update timestamp unless explicitly skipped
1553 | 		if (!opts.skipUpdate) {
1554 | 			tagObj.metadata.updated = now;
1555 | 		}
1556 | 
1557 | 		// Add description if provided and not already present
1558 | 		if (opts.description && !tagObj.metadata.description) {
1559 | 			tagObj.metadata.description = opts.description;
1560 | 		}
1561 | 	}
1562 | 
1563 | 	return tagObj;
1564 | }
1565 | 
1566 | /**
1567 |  * Strip ANSI color codes from a string
1568 |  * Useful for testing, logging to files, or when clean text output is needed
1569 |  * @param {string} text - The text that may contain ANSI color codes
1570 |  * @returns {string} - The text with ANSI color codes removed
1571 |  */
1572 | function stripAnsiCodes(text) {
1573 | 	if (typeof text !== 'string') {
1574 | 		return text;
1575 | 	}
1576 | 	// Remove ANSI escape sequences (color codes, cursor movements, etc.)
1577 | 	return text.replace(/\x1b\[[0-9;]*m/g, '');
1578 | }
1579 | 
1580 | // Export all utility functions and configuration
1581 | export {
1582 | 	LOG_LEVELS,
1583 | 	log,
1584 | 	readJSON,
1585 | 	writeJSON,
1586 | 	sanitizePrompt,
1587 | 	readComplexityReport,
1588 | 	findTaskInComplexityReport,
1589 | 	taskExists,
1590 | 	formatTaskId,
1591 | 	findTaskById,
1592 | 	truncate,
1593 | 	isEmpty,
1594 | 	findCycles,
1595 | 	traverseDependencies,
1596 | 	toKebabCase,
1597 | 	detectCamelCaseFlags,
1598 | 	disableSilentMode,
1599 | 	enableSilentMode,
1600 | 	getTaskManager,
1601 | 	isSilentMode,
1602 | 	addComplexityToTask,
1603 | 	resolveEnvVariable,
1604 | 	findProjectRoot,
1605 | 	getTagAwareFilePath,
1606 | 	slugifyTagForFilePath,
1607 | 	aggregateTelemetry,
1608 | 	getCurrentTag,
1609 | 	resolveTag,
1610 | 	getTasksForTag,
1611 | 	setTasksForTag,
1612 | 	performCompleteTagMigration,
1613 | 	migrateConfigJson,
1614 | 	createStateJson,
1615 | 	markMigrationForNotice,
1616 | 	flattenTasksWithSubtasks,
1617 | 	ensureTagMetadata,
1618 | 	stripAnsiCodes,
1619 | 	normalizeTaskIds
1620 | };
1621 | 
```
Page 60/69FirstPrevNextLast