#
tokens: 37490/50000 2/821 files (page 32/38)
lines: off (toggle) GitHub
raw markdown copy
This is page 32 of 38. Use http://codebase.md/eyaltoledano/claude-task-master?page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── agents
│   │   ├── task-checker.md
│   │   ├── task-executor.md
│   │   └── task-orchestrator.md
│   ├── commands
│   │   ├── dedupe.md
│   │   └── tm
│   │       ├── add-dependency
│   │       │   └── add-dependency.md
│   │       ├── add-subtask
│   │       │   ├── add-subtask.md
│   │       │   └── convert-task-to-subtask.md
│   │       ├── add-task
│   │       │   └── add-task.md
│   │       ├── analyze-complexity
│   │       │   └── analyze-complexity.md
│   │       ├── complexity-report
│   │       │   └── complexity-report.md
│   │       ├── expand
│   │       │   ├── expand-all-tasks.md
│   │       │   └── expand-task.md
│   │       ├── fix-dependencies
│   │       │   └── fix-dependencies.md
│   │       ├── generate
│   │       │   └── generate-tasks.md
│   │       ├── help.md
│   │       ├── init
│   │       │   ├── init-project-quick.md
│   │       │   └── init-project.md
│   │       ├── learn.md
│   │       ├── list
│   │       │   ├── list-tasks-by-status.md
│   │       │   ├── list-tasks-with-subtasks.md
│   │       │   └── list-tasks.md
│   │       ├── models
│   │       │   ├── setup-models.md
│   │       │   └── view-models.md
│   │       ├── next
│   │       │   └── next-task.md
│   │       ├── parse-prd
│   │       │   ├── parse-prd-with-research.md
│   │       │   └── parse-prd.md
│   │       ├── remove-dependency
│   │       │   └── remove-dependency.md
│   │       ├── remove-subtask
│   │       │   └── remove-subtask.md
│   │       ├── remove-subtasks
│   │       │   ├── remove-all-subtasks.md
│   │       │   └── remove-subtasks.md
│   │       ├── remove-task
│   │       │   └── remove-task.md
│   │       ├── set-status
│   │       │   ├── to-cancelled.md
│   │       │   ├── to-deferred.md
│   │       │   ├── to-done.md
│   │       │   ├── to-in-progress.md
│   │       │   ├── to-pending.md
│   │       │   └── to-review.md
│   │       ├── setup
│   │       │   ├── install-taskmaster.md
│   │       │   └── quick-install-taskmaster.md
│   │       ├── show
│   │       │   └── show-task.md
│   │       ├── status
│   │       │   └── project-status.md
│   │       ├── sync-readme
│   │       │   └── sync-readme.md
│   │       ├── tm-main.md
│   │       ├── update
│   │       │   ├── update-single-task.md
│   │       │   ├── update-task.md
│   │       │   └── update-tasks-from-id.md
│   │       ├── utils
│   │       │   └── analyze-project.md
│   │       ├── validate-dependencies
│   │       │   └── validate-dependencies.md
│   │       └── workflows
│   │           ├── auto-implement-tasks.md
│   │           ├── command-pipeline.md
│   │           └── smart-workflow.md
│   └── TM_COMMANDS_GUIDE.md
├── .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
│   └── 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
│   │   ├── 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
│   │   ├── test-prd.txt
│   │   └── tm-core-phase-1.txt
│   ├── reports
│   │   ├── task-complexity-report_cc-kiro-hooks.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.txt
├── .vscode
│   ├── extensions.json
│   └── settings.json
├── apps
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── commands
│   │   │   │   ├── auth.command.ts
│   │   │   │   ├── context.command.ts
│   │   │   │   ├── list.command.ts
│   │   │   │   ├── set-status.command.ts
│   │   │   │   ├── show.command.ts
│   │   │   │   └── start.command.ts
│   │   │   ├── index.ts
│   │   │   ├── ui
│   │   │   │   ├── components
│   │   │   │   │   ├── dashboard.component.ts
│   │   │   │   │   ├── header.component.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── next-task.component.ts
│   │   │   │   │   ├── suggested-steps.component.ts
│   │   │   │   │   └── task-detail.component.ts
│   │   │   │   └── index.ts
│   │   │   └── utils
│   │   │       ├── auto-update.ts
│   │   │       └── ui.ts
│   │   └── tsconfig.json
│   ├── 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
│   │   │   └── task-structure.mdx
│   │   ├── CHANGELOG.md
│   │   ├── docs.json
│   │   ├── favicon.svg
│   │   ├── getting-started
│   │   │   ├── 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
│   │   ├── 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
├── assets
│   ├── .windsurfrules
│   ├── AGENTS.md
│   ├── claude
│   │   ├── agents
│   │   │   ├── task-checker.md
│   │   │   ├── task-executor.md
│   │   │   └── task-orchestrator.md
│   │   ├── commands
│   │   │   └── tm
│   │   │       ├── add-dependency
│   │   │       │   └── add-dependency.md
│   │   │       ├── add-subtask
│   │   │       │   ├── add-subtask.md
│   │   │       │   └── convert-task-to-subtask.md
│   │   │       ├── add-task
│   │   │       │   └── add-task.md
│   │   │       ├── analyze-complexity
│   │   │       │   └── analyze-complexity.md
│   │   │       ├── clear-subtasks
│   │   │       │   ├── clear-all-subtasks.md
│   │   │       │   └── clear-subtasks.md
│   │   │       ├── complexity-report
│   │   │       │   └── complexity-report.md
│   │   │       ├── expand
│   │   │       │   ├── expand-all-tasks.md
│   │   │       │   └── expand-task.md
│   │   │       ├── fix-dependencies
│   │   │       │   └── fix-dependencies.md
│   │   │       ├── generate
│   │   │       │   └── generate-tasks.md
│   │   │       ├── help.md
│   │   │       ├── init
│   │   │       │   ├── init-project-quick.md
│   │   │       │   └── init-project.md
│   │   │       ├── learn.md
│   │   │       ├── list
│   │   │       │   ├── list-tasks-by-status.md
│   │   │       │   ├── list-tasks-with-subtasks.md
│   │   │       │   └── list-tasks.md
│   │   │       ├── models
│   │   │       │   ├── setup-models.md
│   │   │       │   └── view-models.md
│   │   │       ├── next
│   │   │       │   └── next-task.md
│   │   │       ├── parse-prd
│   │   │       │   ├── parse-prd-with-research.md
│   │   │       │   └── parse-prd.md
│   │   │       ├── remove-dependency
│   │   │       │   └── remove-dependency.md
│   │   │       ├── remove-subtask
│   │   │       │   └── remove-subtask.md
│   │   │       ├── remove-subtasks
│   │   │       │   ├── remove-all-subtasks.md
│   │   │       │   └── remove-subtasks.md
│   │   │       ├── remove-task
│   │   │       │   └── remove-task.md
│   │   │       ├── set-status
│   │   │       │   ├── to-cancelled.md
│   │   │       │   ├── to-deferred.md
│   │   │       │   ├── to-done.md
│   │   │       │   ├── to-in-progress.md
│   │   │       │   ├── to-pending.md
│   │   │       │   └── to-review.md
│   │   │       ├── setup
│   │   │       │   ├── install-taskmaster.md
│   │   │       │   └── quick-install-taskmaster.md
│   │   │       ├── show
│   │   │       │   └── show-task.md
│   │   │       ├── status
│   │   │       │   └── project-status.md
│   │   │       ├── sync-readme
│   │   │       │   └── sync-readme.md
│   │   │       ├── tm-main.md
│   │   │       ├── update
│   │   │       │   ├── update-single-task.md
│   │   │       │   ├── update-task.md
│   │   │       │   └── update-tasks-from-id.md
│   │   │       ├── utils
│   │   │       │   └── analyze-project.md
│   │   │       ├── validate-dependencies
│   │   │       │   └── validate-dependencies.md
│   │   │       └── workflows
│   │   │           ├── auto-implement-tasks.md
│   │   │           ├── command-pipeline.md
│   │   │           └── smart-workflow.md
│   │   └── TM_COMMANDS_GUIDE.md
│   ├── config.json
│   ├── env.example
│   ├── example_prd.txt
│   ├── 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.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
│   ├── CLI-COMMANDER-PATTERN.md
│   ├── command-reference.md
│   ├── configuration.md
│   ├── contributor-docs
│   │   └── testing-roo-integration.md
│   ├── cross-tag-task-movement.md
│   ├── examples
│   │   └── claude-code-usage.md
│   ├── examples.md
│   ├── licensing.md
│   ├── mcp-provider-guide.md
│   ├── mcp-provider.md
│   ├── migration-guide.md
│   ├── models.md
│   ├── providers
│   │   └── gemini-cli.md
│   ├── README.md
│   ├── scripts
│   │   └── models-json-to-markdown.js
│   ├── task-structure.md
│   └── tutorial.md
├── images
│   └── 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
│       │   │   ├── list-tasks.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
│       │   │   ├── show-task.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
│           ├── get-task.js
│           ├── get-tasks.js
│           ├── index.js
│           ├── initialize-project.js
│           ├── list-tags.js
│           ├── models.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.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
│   ├── build-config
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   └── tsdown.base.ts
│   │   └── tsconfig.json
│   └── tm-core
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── docs
│       │   └── listTasks-architecture.md
│       ├── package.json
│       ├── POC-STATUS.md
│       ├── README.md
│       ├── src
│       │   ├── auth
│       │   │   ├── auth-manager.test.ts
│       │   │   ├── auth-manager.ts
│       │   │   ├── config.ts
│       │   │   ├── credential-store.test.ts
│       │   │   ├── credential-store.ts
│       │   │   ├── index.ts
│       │   │   ├── oauth-service.ts
│       │   │   ├── supabase-session-storage.ts
│       │   │   └── types.ts
│       │   ├── clients
│       │   │   ├── index.ts
│       │   │   └── supabase-client.ts
│       │   ├── config
│       │   │   ├── config-manager.spec.ts
│       │   │   ├── config-manager.ts
│       │   │   ├── index.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
│       │   ├── constants
│       │   │   └── index.ts
│       │   ├── entities
│       │   │   └── task.entity.ts
│       │   ├── errors
│       │   │   ├── index.ts
│       │   │   └── task-master-error.ts
│       │   ├── executors
│       │   │   ├── base-executor.ts
│       │   │   ├── claude-executor.ts
│       │   │   ├── executor-factory.ts
│       │   │   ├── executor-service.ts
│       │   │   ├── index.ts
│       │   │   └── types.ts
│       │   ├── index.ts
│       │   ├── interfaces
│       │   │   ├── ai-provider.interface.ts
│       │   │   ├── configuration.interface.ts
│       │   │   ├── index.ts
│       │   │   └── storage.interface.ts
│       │   ├── logger
│       │   │   ├── factory.ts
│       │   │   ├── index.ts
│       │   │   └── logger.ts
│       │   ├── mappers
│       │   │   └── TaskMapper.ts
│       │   ├── parser
│       │   │   └── index.ts
│       │   ├── providers
│       │   │   ├── ai
│       │   │   │   ├── base-provider.ts
│       │   │   │   └── index.ts
│       │   │   └── index.ts
│       │   ├── repositories
│       │   │   ├── supabase-task-repository.ts
│       │   │   └── task-repository.interface.ts
│       │   ├── services
│       │   │   ├── index.ts
│       │   │   ├── organization.service.ts
│       │   │   ├── task-execution-service.ts
│       │   │   └── task-service.ts
│       │   ├── storage
│       │   │   ├── api-storage.ts
│       │   │   ├── file-storage
│       │   │   │   ├── file-operations.ts
│       │   │   │   ├── file-storage.ts
│       │   │   │   ├── format-handler.ts
│       │   │   │   ├── index.ts
│       │   │   │   └── path-resolver.ts
│       │   │   ├── index.ts
│       │   │   └── storage-factory.ts
│       │   ├── subpath-exports.test.ts
│       │   ├── task-master-core.ts
│       │   ├── types
│       │   │   ├── database.types.ts
│       │   │   ├── index.ts
│       │   │   └── legacy.ts
│       │   └── utils
│       │       ├── id-generator.ts
│       │       └── index.ts
│       ├── tests
│       │   ├── integration
│       │   │   └── list-tasks.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
│   ├── dev.js
│   ├── init.js
│   ├── modules
│   │   ├── ai-services-unified.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
├── src
│   ├── ai-providers
│   │   ├── anthropic.js
│   │   ├── azure.js
│   │   ├── base-provider.js
│   │   ├── bedrock.js
│   │   ├── claude-code.js
│   │   ├── custom-sdk
│   │   │   ├── claude-code
│   │   │   │   ├── errors.js
│   │   │   │   ├── index.js
│   │   │   │   ├── json-extractor.js
│   │   │   │   ├── language-model.js
│   │   │   │   ├── message-converter.js
│   │   │   │   └── types.js
│   │   │   └── grok-cli
│   │   │       ├── errors.js
│   │   │       ├── index.js
│   │   │       ├── json-extractor.js
│   │   │       ├── language-model.js
│   │   │       ├── message-converter.js
│   │   │       └── types.js
│   │   ├── gemini-cli.js
│   │   ├── google-vertex.js
│   │   ├── google.js
│   │   ├── grok-cli.js
│   │   ├── groq.js
│   │   ├── index.js
│   │   ├── ollama.js
│   │   ├── openai.js
│   │   ├── openrouter.js
│   │   ├── perplexity.js
│   │   └── xai.js
│   ├── constants
│   │   ├── commands.js
│   │   ├── paths.js
│   │   ├── profiles.js
│   │   ├── providers.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
│   ├── 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
│   ├── fixture
│   │   └── test-tasks.json
│   ├── fixtures
│   │   ├── .taskmasterconfig
│   │   ├── sample-claude-response.js
│   │   ├── sample-prd.txt
│   │   └── sample-tasks.js
│   ├── integration
│   │   ├── 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
│   ├── 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
│       │   ├── claude-code.test.js
│       │   ├── custom-sdk
│       │   │   └── claude-code
│       │   │       └── language-model.test.js
│       │   ├── gemini-cli.test.js
│       │   ├── mcp-components.test.js
│       │   └── openai.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
│       ├── 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
│       ├── providers
│       │   └── provider-registry.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
│       │       │   ├── move-task-cross-tag.test.js
│       │       │   ├── move-task.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
```

# Files

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

```javascript
/**
 * utils.js
 * Utility functions for the Task Master CLI
 */

