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

# Directory Structure

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

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

```javascript
   1 | /**
   2 |  * dependency-manager.js
   3 |  * Manages task dependencies and relationships
   4 |  */
   5 | 
   6 | import path from 'path';
   7 | import chalk from 'chalk';
   8 | import boxen from 'boxen';
   9 | 
  10 | import {
  11 | 	log,
  12 | 	readJSON,
  13 | 	writeJSON,
  14 | 	taskExists,
  15 | 	formatTaskId,
  16 | 	findCycles,
  17 | 	traverseDependencies,
  18 | 	isSilentMode
  19 | } from './utils.js';
  20 | 
  21 | import { displayBanner } from './ui.js';
  22 | 
  23 | import generateTaskFiles from './task-manager/generate-task-files.js';
  24 | 
  25 | /**
  26 |  * Structured error class for dependency operations
  27 |  */
  28 | class DependencyError extends Error {
  29 | 	constructor(code, message, data = {}) {
  30 | 		super(message);
  31 | 		this.name = 'DependencyError';
  32 | 		this.code = code;
  33 | 		this.data = data;
  34 | 	}
  35 | }
  36 | 
  37 | /**
  38 |  * Error codes for dependency operations
  39 |  */
  40 | const DEPENDENCY_ERROR_CODES = {
  41 | 	CANNOT_MOVE_SUBTASK: 'CANNOT_MOVE_SUBTASK',
  42 | 	INVALID_TASK_ID: 'INVALID_TASK_ID',
  43 | 	INVALID_SOURCE_TAG: 'INVALID_SOURCE_TAG',
  44 | 	INVALID_TARGET_TAG: 'INVALID_TARGET_TAG'
  45 | };
  46 | 
  47 | /**
  48 |  * Add a dependency to a task
  49 |  * @param {string} tasksPath - Path to the tasks.json file
  50 |  * @param {number|string} taskId - ID of the task to add dependency to
  51 |  * @param {number|string} dependencyId - ID of the task to add as dependency
  52 |  * @param {Object} context - Context object containing projectRoot and tag information
  53 |  * @param {string} [context.projectRoot] - Project root path
  54 |  * @param {string} [context.tag] - Tag for the task
  55 |  */
  56 | async function addDependency(tasksPath, taskId, dependencyId, context = {}) {
  57 | 	log('info', `Adding dependency ${dependencyId} to task ${taskId}...`);
  58 | 
  59 | 	const data = readJSON(tasksPath, context.projectRoot, context.tag);
  60 | 	if (!data || !data.tasks) {
  61 | 		log('error', 'No valid tasks found in tasks.json');
  62 | 		process.exit(1);
  63 | 	}
  64 | 
  65 | 	// Format the task and dependency IDs correctly
  66 | 	const formattedTaskId =
  67 | 		typeof taskId === 'string' && taskId.includes('.')
  68 | 			? taskId
  69 | 			: parseInt(taskId, 10);
  70 | 
  71 | 	const formattedDependencyId = formatTaskId(dependencyId);
  72 | 
  73 | 	// Check if the dependency task or subtask actually exists
  74 | 	if (!taskExists(data.tasks, formattedDependencyId)) {
  75 | 		log(
  76 | 			'error',
  77 | 			`Dependency target ${formattedDependencyId} does not exist in tasks.json`
  78 | 		);
  79 | 		process.exit(1);
  80 | 	}
  81 | 
  82 | 	// Find the task to update
  83 | 	let targetTask = null;
  84 | 	let isSubtask = false;
  85 | 
  86 | 	if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) {
  87 | 		// Handle dot notation for subtasks (e.g., "1.2")
  88 | 		const [parentId, subtaskId] = formattedTaskId
  89 | 			.split('.')
  90 | 			.map((id) => parseInt(id, 10));
  91 | 		const parentTask = data.tasks.find((t) => t.id === parentId);
  92 | 
  93 | 		if (!parentTask) {
  94 | 			log('error', `Parent task ${parentId} not found.`);
  95 | 			process.exit(1);
  96 | 		}
  97 | 
  98 | 		if (!parentTask.subtasks) {
  99 | 			log('error', `Parent task ${parentId} has no subtasks.`);
 100 | 			process.exit(1);
 101 | 		}
 102 | 
 103 | 		targetTask = parentTask.subtasks.find((s) => s.id === subtaskId);
 104 | 		isSubtask = true;
 105 | 
 106 | 		if (!targetTask) {
 107 | 			log('error', `Subtask ${formattedTaskId} not found.`);
 108 | 			process.exit(1);
 109 | 		}
 110 | 	} else {
 111 | 		// Regular task (not a subtask)
 112 | 		targetTask = data.tasks.find((t) => t.id === formattedTaskId);
 113 | 
 114 | 		if (!targetTask) {
 115 | 			log('error', `Task ${formattedTaskId} not found.`);
 116 | 			process.exit(1);
 117 | 		}
 118 | 	}
 119 | 
 120 | 	// Initialize dependencies array if it doesn't exist
 121 | 	if (!targetTask.dependencies) {
 122 | 		targetTask.dependencies = [];
 123 | 	}
 124 | 
 125 | 	// Check if dependency already exists
 126 | 	if (
 127 | 		targetTask.dependencies.some((d) => {
 128 | 			// Convert both to strings for comparison to handle both numeric and string IDs
 129 | 			return String(d) === String(formattedDependencyId);
 130 | 		})
 131 | 	) {
 132 | 		log(
 133 | 			'warn',
 134 | 			`Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.`
 135 | 		);
 136 | 		return;
 137 | 	}
 138 | 
 139 | 	// Check if the task is trying to depend on itself - compare full IDs (including subtask parts)
 140 | 	if (String(formattedTaskId) === String(formattedDependencyId)) {
 141 | 		log('error', `Task ${formattedTaskId} cannot depend on itself.`);
 142 | 		process.exit(1);
 143 | 	}
 144 | 
 145 | 	// For subtasks of the same parent, we need to make sure we're not treating it as a self-dependency
 146 | 	// Check if we're dealing with subtasks with the same parent task
 147 | 	let isSelfDependency = false;
 148 | 
 149 | 	if (
 150 | 		typeof formattedTaskId === 'string' &&
 151 | 		typeof formattedDependencyId === 'string' &&
 152 | 		formattedTaskId.includes('.') &&
 153 | 		formattedDependencyId.includes('.')
 154 | 	) {
 155 | 		const [taskParentId] = formattedTaskId.split('.');
 156 | 		const [depParentId] = formattedDependencyId.split('.');
 157 | 
 158 | 		// Only treat it as a self-dependency if both the parent ID and subtask ID are identical
 159 | 		isSelfDependency = formattedTaskId === formattedDependencyId;
 160 | 
 161 | 		// Log for debugging
 162 | 		log(
 163 | 			'debug',
 164 | 			`Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}`
 165 | 		);
 166 | 		log(
 167 | 			'debug',
 168 | 			`Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}`
 169 | 		);
 170 | 	}
 171 | 
 172 | 	if (isSelfDependency) {
 173 | 		log('error', `Subtask ${formattedTaskId} cannot depend on itself.`);
 174 | 		process.exit(1);
 175 | 	}
 176 | 
 177 | 	// Check for circular dependencies
 178 | 	const dependencyChain = [formattedTaskId];
 179 | 	if (
 180 | 		!isCircularDependency(data.tasks, formattedDependencyId, dependencyChain)
 181 | 	) {
 182 | 		// Add the dependency
 183 | 		targetTask.dependencies.push(formattedDependencyId);
 184 | 
 185 | 		// Sort dependencies numerically or by parent task ID first, then subtask ID
 186 | 		targetTask.dependencies.sort((a, b) => {
 187 | 			if (typeof a === 'number' && typeof b === 'number') {
 188 | 				return a - b;
 189 | 			} else if (typeof a === 'string' && typeof b === 'string') {
 190 | 				const [aParent, aChild] = a.split('.').map(Number);
 191 | 				const [bParent, bChild] = b.split('.').map(Number);
 192 | 				return aParent !== bParent ? aParent - bParent : aChild - bChild;
 193 | 			} else if (typeof a === 'number') {
 194 | 				return -1; // Numbers come before strings
 195 | 			} else {
 196 | 				return 1; // Strings come after numbers
 197 | 			}
 198 | 		});
 199 | 
 200 | 		// Save changes
 201 | 		writeJSON(tasksPath, data, context.projectRoot, context.tag);
 202 | 		log(
 203 | 			'success',
 204 | 			`Added dependency ${formattedDependencyId} to task ${formattedTaskId}`
 205 | 		);
 206 | 
 207 | 		// Display a more visually appealing success message
 208 | 		if (!isSilentMode()) {
 209 | 			console.log(
 210 | 				boxen(
 211 | 					chalk.green(`Successfully added dependency:\n\n`) +
 212 | 						`Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`,
 213 | 					{
 214 | 						padding: 1,
 215 | 						borderColor: 'green',
 216 | 						borderStyle: 'round',
 217 | 						margin: { top: 1 }
 218 | 					}
 219 | 				)
 220 | 			);
 221 | 		}
 222 | 
 223 | 		// Generate updated task files
 224 | 		// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
 225 | 
 226 | 		log('info', 'Task files regenerated with updated dependencies.');
 227 | 	} else {
 228 | 		log(
 229 | 			'error',
 230 | 			`Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.`
 231 | 		);
 232 | 		process.exit(1);
 233 | 	}
 234 | }
 235 | 
 236 | /**
 237 |  * Remove a dependency from a task
 238 |  * @param {string} tasksPath - Path to the tasks.json file
 239 |  * @param {number|string} taskId - ID of the task to remove dependency from
 240 |  * @param {number|string} dependencyId - ID of the task to remove as dependency
 241 |  * @param {Object} context - Context object containing projectRoot and tag information
 242 |  * @param {string} [context.projectRoot] - Project root path
 243 |  * @param {string} [context.tag] - Tag for the task
 244 |  */
 245 | async function removeDependency(tasksPath, taskId, dependencyId, context = {}) {
 246 | 	log('info', `Removing dependency ${dependencyId} from task ${taskId}...`);
 247 | 
 248 | 	// Read tasks file
 249 | 	const data = readJSON(tasksPath, context.projectRoot, context.tag);
 250 | 	if (!data || !data.tasks) {
 251 | 		log('error', 'No valid tasks found.');
 252 | 		process.exit(1);
 253 | 	}
 254 | 
 255 | 	// Format the task and dependency IDs correctly
 256 | 	const formattedTaskId =
 257 | 		typeof taskId === 'string' && taskId.includes('.')
 258 | 			? taskId
 259 | 			: parseInt(taskId, 10);
 260 | 
 261 | 	const formattedDependencyId = formatTaskId(dependencyId);
 262 | 
 263 | 	// Find the task to update
 264 | 	let targetTask = null;
 265 | 	let isSubtask = false;
 266 | 
 267 | 	if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) {
 268 | 		// Handle dot notation for subtasks (e.g., "1.2")
 269 | 		const [parentId, subtaskId] = formattedTaskId
 270 | 			.split('.')
 271 | 			.map((id) => parseInt(id, 10));
 272 | 		const parentTask = data.tasks.find((t) => t.id === parentId);
 273 | 
 274 | 		if (!parentTask) {
 275 | 			log('error', `Parent task ${parentId} not found.`);
 276 | 			process.exit(1);
 277 | 		}
 278 | 
 279 | 		if (!parentTask.subtasks) {
 280 | 			log('error', `Parent task ${parentId} has no subtasks.`);
 281 | 			process.exit(1);
 282 | 		}
 283 | 
 284 | 		targetTask = parentTask.subtasks.find((s) => s.id === subtaskId);
 285 | 		isSubtask = true;
 286 | 
 287 | 		if (!targetTask) {
 288 | 			log('error', `Subtask ${formattedTaskId} not found.`);
 289 | 			process.exit(1);
 290 | 		}
 291 | 	} else {
 292 | 		// Regular task (not a subtask)
 293 | 		targetTask = data.tasks.find((t) => t.id === formattedTaskId);
 294 | 
 295 | 		if (!targetTask) {
 296 | 			log('error', `Task ${formattedTaskId} not found.`);
 297 | 			process.exit(1);
 298 | 		}
 299 | 	}
 300 | 
 301 | 	// Check if the task has any dependencies
 302 | 	if (!targetTask.dependencies || targetTask.dependencies.length === 0) {
 303 | 		log(
 304 | 			'info',
 305 | 			`Task ${formattedTaskId} has no dependencies, nothing to remove.`
 306 | 		);
 307 | 		return;
 308 | 	}
 309 | 
 310 | 	// Normalize the dependency ID for comparison to handle different formats
 311 | 	const normalizedDependencyId = String(formattedDependencyId);
 312 | 
 313 | 	// Check if the dependency exists by comparing string representations
 314 | 	const dependencyIndex = targetTask.dependencies.findIndex((dep) => {
 315 | 		// Convert both to strings for comparison
 316 | 		let depStr = String(dep);
 317 | 
 318 | 		// Special handling for numeric IDs that might be subtask references
 319 | 		if (typeof dep === 'number' && dep < 100 && isSubtask) {
 320 | 			// It's likely a reference to another subtask in the same parent task
 321 | 			// Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1)
 322 | 			const [parentId] = formattedTaskId.split('.');
 323 | 			depStr = `${parentId}.${dep}`;
 324 | 		}
 325 | 
 326 | 		return depStr === normalizedDependencyId;
 327 | 	});
 328 | 
 329 | 	if (dependencyIndex === -1) {
 330 | 		log(
 331 | 			'info',
 332 | 			`Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.`
 333 | 		);
 334 | 		return;
 335 | 	}
 336 | 
 337 | 	// Remove the dependency
 338 | 	targetTask.dependencies.splice(dependencyIndex, 1);
 339 | 
 340 | 	// Save the updated tasks
 341 | 	writeJSON(tasksPath, data, context.projectRoot, context.tag);
 342 | 
 343 | 	// Success message
 344 | 	log(
 345 | 		'success',
 346 | 		`Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}`
 347 | 	);
 348 | 
 349 | 	if (!isSilentMode()) {
 350 | 		// Display a more visually appealing success message
 351 | 		console.log(
 352 | 			boxen(
 353 | 				chalk.green(`Successfully removed dependency:\n\n`) +
 354 | 					`Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`,
 355 | 				{
 356 | 					padding: 1,
 357 | 					borderColor: 'green',
 358 | 					borderStyle: 'round',
 359 | 					margin: { top: 1 }
 360 | 				}
 361 | 			)
 362 | 		);
 363 | 	}
 364 | 
 365 | 	// Regenerate task files
 366 | 	// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
 367 | }
 368 | 
 369 | /**
 370 |  * Check if adding a dependency would create a circular dependency
 371 |  * @param {Array} tasks - Array of all tasks
 372 |  * @param {number|string} taskId - ID of task to check
 373 |  * @param {Array} chain - Chain of dependencies to check
 374 |  * @returns {boolean} True if circular dependency would be created
 375 |  */
 376 | function isCircularDependency(tasks, taskId, chain = []) {
 377 | 	// Convert taskId to string for comparison
 378 | 	const taskIdStr = String(taskId);
 379 | 
 380 | 	// If we've seen this task before in the chain, we have a circular dependency
 381 | 	if (chain.some((id) => String(id) === taskIdStr)) {
 382 | 		return true;
 383 | 	}
 384 | 
 385 | 	// Find the task or subtask
 386 | 	let task = null;
 387 | 	let parentIdForSubtask = null;
 388 | 
 389 | 	// Check if this is a subtask reference (e.g., "1.2")
 390 | 	if (taskIdStr.includes('.')) {
 391 | 		const [parentId, subtaskId] = taskIdStr.split('.').map(Number);
 392 | 		const parentTask = tasks.find((t) => t.id === parentId);
 393 | 		parentIdForSubtask = parentId; // Store parent ID if it's a subtask
 394 | 
 395 | 		if (parentTask && parentTask.subtasks) {
 396 | 			task = parentTask.subtasks.find((st) => st.id === subtaskId);
 397 | 		}
 398 | 	} else {
 399 | 		// Regular task
 400 | 		task = tasks.find((t) => String(t.id) === taskIdStr);
 401 | 	}
 402 | 
 403 | 	if (!task) {
 404 | 		return false; // Task doesn't exist, can't create circular dependency
 405 | 	}
 406 | 
 407 | 	// No dependencies, can't create circular dependency
 408 | 	if (!task.dependencies || task.dependencies.length === 0) {
 409 | 		return false;
 410 | 	}
 411 | 
 412 | 	// Check each dependency recursively
 413 | 	const newChain = [...chain, taskIdStr]; // Use taskIdStr for consistency
 414 | 	return task.dependencies.some((depId) => {
 415 | 		let normalizedDepId = String(depId);
 416 | 		// Normalize relative subtask dependencies
 417 | 		if (typeof depId === 'number' && parentIdForSubtask !== null) {
 418 | 			// If the current task is a subtask AND the dependency is a number,
 419 | 			// assume it refers to a sibling subtask.
 420 | 			normalizedDepId = `${parentIdForSubtask}.${depId}`;
 421 | 		}
 422 | 		// Pass the normalized ID to the recursive call
 423 | 		return isCircularDependency(tasks, normalizedDepId, newChain);
 424 | 	});
 425 | }
 426 | 
 427 | /**
 428 |  * Validate task dependencies
 429 |  * @param {Array} tasks - Array of all tasks
 430 |  * @returns {Object} Validation result with valid flag and issues array
 431 |  */
 432 | function validateTaskDependencies(tasks) {
 433 | 	const issues = [];
 434 | 
 435 | 	// Check each task's dependencies
 436 | 	tasks.forEach((task) => {
 437 | 		if (!task.dependencies) {
 438 | 			return; // No dependencies to validate
 439 | 		}
 440 | 
 441 | 		task.dependencies.forEach((depId) => {
 442 | 			// Check for self-dependencies
 443 | 			if (String(depId) === String(task.id)) {
 444 | 				issues.push({
 445 | 					type: 'self',
 446 | 					taskId: task.id,
 447 | 					message: `Task ${task.id} depends on itself`
 448 | 				});
 449 | 				return;
 450 | 			}
 451 | 
 452 | 			// Check if dependency exists
 453 | 			if (!taskExists(tasks, depId)) {
 454 | 				issues.push({
 455 | 					type: 'missing',
 456 | 					taskId: task.id,
 457 | 					dependencyId: depId,
 458 | 					message: `Task ${task.id} depends on non-existent task ${depId}`
 459 | 				});
 460 | 			}
 461 | 		});
 462 | 
 463 | 		// Check for circular dependencies
 464 | 		if (isCircularDependency(tasks, task.id)) {
 465 | 			issues.push({
 466 | 				type: 'circular',
 467 | 				taskId: task.id,
 468 | 				message: `Task ${task.id} is part of a circular dependency chain`
 469 | 			});
 470 | 		}
 471 | 
 472 | 		// Check subtask dependencies if they exist
 473 | 		if (task.subtasks && task.subtasks.length > 0) {
 474 | 			task.subtasks.forEach((subtask) => {
 475 | 				if (!subtask.dependencies) {
 476 | 					return; // No dependencies to validate
 477 | 				}
 478 | 
 479 | 				// Create a full subtask ID for reference
 480 | 				const fullSubtaskId = `${task.id}.${subtask.id}`;
 481 | 
 482 | 				subtask.dependencies.forEach((depId) => {
 483 | 					// Check for self-dependencies in subtasks
 484 | 					if (
 485 | 						String(depId) === String(fullSubtaskId) ||
 486 | 						(typeof depId === 'number' && depId === subtask.id)
 487 | 					) {
 488 | 						issues.push({
 489 | 							type: 'self',
 490 | 							taskId: fullSubtaskId,
 491 | 							message: `Subtask ${fullSubtaskId} depends on itself`
 492 | 						});
 493 | 						return;
 494 | 					}
 495 | 
 496 | 					// Check if dependency exists
 497 | 					if (!taskExists(tasks, depId)) {
 498 | 						issues.push({
 499 | 							type: 'missing',
 500 | 							taskId: fullSubtaskId,
 501 | 							dependencyId: depId,
 502 | 							message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}`
 503 | 						});
 504 | 					}
 505 | 				});
 506 | 
 507 | 				// Check for circular dependencies in subtasks
 508 | 				if (isCircularDependency(tasks, fullSubtaskId)) {
 509 | 					issues.push({
 510 | 						type: 'circular',
 511 | 						taskId: fullSubtaskId,
 512 | 						message: `Subtask ${fullSubtaskId} is part of a circular dependency chain`
 513 | 					});
 514 | 				}
 515 | 			});
 516 | 		}
 517 | 	});
 518 | 
 519 | 	return {
 520 | 		valid: issues.length === 0,
 521 | 		issues
 522 | 	};
 523 | }
 524 | 
 525 | /**
 526 |  * Remove duplicate dependencies from tasks
 527 |  * @param {Object} tasksData - Tasks data object with tasks array
 528 |  * @returns {Object} Updated tasks data with duplicates removed
 529 |  */
 530 | function removeDuplicateDependencies(tasksData) {
 531 | 	const tasks = tasksData.tasks.map((task) => {
 532 | 		if (!task.dependencies) {
 533 | 			return task;
 534 | 		}
 535 | 
 536 | 		// Convert to Set and back to array to remove duplicates
 537 | 		const uniqueDeps = [...new Set(task.dependencies)];
 538 | 		return {
 539 | 			...task,
 540 | 			dependencies: uniqueDeps
 541 | 		};
 542 | 	});
 543 | 
 544 | 	return {
 545 | 		...tasksData,
 546 | 		tasks
 547 | 	};
 548 | }
 549 | 
 550 | /**
 551 |  * Clean up invalid subtask dependencies
 552 |  * @param {Object} tasksData - Tasks data object with tasks array
 553 |  * @returns {Object} Updated tasks data with invalid subtask dependencies removed
 554 |  */
 555 | function cleanupSubtaskDependencies(tasksData) {
 556 | 	const tasks = tasksData.tasks.map((task) => {
 557 | 		// Handle task's own dependencies
 558 | 		if (task.dependencies) {
 559 | 			task.dependencies = task.dependencies.filter((depId) => {
 560 | 				// Keep only dependencies that exist
 561 | 				return taskExists(tasksData.tasks, depId);
 562 | 			});
 563 | 		}
 564 | 
 565 | 		// Handle subtask dependencies
 566 | 		if (task.subtasks) {
 567 | 			task.subtasks = task.subtasks.map((subtask) => {
 568 | 				if (!subtask.dependencies) {
 569 | 					return subtask;
 570 | 				}
 571 | 
 572 | 				// Filter out dependencies to non-existent subtasks
 573 | 				subtask.dependencies = subtask.dependencies.filter((depId) => {
 574 | 					return taskExists(tasksData.tasks, depId);
 575 | 				});
 576 | 
 577 | 				return subtask;
 578 | 			});
 579 | 		}
 580 | 
 581 | 		return task;
 582 | 	});
 583 | 
 584 | 	return {
 585 | 		...tasksData,
 586 | 		tasks
 587 | 	};
 588 | }
 589 | 
 590 | /**
 591 |  * Validate dependencies in task files
 592 |  * @param {string} tasksPath - Path to tasks.json
 593 |  * @param {Object} options - Options object, including context
 594 |  */
 595 | async function validateDependenciesCommand(tasksPath, options = {}) {
 596 | 	const { context = {} } = options;
 597 | 	log('info', 'Checking for invalid dependencies in task files...');
 598 | 
 599 | 	// Read tasks data
 600 | 	const data = readJSON(tasksPath, context.projectRoot, context.tag);
 601 | 	if (!data || !data.tasks) {
 602 | 		log('error', 'No valid tasks found in tasks.json');
 603 | 		process.exit(1);
 604 | 	}
 605 | 
 606 | 	// Count of tasks and subtasks for reporting
 607 | 	const taskCount = data.tasks.length;
 608 | 	let subtaskCount = 0;
 609 | 	data.tasks.forEach((task) => {
 610 | 		if (task.subtasks && Array.isArray(task.subtasks)) {
 611 | 			subtaskCount += task.subtasks.length;
 612 | 		}
 613 | 	});
 614 | 
 615 | 	log(
 616 | 		'info',
 617 | 		`Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...`
 618 | 	);
 619 | 
 620 | 	try {
 621 | 		// Directly call the validation function
 622 | 		const validationResult = validateTaskDependencies(data.tasks);
 623 | 
 624 | 		if (!validationResult.valid) {
 625 | 			log(
 626 | 				'error',
 627 | 				`Dependency validation failed. Found ${validationResult.issues.length} issue(s):`
 628 | 			);
 629 | 			validationResult.issues.forEach((issue) => {
 630 | 				let errorMsg = `  [${issue.type.toUpperCase()}] Task ${issue.taskId}: ${issue.message}`;
 631 | 				if (issue.dependencyId) {
 632 | 					errorMsg += ` (Dependency: ${issue.dependencyId})`;
 633 | 				}
 634 | 				log('error', errorMsg); // Log each issue as an error
 635 | 			});
 636 | 
 637 | 			// Optionally exit if validation fails, depending on desired behavior
 638 | 			// process.exit(1); // Uncomment if validation failure should stop the process
 639 | 
 640 | 			// Display summary box even on failure, showing issues found
 641 | 			if (!isSilentMode()) {
 642 | 				console.log(
 643 | 					boxen(
 644 | 						chalk.red(`Dependency Validation FAILED\n\n`) +
 645 | 							`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
 646 | 							`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
 647 | 							`${chalk.red('Issues found:')} ${validationResult.issues.length}`, // Display count from result
 648 | 						{
 649 | 							padding: 1,
 650 | 							borderColor: 'red',
 651 | 							borderStyle: 'round',
 652 | 							margin: { top: 1, bottom: 1 }
 653 | 						}
 654 | 					)
 655 | 				);
 656 | 			}
 657 | 		} else {
 658 | 			log(
 659 | 				'success',
 660 | 				'No invalid dependencies found - all dependencies are valid'
 661 | 			);
 662 | 
 663 | 			// Show validation summary - only if not in silent mode
 664 | 			if (!isSilentMode()) {
 665 | 				console.log(
 666 | 					boxen(
 667 | 						chalk.green(`All Dependencies Are Valid\n\n`) +
 668 | 							`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
 669 | 							`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
 670 | 							`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
 671 | 						{
 672 | 							padding: 1,
 673 | 							borderColor: 'green',
 674 | 							borderStyle: 'round',
 675 | 							margin: { top: 1, bottom: 1 }
 676 | 						}
 677 | 					)
 678 | 				);
 679 | 			}
 680 | 		}
 681 | 	} catch (error) {
 682 | 		log('error', 'Error validating dependencies:', error);
 683 | 		process.exit(1);
 684 | 	}
 685 | }
 686 | 
 687 | /**
 688 |  * Helper function to count all dependencies across tasks and subtasks
 689 |  * @param {Array} tasks - All tasks
 690 |  * @returns {number} - Total number of dependencies
 691 |  */
 692 | function countAllDependencies(tasks) {
 693 | 	let count = 0;
 694 | 
 695 | 	tasks.forEach((task) => {
 696 | 		// Count main task dependencies
 697 | 		if (task.dependencies && Array.isArray(task.dependencies)) {
 698 | 			count += task.dependencies.length;
 699 | 		}
 700 | 
 701 | 		// Count subtask dependencies
 702 | 		if (task.subtasks && Array.isArray(task.subtasks)) {
 703 | 			task.subtasks.forEach((subtask) => {
 704 | 				if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
 705 | 					count += subtask.dependencies.length;
 706 | 				}
 707 | 			});
 708 | 		}
 709 | 	});
 710 | 
 711 | 	return count;
 712 | }
 713 | 
 714 | /**
 715 |  * Fixes invalid dependencies in tasks.json
 716 |  * @param {string} tasksPath - Path to tasks.json
 717 |  * @param {Object} options - Options object, including context
 718 |  */
 719 | async function fixDependenciesCommand(tasksPath, options = {}) {
 720 | 	const { context = {} } = options;
 721 | 	log('info', 'Checking for and fixing invalid dependencies in tasks.json...');
 722 | 
 723 | 	try {
 724 | 		// Read tasks data
 725 | 		const data = readJSON(tasksPath, context.projectRoot, context.tag);
 726 | 		if (!data || !data.tasks) {
 727 | 			log('error', 'No valid tasks found in tasks.json');
 728 | 			process.exit(1);
 729 | 		}
 730 | 
 731 | 		// Create a deep copy of the original data for comparison
 732 | 		const originalData = JSON.parse(JSON.stringify(data));
 733 | 
 734 | 		// Track fixes for reporting
 735 | 		const stats = {
 736 | 			nonExistentDependenciesRemoved: 0,
 737 | 			selfDependenciesRemoved: 0,
 738 | 			duplicateDependenciesRemoved: 0,
 739 | 			circularDependenciesFixed: 0,
 740 | 			tasksFixed: 0,
 741 | 			subtasksFixed: 0
 742 | 		};
 743 | 
 744 | 		// First phase: Remove duplicate dependencies in tasks
 745 | 		data.tasks.forEach((task) => {
 746 | 			if (task.dependencies && Array.isArray(task.dependencies)) {
 747 | 				const uniqueDeps = new Set();
 748 | 				const originalLength = task.dependencies.length;
 749 | 				task.dependencies = task.dependencies.filter((depId) => {
 750 | 					const depIdStr = String(depId);
 751 | 					if (uniqueDeps.has(depIdStr)) {
 752 | 						log(
 753 | 							'info',
 754 | 							`Removing duplicate dependency from task ${task.id}: ${depId}`
 755 | 						);
 756 | 						stats.duplicateDependenciesRemoved++;
 757 | 						return false;
 758 | 					}
 759 | 					uniqueDeps.add(depIdStr);
 760 | 					return true;
 761 | 				});
 762 | 				if (task.dependencies.length < originalLength) {
 763 | 					stats.tasksFixed++;
 764 | 				}
 765 | 			}
 766 | 
 767 | 			// Check for duplicates in subtasks
 768 | 			if (task.subtasks && Array.isArray(task.subtasks)) {
 769 | 				task.subtasks.forEach((subtask) => {
 770 | 					if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
 771 | 						const uniqueDeps = new Set();
 772 | 						const originalLength = subtask.dependencies.length;
 773 | 						subtask.dependencies = subtask.dependencies.filter((depId) => {
 774 | 							let depIdStr = String(depId);
 775 | 							if (typeof depId === 'number' && depId < 100) {
 776 | 								depIdStr = `${task.id}.${depId}`;
 777 | 							}
 778 | 							if (uniqueDeps.has(depIdStr)) {
 779 | 								log(
 780 | 									'info',
 781 | 									`Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}`
 782 | 								);
 783 | 								stats.duplicateDependenciesRemoved++;
 784 | 								return false;
 785 | 							}
 786 | 							uniqueDeps.add(depIdStr);
 787 | 							return true;
 788 | 						});
 789 | 						if (subtask.dependencies.length < originalLength) {
 790 | 							stats.subtasksFixed++;
 791 | 						}
 792 | 					}
 793 | 				});
 794 | 			}
 795 | 		});
 796 | 
 797 | 		// Create validity maps for tasks and subtasks
 798 | 		const validTaskIds = new Set(data.tasks.map((t) => t.id));
 799 | 		const validSubtaskIds = new Set();
 800 | 		data.tasks.forEach((task) => {
 801 | 			if (task.subtasks && Array.isArray(task.subtasks)) {
 802 | 				task.subtasks.forEach((subtask) => {
 803 | 					validSubtaskIds.add(`${task.id}.${subtask.id}`);
 804 | 				});
 805 | 			}
 806 | 		});
 807 | 
 808 | 		// Second phase: Remove invalid task dependencies (non-existent tasks)
 809 | 		data.tasks.forEach((task) => {
 810 | 			if (task.dependencies && Array.isArray(task.dependencies)) {
 811 | 				const originalLength = task.dependencies.length;
 812 | 				task.dependencies = task.dependencies.filter((depId) => {
 813 | 					const isSubtask = typeof depId === 'string' && depId.includes('.');
 814 | 
 815 | 					if (isSubtask) {
 816 | 						// Check if the subtask exists
 817 | 						if (!validSubtaskIds.has(depId)) {
 818 | 							log(
 819 | 								'info',
 820 | 								`Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)`
 821 | 							);
 822 | 							stats.nonExistentDependenciesRemoved++;
 823 | 							return false;
 824 | 						}
 825 | 						return true;
 826 | 					} else {
 827 | 						// Check if the task exists
 828 | 						const numericId =
 829 | 							typeof depId === 'string' ? parseInt(depId, 10) : depId;
 830 | 						if (!validTaskIds.has(numericId)) {
 831 | 							log(
 832 | 								'info',
 833 | 								`Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)`
 834 | 							);
 835 | 							stats.nonExistentDependenciesRemoved++;
 836 | 							return false;
 837 | 						}
 838 | 						return true;
 839 | 					}
 840 | 				});
 841 | 
 842 | 				if (task.dependencies.length < originalLength) {
 843 | 					stats.tasksFixed++;
 844 | 				}
 845 | 			}
 846 | 
 847 | 			// Check subtask dependencies for invalid references
 848 | 			if (task.subtasks && Array.isArray(task.subtasks)) {
 849 | 				task.subtasks.forEach((subtask) => {
 850 | 					if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
 851 | 						const originalLength = subtask.dependencies.length;
 852 | 						const subtaskId = `${task.id}.${subtask.id}`;
 853 | 
 854 | 						// First check for self-dependencies
 855 | 						const hasSelfDependency = subtask.dependencies.some((depId) => {
 856 | 							if (typeof depId === 'string' && depId.includes('.')) {
 857 | 								return depId === subtaskId;
 858 | 							} else if (typeof depId === 'number' && depId < 100) {
 859 | 								return depId === subtask.id;
 860 | 							}
 861 | 							return false;
 862 | 						});
 863 | 
 864 | 						if (hasSelfDependency) {
 865 | 							subtask.dependencies = subtask.dependencies.filter((depId) => {
 866 | 								const normalizedDepId =
 867 | 									typeof depId === 'number' && depId < 100
 868 | 										? `${task.id}.${depId}`
 869 | 										: String(depId);
 870 | 
 871 | 								if (normalizedDepId === subtaskId) {
 872 | 									log(
 873 | 										'info',
 874 | 										`Removing self-dependency from subtask ${subtaskId}`
 875 | 									);
 876 | 									stats.selfDependenciesRemoved++;
 877 | 									return false;
 878 | 								}
 879 | 								return true;
 880 | 							});
 881 | 						}
 882 | 
 883 | 						// Then check for non-existent dependencies
 884 | 						subtask.dependencies = subtask.dependencies.filter((depId) => {
 885 | 							if (typeof depId === 'string' && depId.includes('.')) {
 886 | 								if (!validSubtaskIds.has(depId)) {
 887 | 									log(
 888 | 										'info',
 889 | 										`Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)`
 890 | 									);
 891 | 									stats.nonExistentDependenciesRemoved++;
 892 | 									return false;
 893 | 								}
 894 | 								return true;
 895 | 							}
 896 | 
 897 | 							// Handle numeric dependencies
 898 | 							const numericId =
 899 | 								typeof depId === 'number' ? depId : parseInt(depId, 10);
 900 | 
 901 | 							// Small numbers likely refer to subtasks in the same task
 902 | 							if (numericId < 100) {
 903 | 								const fullSubtaskId = `${task.id}.${numericId}`;
 904 | 
 905 | 								if (!validSubtaskIds.has(fullSubtaskId)) {
 906 | 									log(
 907 | 										'info',
 908 | 										`Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}`
 909 | 									);
 910 | 									stats.nonExistentDependenciesRemoved++;
 911 | 									return false;
 912 | 								}
 913 | 
 914 | 								return true;
 915 | 							}
 916 | 
 917 | 							// Otherwise it's a task reference
 918 | 							if (!validTaskIds.has(numericId)) {
 919 | 								log(
 920 | 									'info',
 921 | 									`Removing invalid task dependency from subtask ${subtaskId}: ${numericId}`
 922 | 								);
 923 | 								stats.nonExistentDependenciesRemoved++;
 924 | 								return false;
 925 | 							}
 926 | 
 927 | 							return true;
 928 | 						});
 929 | 
 930 | 						if (subtask.dependencies.length < originalLength) {
 931 | 							stats.subtasksFixed++;
 932 | 						}
 933 | 					}
 934 | 				});
 935 | 			}
 936 | 		});
 937 | 
 938 | 		// Third phase: Check for circular dependencies
 939 | 		log('info', 'Checking for circular dependencies...');
 940 | 
 941 | 		// Build the dependency map for subtasks
 942 | 		const subtaskDependencyMap = new Map();
 943 | 		data.tasks.forEach((task) => {
 944 | 			if (task.subtasks && Array.isArray(task.subtasks)) {
 945 | 				task.subtasks.forEach((subtask) => {
 946 | 					const subtaskId = `${task.id}.${subtask.id}`;
 947 | 
 948 | 					if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
 949 | 						const normalizedDeps = subtask.dependencies.map((depId) => {
 950 | 							if (typeof depId === 'string' && depId.includes('.')) {
 951 | 								return depId;
 952 | 							} else if (typeof depId === 'number' && depId < 100) {
 953 | 								return `${task.id}.${depId}`;
 954 | 							}
 955 | 							return String(depId);
 956 | 						});
 957 | 						subtaskDependencyMap.set(subtaskId, normalizedDeps);
 958 | 					} else {
 959 | 						subtaskDependencyMap.set(subtaskId, []);
 960 | 					}
 961 | 				});
 962 | 			}
 963 | 		});
 964 | 
 965 | 		// Check for and fix circular dependencies
 966 | 		for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) {
 967 | 			const visited = new Set();
 968 | 			const recursionStack = new Set();
 969 | 
 970 | 			// Detect cycles
 971 | 			const cycleEdges = findCycles(
 972 | 				subtaskId,
 973 | 				subtaskDependencyMap,
 974 | 				visited,
 975 | 				recursionStack
 976 | 			);
 977 | 
 978 | 			if (cycleEdges.length > 0) {
 979 | 				const [taskId, subtaskNum] = subtaskId
 980 | 					.split('.')
 981 | 					.map((part) => Number(part));
 982 | 				const task = data.tasks.find((t) => t.id === taskId);
 983 | 
 984 | 				if (task && task.subtasks) {
 985 | 					const subtask = task.subtasks.find((st) => st.id === subtaskNum);
 986 | 
 987 | 					if (subtask && subtask.dependencies) {
 988 | 						const originalLength = subtask.dependencies.length;
 989 | 
 990 | 						const edgesToRemove = cycleEdges.map((edge) => {
 991 | 							if (edge.includes('.')) {
 992 | 								const [depTaskId, depSubtaskId] = edge
 993 | 									.split('.')
 994 | 									.map((part) => Number(part));
 995 | 
 996 | 								if (depTaskId === taskId) {
 997 | 									return depSubtaskId;
 998 | 								}
 999 | 
1000 | 								return edge;
1001 | 							}
1002 | 
1003 | 							return Number(edge);
1004 | 						});
1005 | 
1006 | 						subtask.dependencies = subtask.dependencies.filter((depId) => {
1007 | 							const normalizedDepId =
1008 | 								typeof depId === 'number' && depId < 100
1009 | 									? `${taskId}.${depId}`
1010 | 									: String(depId);
1011 | 
1012 | 							if (
1013 | 								edgesToRemove.includes(depId) ||
1014 | 								edgesToRemove.includes(normalizedDepId)
1015 | 							) {
1016 | 								log(
1017 | 									'info',
1018 | 									`Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}`
1019 | 								);
1020 | 								stats.circularDependenciesFixed++;
1021 | 								return false;
1022 | 							}
1023 | 							return true;
1024 | 						});
1025 | 
1026 | 						if (subtask.dependencies.length < originalLength) {
1027 | 							stats.subtasksFixed++;
1028 | 						}
1029 | 					}
1030 | 				}
1031 | 			}
1032 | 		}
1033 | 
1034 | 		// Check if any changes were made by comparing with original data
1035 | 		const dataChanged = JSON.stringify(data) !== JSON.stringify(originalData);
1036 | 
1037 | 		if (dataChanged) {
1038 | 			// Save the changes
1039 | 			writeJSON(tasksPath, data, context.projectRoot, context.tag);
1040 | 			log('success', 'Fixed dependency issues in tasks.json');
1041 | 
1042 | 			// Regenerate task files
1043 | 			log('info', 'Regenerating task files to reflect dependency changes...');
1044 | 			// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
1045 | 		} else {
1046 | 			log('info', 'No changes needed to fix dependencies');
1047 | 		}
1048 | 
1049 | 		// Show detailed statistics report
1050 | 		const totalFixedAll =
1051 | 			stats.nonExistentDependenciesRemoved +
1052 | 			stats.selfDependenciesRemoved +
1053 | 			stats.duplicateDependenciesRemoved +
1054 | 			stats.circularDependenciesFixed;
1055 | 
1056 | 		if (!isSilentMode()) {
1057 | 			if (totalFixedAll > 0) {
1058 | 				log('success', `Fixed ${totalFixedAll} dependency issues in total!`);
1059 | 
1060 | 				console.log(
1061 | 					boxen(
1062 | 						chalk.green(`Dependency Fixes Summary:\n\n`) +
1063 | 							`${chalk.cyan('Invalid dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` +
1064 | 							`${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` +
1065 | 							`${chalk.cyan('Duplicate dependencies removed:')} ${stats.duplicateDependenciesRemoved}\n` +
1066 | 							`${chalk.cyan('Circular dependencies fixed:')} ${stats.circularDependenciesFixed}\n\n` +
1067 | 							`${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` +
1068 | 							`${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}\n`,
1069 | 						{
1070 | 							padding: 1,
1071 | 							borderColor: 'green',
1072 | 							borderStyle: 'round',
1073 | 							margin: { top: 1, bottom: 1 }
1074 | 						}
1075 | 					)
1076 | 				);
1077 | 			} else {
1078 | 				log(
1079 | 					'success',
1080 | 					'No dependency issues found - all dependencies are valid'
1081 | 				);
1082 | 
1083 | 				console.log(
1084 | 					boxen(
1085 | 						chalk.green(`All Dependencies Are Valid\n\n`) +
1086 | 							`${chalk.cyan('Tasks checked:')} ${data.tasks.length}\n` +
1087 | 							`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
1088 | 						{
1089 | 							padding: 1,
1090 | 							borderColor: 'green',
1091 | 							borderStyle: 'round',
1092 | 							margin: { top: 1, bottom: 1 }
1093 | 						}
1094 | 					)
1095 | 				);
1096 | 			}
1097 | 		}
1098 | 	} catch (error) {
1099 | 		log('error', 'Error in fix-dependencies command:', error);
1100 | 		process.exit(1);
1101 | 	}
1102 | }
1103 | 
1104 | /**
1105 |  * Ensure at least one subtask in each task has no dependencies
1106 |  * @param {Object} tasksData - The tasks data object with tasks array
1107 |  * @returns {boolean} - True if any changes were made
1108 |  */
1109 | function ensureAtLeastOneIndependentSubtask(tasksData) {
1110 | 	if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
1111 | 		return false;
1112 | 	}
1113 | 
1114 | 	let changesDetected = false;
1115 | 
1116 | 	tasksData.tasks.forEach((task) => {
1117 | 		if (
1118 | 			!task.subtasks ||
1119 | 			!Array.isArray(task.subtasks) ||
1120 | 			task.subtasks.length === 0
1121 | 		) {
1122 | 			return;
1123 | 		}
1124 | 
1125 | 		// Check if any subtask has no dependencies
1126 | 		const hasIndependentSubtask = task.subtasks.some(
1127 | 			(st) =>
1128 | 				!st.dependencies ||
1129 | 				!Array.isArray(st.dependencies) ||
1130 | 				st.dependencies.length === 0
1131 | 		);
1132 | 
1133 | 		if (!hasIndependentSubtask) {
1134 | 			// Find the first subtask and clear its dependencies
1135 | 			if (task.subtasks.length > 0) {
1136 | 				const firstSubtask = task.subtasks[0];
1137 | 				log(
1138 | 					'debug',
1139 | 					`Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}`
1140 | 				);
1141 | 				firstSubtask.dependencies = [];
1142 | 				changesDetected = true;
1143 | 			}
1144 | 		}
1145 | 	});
1146 | 
1147 | 	return changesDetected;
1148 | }
1149 | 
1150 | /**
1151 |  * Validate and fix dependencies across all tasks and subtasks
1152 |  * This function is designed to be called after any task modification
1153 |  * @param {Object} tasksData - The tasks data object with tasks array
1154 |  * @param {string} tasksPath - Optional path to save the changes
1155 |  * @param {string} projectRoot - Optional project root for tag context
1156 |  * @param {string} tag - Optional tag for tag context
1157 |  * @returns {boolean} - True if any changes were made
1158 |  */
1159 | function validateAndFixDependencies(
1160 | 	tasksData,
1161 | 	tasksPath = null,
1162 | 	projectRoot = null,
1163 | 	tag = null
1164 | ) {
1165 | 	if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
1166 | 		log('error', 'Invalid tasks data');
1167 | 		return false;
1168 | 	}
1169 | 
1170 | 	log('debug', 'Validating and fixing dependencies...');
1171 | 
1172 | 	// Create a deep copy for comparison
1173 | 	const originalData = JSON.parse(JSON.stringify(tasksData));
1174 | 
1175 | 	// 1. Remove duplicate dependencies from tasks and subtasks
1176 | 	tasksData.tasks = tasksData.tasks.map((task) => {
1177 | 		// Handle task dependencies
1178 | 		if (task.dependencies) {
1179 | 			const uniqueDeps = [...new Set(task.dependencies)];
1180 | 			task.dependencies = uniqueDeps;
1181 | 		}
1182 | 
1183 | 		// Handle subtask dependencies
1184 | 		if (task.subtasks) {
1185 | 			task.subtasks = task.subtasks.map((subtask) => {
1186 | 				if (subtask.dependencies) {
1187 | 					const uniqueDeps = [...new Set(subtask.dependencies)];
1188 | 					subtask.dependencies = uniqueDeps;
1189 | 				}
1190 | 				return subtask;
1191 | 			});
1192 | 		}
1193 | 		return task;
1194 | 	});
1195 | 
1196 | 	// 2. Remove invalid task dependencies (non-existent tasks)
1197 | 	tasksData.tasks.forEach((task) => {
1198 | 		// Clean up task dependencies
1199 | 		if (task.dependencies) {
1200 | 			task.dependencies = task.dependencies.filter((depId) => {
1201 | 				// Remove self-dependencies
1202 | 				if (String(depId) === String(task.id)) {
1203 | 					return false;
1204 | 				}
1205 | 				// Remove non-existent dependencies
1206 | 				return taskExists(tasksData.tasks, depId);
1207 | 			});
1208 | 		}
1209 | 
1210 | 		// Clean up subtask dependencies
1211 | 		if (task.subtasks) {
1212 | 			task.subtasks.forEach((subtask) => {
1213 | 				if (subtask.dependencies) {
1214 | 					subtask.dependencies = subtask.dependencies.filter((depId) => {
1215 | 						// Handle numeric subtask references
1216 | 						if (typeof depId === 'number' && depId < 100) {
1217 | 							const fullSubtaskId = `${task.id}.${depId}`;
1218 | 							return taskExists(tasksData.tasks, fullSubtaskId);
1219 | 						}
1220 | 						// Handle full task/subtask references
1221 | 						return taskExists(tasksData.tasks, depId);
1222 | 					});
1223 | 				}
1224 | 			});
1225 | 		}
1226 | 	});
1227 | 
1228 | 	// 3. Ensure at least one subtask has no dependencies in each task
1229 | 	tasksData.tasks.forEach((task) => {
1230 | 		if (task.subtasks && task.subtasks.length > 0) {
1231 | 			const hasIndependentSubtask = task.subtasks.some(
1232 | 				(st) =>
1233 | 					!st.dependencies ||
1234 | 					!Array.isArray(st.dependencies) ||
1235 | 					st.dependencies.length === 0
1236 | 			);
1237 | 
1238 | 			if (!hasIndependentSubtask) {
1239 | 				task.subtasks[0].dependencies = [];
1240 | 			}
1241 | 		}
1242 | 	});
1243 | 
1244 | 	// Check if any changes were made by comparing with original data
1245 | 	const changesDetected =
1246 | 		JSON.stringify(tasksData) !== JSON.stringify(originalData);
1247 | 
1248 | 	// Save changes if needed
1249 | 	if (tasksPath && changesDetected) {
1250 | 		try {
1251 | 			writeJSON(tasksPath, tasksData, projectRoot, tag);
1252 | 			log('debug', 'Saved dependency fixes to tasks.json');
1253 | 		} catch (error) {
1254 | 			log('error', 'Failed to save dependency fixes to tasks.json', error);
1255 | 		}
1256 | 	}
1257 | 
1258 | 	return changesDetected;
1259 | }
1260 | 
1261 | /**
1262 |  * Recursively find all dependencies for a set of tasks with depth limiting
1263 |  * Recursively find all dependencies for a set of tasks with depth limiting
1264 |  *
1265 |  * @note This function depends on the traverseDependencies utility from utils.js
1266 |  * for the actual dependency traversal logic.
1267 |  *
1268 |  * @param {Array} sourceTasks - Array of source tasks to find dependencies for
1269 |  * @param {Array} allTasks - Array of all available tasks
1270 |  * @param {Object} options - Options object
1271 |  * @param {number} options.maxDepth - Maximum recursion depth (default: 50)
1272 |  * @param {boolean} options.includeSelf - Whether to include self-references (default: false)
1273 |  * @returns {Array} Array of all dependency task IDs
1274 |  */
1275 | function findAllDependenciesRecursively(sourceTasks, allTasks, options = {}) {
1276 | 	if (!Array.isArray(sourceTasks)) {
1277 | 		throw new Error('Source tasks parameter must be an array');
1278 | 	}
1279 | 	if (!Array.isArray(allTasks)) {
1280 | 		throw new Error('All tasks parameter must be an array');
1281 | 	}
1282 | 	return traverseDependencies(sourceTasks, allTasks, {
1283 | 		...options,
1284 | 		direction: 'forward',
1285 | 		logger: { warn: log.warn || console.warn }
1286 | 	});
1287 | }
1288 | 
1289 | /**
1290 |  * Find dependency task by ID, handling various ID formats
1291 |  * @param {string|number} depId - Dependency ID to find
1292 |  * @param {string} taskId - ID of the task that has this dependency
1293 |  * @param {Array} allTasks - Array of all tasks to search
1294 |  * @returns {Object|null} Found dependency task or null
1295 |  */
1296 | /**
1297 |  * Find a subtask within a parent task's subtasks array
1298 |  * @param {string} parentId - The parent task ID
1299 |  * @param {string|number} subtaskId - The subtask ID to find
1300 |  * @param {Array} allTasks - Array of all tasks to search in
1301 |  * @param {boolean} useStringComparison - Whether to use string comparison for subtaskId
1302 |  * @returns {Object|null} The found subtask with full ID or null if not found
1303 |  */
1304 | function findSubtaskInParent(
1305 | 	parentId,
1306 | 	subtaskId,
1307 | 	allTasks,
1308 | 	useStringComparison = false
1309 | ) {
1310 | 	// Convert parentId to numeric for proper comparison with top-level task IDs
1311 | 	const numericParentId = parseInt(parentId, 10);
1312 | 	const parentTask = allTasks.find((t) => t.id === numericParentId);
1313 | 
1314 | 	if (parentTask && parentTask.subtasks && Array.isArray(parentTask.subtasks)) {
1315 | 		const foundSubtask = parentTask.subtasks.find((subtask) =>
1316 | 			useStringComparison
1317 | 				? String(subtask.id) === String(subtaskId)
1318 | 				: subtask.id === subtaskId
1319 | 		);
1320 | 		if (foundSubtask) {
1321 | 			// Return a task-like object that represents the subtask with full ID
1322 | 			return {
1323 | 				...foundSubtask,
1324 | 				id: `${parentId}.${foundSubtask.id}`
1325 | 			};
1326 | 		}
1327 | 	}
1328 | 
1329 | 	return null;
1330 | }
1331 | 
1332 | function findDependencyTask(depId, taskId, allTasks) {
1333 | 	if (!depId) {
1334 | 		return null;
1335 | 	}
1336 | 
1337 | 	// Convert depId to string for consistent comparison
1338 | 	const depIdStr = String(depId);
1339 | 
1340 | 	// Find the dependency task - handle both top-level and subtask IDs
1341 | 	let depTask = null;
1342 | 
1343 | 	// First try exact match (for top-level tasks)
1344 | 	depTask = allTasks.find((t) => String(t.id) === depIdStr);
1345 | 
1346 | 	// If not found and it's a subtask reference (contains dot), find the parent task first
1347 | 	if (!depTask && depIdStr.includes('.')) {
1348 | 		const [parentId, subtaskId] = depIdStr.split('.');
1349 | 		depTask = findSubtaskInParent(parentId, subtaskId, allTasks, true);
1350 | 	}
1351 | 
1352 | 	// If still not found, try numeric comparison for relative subtask references
1353 | 	if (!depTask && !isNaN(depId)) {
1354 | 		const numericId = parseInt(depId, 10);
1355 | 		// For subtasks, this might be a relative reference within the same parent
1356 | 		if (taskId && typeof taskId === 'string' && taskId.includes('.')) {
1357 | 			const [parentId] = taskId.split('.');
1358 | 			depTask = findSubtaskInParent(parentId, numericId, allTasks, false);
1359 | 		}
1360 | 	}
1361 | 
1362 | 	return depTask;
1363 | }
1364 | 
1365 | /**
1366 |  * Check if a task has cross-tag dependencies
1367 |  * @param {Object} task - Task to check
1368 |  * @param {string} targetTag - Target tag name
1369 |  * @param {Array} allTasks - Array of all tasks from all tags
1370 |  * @returns {Array} Array of cross-tag dependency conflicts
1371 |  */
1372 | function findTaskCrossTagConflicts(task, targetTag, allTasks) {
1373 | 	const conflicts = [];
1374 | 
1375 | 	// Validate task.dependencies is an array before processing
1376 | 	if (!Array.isArray(task.dependencies) || task.dependencies.length === 0) {
1377 | 		return conflicts;
1378 | 	}
1379 | 
1380 | 	// Filter out null/undefined dependencies and check each valid dependency
1381 | 	const validDependencies = task.dependencies.filter((depId) => depId != null);
1382 | 
1383 | 	validDependencies.forEach((depId) => {
1384 | 		const depTask = findDependencyTask(depId, task.id, allTasks);
1385 | 
1386 | 		if (depTask && depTask.tag !== targetTag) {
1387 | 			conflicts.push({
1388 | 				taskId: task.id,
1389 | 				dependencyId: depId,
1390 | 				dependencyTag: depTask.tag,
1391 | 				message: `Task ${task.id} depends on ${depId} (in ${depTask.tag})`
1392 | 			});
1393 | 		}
1394 | 	});
1395 | 
1396 | 	return conflicts;
1397 | }
1398 | 
1399 | function validateCrossTagMove(task, sourceTag, targetTag, allTasks) {
1400 | 	// Parameter validation
1401 | 	if (!task || typeof task !== 'object') {
1402 | 		throw new Error('Task parameter must be a valid object');
1403 | 	}
1404 | 
1405 | 	if (!sourceTag || typeof sourceTag !== 'string') {
1406 | 		throw new Error('Source tag must be a valid string');
1407 | 	}
1408 | 
1409 | 	if (!targetTag || typeof targetTag !== 'string') {
1410 | 		throw new Error('Target tag must be a valid string');
1411 | 	}
1412 | 
1413 | 	if (!Array.isArray(allTasks)) {
1414 | 		throw new Error('All tasks parameter must be an array');
1415 | 	}
1416 | 
1417 | 	const conflicts = findTaskCrossTagConflicts(task, targetTag, allTasks);
1418 | 
1419 | 	return {
1420 | 		canMove: conflicts.length === 0,
1421 | 		conflicts
1422 | 	};
1423 | }
1424 | 
1425 | /**
1426 |  * Find all cross-tag dependencies for a set of tasks
1427 |  * @param {Array} sourceTasks - Array of tasks to check
1428 |  * @param {string} sourceTag - Source tag name
1429 |  * @param {string} targetTag - Target tag name
1430 |  * @param {Array} allTasks - Array of all tasks from all tags
1431 |  * @returns {Array} Array of cross-tag dependency conflicts
1432 |  */
1433 | function findCrossTagDependencies(sourceTasks, sourceTag, targetTag, allTasks) {
1434 | 	// Parameter validation
1435 | 	if (!Array.isArray(sourceTasks)) {
1436 | 		throw new Error('Source tasks parameter must be an array');
1437 | 	}
1438 | 
1439 | 	if (!sourceTag || typeof sourceTag !== 'string') {
1440 | 		throw new Error('Source tag must be a valid string');
1441 | 	}
1442 | 
1443 | 	if (!targetTag || typeof targetTag !== 'string') {
1444 | 		throw new Error('Target tag must be a valid string');
1445 | 	}
1446 | 
1447 | 	if (!Array.isArray(allTasks)) {
1448 | 		throw new Error('All tasks parameter must be an array');
1449 | 	}
1450 | 
1451 | 	const conflicts = [];
1452 | 
1453 | 	sourceTasks.forEach((task) => {
1454 | 		// Validate task object and dependencies array
1455 | 		if (
1456 | 			!task ||
1457 | 			typeof task !== 'object' ||
1458 | 			!Array.isArray(task.dependencies) ||
1459 | 			task.dependencies.length === 0
1460 | 		) {
1461 | 			return;
1462 | 		}
1463 | 
1464 | 		// Use the shared helper function to find conflicts for this task
1465 | 		const taskConflicts = findTaskCrossTagConflicts(task, targetTag, allTasks);
1466 | 		conflicts.push(...taskConflicts);
1467 | 	});
1468 | 
1469 | 	return conflicts;
1470 | }
1471 | 
1472 | /**
1473 |  * Helper function to find all tasks that depend on a given task (reverse dependencies)
1474 |  * @param {string|number} taskId - The task ID to find dependencies for
1475 |  * @param {Array} allTasks - Array of all tasks to search
1476 |  * @param {Set} dependentTaskIds - Set to add found dependencies to
1477 |  */
1478 | function findTasksThatDependOn(taskId, allTasks, dependentTaskIds) {
1479 | 	// Find the task object for the given ID
1480 | 	const sourceTask = allTasks.find((t) => t.id === taskId);
1481 | 	if (!sourceTask) {
1482 | 		return;
1483 | 	}
1484 | 
1485 | 	// Use the shared utility for reverse dependency traversal
1486 | 	const reverseDeps = traverseDependencies([sourceTask], allTasks, {
1487 | 		direction: 'reverse',
1488 | 		includeSelf: false,
1489 | 		logger: { warn: log.warn || console.warn }
1490 | 	});
1491 | 
1492 | 	// Add all found reverse dependencies to the dependentTaskIds set
1493 | 	reverseDeps.forEach((depId) => dependentTaskIds.add(depId));
1494 | }
1495 | 
1496 | /**
1497 |  * Helper function to check if a task depends on a source task
1498 |  * @param {Object} task - Task to check for dependencies
1499 |  * @param {Object} sourceTask - Source task to check dependency against
1500 |  * @returns {boolean} True if task depends on source task
1501 |  */
1502 | function taskDependsOnSource(task, sourceTask) {
1503 | 	if (!task || !Array.isArray(task.dependencies)) {
1504 | 		return false;
1505 | 	}
1506 | 
1507 | 	const sourceTaskIdStr = String(sourceTask.id);
1508 | 
1509 | 	return task.dependencies.some((depId) => {
1510 | 		if (!depId) return false;
1511 | 
1512 | 		const depIdStr = String(depId);
1513 | 
1514 | 		// Exact match
1515 | 		if (depIdStr === sourceTaskIdStr) {
1516 | 			return true;
1517 | 		}
1518 | 
1519 | 		// Handle subtask references
1520 | 		if (
1521 | 			sourceTaskIdStr &&
1522 | 			typeof sourceTaskIdStr === 'string' &&
1523 | 			sourceTaskIdStr.includes('.')
1524 | 		) {
1525 | 			// If source is a subtask, check if dependency references the parent
1526 | 			const [parentId] = sourceTaskIdStr.split('.');
1527 | 			if (depIdStr === parentId) {
1528 | 				return true;
1529 | 			}
1530 | 		}
1531 | 
1532 | 		// Handle relative subtask references
1533 | 		if (
1534 | 			depIdStr &&
1535 | 			typeof depIdStr === 'string' &&
1536 | 			depIdStr.includes('.') &&
1537 | 			sourceTaskIdStr &&
1538 | 			typeof sourceTaskIdStr === 'string' &&
1539 | 			sourceTaskIdStr.includes('.')
1540 | 		) {
1541 | 			const [depParentId] = depIdStr.split('.');
1542 | 			const [sourceParentId] = sourceTaskIdStr.split('.');
1543 | 			if (depParentId === sourceParentId) {
1544 | 				// Both are subtasks of the same parent, check if they reference each other
1545 | 				const depSubtaskNum = parseInt(depIdStr.split('.')[1], 10);
1546 | 				const sourceSubtaskNum = parseInt(sourceTaskIdStr.split('.')[1], 10);
1547 | 				if (depSubtaskNum === sourceSubtaskNum) {
1548 | 					return true;
1549 | 				}
1550 | 			}
1551 | 		}
1552 | 
1553 | 		return false;
1554 | 	});
1555 | }
1556 | 
1557 | /**
1558 |  * Helper function to check if any subtasks of a task depend on source tasks
1559 |  * @param {Object} task - Task to check subtasks of
1560 |  * @param {Array} sourceTasks - Array of source tasks to check dependencies against
1561 |  * @returns {boolean} True if any subtasks depend on source tasks
1562 |  */
1563 | function subtasksDependOnSource(task, sourceTasks) {
1564 | 	if (!task.subtasks || !Array.isArray(task.subtasks)) {
1565 | 		return false;
1566 | 	}
1567 | 
1568 | 	return task.subtasks.some((subtask) => {
1569 | 		// Check if this subtask depends on any source task
1570 | 		const subtaskDependsOnSource = sourceTasks.some((sourceTask) =>
1571 | 			taskDependsOnSource(subtask, sourceTask)
1572 | 		);
1573 | 
1574 | 		if (subtaskDependsOnSource) {
1575 | 			return true;
1576 | 		}
1577 | 
1578 | 		// Recursively check if any nested subtasks depend on source tasks
1579 | 		if (subtask.subtasks && Array.isArray(subtask.subtasks)) {
1580 | 			return subtasksDependOnSource(subtask, sourceTasks);
1581 | 		}
1582 | 
1583 | 		return false;
1584 | 	});
1585 | }
1586 | 
1587 | /**
1588 |  * Get all dependent task IDs for a set of cross-tag dependencies
1589 |  * @param {Array} sourceTasks - Array of source tasks
1590 |  * @param {Array} crossTagDependencies - Array of cross-tag dependency conflicts
1591 |  * @param {Array} allTasks - Array of all tasks from all tags
1592 |  * @returns {Array} Array of dependent task IDs to move
1593 |  */
1594 | function getDependentTaskIds(sourceTasks, crossTagDependencies, allTasks) {
1595 | 	// Enhanced parameter validation
1596 | 	if (!Array.isArray(sourceTasks)) {
1597 | 		throw new Error('Source tasks parameter must be an array');
1598 | 	}
1599 | 
1600 | 	if (!Array.isArray(crossTagDependencies)) {
1601 | 		throw new Error('Cross tag dependencies parameter must be an array');
1602 | 	}
1603 | 
1604 | 	if (!Array.isArray(allTasks)) {
1605 | 		throw new Error('All tasks parameter must be an array');
1606 | 	}
1607 | 
1608 | 	// Use the shared recursive dependency finder
1609 | 	const dependentTaskIds = new Set(
1610 | 		findAllDependenciesRecursively(sourceTasks, allTasks, {
1611 | 			includeSelf: false
1612 | 		})
1613 | 	);
1614 | 
1615 | 	// Add immediate dependency IDs from conflicts and find their dependencies recursively
1616 | 	const conflictTasksToProcess = [];
1617 | 	crossTagDependencies.forEach((conflict) => {
1618 | 		if (conflict && conflict.dependencyId) {
1619 | 			const depId =
1620 | 				typeof conflict.dependencyId === 'string'
1621 | 					? parseInt(conflict.dependencyId, 10)
1622 | 					: conflict.dependencyId;
1623 | 			if (!isNaN(depId)) {
1624 | 				dependentTaskIds.add(depId);
1625 | 				// Find the task object for recursive dependency finding
1626 | 				const depTask = allTasks.find((t) => t.id === depId);
1627 | 				if (depTask) {
1628 | 					conflictTasksToProcess.push(depTask);
1629 | 				}
1630 | 			}
1631 | 		}
1632 | 	});
1633 | 
1634 | 	// Find dependencies of conflict tasks
1635 | 	if (conflictTasksToProcess.length > 0) {
1636 | 		const conflictDependencies = findAllDependenciesRecursively(
1637 | 			conflictTasksToProcess,
1638 | 			allTasks,
1639 | 			{ includeSelf: false }
1640 | 		);
1641 | 		conflictDependencies.forEach((depId) => dependentTaskIds.add(depId));
1642 | 	}
1643 | 
1644 | 	// For --with-dependencies, we also need to find all dependencies of the source tasks
1645 | 	sourceTasks.forEach((sourceTask) => {
1646 | 		if (sourceTask && sourceTask.id) {
1647 | 			// Find all tasks that this source task depends on (forward dependencies) - already handled above
1648 | 
1649 | 			// Find all tasks that depend on this source task (reverse dependencies)
1650 | 			findTasksThatDependOn(sourceTask.id, allTasks, dependentTaskIds);
1651 | 		}
1652 | 	});
1653 | 
1654 | 	// Also include any tasks that depend on the source tasks
1655 | 	sourceTasks.forEach((sourceTask) => {
1656 | 		if (!sourceTask || typeof sourceTask !== 'object' || !sourceTask.id) {
1657 | 			return; // Skip invalid source tasks
1658 | 		}
1659 | 
1660 | 		allTasks.forEach((task) => {
1661 | 			// Validate task and dependencies array
1662 | 			if (
1663 | 				!task ||
1664 | 				typeof task !== 'object' ||
1665 | 				!Array.isArray(task.dependencies)
1666 | 			) {
1667 | 				return;
1668 | 			}
1669 | 
1670 | 			// Check if this task depends on the source task
1671 | 			const hasDependency = taskDependsOnSource(task, sourceTask);
1672 | 
1673 | 			// Check if any subtasks of this task depend on the source task
1674 | 			const subtasksHaveDependency = subtasksDependOnSource(task, [sourceTask]);
1675 | 
1676 | 			if (hasDependency || subtasksHaveDependency) {
1677 | 				dependentTaskIds.add(task.id);
1678 | 			}
1679 | 		});
1680 | 	});
1681 | 
1682 | 	return Array.from(dependentTaskIds);
1683 | }
1684 | 
1685 | /**
1686 |  * Validate subtask movement - block direct cross-tag subtask moves
1687 |  * @param {string} taskId - Task ID to validate
1688 |  * @param {string} sourceTag - Source tag name
1689 |  * @param {string} targetTag - Target tag name
1690 |  * @throws {Error} If subtask movement is attempted
1691 |  */
1692 | function validateSubtaskMove(taskId, sourceTag, targetTag) {
1693 | 	// Parameter validation
1694 | 	if (!taskId || typeof taskId !== 'string') {
1695 | 		throw new DependencyError(
1696 | 			DEPENDENCY_ERROR_CODES.INVALID_TASK_ID,
1697 | 			'Task ID must be a valid string'
1698 | 		);
1699 | 	}
1700 | 
1701 | 	if (!sourceTag || typeof sourceTag !== 'string') {
1702 | 		throw new DependencyError(
1703 | 			DEPENDENCY_ERROR_CODES.INVALID_SOURCE_TAG,
1704 | 			'Source tag must be a valid string'
1705 | 		);
1706 | 	}
1707 | 
1708 | 	if (!targetTag || typeof targetTag !== 'string') {
1709 | 		throw new DependencyError(
1710 | 			DEPENDENCY_ERROR_CODES.INVALID_TARGET_TAG,
1711 | 			'Target tag must be a valid string'
1712 | 		);
1713 | 	}
1714 | 
1715 | 	if (taskId.includes('.')) {
1716 | 		throw new DependencyError(
1717 | 			DEPENDENCY_ERROR_CODES.CANNOT_MOVE_SUBTASK,
1718 | 			`Cannot move subtask ${taskId} directly between tags.
1719 | 
1720 | First promote it to a full task using:
1721 |   task-master remove-subtask --id=${taskId} --convert`,
1722 | 			{
1723 | 				taskId,
1724 | 				sourceTag,
1725 | 				targetTag
1726 | 			}
1727 | 		);
1728 | 	}
1729 | }
1730 | 
1731 | /**
1732 |  * Check if a task can be moved with its dependencies
1733 |  * @param {string} taskId - Task ID to check
1734 |  * @param {string} sourceTag - Source tag name
1735 |  * @param {string} targetTag - Target tag name
1736 |  * @param {Array} allTasks - Array of all tasks from all tags
1737 |  * @returns {Object} Object with canMove boolean and dependentTaskIds array
1738 |  */
1739 | function canMoveWithDependencies(taskId, sourceTag, targetTag, allTasks) {
1740 | 	// Parameter validation
1741 | 	if (!taskId || typeof taskId !== 'string') {
1742 | 		throw new Error('Task ID must be a valid string');
1743 | 	}
1744 | 
1745 | 	if (!sourceTag || typeof sourceTag !== 'string') {
1746 | 		throw new Error('Source tag must be a valid string');
1747 | 	}
1748 | 
1749 | 	if (!targetTag || typeof targetTag !== 'string') {
1750 | 		throw new Error('Target tag must be a valid string');
1751 | 	}
1752 | 
1753 | 	if (!Array.isArray(allTasks)) {
1754 | 		throw new Error('All tasks parameter must be an array');
1755 | 	}
1756 | 
1757 | 	// Enhanced task lookup to handle subtasks properly
1758 | 	let sourceTask = null;
1759 | 
1760 | 	// Check if it's a subtask ID (e.g., "1.2")
1761 | 	if (taskId.includes('.')) {
1762 | 		const [parentId, subtaskId] = taskId
1763 | 			.split('.')
1764 | 			.map((id) => parseInt(id, 10));
1765 | 		const parentTask = allTasks.find(
1766 | 			(t) => t.id === parentId && t.tag === sourceTag
1767 | 		);
1768 | 
1769 | 		if (
1770 | 			parentTask &&
1771 | 			parentTask.subtasks &&
1772 | 			Array.isArray(parentTask.subtasks)
1773 | 		) {
1774 | 			const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
1775 | 			if (subtask) {
1776 | 				// Create a copy of the subtask with parent context
1777 | 				sourceTask = {
1778 | 					...subtask,
1779 | 					parentTask: {
1780 | 						id: parentTask.id,
1781 | 						title: parentTask.title,
1782 | 						status: parentTask.status
1783 | 					},
1784 | 					isSubtask: true
1785 | 				};
1786 | 			}
1787 | 		}
1788 | 	} else {
1789 | 		// Regular task lookup - handle both string and numeric IDs
1790 | 		sourceTask = allTasks.find((t) => {
1791 | 			const taskIdNum = parseInt(taskId, 10);
1792 | 			return (t.id === taskIdNum || t.id === taskId) && t.tag === sourceTag;
1793 | 		});
1794 | 	}
1795 | 
1796 | 	if (!sourceTask) {
1797 | 		return {
1798 | 			canMove: false,
1799 | 			dependentTaskIds: [],
1800 | 			conflicts: [],
1801 | 			error: 'Task not found'
1802 | 		};
1803 | 	}
1804 | 
1805 | 	const validation = validateCrossTagMove(
1806 | 		sourceTask,
1807 | 		sourceTag,
1808 | 		targetTag,
1809 | 		allTasks
1810 | 	);
1811 | 
1812 | 	// Fix contradictory logic: return canMove: false when conflicts exist
1813 | 	if (validation.canMove) {
1814 | 		return {
1815 | 			canMove: true,
1816 | 			dependentTaskIds: [],
1817 | 			conflicts: []
1818 | 		};
1819 | 	}
1820 | 
1821 | 	// When conflicts exist, return canMove: false with conflicts and dependent task IDs
1822 | 	const dependentTaskIds = getDependentTaskIds(
1823 | 		[sourceTask],
1824 | 		validation.conflicts,
1825 | 		allTasks
1826 | 	);
1827 | 
1828 | 	return {
1829 | 		canMove: false,
1830 | 		dependentTaskIds,
1831 | 		conflicts: validation.conflicts
1832 | 	};
1833 | }
1834 | 
1835 | export {
1836 | 	addDependency,
1837 | 	removeDependency,
1838 | 	isCircularDependency,
1839 | 	validateTaskDependencies,
1840 | 	validateDependenciesCommand,
1841 | 	fixDependenciesCommand,
1842 | 	removeDuplicateDependencies,
1843 | 	cleanupSubtaskDependencies,
1844 | 	ensureAtLeastOneIndependentSubtask,
1845 | 	validateAndFixDependencies,
1846 | 	findDependencyTask,
1847 | 	findTaskCrossTagConflicts,
1848 | 	validateCrossTagMove,
1849 | 	findCrossTagDependencies,
1850 | 	getDependentTaskIds,
1851 | 	validateSubtaskMove,
1852 | 	canMoveWithDependencies,
1853 | 	findAllDependenciesRecursively,
1854 | 	DependencyError,
1855 | 	DEPENDENCY_ERROR_CODES
1856 | };
1857 | 
```
Page 44/52FirstPrevNextLast