import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import dotenv from 'dotenv';
// Import specific config getters needed here
import { getLogLevel, getDebugFlag } from './config-manager.js';
import * as gitUtils from './utils/git-utils.js';
import {
	COMPLEXITY_REPORT_FILE,
	LEGACY_COMPLEXITY_REPORT_FILE,
	LEGACY_CONFIG_FILE
} from '../../src/constants/paths.js';

// Global silent mode flag
let silentMode = false;

// --- Environment Variable Resolution Utility ---
/**
 * Resolves an environment variable's value.
 * Precedence:
 * 1. session.env (if session provided)
 * 2. process.env
 * 3. .env file at projectRoot (if projectRoot provided)
 * @param {string} key - The environment variable key.
 * @param {object|null} [session=null] - The MCP session object.
 * @param {string|null} [projectRoot=null] - The project root directory (for .env fallback).
 * @returns {string|undefined} The value of the environment variable or undefined if not found.
 */
function resolveEnvVariable(key, session = null, projectRoot = null) {
	// 1. Check session.env
	if (session?.env?.[key]) {
		return session.env[key];
	}

	// 2. Read .env file at projectRoot
	if (projectRoot) {
		const envPath = path.join(projectRoot, '.env');
		if (fs.existsSync(envPath)) {
			try {
				const envFileContent = fs.readFileSync(envPath, 'utf-8');
				const parsedEnv = dotenv.parse(envFileContent); // Use dotenv to parse
				if (parsedEnv && parsedEnv[key]) {
					// console.log(`DEBUG: Found key ${key} in ${envPath}`); // Optional debug log
					return parsedEnv[key];
				}
			} catch (error) {
				// Log error but don't crash, just proceed as if key wasn't found in file
				log('warn', `Could not read or parse ${envPath}: ${error.message}`);
			}
		}
	}

	// 3. Fallback: Check process.env
	if (process.env[key]) {
		return process.env[key];
	}

	// Not found anywhere
	return undefined;
}

// --- Tag-Aware Path Resolution Utility ---

/**
 * Slugifies a tag name to be filesystem-safe
 * @param {string} tagName - The tag name to slugify
 * @returns {string} Slugified tag name safe for filesystem use
 */
function slugifyTagForFilePath(tagName) {
	if (!tagName || typeof tagName !== 'string') {
		return 'unknown-tag';
	}

	// Replace invalid filesystem characters with hyphens and clean up
	return tagName
		.replace(/[^a-zA-Z0-9_-]/g, '-') // Replace invalid chars with hyphens
		.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
		.replace(/-+/g, '-') // Collapse multiple hyphens
		.toLowerCase() // Convert to lowercase
		.substring(0, 50); // Limit length to prevent overly long filenames
}

/**
 * Resolves a file path to be tag-aware, following the pattern used by other commands.
 * For non-master tags, appends _slugified-tagname before the file extension.
 * @param {string} basePath - The base file path (e.g., '.taskmaster/reports/task-complexity-report.json')
 * @param {string|null} tag - The tag name (null, undefined, or 'master' uses base path)
 * @param {string} [projectRoot='.'] - The project root directory
 * @returns {string} The resolved file path
 */
function getTagAwareFilePath(basePath, tag, projectRoot = '.') {
	// Use path.parse and format for clean tag insertion
	const parsedPath = path.parse(basePath);
	if (!tag || tag === 'master') {
		return path.join(projectRoot, basePath);
	}

	// Slugify the tag for filesystem safety
	const slugifiedTag = slugifyTagForFilePath(tag);

	// Append slugified tag before file extension
	parsedPath.base = `${parsedPath.name}_${slugifiedTag}${parsedPath.ext}`;
	const relativePath = path.format(parsedPath);
	return path.join(projectRoot, relativePath);
}

// --- Project Root Finding Utility ---
/**
 * Recursively searches upwards for project root starting from a given directory.
 * @param {string} [startDir=process.cwd()] - The directory to start searching from.
 * @param {string[]} [markers=['package.json', '.git', LEGACY_CONFIG_FILE]] - Marker files/dirs to look for.
 * @returns {string|null} The path to the project root, or null if not found.
 */
function findProjectRoot(
	startDir = process.cwd(),
	markers = ['package.json', 'pyproject.toml', '.git', LEGACY_CONFIG_FILE]
) {
	let currentPath = path.resolve(startDir);
	const rootPath = path.parse(currentPath).root;

	while (currentPath !== rootPath) {
		// Check if any marker exists in the current directory
		const hasMarker = markers.some((marker) => {
			const markerPath = path.join(currentPath, marker);
			return fs.existsSync(markerPath);
		});

		if (hasMarker) {
			return currentPath;
		}

		// Move up one directory
		currentPath = path.dirname(currentPath);
	}

	// Check the root directory as well
	const hasMarkerInRoot = markers.some((marker) => {
		const markerPath = path.join(rootPath, marker);
		return fs.existsSync(markerPath);
	});

	return hasMarkerInRoot ? rootPath : null;
}

// --- Dynamic Configuration Function --- (REMOVED)

// --- Logging and Utility Functions ---

// Set up logging based on log level
const LOG_LEVELS = {
	debug: 0,
	info: 1,
	warn: 2,
	error: 3,
	success: 1 // Treat success like info level
};

/**
 * Returns the task manager module
 * @returns {Promise<Object>} The task manager module object
 */
async function getTaskManager() {
	return import('./task-manager.js');
}

/**
 * Enable silent logging mode
 */
function enableSilentMode() {
	silentMode = true;
}

/**
 * Disable silent logging mode
 */
function disableSilentMode() {
	silentMode = false;
}

/**
 * Check if silent mode is enabled
 * @returns {boolean} True if silent mode is enabled
 */
function isSilentMode() {
	return silentMode;
}

/**
 * Logs a message at the specified level
 * @param {string} level - The log level (debug, info, warn, error)
 * @param  {...any} args - Arguments to log
 */
function log(level, ...args) {
	// Immediately return if silentMode is enabled
	if (isSilentMode()) {
		return;
	}

	// GUARD: Prevent circular dependency during config loading
	// Use a simple fallback log level instead of calling getLogLevel()
	let configLevel = 'info'; // Default fallback
	try {
		// Only try to get config level if we're not in the middle of config loading
		configLevel = getLogLevel() || 'info';
	} catch (error) {
		// If getLogLevel() fails (likely due to circular dependency),
		// use default 'info' level and continue
		configLevel = 'info';
	}

	// Use text prefixes instead of emojis
	const prefixes = {
		debug: chalk.gray('[DEBUG]'),
		info: chalk.blue('[INFO]'),
		warn: chalk.yellow('[WARN]'),
		error: chalk.red('[ERROR]'),
		success: chalk.green('[SUCCESS]')
	};

	// Ensure level exists, default to info if not
	const currentLevel = LOG_LEVELS.hasOwnProperty(level) ? level : 'info';

	// Check log level configuration
	if (
		LOG_LEVELS[currentLevel] >= (LOG_LEVELS[configLevel] ?? LOG_LEVELS.info)
	) {
		const prefix = prefixes[currentLevel] || '';
		// Use console.log for all levels, let chalk handle coloring
		// Construct the message properly
		const message = args
			.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : arg))
			.join(' ');
		console.log(`${prefix} ${message}`);
	}
}

/**
 * Checks if the data object has a tagged structure (contains tag objects with tasks arrays)
 * @param {Object} data - The data object to check
 * @returns {boolean} True if the data has a tagged structure
 */
function hasTaggedStructure(data) {
	if (!data || typeof data !== 'object') {
		return false;
	}

	// Check if any top-level properties are objects with tasks arrays
	for (const key in data) {
		if (
			data.hasOwnProperty(key) &&
			typeof data[key] === 'object' &&
			Array.isArray(data[key].tasks)
		) {
			return true;
		}
	}
	return false;
}

/**
 * Normalizes task IDs to ensure they are numbers instead of strings
 * @param {Array} tasks - Array of tasks to normalize
 */
function normalizeTaskIds(tasks) {
	if (!Array.isArray(tasks)) return;

	tasks.forEach((task) => {
		// Convert task ID to number with validation
		if (task.id !== undefined) {
			const parsedId = parseInt(task.id, 10);
			if (!isNaN(parsedId) && parsedId > 0) {
				task.id = parsedId;
			}
		}

		// Convert subtask IDs to numbers with validation
		if (Array.isArray(task.subtasks)) {
			task.subtasks.forEach((subtask) => {
				if (subtask.id !== undefined) {
					// Check for dot notation (which shouldn't exist in storage)
					if (typeof subtask.id === 'string' && subtask.id.includes('.')) {
						// Extract the subtask part after the dot
						const parts = subtask.id.split('.');
						subtask.id = parseInt(parts[parts.length - 1], 10);
					} else {
						const parsedSubtaskId = parseInt(subtask.id, 10);
						if (!isNaN(parsedSubtaskId) && parsedSubtaskId > 0) {
							subtask.id = parsedSubtaskId;
						}
					}
				}
			});
		}
	});
}

/**
 * Reads and parses a JSON file
 * @param {string} filepath - Path to the JSON file
 * @param {string} [projectRoot] - Optional project root for tag resolution (used by MCP)
 * @param {string} [tag] - Optional tag to use instead of current tag resolution
 * @returns {Object|null} The parsed JSON data or null if error
 */
function readJSON(filepath, projectRoot = null, tag = null) {
	// GUARD: Prevent circular dependency during config loading
	let isDebug = false; // Default fallback
	try {
		// Only try to get debug flag if we're not in the middle of config loading
		isDebug = getDebugFlag();
	} catch (error) {
		// If getDebugFlag() fails (likely due to circular dependency),
		// use default false and continue
	}

	if (isDebug) {
		console.log(
			`readJSON called with: ${filepath}, projectRoot: ${projectRoot}, tag: ${tag}`
		);
	}

	if (!filepath) {
		return null;
	}

	let data;
	try {
		data = JSON.parse(fs.readFileSync(filepath, 'utf8'));
		if (isDebug) {
			console.log(`Successfully read JSON from ${filepath}`);
		}
	} catch (err) {
		if (isDebug) {
			console.log(`Failed to read JSON from ${filepath}: ${err.message}`);
		}
		return null;
	}

	// If it's not a tasks.json file, return as-is
	if (!filepath.includes('tasks.json') || !data) {
		if (isDebug) {
			console.log(`File is not tasks.json or data is null, returning as-is`);
		}
		return data;
	}

	// Check if this is legacy format that needs migration
	// Only migrate if we have tasks at the ROOT level AND no tag-like structure
	if (
		Array.isArray(data.tasks) &&
		!data._rawTaggedData &&
		!hasTaggedStructure(data)
	) {
		if (isDebug) {
			console.log(`File is in legacy format, performing migration...`);
		}

		normalizeTaskIds(data.tasks);

		// This is legacy format - migrate it to tagged format
		const migratedData = {
			master: {
				tasks: data.tasks,
				metadata: data.metadata || {
					created: new Date().toISOString(),
					updated: new Date().toISOString(),
					description: 'Tasks for master context'
				}
			}
		};

		// Write the migrated data back to the file
		try {
			writeJSON(filepath, migratedData);
			if (isDebug) {
				console.log(`Successfully migrated legacy format to tagged format`);
			}

			// Perform complete migration (config.json, state.json)
			performCompleteTagMigration(filepath);

			// Check and auto-switch git tags if enabled (after migration)
			// This needs to run synchronously BEFORE tag resolution
			if (projectRoot) {
				try {
					// Run git integration synchronously
					gitUtils.checkAndAutoSwitchGitTagSync(projectRoot, filepath);
				} catch (error) {
					// Silent fail - don't break normal operations
				}
			}

			// Mark for migration notice
			markMigrationForNotice(filepath);
		} catch (writeError) {
			if (isDebug) {
				console.log(`Error writing migrated data: ${writeError.message}`);
			}
			// If write fails, continue with the original data
		}

		// Continue processing with the migrated data structure
		data = migratedData;
	}

	// If we have tagged data, we need to resolve which tag to use
	if (typeof data === 'object' && !data.tasks) {
		// This is tagged format
		if (isDebug) {
			console.log(`File is in tagged format, resolving tag...`);
		}

		// Ensure all tags have proper metadata before proceeding
		for (const tagName in data) {
			if (
				data.hasOwnProperty(tagName) &&
				typeof data[tagName] === 'object' &&
				data[tagName].tasks
			) {
				try {
					ensureTagMetadata(data[tagName], {
						description: `Tasks for ${tagName} context`,
						skipUpdate: true // Don't update timestamp during read operations
					});
				} catch (error) {
					// If ensureTagMetadata fails, continue without metadata
					if (isDebug) {
						console.log(
							`Failed to ensure metadata for tag ${tagName}: ${error.message}`
						);
					}
				}
			}
		}

		// Store reference to the raw tagged data for functions that need it
		const originalTaggedData = JSON.parse(JSON.stringify(data));

		// Normalize IDs in all tags before storing as originalTaggedData
		for (const tagName in originalTaggedData) {
			if (
				originalTaggedData[tagName] &&
				Array.isArray(originalTaggedData[tagName].tasks)
			) {
				normalizeTaskIds(originalTaggedData[tagName].tasks);
			}
		}

		// Check and auto-switch git tags if enabled (for existing tagged format)
		// This needs to run synchronously BEFORE tag resolution
		if (projectRoot) {
			try {
				// Run git integration synchronously
				gitUtils.checkAndAutoSwitchGitTagSync(projectRoot, filepath);
			} catch (error) {
				// Silent fail - don't break normal operations
			}
		}

		try {
			// Default to master tag if anything goes wrong
			let resolvedTag = 'master';

			// Try to resolve the correct tag, but don't fail if it doesn't work
			try {
				// If tag is provided, use it directly
				if (tag) {
					resolvedTag = tag;
				} else if (projectRoot) {
					// Use provided projectRoot
					resolvedTag = resolveTag({ projectRoot });
				} else {
					// Try to derive projectRoot from filepath
					const derivedProjectRoot = findProjectRoot(path.dirname(filepath));
					if (derivedProjectRoot) {
						resolvedTag = resolveTag({ projectRoot: derivedProjectRoot });
					}
					// If derivedProjectRoot is null, stick with 'master'
				}
			} catch (tagResolveError) {
				if (isDebug) {
					console.log(
						`Tag resolution failed, using master: ${tagResolveError.message}`
					);
				}
				// resolvedTag stays as 'master'
			}

			if (isDebug) {
				console.log(`Resolved tag: ${resolvedTag}`);
			}

			// Get the data for the resolved tag
			const tagData = data[resolvedTag];
			if (tagData && tagData.tasks) {
				normalizeTaskIds(tagData.tasks);

				// Add the _rawTaggedData property and the resolved tag to the returned data
				const result = {
					...tagData,
					tag: resolvedTag,
					_rawTaggedData: originalTaggedData
				};
				if (isDebug) {
					console.log(
						`Returning data for tag '${resolvedTag}' with ${tagData.tasks.length} tasks`
					);
				}
				return result;
			} else {
				// If the resolved tag doesn't exist, fall back to master
				const masterData = data.master;
				if (masterData && masterData.tasks) {
					normalizeTaskIds(masterData.tasks);

					if (isDebug) {
						console.log(
							`Tag '${resolvedTag}' not found, falling back to master with ${masterData.tasks.length} tasks`
						);
					}
					return {
						...masterData,
						tag: 'master',
						_rawTaggedData: originalTaggedData
					};
				} else {
					if (isDebug) {
						console.log(`No valid tag data found, returning empty structure`);
					}
					// Return empty structure if no valid data
					return {
						tasks: [],
						tag: 'master',
						_rawTaggedData: originalTaggedData
					};
				}
			}
		} catch (error) {
			if (isDebug) {
				console.log(`Error during tag resolution: ${error.message}`);
			}
			// If anything goes wrong, try to return master or empty
			const masterData = data.master;
			if (masterData && masterData.tasks) {
				normalizeTaskIds(masterData.tasks);
				return {
					...masterData,
					_rawTaggedData: originalTaggedData
				};
			}
			return {
				tasks: [],
				_rawTaggedData: originalTaggedData
			};
		}
	}

	// If we reach here, it's some other format
	if (isDebug) {
		console.log(`File format not recognized, returning as-is`);
	}
	return data;
}

/**
 * Performs complete tag migration including config.json and state.json updates
 * @param {string} tasksJsonPath - Path to the tasks.json file that was migrated
 */
function performCompleteTagMigration(tasksJsonPath) {
	try {
		// Derive project root from tasks.json path
		const projectRoot =
			findProjectRoot(path.dirname(tasksJsonPath)) ||
			path.dirname(tasksJsonPath);

		// 1. Migrate config.json - add defaultTag and tags section
		const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
		if (fs.existsSync(configPath)) {
			migrateConfigJson(configPath);
		}

		// 2. Create state.json if it doesn't exist
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
		if (!fs.existsSync(statePath)) {
			createStateJson(statePath);
		}

		if (getDebugFlag()) {
			log(
				'debug',
				`Complete tag migration performed for project: ${projectRoot}`
			);
		}
	} catch (error) {
		if (getDebugFlag()) {
			log('warn', `Error during complete tag migration: ${error.message}`);
		}
	}
}

/**
 * Migrates config.json to add tagged task system configuration
 * @param {string} configPath - Path to the config.json file
 */
function migrateConfigJson(configPath) {
	try {
		const rawConfig = fs.readFileSync(configPath, 'utf8');
		const config = JSON.parse(rawConfig);
		if (!config) return;

		let modified = false;

		// Add global.defaultTag if missing
		if (!config.global) {
			config.global = {};
		}
		if (!config.global.defaultTag) {
			config.global.defaultTag = 'master';
			modified = true;
		}

		if (modified) {
			fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
			if (process.env.TASKMASTER_DEBUG === 'true') {
				console.log(
					'[DEBUG] Updated config.json with tagged task system settings'
				);
			}
		}
	} catch (error) {
		if (process.env.TASKMASTER_DEBUG === 'true') {
			console.warn(`[WARN] Error migrating config.json: ${error.message}`);
		}
	}
}

/**
 * Creates initial state.json file for tagged task system
 * @param {string} statePath - Path where state.json should be created
 */
function createStateJson(statePath) {
	try {
		const initialState = {
			currentTag: 'master',
			lastSwitched: new Date().toISOString(),
			branchTagMapping: {},
			migrationNoticeShown: false
		};

		fs.writeFileSync(statePath, JSON.stringify(initialState, null, 2), 'utf8');
		if (process.env.TASKMASTER_DEBUG === 'true') {
			console.log('[DEBUG] Created initial state.json for tagged task system');
		}
	} catch (error) {
		if (process.env.TASKMASTER_DEBUG === 'true') {
			console.warn(`[WARN] Error creating state.json: ${error.message}`);
		}
	}
}

/**
 * Marks in state.json that migration occurred and notice should be shown
 * @param {string} tasksJsonPath - Path to the tasks.json file
 */
function markMigrationForNotice(tasksJsonPath) {
	try {
		const projectRoot = path.dirname(path.dirname(tasksJsonPath));
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');

		// Ensure state.json exists
		if (!fs.existsSync(statePath)) {
			createStateJson(statePath);
		}

		// Read and update state to mark migration occurred using fs directly
		try {
			const rawState = fs.readFileSync(statePath, 'utf8');
			const stateData = JSON.parse(rawState) || {};
			// Only set to false if it's not already set (i.e., first time migration)
			if (stateData.migrationNoticeShown === undefined) {
				stateData.migrationNoticeShown = false;
				fs.writeFileSync(statePath, JSON.stringify(stateData, null, 2), 'utf8');
			}
		} catch (stateError) {
			if (process.env.TASKMASTER_DEBUG === 'true') {
				console.warn(
					`[WARN] Error updating state for migration notice: ${stateError.message}`
				);
			}
		}
	} catch (error) {
		if (process.env.TASKMASTER_DEBUG === 'true') {
			console.warn(
				`[WARN] Error marking migration for notice: ${error.message}`
			);
		}
	}
}

/**
 * Writes and saves a JSON file. Handles tagged task lists properly.
 * @param {string} filepath - Path to the JSON file
 * @param {Object} data - Data to write (can be resolved tag data or raw tagged data)
 * @param {string} projectRoot - Optional project root for tag context
 * @param {string} tag - Optional tag for tag context
 */
function writeJSON(filepath, data, projectRoot = null, tag = null) {
	const isDebug = process.env.TASKMASTER_DEBUG === 'true';

	try {
		let finalData = data;

		// If data represents resolved tag data but lost _rawTaggedData (edge-case observed in MCP path)
		if (
			!data._rawTaggedData &&
			projectRoot &&
			Array.isArray(data.tasks) &&
			!hasTaggedStructure(data)
		) {
			const resolvedTag = tag || getCurrentTag(projectRoot);

			if (isDebug) {
				console.log(
					`writeJSON: Detected resolved tag data missing _rawTaggedData. Re-reading raw data to prevent data loss for tag '${resolvedTag}'.`
				);
			}

			// Re-read the full file to get the complete tagged structure
			const rawFullData = JSON.parse(fs.readFileSync(filepath, 'utf8'));

			// Merge the updated data into the full structure
			finalData = {
				...rawFullData,
				[resolvedTag]: {
					// Preserve existing tag metadata if it exists, otherwise use what's passed
					...(rawFullData[resolvedTag]?.metadata || {}),
					...(data.metadata ? { metadata: data.metadata } : {}),
					tasks: data.tasks // The updated tasks array is the source of truth here
				}
			};
		}
		// If we have _rawTaggedData, this means we're working with resolved tag data
		// and need to merge it back into the full tagged structure
		else if (data && data._rawTaggedData && projectRoot) {
			const resolvedTag = tag || getCurrentTag(projectRoot);

			// Get the original tagged data
			const originalTaggedData = data._rawTaggedData;

			// Create a clean copy of the current resolved data (without internal properties)
			const { _rawTaggedData, tag: _, ...cleanResolvedData } = data;

			// Update the specific tag with the resolved data
			finalData = {
				...originalTaggedData,
				[resolvedTag]: cleanResolvedData
			};

			if (isDebug) {
				console.log(
					`writeJSON: Merging resolved data back into tag '${resolvedTag}'`
				);
			}
		}

		// Clean up any internal properties that shouldn't be persisted
		let cleanData = finalData;
		if (cleanData && typeof cleanData === 'object') {
			// Remove any _rawTaggedData or tag properties from root level
			const { _rawTaggedData, tag: tagProp, ...rootCleanData } = cleanData;
			cleanData = rootCleanData;

			// Additional cleanup for tag objects
			if (typeof cleanData === 'object' && !Array.isArray(cleanData)) {
				const finalCleanData = {};
				for (const [key, value] of Object.entries(cleanData)) {
					if (
						value &&
						typeof value === 'object' &&
						Array.isArray(value.tasks)
					) {
						// This is a tag object - clean up any rogue root-level properties
						const { created, description, ...cleanTagData } = value;

						// Only keep the description if there's no metadata.description
						if (
							description &&
							(!cleanTagData.metadata || !cleanTagData.metadata.description)
						) {
							cleanTagData.description = description;
						}

						finalCleanData[key] = cleanTagData;
					} else {
						finalCleanData[key] = value;
					}
				}
				cleanData = finalCleanData;
			}
		}

		fs.writeFileSync(filepath, JSON.stringify(cleanData, null, 2), 'utf8');

		if (isDebug) {
			console.log(`writeJSON: Successfully wrote to ${filepath}`);
		}
	} catch (error) {
		log('error', `Error writing JSON file ${filepath}:`, error.message);
		if (isDebug) {
			log('error', 'Full error details:', error);
		}
	}
}

/**
 * Sanitizes a prompt string for use in a shell command
 * @param {string} prompt The prompt to sanitize
 * @returns {string} Sanitized prompt
 */
function sanitizePrompt(prompt) {
	// Replace double quotes with escaped double quotes
	return prompt.replace(/"/g, '\\"');
}

/**
 * Reads the complexity report from file
 * @param {string} customPath - Optional custom path to the report
 * @returns {Object|null} The parsed complexity report or null if not found
 */
function readComplexityReport(customPath = null) {
	// GUARD: Prevent circular dependency during config loading
	let isDebug = false; // Default fallback
	try {
		// Only try to get debug flag if we're not in the middle of config loading
		isDebug = getDebugFlag();
	} catch (error) {
		// If getDebugFlag() fails (likely due to circular dependency),
		// use default false and continue
		isDebug = false;
	}

	try {
		let reportPath;
		if (customPath) {
			reportPath = customPath;
		} else {
			// Try new location first, then fall back to legacy
			const newPath = path.join(process.cwd(), COMPLEXITY_REPORT_FILE);
			const legacyPath = path.join(
				process.cwd(),
				LEGACY_COMPLEXITY_REPORT_FILE
			);

			reportPath = fs.existsSync(newPath) ? newPath : legacyPath;
		}

		if (!fs.existsSync(reportPath)) {
			if (isDebug) {
				log('debug', `Complexity report not found at ${reportPath}`);
			}
			return null;
		}

		const reportData = readJSON(reportPath);
		if (isDebug) {
			log('debug', `Successfully read complexity report from ${reportPath}`);
		}
		return reportData;
	} catch (error) {
		if (isDebug) {
			log('error', `Error reading complexity report: ${error.message}`);
		}
		return null;
	}
}

/**
 * Finds a task analysis in the complexity report
 * @param {Object} report - The complexity report
 * @param {number} taskId - The task ID to find
 * @returns {Object|null} The task analysis or null if not found
 */
function findTaskInComplexityReport(report, taskId) {
	if (
		!report ||
		!report.complexityAnalysis ||
		!Array.isArray(report.complexityAnalysis)
	) {
		return null;
	}

	return report.complexityAnalysis.find((task) => task.taskId === taskId);
}

function addComplexityToTask(task, complexityReport) {
	let taskId;
	if (task.isSubtask) {
		taskId = task.parentTask.id;
	} else if (task.parentId) {
		taskId = task.parentId;
	} else {
		taskId = task.id;
	}

	const taskAnalysis = findTaskInComplexityReport(complexityReport, taskId);
	if (taskAnalysis) {
		task.complexityScore = taskAnalysis.complexityScore;
	}
}

/**
 * Checks if a task exists in the tasks array
 * @param {Array} tasks - The tasks array
 * @param {string|number} taskId - The task ID to check
 * @returns {boolean} True if the task exists, false otherwise
 */
function taskExists(tasks, taskId) {
	if (!taskId || !tasks || !Array.isArray(tasks)) {
		return false;
	}

	// Handle both regular task IDs and subtask IDs (e.g., "1.2")
	if (typeof taskId === 'string' && taskId.includes('.')) {
		const [parentId, subtaskId] = taskId
			.split('.')
			.map((id) => parseInt(id, 10));
		const parentTask = tasks.find((t) => t.id === parentId);

		if (!parentTask || !parentTask.subtasks) {
			return false;
		}

		return parentTask.subtasks.some((st) => st.id === subtaskId);
	}

	const id = parseInt(taskId, 10);
	return tasks.some((t) => t.id === id);
}

/**
 * Formats a task ID as a string
 * @param {string|number} id - The task ID to format
 * @returns {string} The formatted task ID
 */
function formatTaskId(id) {
	if (typeof id === 'string' && id.includes('.')) {
		return id; // Already formatted as a string with a dot (e.g., "1.2")
	}

	if (typeof id === 'number') {
		return id.toString();
	}

	return id;
}

/**
 * Finds a task by ID in the tasks array. Optionally filters subtasks by status.
 * @param {Array} tasks - The tasks array
 * @param {string|number} taskId - The task ID to find
 * @param {Object|null} complexityReport - Optional pre-loaded complexity report
 * @param {string} [statusFilter] - Optional status to filter subtasks by
 * @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.
 */
function findTaskById(
	tasks,
	taskId,
	complexityReport = null,
	statusFilter = null
) {
	if (!taskId || !tasks || !Array.isArray(tasks)) {
		return { task: null, originalSubtaskCount: null };
	}

	// Check if it's a subtask ID (e.g., "1.2")
	if (typeof taskId === 'string' && taskId.includes('.')) {
		// If looking for a subtask, statusFilter doesn't apply directly here.
		const [parentId, subtaskId] = taskId
			.split('.')
			.map((id) => parseInt(id, 10));
		const parentTask = tasks.find((t) => t.id === parentId);

		if (!parentTask || !parentTask.subtasks) {
			return { task: null, originalSubtaskCount: null, originalSubtasks: null };
		}

		const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
		if (subtask) {
			// Add reference to parent task for context
			subtask.parentTask = {
				id: parentTask.id,
				title: parentTask.title,
				status: parentTask.status
			};
			subtask.isSubtask = true;
		}

		// If we found a task, check for complexity data
		if (subtask && complexityReport) {
			addComplexityToTask(subtask, complexityReport);
		}

		return {
			task: subtask || null,
			originalSubtaskCount: null,
			originalSubtasks: null
		};
	}

	let taskResult = null;
	let originalSubtaskCount = null;
	let originalSubtasks = null;

	// Find the main task
	const id = parseInt(taskId, 10);
	const task = tasks.find((t) => t.id === id) || null;

	// If task not found, return nulls
	if (!task) {
		return { task: null, originalSubtaskCount: null, originalSubtasks: null };
	}

	taskResult = task;

	// If task found and statusFilter provided, filter its subtasks
	if (statusFilter && task.subtasks && Array.isArray(task.subtasks)) {
		// Store original subtasks and count before filtering
		originalSubtasks = [...task.subtasks]; // Clone the original subtasks array
		originalSubtaskCount = task.subtasks.length;

		// Clone the task to avoid modifying the original array
		const filteredTask = { ...task };
		filteredTask.subtasks = task.subtasks.filter(
			(subtask) =>
				subtask.status &&
				subtask.status.toLowerCase() === statusFilter.toLowerCase()
		);

		taskResult = filteredTask;
	}

	// If task found and complexityReport provided, add complexity data
	if (taskResult && complexityReport) {
		addComplexityToTask(taskResult, complexityReport);
	}

	// Return the found task, original subtask count, and original subtasks
	return { task: taskResult, originalSubtaskCount, originalSubtasks };
}

/**
 * Truncates text to a specified length
 * @param {string} text - The text to truncate
 * @param {number} maxLength - The maximum length
 * @returns {string} The truncated text
 */
function truncate(text, maxLength) {
	if (!text || text.length <= maxLength) {
		return text;
	}

	return `${text.slice(0, maxLength - 3)}...`;
}

/**
 * Checks if array or object are empty
 * @param {*} value - The value to check
 * @returns {boolean} True if empty, false otherwise
 */
function isEmpty(value) {
	if (Array.isArray(value)) {
		return value.length === 0;
	} else if (typeof value === 'object' && value !== null) {
		return Object.keys(value).length === 0;
	}

	return false; // Not an array or object, or is null
}

/**
 * Find cycles in a dependency graph using DFS
 * @param {string} subtaskId - Current subtask ID
 * @param {Map} dependencyMap - Map of subtask IDs to their dependencies
 * @param {Set} visited - Set of visited nodes
 * @param {Set} recursionStack - Set of nodes in current recursion stack
 * @returns {Array} - List of dependency edges that need to be removed to break cycles
 */
function findCycles(
	subtaskId,
	dependencyMap,
	visited = new Set(),
	recursionStack = new Set(),
	path = []
) {
	// Mark the current node as visited and part of recursion stack
	visited.add(subtaskId);
	recursionStack.add(subtaskId);
	path.push(subtaskId);

	const cyclesToBreak = [];

	// Get all dependencies of the current subtask
	const dependencies = dependencyMap.get(subtaskId) || [];

	// For each dependency
	for (const depId of dependencies) {
		// If not visited, recursively check for cycles
		if (!visited.has(depId)) {
			const cycles = findCycles(depId, dependencyMap, visited, recursionStack, [
				...path
			]);
			cyclesToBreak.push(...cycles);
		}
		// If the dependency is in the recursion stack, we found a cycle
		else if (recursionStack.has(depId)) {
			// Find the position of the dependency in the path
			const cycleStartIndex = path.indexOf(depId);
			// The last edge in the cycle is what we want to remove
			const cycleEdges = path.slice(cycleStartIndex);
			// We'll remove the last edge in the cycle (the one that points back)
			cyclesToBreak.push(depId);
		}
	}

	// Remove the node from recursion stack before returning
	recursionStack.delete(subtaskId);

	return cyclesToBreak;
}

/**
 * Unified dependency traversal utility that supports both forward and reverse dependency traversal
 * @param {Array} sourceTasks - Array of source tasks to start traversal from
 * @param {Array} allTasks - Array of all tasks to search within
 * @param {Object} options - Configuration options
 * @param {number} options.maxDepth - Maximum recursion depth (default: 50)
 * @param {boolean} options.includeSelf - Whether to include self-references (default: false)
 * @param {'forward'|'reverse'} options.direction - Direction of traversal (default: 'forward')
 * @param {Function} options.logger - Optional logger function for warnings
 * @returns {Array} Array of all dependency task IDs found through traversal
 */
function traverseDependencies(sourceTasks, allTasks, options = {}) {
	const {
		maxDepth = 50,
		includeSelf = false,
		direction = 'forward',
		logger = null
	} = options;

	const dependentTaskIds = new Set();
	const processedIds = new Set();

	// Helper function to normalize dependency IDs while preserving subtask format
	function normalizeDependencyId(depId) {
		if (typeof depId === 'string') {
			// Preserve string format for subtask IDs like "1.2"
			if (depId.includes('.')) {
				return depId;
			}
			// Convert simple string numbers to numbers for consistency
			const parsed = parseInt(depId, 10);
			return isNaN(parsed) ? depId : parsed;
		}
		return depId;
	}

	// Helper function for forward dependency traversal
	function findForwardDependencies(taskId, currentDepth = 0) {
		// Check depth limit
		if (currentDepth >= maxDepth) {
			const warnMsg = `Maximum recursion depth (${maxDepth}) reached for task ${taskId}`;
			if (logger && typeof logger.warn === 'function') {
				logger.warn(warnMsg);
			} else if (typeof log !== 'undefined' && log.warn) {
				log.warn(warnMsg);
			} else {
				console.warn(warnMsg);
			}
			return;
		}

		if (processedIds.has(taskId)) {
			return; // Avoid infinite loops
		}
		processedIds.add(taskId);

		const task = allTasks.find((t) => t.id === taskId);
		if (!task || !Array.isArray(task.dependencies)) {
			return;
		}

		task.dependencies.forEach((depId) => {
			const normalizedDepId = normalizeDependencyId(depId);

			// Skip invalid dependencies and optionally skip self-references
			if (
				normalizedDepId == null ||
				(!includeSelf && normalizedDepId === taskId)
			) {
				return;
			}

			dependentTaskIds.add(normalizedDepId);
			// Recursively find dependencies of this dependency
			findForwardDependencies(normalizedDepId, currentDepth + 1);
		});
	}

	// Helper function for reverse dependency traversal
	function findReverseDependencies(taskId, currentDepth = 0) {
		// Check depth limit
		if (currentDepth >= maxDepth) {
			const warnMsg = `Maximum recursion depth (${maxDepth}) reached for task ${taskId}`;
			if (logger && typeof logger.warn === 'function') {
				logger.warn(warnMsg);
			} else if (typeof log !== 'undefined' && log.warn) {
				log.warn(warnMsg);
			} else {
				console.warn(warnMsg);
			}
			return;
		}

		if (processedIds.has(taskId)) {
			return; // Avoid infinite loops
		}
		processedIds.add(taskId);

		allTasks.forEach((task) => {
			if (task.dependencies && Array.isArray(task.dependencies)) {
				const dependsOnTaskId = task.dependencies.some((depId) => {
					const normalizedDepId = normalizeDependencyId(depId);
					return normalizedDepId === taskId;
				});

				if (dependsOnTaskId) {
					// Skip invalid dependencies and optionally skip self-references
					if (task.id == null || (!includeSelf && task.id === taskId)) {
						return;
					}

					dependentTaskIds.add(task.id);
					// Recursively find tasks that depend on this task
					findReverseDependencies(task.id, currentDepth + 1);
				}
			}
		});
	}

	// Choose traversal function based on direction
	const traversalFunc =
		direction === 'reverse' ? findReverseDependencies : findForwardDependencies;

	// Start traversal from each source task
	sourceTasks.forEach((sourceTask) => {
		if (sourceTask && sourceTask.id) {
			traversalFunc(sourceTask.id);
		}
	});

	return Array.from(dependentTaskIds);
}

/**
 * Convert a string from camelCase to kebab-case
 * @param {string} str - The string to convert
 * @returns {string} The kebab-case version of the string
 */
const toKebabCase = (str) => {
	// Special handling for common acronyms
	const withReplacedAcronyms = str
		.replace(/ID/g, 'Id')
		.replace(/API/g, 'Api')
		.replace(/UI/g, 'Ui')
		.replace(/URL/g, 'Url')
		.replace(/URI/g, 'Uri')
		.replace(/JSON/g, 'Json')
		.replace(/XML/g, 'Xml')
		.replace(/HTML/g, 'Html')
		.replace(/CSS/g, 'Css');

	// Insert hyphens before capital letters and convert to lowercase
	return withReplacedAcronyms
		.replace(/([A-Z])/g, '-$1')
		.toLowerCase()
		.replace(/^-/, ''); // Remove leading hyphen if present
};

/**
 * Detect camelCase flags in command arguments
 * @param {string[]} args - Command line arguments to check
 * @returns {Array<{original: string, kebabCase: string}>} - List of flags that should be converted
 */
function detectCamelCaseFlags(args) {
	const camelCaseFlags = [];
	for (const arg of args) {
		if (arg.startsWith('--')) {
			const flagName = arg.split('=')[0].slice(2); // Remove -- and anything after =

			// Skip single-word flags - they can't be camelCase
			if (!flagName.includes('-') && !/[A-Z]/.test(flagName)) {
				continue;
			}

			// Check for camelCase pattern (lowercase followed by uppercase)
			if (/[a-z][A-Z]/.test(flagName)) {
				const kebabVersion = toKebabCase(flagName);
				if (kebabVersion !== flagName) {
					camelCaseFlags.push({
						original: flagName,
						kebabCase: kebabVersion
					});
				}
			}
		}
	}
	return camelCaseFlags;
}

/**
 * Aggregates an array of telemetry objects into a single summary object.
 * @param {Array<Object>} telemetryArray - Array of telemetryData objects.
 * @param {string} overallCommandName - The name for the aggregated command.
 * @returns {Object|null} Aggregated telemetry object or null if input is empty.
 */
function aggregateTelemetry(telemetryArray, overallCommandName) {
	if (!telemetryArray || telemetryArray.length === 0) {
		return null;
	}

	const aggregated = {
		timestamp: new Date().toISOString(), // Use current time for aggregation time
		userId: telemetryArray[0].userId, // Assume userId is consistent
		commandName: overallCommandName,
		modelUsed: 'Multiple', // Default if models vary
		providerName: 'Multiple', // Default if providers vary
		inputTokens: 0,
		outputTokens: 0,
		totalTokens: 0,
		totalCost: 0,
		currency: telemetryArray[0].currency || 'USD' // Assume consistent currency or default
	};

	const uniqueModels = new Set();
	const uniqueProviders = new Set();
	const uniqueCurrencies = new Set();

	telemetryArray.forEach((item) => {
		aggregated.inputTokens += item.inputTokens || 0;
		aggregated.outputTokens += item.outputTokens || 0;
		aggregated.totalCost += item.totalCost || 0;
		uniqueModels.add(item.modelUsed);
		uniqueProviders.add(item.providerName);
		uniqueCurrencies.add(item.currency || 'USD');
	});

	aggregated.totalTokens = aggregated.inputTokens + aggregated.outputTokens;
	aggregated.totalCost = parseFloat(aggregated.totalCost.toFixed(6)); // Fix precision

	if (uniqueModels.size === 1) {
		aggregated.modelUsed = [...uniqueModels][0];
	}
	if (uniqueProviders.size === 1) {
		aggregated.providerName = [...uniqueProviders][0];
	}
	if (uniqueCurrencies.size > 1) {
		aggregated.currency = 'Multiple'; // Mark if currencies actually differ
	} else if (uniqueCurrencies.size === 1) {
		aggregated.currency = [...uniqueCurrencies][0];
	}

	return aggregated;
}

/**
 * @deprecated Use TaskMaster.getCurrentTag() instead
 * Gets the current tag from state.json or falls back to defaultTag from config
 * @param {string} projectRoot - The project root directory (required)
 * @returns {string} The current tag name
 */
function getCurrentTag(projectRoot) {
	if (!projectRoot) {
		throw new Error('projectRoot is required for getCurrentTag');
	}

	try {
		// Try to read current tag from state.json using fs directly
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
		if (fs.existsSync(statePath)) {
			const rawState = fs.readFileSync(statePath, 'utf8');
			const stateData = JSON.parse(rawState);
			if (stateData && stateData.currentTag) {
				return stateData.currentTag;
			}
		}
	} catch (error) {
		// Ignore errors, fall back to default
	}

	// Fall back to defaultTag from config using fs directly
	try {
		const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
		if (fs.existsSync(configPath)) {
			const rawConfig = fs.readFileSync(configPath, 'utf8');
			const configData = JSON.parse(rawConfig);
			if (configData && configData.global && configData.global.defaultTag) {
				return configData.global.defaultTag;
			}
		}
	} catch (error) {
		// Ignore errors, use hardcoded default
	}

	// Final fallback
	return 'master';
}

/**
 * Resolves the tag to use based on options
 * @param {Object} options - Options object
 * @param {string} options.projectRoot - The project root directory (required)
 * @param {string} [options.tag] - Explicit tag to use
 * @returns {string} The resolved tag name
 */
function resolveTag(options = {}) {
	const { projectRoot, tag } = options;

	if (!projectRoot) {
		throw new Error('projectRoot is required for resolveTag');
	}

	// If explicit tag provided, use it
	if (tag) {
		return tag;
	}

	// Otherwise get current tag from state/config
	return getCurrentTag(projectRoot);
}

/**
 * Gets the tasks array for a specific tag from tagged tasks.json data
 * @param {Object} data - The parsed tasks.json data (after migration)
 * @param {string} tagName - The tag name to get tasks for
 * @returns {Array} The tasks array for the specified tag, or empty array if not found
 */
function getTasksForTag(data, tagName) {
	if (!data || !tagName) {
		return [];
	}

	// Handle migrated format: { "master": { "tasks": [...] }, "otherTag": { "tasks": [...] } }
	if (
		data[tagName] &&
		data[tagName].tasks &&
		Array.isArray(data[tagName].tasks)
	) {
		return data[tagName].tasks;
	}

	return [];
}

/**
 * Sets the tasks array for a specific tag in the data structure
 * @param {Object} data - The tasks.json data object
 * @param {string} tagName - The tag name to set tasks for
 * @param {Array} tasks - The tasks array to set
 * @returns {Object} The updated data object
 */
function setTasksForTag(data, tagName, tasks) {
	if (!data) {
		data = {};
	}

	if (!data[tagName]) {
		data[tagName] = {};
	}

	data[tagName].tasks = tasks || [];
	return data;
}

/**
 * Flatten tasks array to include subtasks as individual searchable items
 * @param {Array} tasks - Array of task objects
 * @returns {Array} Flattened array including both tasks and subtasks
 */
function flattenTasksWithSubtasks(tasks) {
	const flattened = [];

	for (const task of tasks) {
		// Add the main task
		flattened.push({
			...task,
			searchableId: task.id.toString(), // For consistent ID handling
			isSubtask: false
		});

		// Add subtasks if they exist
		if (task.subtasks && task.subtasks.length > 0) {
			for (const subtask of task.subtasks) {
				flattened.push({
					...subtask,
					searchableId: `${task.id}.${subtask.id}`, // Format: "15.2"
					isSubtask: true,
					parentId: task.id,
					parentTitle: task.title,
					// Enhance subtask context with parent information
					title: `${subtask.title} (subtask of: ${task.title})`,
					description: `${subtask.description} [Parent: ${task.description}]`
				});
			}
		}
	}

	return flattened;
}

/**
 * Ensures the tag object has a metadata object with created/updated timestamps.
 * @param {Object} tagObj - The tag object (e.g., data['master'])
 * @param {Object} [opts] - Optional fields (e.g., description, skipUpdate)
 * @param {string} [opts.description] - Description for the tag
 * @param {boolean} [opts.skipUpdate] - If true, don't update the 'updated' timestamp
 * @returns {Object} The updated tag object (for chaining)
 */
function ensureTagMetadata(tagObj, opts = {}) {
	if (!tagObj || typeof tagObj !== 'object') {
		throw new Error('tagObj must be a valid object');
	}

	const now = new Date().toISOString();

	if (!tagObj.metadata) {
		// Create new metadata object
		tagObj.metadata = {
			created: now,
			updated: now,
			...(opts.description ? { description: opts.description } : {})
		};
	} else {
		// Ensure existing metadata has required fields
		if (!tagObj.metadata.created) {
			tagObj.metadata.created = now;
		}

		// Update timestamp unless explicitly skipped
		if (!opts.skipUpdate) {
			tagObj.metadata.updated = now;
		}

		// Add description if provided and not already present
		if (opts.description && !tagObj.metadata.description) {
			tagObj.metadata.description = opts.description;
		}
	}

	return tagObj;
}

/**
 * Strip ANSI color codes from a string
 * Useful for testing, logging to files, or when clean text output is needed
 * @param {string} text - The text that may contain ANSI color codes
 * @returns {string} - The text with ANSI color codes removed
 */
function stripAnsiCodes(text) {
	if (typeof text !== 'string') {
		return text;
	}
	// Remove ANSI escape sequences (color codes, cursor movements, etc.)
	return text.replace(/\x1b\[[0-9;]*m/g, '');
}

// Export all utility functions and configuration
export {
	LOG_LEVELS,
	log,
	readJSON,
	writeJSON,
	sanitizePrompt,
	readComplexityReport,
	findTaskInComplexityReport,
	taskExists,
	formatTaskId,
	findTaskById,
	truncate,
	isEmpty,
	findCycles,
	traverseDependencies,
	toKebabCase,
	detectCamelCaseFlags,
	disableSilentMode,
	enableSilentMode,
	getTaskManager,
	isSilentMode,
	addComplexityToTask,
	resolveEnvVariable,
	findProjectRoot,
	getTagAwareFilePath,
	slugifyTagForFilePath,
	aggregateTelemetry,
	getCurrentTag,
	resolveTag,
	getTasksForTag,
	setTasksForTag,
	performCompleteTagMigration,
	migrateConfigJson,
	createStateJson,
	markMigrationForNotice,
	flattenTasksWithSubtasks,
	ensureTagMetadata,
	stripAnsiCodes,
	normalizeTaskIds
};

```

--------------------------------------------------------------------------------
/scripts/modules/dependency-manager.js:
--------------------------------------------------------------------------------

```javascript
/**
 * dependency-manager.js
 * Manages task dependencies and relationships
 */

import path from 'path';
import chalk from 'chalk';
import boxen from 'boxen';

import {
	log,
	readJSON,
	writeJSON,
	taskExists,
	formatTaskId,
	findCycles,
	traverseDependencies,
	isSilentMode
} from './utils.js';

import { displayBanner } from './ui.js';

import generateTaskFiles from './task-manager/generate-task-files.js';

/**
 * Structured error class for dependency operations
 */
class DependencyError extends Error {
	constructor(code, message, data = {}) {
		super(message);
		this.name = 'DependencyError';
		this.code = code;
		this.data = data;
	}
}

/**
 * Error codes for dependency operations
 */
const DEPENDENCY_ERROR_CODES = {
	CANNOT_MOVE_SUBTASK: 'CANNOT_MOVE_SUBTASK',
	INVALID_TASK_ID: 'INVALID_TASK_ID',
	INVALID_SOURCE_TAG: 'INVALID_SOURCE_TAG',
	INVALID_TARGET_TAG: 'INVALID_TARGET_TAG'
};

/**
 * Add a dependency to a task
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {number|string} taskId - ID of the task to add dependency to
 * @param {number|string} dependencyId - ID of the task to add as dependency
 * @param {Object} context - Context object containing projectRoot and tag information
 * @param {string} [context.projectRoot] - Project root path
 * @param {string} [context.tag] - Tag for the task
 */
async function addDependency(tasksPath, taskId, dependencyId, context = {}) {
	log('info', `Adding dependency ${dependencyId} to task ${taskId}...`);

	const data = readJSON(tasksPath, context.projectRoot, context.tag);
	if (!data || !data.tasks) {
		log('error', 'No valid tasks found in tasks.json');
		process.exit(1);
	}

	// Format the task and dependency IDs correctly
	const formattedTaskId =
		typeof taskId === 'string' && taskId.includes('.')
			? taskId
			: parseInt(taskId, 10);

	const formattedDependencyId = formatTaskId(dependencyId);

	// Check if the dependency task or subtask actually exists
	if (!taskExists(data.tasks, formattedDependencyId)) {
		log(
			'error',
			`Dependency target ${formattedDependencyId} does not exist in tasks.json`
		);
		process.exit(1);
	}

	// Find the task to update
	let targetTask = null;
	let isSubtask = false;

	if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) {
		// Handle dot notation for subtasks (e.g., "1.2")
		const [parentId, subtaskId] = formattedTaskId
			.split('.')
			.map((id) => parseInt(id, 10));
		const parentTask = data.tasks.find((t) => t.id === parentId);

		if (!parentTask) {
			log('error', `Parent task ${parentId} not found.`);
			process.exit(1);
		}

		if (!parentTask.subtasks) {
			log('error', `Parent task ${parentId} has no subtasks.`);
			process.exit(1);
		}

		targetTask = parentTask.subtasks.find((s) => s.id === subtaskId);
		isSubtask = true;

		if (!targetTask) {
			log('error', `Subtask ${formattedTaskId} not found.`);
			process.exit(1);
		}
	} else {
		// Regular task (not a subtask)
		targetTask = data.tasks.find((t) => t.id === formattedTaskId);

		if (!targetTask) {
			log('error', `Task ${formattedTaskId} not found.`);
			process.exit(1);
		}
	}

	// Initialize dependencies array if it doesn't exist
	if (!targetTask.dependencies) {
		targetTask.dependencies = [];
	}

	// Check if dependency already exists
	if (
		targetTask.dependencies.some((d) => {
			// Convert both to strings for comparison to handle both numeric and string IDs
			return String(d) === String(formattedDependencyId);
		})
	) {
		log(
			'warn',
			`Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.`
		);
		return;
	}

	// Check if the task is trying to depend on itself - compare full IDs (including subtask parts)
	if (String(formattedTaskId) === String(formattedDependencyId)) {
		log('error', `Task ${formattedTaskId} cannot depend on itself.`);
		process.exit(1);
	}

	// For subtasks of the same parent, we need to make sure we're not treating it as a self-dependency
	// Check if we're dealing with subtasks with the same parent task
	let isSelfDependency = false;

	if (
		typeof formattedTaskId === 'string' &&
		typeof formattedDependencyId === 'string' &&
		formattedTaskId.includes('.') &&
		formattedDependencyId.includes('.')
	) {
		const [taskParentId] = formattedTaskId.split('.');
		const [depParentId] = formattedDependencyId.split('.');

		// Only treat it as a self-dependency if both the parent ID and subtask ID are identical
		isSelfDependency = formattedTaskId === formattedDependencyId;

		// Log for debugging
		log(
			'debug',
			`Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}`
		);
		log(
			'debug',
			`Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}`
		);
	}

	if (isSelfDependency) {
		log('error', `Subtask ${formattedTaskId} cannot depend on itself.`);
		process.exit(1);
	}

	// Check for circular dependencies
	const dependencyChain = [formattedTaskId];
	if (
		!isCircularDependency(data.tasks, formattedDependencyId, dependencyChain)
	) {
		// Add the dependency
		targetTask.dependencies.push(formattedDependencyId);

		// Sort dependencies numerically or by parent task ID first, then subtask ID
		targetTask.dependencies.sort((a, b) => {
			if (typeof a === 'number' && typeof b === 'number') {
				return a - b;
			} else if (typeof a === 'string' && typeof b === 'string') {
				const [aParent, aChild] = a.split('.').map(Number);
				const [bParent, bChild] = b.split('.').map(Number);
				return aParent !== bParent ? aParent - bParent : aChild - bChild;
			} else if (typeof a === 'number') {
				return -1; // Numbers come before strings
			} else {
				return 1; // Strings come after numbers
			}
		});

		// Save changes
		writeJSON(tasksPath, data, context.projectRoot, context.tag);
		log(
			'success',
			`Added dependency ${formattedDependencyId} to task ${formattedTaskId}`
		);

		// Display a more visually appealing success message
		if (!isSilentMode()) {
			console.log(
				boxen(
					chalk.green(`Successfully added dependency:\n\n`) +
						`Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`,
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1 }
					}
				)
			);
		}

		// Generate updated task files
		// await generateTaskFiles(tasksPath, path.dirname(tasksPath));

		log('info', 'Task files regenerated with updated dependencies.');
	} else {
		log(
			'error',
			`Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.`
		);
		process.exit(1);
	}
}

/**
 * Remove a dependency from a task
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {number|string} taskId - ID of the task to remove dependency from
 * @param {number|string} dependencyId - ID of the task to remove as dependency
 * @param {Object} context - Context object containing projectRoot and tag information
 * @param {string} [context.projectRoot] - Project root path
 * @param {string} [context.tag] - Tag for the task
 */
async function removeDependency(tasksPath, taskId, dependencyId, context = {}) {
	log('info', `Removing dependency ${dependencyId} from task ${taskId}...`);

	// Read tasks file
	const data = readJSON(tasksPath, context.projectRoot, context.tag);
	if (!data || !data.tasks) {
		log('error', 'No valid tasks found.');
		process.exit(1);
	}

	// Format the task and dependency IDs correctly
	const formattedTaskId =
		typeof taskId === 'string' && taskId.includes('.')
			? taskId
			: parseInt(taskId, 10);

	const formattedDependencyId = formatTaskId(dependencyId);

	// Find the task to update
	let targetTask = null;
	let isSubtask = false;

	if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) {
		// Handle dot notation for subtasks (e.g., "1.2")
		const [parentId, subtaskId] = formattedTaskId
			.split('.')
			.map((id) => parseInt(id, 10));
		const parentTask = data.tasks.find((t) => t.id === parentId);

		if (!parentTask) {
			log('error', `Parent task ${parentId} not found.`);
			process.exit(1);
		}

		if (!parentTask.subtasks) {
			log('error', `Parent task ${parentId} has no subtasks.`);
			process.exit(1);
		}

		targetTask = parentTask.subtasks.find((s) => s.id === subtaskId);
		isSubtask = true;

		if (!targetTask) {
			log('error', `Subtask ${formattedTaskId} not found.`);
			process.exit(1);
		}
	} else {
		// Regular task (not a subtask)
		targetTask = data.tasks.find((t) => t.id === formattedTaskId);

		if (!targetTask) {
			log('error', `Task ${formattedTaskId} not found.`);
			process.exit(1);
		}
	}

	// Check if the task has any dependencies
	if (!targetTask.dependencies || targetTask.dependencies.length === 0) {
		log(
			'info',
			`Task ${formattedTaskId} has no dependencies, nothing to remove.`
		);
		return;
	}

	// Normalize the dependency ID for comparison to handle different formats
	const normalizedDependencyId = String(formattedDependencyId);

	// Check if the dependency exists by comparing string representations
	const dependencyIndex = targetTask.dependencies.findIndex((dep) => {
		// Convert both to strings for comparison
		let depStr = String(dep);

		// Special handling for numeric IDs that might be subtask references
		if (typeof dep === 'number' && dep < 100 && isSubtask) {
			// It's likely a reference to another subtask in the same parent task
			// Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1)
			const [parentId] = formattedTaskId.split('.');
			depStr = `${parentId}.${dep}`;
		}

		return depStr === normalizedDependencyId;
	});

	if (dependencyIndex === -1) {
		log(
			'info',
			`Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.`
		);
		return;
	}

	// Remove the dependency
	targetTask.dependencies.splice(dependencyIndex, 1);

	// Save the updated tasks
	writeJSON(tasksPath, data, context.projectRoot, context.tag);

	// Success message
	log(
		'success',
		`Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}`
	);

	if (!isSilentMode()) {
		// Display a more visually appealing success message
		console.log(
			boxen(
				chalk.green(`Successfully removed dependency:\n\n`) +
					`Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`,
				{
					padding: 1,
					borderColor: 'green',
					borderStyle: 'round',
					margin: { top: 1 }
				}
			)
		);
	}

	// Regenerate task files
	// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
}

/**
 * Check if adding a dependency would create a circular dependency
 * @param {Array} tasks - Array of all tasks
 * @param {number|string} taskId - ID of task to check
 * @param {Array} chain - Chain of dependencies to check
 * @returns {boolean} True if circular dependency would be created
 */
function isCircularDependency(tasks, taskId, chain = []) {
	// Convert taskId to string for comparison
	const taskIdStr = String(taskId);

	// If we've seen this task before in the chain, we have a circular dependency
	if (chain.some((id) => String(id) === taskIdStr)) {
		return true;
	}

	// Find the task or subtask
	let task = null;
	let parentIdForSubtask = null;

	// Check if this is a subtask reference (e.g., "1.2")
	if (taskIdStr.includes('.')) {
		const [parentId, subtaskId] = taskIdStr.split('.').map(Number);
		const parentTask = tasks.find((t) => t.id === parentId);
		parentIdForSubtask = parentId; // Store parent ID if it's a subtask

		if (parentTask && parentTask.subtasks) {
			task = parentTask.subtasks.find((st) => st.id === subtaskId);
		}
	} else {
		// Regular task
		task = tasks.find((t) => String(t.id) === taskIdStr);
	}

	if (!task) {
		return false; // Task doesn't exist, can't create circular dependency
	}

	// No dependencies, can't create circular dependency
	if (!task.dependencies || task.dependencies.length === 0) {
		return false;
	}

	// Check each dependency recursively
	const newChain = [...chain, taskIdStr]; // Use taskIdStr for consistency
	return task.dependencies.some((depId) => {
		let normalizedDepId = String(depId);
		// Normalize relative subtask dependencies
		if (typeof depId === 'number' && parentIdForSubtask !== null) {
			// If the current task is a subtask AND the dependency is a number,
			// assume it refers to a sibling subtask.
			normalizedDepId = `${parentIdForSubtask}.${depId}`;
		}
		// Pass the normalized ID to the recursive call
		return isCircularDependency(tasks, normalizedDepId, newChain);
	});
}

/**
 * Validate task dependencies
 * @param {Array} tasks - Array of all tasks
 * @returns {Object} Validation result with valid flag and issues array
 */
function validateTaskDependencies(tasks) {
	const issues = [];

	// Check each task's dependencies
	tasks.forEach((task) => {
		if (!task.dependencies) {
			return; // No dependencies to validate
		}

		task.dependencies.forEach((depId) => {
			// Check for self-dependencies
			if (String(depId) === String(task.id)) {
				issues.push({
					type: 'self',
					taskId: task.id,
					message: `Task ${task.id} depends on itself`
				});
				return;
			}

			// Check if dependency exists
			if (!taskExists(tasks, depId)) {
				issues.push({
					type: 'missing',
					taskId: task.id,
					dependencyId: depId,
					message: `Task ${task.id} depends on non-existent task ${depId}`
				});
			}
		});

		// Check for circular dependencies
		if (isCircularDependency(tasks, task.id)) {
			issues.push({
				type: 'circular',
				taskId: task.id,
				message: `Task ${task.id} is part of a circular dependency chain`
			});
		}

		// Check subtask dependencies if they exist
		if (task.subtasks && task.subtasks.length > 0) {
			task.subtasks.forEach((subtask) => {
				if (!subtask.dependencies) {
					return; // No dependencies to validate
				}

				// Create a full subtask ID for reference
				const fullSubtaskId = `${task.id}.${subtask.id}`;

				subtask.dependencies.forEach((depId) => {
					// Check for self-dependencies in subtasks
					if (
						String(depId) === String(fullSubtaskId) ||
						(typeof depId === 'number' && depId === subtask.id)
					) {
						issues.push({
							type: 'self',
							taskId: fullSubtaskId,
							message: `Subtask ${fullSubtaskId} depends on itself`
						});
						return;
					}

					// Check if dependency exists
					if (!taskExists(tasks, depId)) {
						issues.push({
							type: 'missing',
							taskId: fullSubtaskId,
							dependencyId: depId,
							message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}`
						});
					}
				});

				// Check for circular dependencies in subtasks
				if (isCircularDependency(tasks, fullSubtaskId)) {
					issues.push({
						type: 'circular',
						taskId: fullSubtaskId,
						message: `Subtask ${fullSubtaskId} is part of a circular dependency chain`
					});
				}
			});
		}
	});

	return {
		valid: issues.length === 0,
		issues
	};
}

/**
 * Remove duplicate dependencies from tasks
 * @param {Object} tasksData - Tasks data object with tasks array
 * @returns {Object} Updated tasks data with duplicates removed
 */
function removeDuplicateDependencies(tasksData) {
	const tasks = tasksData.tasks.map((task) => {
		if (!task.dependencies) {
			return task;
		}

		// Convert to Set and back to array to remove duplicates
		const uniqueDeps = [...new Set(task.dependencies)];
		return {
			...task,
			dependencies: uniqueDeps
		};
	});

	return {
		...tasksData,
		tasks
	};
}

/**
 * Clean up invalid subtask dependencies
 * @param {Object} tasksData - Tasks data object with tasks array
 * @returns {Object} Updated tasks data with invalid subtask dependencies removed
 */
function cleanupSubtaskDependencies(tasksData) {
	const tasks = tasksData.tasks.map((task) => {
		// Handle task's own dependencies
		if (task.dependencies) {
			task.dependencies = task.dependencies.filter((depId) => {
				// Keep only dependencies that exist
				return taskExists(tasksData.tasks, depId);
			});
		}

		// Handle subtask dependencies
		if (task.subtasks) {
			task.subtasks = task.subtasks.map((subtask) => {
				if (!subtask.dependencies) {
					return subtask;
				}

				// Filter out dependencies to non-existent subtasks
				subtask.dependencies = subtask.dependencies.filter((depId) => {
					return taskExists(tasksData.tasks, depId);
				});

				return subtask;
			});
		}

		return task;
	});

	return {
		...tasksData,
		tasks
	};
}

/**
 * Validate dependencies in task files
 * @param {string} tasksPath - Path to tasks.json
 * @param {Object} options - Options object, including context
 */
async function validateDependenciesCommand(tasksPath, options = {}) {
	const { context = {} } = options;
	log('info', 'Checking for invalid dependencies in task files...');

	// Read tasks data
	const data = readJSON(tasksPath, context.projectRoot, context.tag);
	if (!data || !data.tasks) {
		log('error', 'No valid tasks found in tasks.json');
		process.exit(1);
	}

	// Count of tasks and subtasks for reporting
	const taskCount = data.tasks.length;
	let subtaskCount = 0;
	data.tasks.forEach((task) => {
		if (task.subtasks && Array.isArray(task.subtasks)) {
			subtaskCount += task.subtasks.length;
		}
	});

	log(
		'info',
		`Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...`
	);

	try {
		// Directly call the validation function
		const validationResult = validateTaskDependencies(data.tasks);

		if (!validationResult.valid) {
			log(
				'error',
				`Dependency validation failed. Found ${validationResult.issues.length} issue(s):`
			);
			validationResult.issues.forEach((issue) => {
				let errorMsg = `  [${issue.type.toUpperCase()}] Task ${issue.taskId}: ${issue.message}`;
				if (issue.dependencyId) {
					errorMsg += ` (Dependency: ${issue.dependencyId})`;
				}
				log('error', errorMsg); // Log each issue as an error
			});

			// Optionally exit if validation fails, depending on desired behavior
			// process.exit(1); // Uncomment if validation failure should stop the process

			// Display summary box even on failure, showing issues found
			if (!isSilentMode()) {
				console.log(
					boxen(
						chalk.red(`Dependency Validation FAILED\n\n`) +
							`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
							`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
							`${chalk.red('Issues found:')} ${validationResult.issues.length}`, // Display count from result
						{
							padding: 1,
							borderColor: 'red',
							borderStyle: 'round',
							margin: { top: 1, bottom: 1 }
						}
					)
				);
			}
		} else {
			log(
				'success',
				'No invalid dependencies found - all dependencies are valid'
			);

			// Show validation summary - only if not in silent mode
			if (!isSilentMode()) {
				console.log(
					boxen(
						chalk.green(`All Dependencies Are Valid\n\n`) +
							`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
							`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
							`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
						{
							padding: 1,
							borderColor: 'green',
							borderStyle: 'round',
							margin: { top: 1, bottom: 1 }
						}
					)
				);
			}
		}
	} catch (error) {
		log('error', 'Error validating dependencies:', error);
		process.exit(1);
	}
}

/**
 * Helper function to count all dependencies across tasks and subtasks
 * @param {Array} tasks - All tasks
 * @returns {number} - Total number of dependencies
 */
function countAllDependencies(tasks) {
	let count = 0;

	tasks.forEach((task) => {
		// Count main task dependencies
		if (task.dependencies && Array.isArray(task.dependencies)) {
			count += task.dependencies.length;
		}

		// Count subtask dependencies
		if (task.subtasks && Array.isArray(task.subtasks)) {
			task.subtasks.forEach((subtask) => {
				if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
					count += subtask.dependencies.length;
				}
			});
		}
	});

	return count;
}

/**
 * Fixes invalid dependencies in tasks.json
 * @param {string} tasksPath - Path to tasks.json
 * @param {Object} options - Options object, including context
 */
async function fixDependenciesCommand(tasksPath, options = {}) {
	const { context = {} } = options;
	log('info', 'Checking for and fixing invalid dependencies in tasks.json...');

	try {
		// Read tasks data
		const data = readJSON(tasksPath, context.projectRoot, context.tag);
		if (!data || !data.tasks) {
			log('error', 'No valid tasks found in tasks.json');
			process.exit(1);
		}

		// Create a deep copy of the original data for comparison
		const originalData = JSON.parse(JSON.stringify(data));

		// Track fixes for reporting
		const stats = {
			nonExistentDependenciesRemoved: 0,
			selfDependenciesRemoved: 0,
			duplicateDependenciesRemoved: 0,
			circularDependenciesFixed: 0,
			tasksFixed: 0,
			subtasksFixed: 0
		};

		// First phase: Remove duplicate dependencies in tasks
		data.tasks.forEach((task) => {
			if (task.dependencies && Array.isArray(task.dependencies)) {
				const uniqueDeps = new Set();
				const originalLength = task.dependencies.length;
				task.dependencies = task.dependencies.filter((depId) => {
					const depIdStr = String(depId);
					if (uniqueDeps.has(depIdStr)) {
						log(
							'info',
							`Removing duplicate dependency from task ${task.id}: ${depId}`
						);
						stats.duplicateDependenciesRemoved++;
						return false;
					}
					uniqueDeps.add(depIdStr);
					return true;
				});
				if (task.dependencies.length < originalLength) {
					stats.tasksFixed++;
				}
			}

			// Check for duplicates in subtasks
			if (task.subtasks && Array.isArray(task.subtasks)) {
				task.subtasks.forEach((subtask) => {
					if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
						const uniqueDeps = new Set();
						const originalLength = subtask.dependencies.length;
						subtask.dependencies = subtask.dependencies.filter((depId) => {
							let depIdStr = String(depId);
							if (typeof depId === 'number' && depId < 100) {
								depIdStr = `${task.id}.${depId}`;
							}
							if (uniqueDeps.has(depIdStr)) {
								log(
									'info',
									`Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}`
								);
								stats.duplicateDependenciesRemoved++;
								return false;
							}
							uniqueDeps.add(depIdStr);
							return true;
						});
						if (subtask.dependencies.length < originalLength) {
							stats.subtasksFixed++;
						}
					}
				});
			}
		});

		// Create validity maps for tasks and subtasks
		const validTaskIds = new Set(data.tasks.map((t) => t.id));
		const validSubtaskIds = new Set();
		data.tasks.forEach((task) => {
			if (task.subtasks && Array.isArray(task.subtasks)) {
				task.subtasks.forEach((subtask) => {
					validSubtaskIds.add(`${task.id}.${subtask.id}`);
				});
			}
		});

		// Second phase: Remove invalid task dependencies (non-existent tasks)
		data.tasks.forEach((task) => {
			if (task.dependencies && Array.isArray(task.dependencies)) {
				const originalLength = task.dependencies.length;
				task.dependencies = task.dependencies.filter((depId) => {
					const isSubtask = typeof depId === 'string' && depId.includes('.');

					if (isSubtask) {
						// Check if the subtask exists
						if (!validSubtaskIds.has(depId)) {
							log(
								'info',
								`Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)`
							);
							stats.nonExistentDependenciesRemoved++;
							return false;
						}
						return true;
					} else {
						// Check if the task exists
						const numericId =
							typeof depId === 'string' ? parseInt(depId, 10) : depId;
						if (!validTaskIds.has(numericId)) {
							log(
								'info',
								`Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)`
							);
							stats.nonExistentDependenciesRemoved++;
							return false;
						}
						return true;
					}
				});

				if (task.dependencies.length < originalLength) {
					stats.tasksFixed++;
				}
			}

			// Check subtask dependencies for invalid references
			if (task.subtasks && Array.isArray(task.subtasks)) {
				task.subtasks.forEach((subtask) => {
					if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
						const originalLength = subtask.dependencies.length;
						const subtaskId = `${task.id}.${subtask.id}`;

						// First check for self-dependencies
						const hasSelfDependency = subtask.dependencies.some((depId) => {
							if (typeof depId === 'string' && depId.includes('.')) {
								return depId === subtaskId;
							} else if (typeof depId === 'number' && depId < 100) {
								return depId === subtask.id;
							}
							return false;
						});

						if (hasSelfDependency) {
							subtask.dependencies = subtask.dependencies.filter((depId) => {
								const normalizedDepId =
									typeof depId === 'number' && depId < 100
										? `${task.id}.${depId}`
										: String(depId);

								if (normalizedDepId === subtaskId) {
									log(
										'info',
										`Removing self-dependency from subtask ${subtaskId}`
									);
									stats.selfDependenciesRemoved++;
									return false;
								}
								return true;
							});
						}

						// Then check for non-existent dependencies
						subtask.dependencies = subtask.dependencies.filter((depId) => {
							if (typeof depId === 'string' && depId.includes('.')) {
								if (!validSubtaskIds.has(depId)) {
									log(
										'info',
										`Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)`
									);
									stats.nonExistentDependenciesRemoved++;
									return false;
								}
								return true;
							}

							// Handle numeric dependencies
							const numericId =
								typeof depId === 'number' ? depId : parseInt(depId, 10);

							// Small numbers likely refer to subtasks in the same task
							if (numericId < 100) {
								const fullSubtaskId = `${task.id}.${numericId}`;

								if (!validSubtaskIds.has(fullSubtaskId)) {
									log(
										'info',
										`Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}`
									);
									stats.nonExistentDependenciesRemoved++;
									return false;
								}

								return true;
							}

							// Otherwise it's a task reference
							if (!validTaskIds.has(numericId)) {
								log(
									'info',
									`Removing invalid task dependency from subtask ${subtaskId}: ${numericId}`
								);
								stats.nonExistentDependenciesRemoved++;
								return false;
							}

							return true;
						});

						if (subtask.dependencies.length < originalLength) {
							stats.subtasksFixed++;
						}
					}
				});
			}
		});

		// Third phase: Check for circular dependencies
		log('info', 'Checking for circular dependencies...');

		// Build the dependency map for subtasks
		const subtaskDependencyMap = new Map();
		data.tasks.forEach((task) => {
			if (task.subtasks && Array.isArray(task.subtasks)) {
				task.subtasks.forEach((subtask) => {
					const subtaskId = `${task.id}.${subtask.id}`;

					if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
						const normalizedDeps = subtask.dependencies.map((depId) => {
							if (typeof depId === 'string' && depId.includes('.')) {
								return depId;
							} else if (typeof depId === 'number' && depId < 100) {
								return `${task.id}.${depId}`;
							}
							return String(depId);
						});
						subtaskDependencyMap.set(subtaskId, normalizedDeps);
					} else {
						subtaskDependencyMap.set(subtaskId, []);
					}
				});
			}
		});

		// Check for and fix circular dependencies
		for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) {
			const visited = new Set();
			const recursionStack = new Set();

			// Detect cycles
			const cycleEdges = findCycles(
				subtaskId,
				subtaskDependencyMap,
				visited,
				recursionStack
			);

			if (cycleEdges.length > 0) {
				const [taskId, subtaskNum] = subtaskId
					.split('.')
					.map((part) => Number(part));
				const task = data.tasks.find((t) => t.id === taskId);

				if (task && task.subtasks) {
					const subtask = task.subtasks.find((st) => st.id === subtaskNum);

					if (subtask && subtask.dependencies) {
						const originalLength = subtask.dependencies.length;

						const edgesToRemove = cycleEdges.map((edge) => {
							if (edge.includes('.')) {
								const [depTaskId, depSubtaskId] = edge
									.split('.')
									.map((part) => Number(part));

								if (depTaskId === taskId) {
									return depSubtaskId;
								}

								return edge;
							}

							return Number(edge);
						});

						subtask.dependencies = subtask.dependencies.filter((depId) => {
							const normalizedDepId =
								typeof depId === 'number' && depId < 100
									? `${taskId}.${depId}`
									: String(depId);

							if (
								edgesToRemove.includes(depId) ||
								edgesToRemove.includes(normalizedDepId)
							) {
								log(
									'info',
									`Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}`
								);
								stats.circularDependenciesFixed++;
								return false;
							}
							return true;
						});

						if (subtask.dependencies.length < originalLength) {
							stats.subtasksFixed++;
						}
					}
				}
			}
		}

		// Check if any changes were made by comparing with original data
		const dataChanged = JSON.stringify(data) !== JSON.stringify(originalData);

		if (dataChanged) {
			// Save the changes
			writeJSON(tasksPath, data, context.projectRoot, context.tag);
			log('success', 'Fixed dependency issues in tasks.json');

			// Regenerate task files
			log('info', 'Regenerating task files to reflect dependency changes...');
			// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
		} else {
			log('info', 'No changes needed to fix dependencies');
		}

		// Show detailed statistics report
		const totalFixedAll =
			stats.nonExistentDependenciesRemoved +
			stats.selfDependenciesRemoved +
			stats.duplicateDependenciesRemoved +
			stats.circularDependenciesFixed;

		if (!isSilentMode()) {
			if (totalFixedAll > 0) {
				log('success', `Fixed ${totalFixedAll} dependency issues in total!`);

				console.log(
					boxen(
						chalk.green(`Dependency Fixes Summary:\n\n`) +
							`${chalk.cyan('Invalid dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` +
							`${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` +
							`${chalk.cyan('Duplicate dependencies removed:')} ${stats.duplicateDependenciesRemoved}\n` +
							`${chalk.cyan('Circular dependencies fixed:')} ${stats.circularDependenciesFixed}\n\n` +
							`${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` +
							`${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}\n`,
						{
							padding: 1,
							borderColor: 'green',
							borderStyle: 'round',
							margin: { top: 1, bottom: 1 }
						}
					)
				);
			} else {
				log(
					'success',
					'No dependency issues found - all dependencies are valid'
				);

				console.log(
					boxen(
						chalk.green(`All Dependencies Are Valid\n\n`) +
							`${chalk.cyan('Tasks checked:')} ${data.tasks.length}\n` +
							`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
						{
							padding: 1,
							borderColor: 'green',
							borderStyle: 'round',
							margin: { top: 1, bottom: 1 }
						}
					)
				);
			}
		}
	} catch (error) {
		log('error', 'Error in fix-dependencies command:', error);
		process.exit(1);
	}
}

/**
 * Ensure at least one subtask in each task has no dependencies
 * @param {Object} tasksData - The tasks data object with tasks array
 * @returns {boolean} - True if any changes were made
 */
function ensureAtLeastOneIndependentSubtask(tasksData) {
	if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
		return false;
	}

	let changesDetected = false;

	tasksData.tasks.forEach((task) => {
		if (
			!task.subtasks ||
			!Array.isArray(task.subtasks) ||
			task.subtasks.length === 0
		) {
			return;
		}

		// Check if any subtask has no dependencies
		const hasIndependentSubtask = task.subtasks.some(
			(st) =>
				!st.dependencies ||
				!Array.isArray(st.dependencies) ||
				st.dependencies.length === 0
		);

		if (!hasIndependentSubtask) {
			// Find the first subtask and clear its dependencies
			if (task.subtasks.length > 0) {
				const firstSubtask = task.subtasks[0];
				log(
					'debug',
					`Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}`
				);
				firstSubtask.dependencies = [];
				changesDetected = true;
			}
		}
	});

	return changesDetected;
}

/**
 * Validate and fix dependencies across all tasks and subtasks
 * This function is designed to be called after any task modification
 * @param {Object} tasksData - The tasks data object with tasks array
 * @param {string} tasksPath - Optional path to save the changes
 * @param {string} projectRoot - Optional project root for tag context
 * @param {string} tag - Optional tag for tag context
 * @returns {boolean} - True if any changes were made
 */
function validateAndFixDependencies(
	tasksData,
	tasksPath = null,
	projectRoot = null,
	tag = null
) {
	if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
		log('error', 'Invalid tasks data');
		return false;
	}

	log('debug', 'Validating and fixing dependencies...');

	// Create a deep copy for comparison
	const originalData = JSON.parse(JSON.stringify(tasksData));

	// 1. Remove duplicate dependencies from tasks and subtasks
	tasksData.tasks = tasksData.tasks.map((task) => {
		// Handle task dependencies
		if (task.dependencies) {
			const uniqueDeps = [...new Set(task.dependencies)];
			task.dependencies = uniqueDeps;
		}

		// Handle subtask dependencies
		if (task.subtasks) {
			task.subtasks = task.subtasks.map((subtask) => {
				if (subtask.dependencies) {
					const uniqueDeps = [...new Set(subtask.dependencies)];
					subtask.dependencies = uniqueDeps;
				}
				return subtask;
			});
		}
		return task;
	});

	// 2. Remove invalid task dependencies (non-existent tasks)
	tasksData.tasks.forEach((task) => {
		// Clean up task dependencies
		if (task.dependencies) {
			task.dependencies = task.dependencies.filter((depId) => {
				// Remove self-dependencies
				if (String(depId) === String(task.id)) {
					return false;
				}
				// Remove non-existent dependencies
				return taskExists(tasksData.tasks, depId);
			});
		}

		// Clean up subtask dependencies
		if (task.subtasks) {
			task.subtasks.forEach((subtask) => {
				if (subtask.dependencies) {
					subtask.dependencies = subtask.dependencies.filter((depId) => {
						// Handle numeric subtask references
						if (typeof depId === 'number' && depId < 100) {
							const fullSubtaskId = `${task.id}.${depId}`;
							return taskExists(tasksData.tasks, fullSubtaskId);
						}
						// Handle full task/subtask references
						return taskExists(tasksData.tasks, depId);
					});
				}
			});
		}
	});

	// 3. Ensure at least one subtask has no dependencies in each task
	tasksData.tasks.forEach((task) => {
		if (task.subtasks && task.subtasks.length > 0) {
			const hasIndependentSubtask = task.subtasks.some(
				(st) =>
					!st.dependencies ||
					!Array.isArray(st.dependencies) ||
					st.dependencies.length === 0
			);

			if (!hasIndependentSubtask) {
				task.subtasks[0].dependencies = [];
			}
		}
	});

	// Check if any changes were made by comparing with original data
	const changesDetected =
		JSON.stringify(tasksData) !== JSON.stringify(originalData);

	// Save changes if needed
	if (tasksPath && changesDetected) {
		try {
			writeJSON(tasksPath, tasksData, projectRoot, tag);
			log('debug', 'Saved dependency fixes to tasks.json');
		} catch (error) {
			log('error', 'Failed to save dependency fixes to tasks.json', error);
		}
	}

	return changesDetected;
}

/**
 * Recursively find all dependencies for a set of tasks with depth limiting
 * Recursively find all dependencies for a set of tasks with depth limiting
 *
 * @note This function depends on the traverseDependencies utility from utils.js
 * for the actual dependency traversal logic.
 *
 * @param {Array} sourceTasks - Array of source tasks to find dependencies for
 * @param {Array} allTasks - Array of all available tasks
 * @param {Object} options - Options object
 * @param {number} options.maxDepth - Maximum recursion depth (default: 50)
 * @param {boolean} options.includeSelf - Whether to include self-references (default: false)
 * @returns {Array} Array of all dependency task IDs
 */
function findAllDependenciesRecursively(sourceTasks, allTasks, options = {}) {
	if (!Array.isArray(sourceTasks)) {
		throw new Error('Source tasks parameter must be an array');
	}
	if (!Array.isArray(allTasks)) {
		throw new Error('All tasks parameter must be an array');
	}
	return traverseDependencies(sourceTasks, allTasks, {
		...options,
		direction: 'forward',
		logger: { warn: log.warn || console.warn }
	});
}

/**
 * Find dependency task by ID, handling various ID formats
 * @param {string|number} depId - Dependency ID to find
 * @param {string} taskId - ID of the task that has this dependency
 * @param {Array} allTasks - Array of all tasks to search
 * @returns {Object|null} Found dependency task or null
 */
/**
 * Find a subtask within a parent task's subtasks array
 * @param {string} parentId - The parent task ID
 * @param {string|number} subtaskId - The subtask ID to find
 * @param {Array} allTasks - Array of all tasks to search in
 * @param {boolean} useStringComparison - Whether to use string comparison for subtaskId
 * @returns {Object|null} The found subtask with full ID or null if not found
 */
function findSubtaskInParent(
	parentId,
	subtaskId,
	allTasks,
	useStringComparison = false
) {
	// Convert parentId to numeric for proper comparison with top-level task IDs
	const numericParentId = parseInt(parentId, 10);
	const parentTask = allTasks.find((t) => t.id === numericParentId);

	if (parentTask && parentTask.subtasks && Array.isArray(parentTask.subtasks)) {
		const foundSubtask = parentTask.subtasks.find((subtask) =>
			useStringComparison
				? String(subtask.id) === String(subtaskId)
				: subtask.id === subtaskId
		);
		if (foundSubtask) {
			// Return a task-like object that represents the subtask with full ID
			return {
				...foundSubtask,
				id: `${parentId}.${foundSubtask.id}`
			};
		}
	}

	return null;
}

function findDependencyTask(depId, taskId, allTasks) {
	if (!depId) {
		return null;
	}

	// Convert depId to string for consistent comparison
	const depIdStr = String(depId);

	// Find the dependency task - handle both top-level and subtask IDs
	let depTask = null;

	// First try exact match (for top-level tasks)
	depTask = allTasks.find((t) => String(t.id) === depIdStr);

	// If not found and it's a subtask reference (contains dot), find the parent task first
	if (!depTask && depIdStr.includes('.')) {
		const [parentId, subtaskId] = depIdStr.split('.');
		depTask = findSubtaskInParent(parentId, subtaskId, allTasks, true);
	}

	// If still not found, try numeric comparison for relative subtask references
	if (!depTask && !isNaN(depId)) {
		const numericId = parseInt(depId, 10);
		// For subtasks, this might be a relative reference within the same parent
		if (taskId && typeof taskId === 'string' && taskId.includes('.')) {
			const [parentId] = taskId.split('.');
			depTask = findSubtaskInParent(parentId, numericId, allTasks, false);
		}
	}

	return depTask;
}

/**
 * Check if a task has cross-tag dependencies
 * @param {Object} task - Task to check
 * @param {string} targetTag - Target tag name
 * @param {Array} allTasks - Array of all tasks from all tags
 * @returns {Array} Array of cross-tag dependency conflicts
 */
function findTaskCrossTagConflicts(task, targetTag, allTasks) {
	const conflicts = [];

	// Validate task.dependencies is an array before processing
	if (!Array.isArray(task.dependencies) || task.dependencies.length === 0) {
		return conflicts;
	}

	// Filter out null/undefined dependencies and check each valid dependency
	const validDependencies = task.dependencies.filter((depId) => depId != null);

	validDependencies.forEach((depId) => {
		const depTask = findDependencyTask(depId, task.id, allTasks);

		if (depTask && depTask.tag !== targetTag) {
			conflicts.push({
				taskId: task.id,
				dependencyId: depId,
				dependencyTag: depTask.tag,
				message: `Task ${task.id} depends on ${depId} (in ${depTask.tag})`
			});
		}
	});

	return conflicts;
}

function validateCrossTagMove(task, sourceTag, targetTag, allTasks) {
	// Parameter validation
	if (!task || typeof task !== 'object') {
		throw new Error('Task parameter must be a valid object');
	}

	if (!sourceTag || typeof sourceTag !== 'string') {
		throw new Error('Source tag must be a valid string');
	}

	if (!targetTag || typeof targetTag !== 'string') {
		throw new Error('Target tag must be a valid string');
	}

	if (!Array.isArray(allTasks)) {
		throw new Error('All tasks parameter must be an array');
	}

	const conflicts = findTaskCrossTagConflicts(task, targetTag, allTasks);

	return {
		canMove: conflicts.length === 0,
		conflicts
	};
}

/**
 * Find all cross-tag dependencies for a set of tasks
 * @param {Array} sourceTasks - Array of tasks to check
 * @param {string} sourceTag - Source tag name
 * @param {string} targetTag - Target tag name
 * @param {Array} allTasks - Array of all tasks from all tags
 * @returns {Array} Array of cross-tag dependency conflicts
 */
function findCrossTagDependencies(sourceTasks, sourceTag, targetTag, allTasks) {
	// Parameter validation
	if (!Array.isArray(sourceTasks)) {
		throw new Error('Source tasks parameter must be an array');
	}

	if (!sourceTag || typeof sourceTag !== 'string') {
		throw new Error('Source tag must be a valid string');
	}

	if (!targetTag || typeof targetTag !== 'string') {
		throw new Error('Target tag must be a valid string');
	}

	if (!Array.isArray(allTasks)) {
		throw new Error('All tasks parameter must be an array');
	}

	const conflicts = [];

	sourceTasks.forEach((task) => {
		// Validate task object and dependencies array
		if (
			!task ||
			typeof task !== 'object' ||
			!Array.isArray(task.dependencies) ||
			task.dependencies.length === 0
		) {
			return;
		}

		// Use the shared helper function to find conflicts for this task
		const taskConflicts = findTaskCrossTagConflicts(task, targetTag, allTasks);
		conflicts.push(...taskConflicts);
	});

	return conflicts;
}

/**
 * Helper function to find all tasks that depend on a given task (reverse dependencies)
 * @param {string|number} taskId - The task ID to find dependencies for
 * @param {Array} allTasks - Array of all tasks to search
 * @param {Set} dependentTaskIds - Set to add found dependencies to
 */
function findTasksThatDependOn(taskId, allTasks, dependentTaskIds) {
	// Find the task object for the given ID
	const sourceTask = allTasks.find((t) => t.id === taskId);
	if (!sourceTask) {
		return;
	}

	// Use the shared utility for reverse dependency traversal
	const reverseDeps = traverseDependencies([sourceTask], allTasks, {
		direction: 'reverse',
		includeSelf: false,
		logger: { warn: log.warn || console.warn }
	});

	// Add all found reverse dependencies to the dependentTaskIds set
	reverseDeps.forEach((depId) => dependentTaskIds.add(depId));
}

/**
 * Helper function to check if a task depends on a source task
 * @param {Object} task - Task to check for dependencies
 * @param {Object} sourceTask - Source task to check dependency against
 * @returns {boolean} True if task depends on source task
 */
function taskDependsOnSource(task, sourceTask) {
	if (!task || !Array.isArray(task.dependencies)) {
		return false;
	}

	const sourceTaskIdStr = String(sourceTask.id);

	return task.dependencies.some((depId) => {
		if (!depId) return false;

		const depIdStr = String(depId);

		// Exact match
		if (depIdStr === sourceTaskIdStr) {
			return true;
		}

		// Handle subtask references
		if (
			sourceTaskIdStr &&
			typeof sourceTaskIdStr === 'string' &&
			sourceTaskIdStr.includes('.')
		) {
			// If source is a subtask, check if dependency references the parent
			const [parentId] = sourceTaskIdStr.split('.');
			if (depIdStr === parentId) {
				return true;
			}
		}

		// Handle relative subtask references
		if (
			depIdStr &&
			typeof depIdStr === 'string' &&
			depIdStr.includes('.') &&
			sourceTaskIdStr &&
			typeof sourceTaskIdStr === 'string' &&
			sourceTaskIdStr.includes('.')
		) {
			const [depParentId] = depIdStr.split('.');
			const [sourceParentId] = sourceTaskIdStr.split('.');
			if (depParentId === sourceParentId) {
				// Both are subtasks of the same parent, check if they reference each other
				const depSubtaskNum = parseInt(depIdStr.split('.')[1], 10);
				const sourceSubtaskNum = parseInt(sourceTaskIdStr.split('.')[1], 10);
				if (depSubtaskNum === sourceSubtaskNum) {
					return true;
				}
			}
		}

		return false;
	});
}

/**
 * Helper function to check if any subtasks of a task depend on source tasks
 * @param {Object} task - Task to check subtasks of
 * @param {Array} sourceTasks - Array of source tasks to check dependencies against
 * @returns {boolean} True if any subtasks depend on source tasks
 */
function subtasksDependOnSource(task, sourceTasks) {
	if (!task.subtasks || !Array.isArray(task.subtasks)) {
		return false;
	}

	return task.subtasks.some((subtask) => {
		// Check if this subtask depends on any source task
		const subtaskDependsOnSource = sourceTasks.some((sourceTask) =>
			taskDependsOnSource(subtask, sourceTask)
		);

		if (subtaskDependsOnSource) {
			return true;
		}

		// Recursively check if any nested subtasks depend on source tasks
		if (subtask.subtasks && Array.isArray(subtask.subtasks)) {
			return subtasksDependOnSource(subtask, sourceTasks);
		}

		return false;
	});
}

/**
 * Get all dependent task IDs for a set of cross-tag dependencies
 * @param {Array} sourceTasks - Array of source tasks
 * @param {Array} crossTagDependencies - Array of cross-tag dependency conflicts
 * @param {Array} allTasks - Array of all tasks from all tags
 * @returns {Array} Array of dependent task IDs to move
 */
function getDependentTaskIds(sourceTasks, crossTagDependencies, allTasks) {
	// Enhanced parameter validation
	if (!Array.isArray(sourceTasks)) {
		throw new Error('Source tasks parameter must be an array');
	}

	if (!Array.isArray(crossTagDependencies)) {
		throw new Error('Cross tag dependencies parameter must be an array');
	}

	if (!Array.isArray(allTasks)) {
		throw new Error('All tasks parameter must be an array');
	}

	// Use the shared recursive dependency finder
	const dependentTaskIds = new Set(
		findAllDependenciesRecursively(sourceTasks, allTasks, {
			includeSelf: false
		})
	);

	// Add immediate dependency IDs from conflicts and find their dependencies recursively
	const conflictTasksToProcess = [];
	crossTagDependencies.forEach((conflict) => {
		if (conflict && conflict.dependencyId) {
			const depId =
				typeof conflict.dependencyId === 'string'
					? parseInt(conflict.dependencyId, 10)
					: conflict.dependencyId;
			if (!isNaN(depId)) {
				dependentTaskIds.add(depId);
				// Find the task object for recursive dependency finding
				const depTask = allTasks.find((t) => t.id === depId);
				if (depTask) {
					conflictTasksToProcess.push(depTask);
				}
			}
		}
	});

	// Find dependencies of conflict tasks
	if (conflictTasksToProcess.length > 0) {
		const conflictDependencies = findAllDependenciesRecursively(
			conflictTasksToProcess,
			allTasks,
			{ includeSelf: false }
		);
		conflictDependencies.forEach((depId) => dependentTaskIds.add(depId));
	}

	// For --with-dependencies, we also need to find all dependencies of the source tasks
	sourceTasks.forEach((sourceTask) => {
		if (sourceTask && sourceTask.id) {
			// Find all tasks that this source task depends on (forward dependencies) - already handled above

			// Find all tasks that depend on this source task (reverse dependencies)
			findTasksThatDependOn(sourceTask.id, allTasks, dependentTaskIds);
		}
	});

	// Also include any tasks that depend on the source tasks
	sourceTasks.forEach((sourceTask) => {
		if (!sourceTask || typeof sourceTask !== 'object' || !sourceTask.id) {
			return; // Skip invalid source tasks
		}

		allTasks.forEach((task) => {
			// Validate task and dependencies array
			if (
				!task ||
				typeof task !== 'object' ||
				!Array.isArray(task.dependencies)
			) {
				return;
			}

			// Check if this task depends on the source task
			const hasDependency = taskDependsOnSource(task, sourceTask);

			// Check if any subtasks of this task depend on the source task
			const subtasksHaveDependency = subtasksDependOnSource(task, [sourceTask]);

			if (hasDependency || subtasksHaveDependency) {
				dependentTaskIds.add(task.id);
			}
		});
	});

	return Array.from(dependentTaskIds);
}

/**
 * Validate subtask movement - block direct cross-tag subtask moves
 * @param {string} taskId - Task ID to validate
 * @param {string} sourceTag - Source tag name
 * @param {string} targetTag - Target tag name
 * @throws {Error} If subtask movement is attempted
 */
function validateSubtaskMove(taskId, sourceTag, targetTag) {
	// Parameter validation
	if (!taskId || typeof taskId !== 'string') {
		throw new DependencyError(
			DEPENDENCY_ERROR_CODES.INVALID_TASK_ID,
			'Task ID must be a valid string'
		);
	}

	if (!sourceTag || typeof sourceTag !== 'string') {
		throw new DependencyError(
			DEPENDENCY_ERROR_CODES.INVALID_SOURCE_TAG,
			'Source tag must be a valid string'
		);
	}

	if (!targetTag || typeof targetTag !== 'string') {
		throw new DependencyError(
			DEPENDENCY_ERROR_CODES.INVALID_TARGET_TAG,
			'Target tag must be a valid string'
		);
	}

	if (taskId.includes('.')) {
		throw new DependencyError(
			DEPENDENCY_ERROR_CODES.CANNOT_MOVE_SUBTASK,
			`Cannot move subtask ${taskId} directly between tags.

First promote it to a full task using:
  task-master remove-subtask --id=${taskId} --convert`,
			{
				taskId,
				sourceTag,
				targetTag
			}
		);
	}
}

/**
 * Check if a task can be moved with its dependencies
 * @param {string} taskId - Task ID to check
 * @param {string} sourceTag - Source tag name
 * @param {string} targetTag - Target tag name
 * @param {Array} allTasks - Array of all tasks from all tags
 * @returns {Object} Object with canMove boolean and dependentTaskIds array
 */
function canMoveWithDependencies(taskId, sourceTag, targetTag, allTasks) {
	// Parameter validation
	if (!taskId || typeof taskId !== 'string') {
		throw new Error('Task ID must be a valid string');
	}

	if (!sourceTag || typeof sourceTag !== 'string') {
		throw new Error('Source tag must be a valid string');
	}

	if (!targetTag || typeof targetTag !== 'string') {
		throw new Error('Target tag must be a valid string');
	}

	if (!Array.isArray(allTasks)) {
		throw new Error('All tasks parameter must be an array');
	}

	// Enhanced task lookup to handle subtasks properly
	let sourceTask = null;

	// Check if it's a subtask ID (e.g., "1.2")
	if (taskId.includes('.')) {
		const [parentId, subtaskId] = taskId
			.split('.')
			.map((id) => parseInt(id, 10));
		const parentTask = allTasks.find(
			(t) => t.id === parentId && t.tag === sourceTag
		);

		if (
			parentTask &&
			parentTask.subtasks &&
			Array.isArray(parentTask.subtasks)
		) {
			const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
			if (subtask) {
				// Create a copy of the subtask with parent context
				sourceTask = {
					...subtask,
					parentTask: {
						id: parentTask.id,
						title: parentTask.title,
						status: parentTask.status
					},
					isSubtask: true
				};
			}
		}
	} else {
		// Regular task lookup - handle both string and numeric IDs
		sourceTask = allTasks.find((t) => {
			const taskIdNum = parseInt(taskId, 10);
			return (t.id === taskIdNum || t.id === taskId) && t.tag === sourceTag;
		});
	}

	if (!sourceTask) {
		return {
			canMove: false,
			dependentTaskIds: [],
			conflicts: [],
			error: 'Task not found'
		};
	}

	const validation = validateCrossTagMove(
		sourceTask,
		sourceTag,
		targetTag,
		allTasks
	);

	// Fix contradictory logic: return canMove: false when conflicts exist
	if (validation.canMove) {
		return {
			canMove: true,
			dependentTaskIds: [],
			conflicts: []
		};
	}

	// When conflicts exist, return canMove: false with conflicts and dependent task IDs
	const dependentTaskIds = getDependentTaskIds(
		[sourceTask],
		validation.conflicts,
		allTasks
	);

	return {
		canMove: false,
		dependentTaskIds,
		conflicts: validation.conflicts
	};
}

export {
	addDependency,
	removeDependency,
	isCircularDependency,
	validateTaskDependencies,
	validateDependenciesCommand,
	fixDependenciesCommand,
	removeDuplicateDependencies,
	cleanupSubtaskDependencies,
	ensureAtLeastOneIndependentSubtask,
	validateAndFixDependencies,
	findDependencyTask,
	findTaskCrossTagConflicts,
	validateCrossTagMove,
	findCrossTagDependencies,
	getDependentTaskIds,
	validateSubtaskMove,
	canMoveWithDependencies,
	findAllDependenciesRecursively,
	DependencyError,
	DEPENDENCY_ERROR_CODES
};

```
Page 32/38FirstPrevNextLast