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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/packages/tm-bridge/src/tags-bridge.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ui } from '@tm/cli';
  2 | import boxen from 'boxen';
  3 | import chalk from 'chalk';
  4 | import Table from 'cli-table3';
  5 | import type { TagInfo } from '@tm/core';
  6 | import type { BaseBridgeParams } from './bridge-types.js';
  7 | import { checkStorageType } from './bridge-utils.js';
  8 | 
  9 | // Re-export for convenience
 10 | export type { TagInfo };
 11 | 
 12 | /**
 13 |  * Parameters for the tags bridge function
 14 |  */
 15 | export interface TagsBridgeParams extends BaseBridgeParams {
 16 | 	/** Whether to show metadata (default: false) */
 17 | 	showMetadata?: boolean;
 18 | }
 19 | 
 20 | /**
 21 |  * Result returned when API storage handles the tags listing
 22 |  */
 23 | export interface RemoteTagsResult {
 24 | 	success: boolean;
 25 | 	tags: TagInfo[];
 26 | 	currentTag: string | null;
 27 | 	totalTags: number;
 28 | 	message: string;
 29 | }
 30 | 
 31 | /**
 32 |  * Shared bridge function for list-tags command.
 33 |  * Checks if using API storage and delegates to remote service if so.
 34 |  *
 35 |  * For API storage, tags are called "briefs" and task counts are fetched
 36 |  * from the remote database.
 37 |  *
 38 |  * @param params - Bridge parameters
 39 |  * @returns Result object if API storage handled it, null if should fall through to file storage
 40 |  */
 41 | export async function tryListTagsViaRemote(
 42 | 	params: TagsBridgeParams
 43 | ): Promise<RemoteTagsResult | null> {
 44 | 	const { projectRoot, isMCP = false, outputFormat = 'text', report } = params;
 45 | 
 46 | 	// Check storage type using shared utility
 47 | 	const { isApiStorage, tmCore } = await checkStorageType(
 48 | 		projectRoot,
 49 | 		report,
 50 | 		'falling back to file-based tags'
 51 | 	);
 52 | 
 53 | 	if (!isApiStorage || !tmCore) {
 54 | 		// Not API storage - signal caller to fall through to file-based logic
 55 | 		return null;
 56 | 	}
 57 | 
 58 | 	try {
 59 | 		// Get tags with statistics from tm-core
 60 | 		// Tags are already sorted by status and updatedAt from brief-service
 61 | 		const tagsResult = await tmCore.tasks.getTagsWithStats();
 62 | 
 63 | 		// Sort tags: current tag first, then preserve status/updatedAt ordering from service
 64 | 		tagsResult.tags.sort((a, b) => {
 65 | 			// Always keep current tag at the top
 66 | 			if (a.isCurrent) return -1;
 67 | 			if (b.isCurrent) return 1;
 68 | 			// For non-current tags, preserve the status/updatedAt ordering already applied
 69 | 			return 0;
 70 | 		});
 71 | 
 72 | 		if (outputFormat === 'text' && !isMCP) {
 73 | 			// Display results in a table format
 74 | 			if (tagsResult.tags.length === 0) {
 75 | 				console.log(
 76 | 					boxen(chalk.yellow('No tags found'), {
 77 | 						padding: 1,
 78 | 						borderColor: 'yellow',
 79 | 						borderStyle: 'round',
 80 | 						margin: { top: 1, bottom: 1 }
 81 | 					})
 82 | 				);
 83 | 			} else {
 84 | 				// Create table headers (with temporary Updated column)
 85 | 				const headers = [
 86 | 					chalk.cyan.bold('Tag Name'),
 87 | 					chalk.cyan.bold('Status'),
 88 | 					chalk.cyan.bold('Updated'),
 89 | 					chalk.cyan.bold('Tasks'),
 90 | 					chalk.cyan.bold('Completed')
 91 | 				];
 92 | 
 93 | 				// Calculate dynamic column widths based on terminal width
 94 | 				const terminalWidth = Math.max(
 95 | 					(process.stdout.columns as number) || 120,
 96 | 					80
 97 | 				);
 98 | 				const usableWidth = Math.floor(terminalWidth * 0.95);
 99 | 
100 | 				// Column order: Tag Name, Status, Updated, Tasks, Completed
101 | 				const widths = [0.35, 0.25, 0.2, 0.1, 0.1];
102 | 				const colWidths = widths.map((w, i) =>
103 | 					Math.max(Math.floor(usableWidth * w), i === 0 ? 20 : 8)
104 | 				);
105 | 
106 | 				const table = new Table({
107 | 					head: headers,
108 | 					colWidths: colWidths,
109 | 					wordWrap: true
110 | 				});
111 | 
112 | 				// Add rows
113 | 				tagsResult.tags.forEach((tag) => {
114 | 					const row = [];
115 | 
116 | 					// Tag name with current indicator and short ID (last 8 chars)
117 | 					const shortId = tag.briefId ? tag.briefId.slice(-8) : 'unknown';
118 | 					const tagDisplay = tag.isCurrent
119 | 						? `${chalk.green('●')} ${chalk.green.bold(tag.name)} ${chalk.gray(`(current - ${shortId})`)}`
120 | 						: `  ${tag.name} ${chalk.gray(`(${shortId})`)}`;
121 | 					row.push(tagDisplay);
122 | 
123 | 					row.push(ui.getBriefStatusWithColor(tag.status, true));
124 | 
125 | 					// Updated date (temporary for validation)
126 | 					const updatedDate = tag.updatedAt
127 | 						? new Date(tag.updatedAt).toLocaleDateString('en-US', {
128 | 								month: 'short',
129 | 								day: 'numeric',
130 | 								year: 'numeric',
131 | 								hour: '2-digit',
132 | 								minute: '2-digit'
133 | 							})
134 | 						: chalk.gray('N/A');
135 | 					row.push(chalk.gray(updatedDate));
136 | 
137 | 					// Task counts
138 | 					row.push(chalk.white(tag.taskCount.toString()));
139 | 					row.push(chalk.green(tag.completedTasks.toString()));
140 | 
141 | 					table.push(row);
142 | 				});
143 | 
144 | 				console.log(table.toString());
145 | 			}
146 | 		}
147 | 
148 | 		// Return success result - signals that we handled it
149 | 		return {
150 | 			success: true,
151 | 			tags: tagsResult.tags,
152 | 			currentTag: tagsResult.currentTag,
153 | 			totalTags: tagsResult.totalTags,
154 | 			message: `Found ${tagsResult.totalTags} tag(s)`
155 | 		};
156 | 	} catch (error) {
157 | 		// tm-core already formatted the error properly, just re-throw
158 | 		throw error;
159 | 	}
160 | }
161 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/tests/mocks/mock-provider.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Mock provider for testing BaseProvider functionality
  3 |  */
  4 | 
  5 | import type {
  6 | 	AIModel,
  7 | 	AIOptions,
  8 | 	AIResponse,
  9 | 	ProviderInfo,
 10 | 	ProviderUsageStats
 11 | } from '../../src/interfaces/ai-provider.interface';
 12 | import {
 13 | 	BaseProvider,
 14 | 	type BaseProviderConfig,
 15 | 	type CompletionResult
 16 | } from '../../src/providers/ai/base-provider';
 17 | 
 18 | /**
 19 |  * Configuration for MockProvider behavior
 20 |  */
 21 | export interface MockProviderOptions extends BaseProviderConfig {
 22 | 	shouldFail?: boolean;
 23 | 	failAfterAttempts?: number;
 24 | 	simulateRateLimit?: boolean;
 25 | 	simulateTimeout?: boolean;
 26 | 	responseDelay?: number;
 27 | 	tokenMultiplier?: number;
 28 | }
 29 | 
 30 | /**
 31 |  * Mock provider for testing BaseProvider functionality
 32 |  */
 33 | export class MockProvider extends BaseProvider {
 34 | 	private attemptCount = 0;
 35 | 	private readonly options: MockProviderOptions;
 36 | 
 37 | 	constructor(options: MockProviderOptions) {
 38 | 		super(options);
 39 | 		this.options = options;
 40 | 	}
 41 | 
 42 | 	/**
 43 | 	 * Simulate completion generation with configurable behavior
 44 | 	 */
 45 | 	protected async generateCompletionInternal(
 46 | 		prompt: string,
 47 | 		_options?: AIOptions
 48 | 	): Promise<CompletionResult> {
 49 | 		this.attemptCount++;
 50 | 
 51 | 		// Simulate delay if configured
 52 | 		if (this.options.responseDelay) {
 53 | 			await this.sleep(this.options.responseDelay);
 54 | 		}
 55 | 
 56 | 		// Simulate failures based on configuration
 57 | 		if (this.options.shouldFail) {
 58 | 			throw new Error('Mock provider error');
 59 | 		}
 60 | 
 61 | 		if (
 62 | 			this.options.failAfterAttempts &&
 63 | 			this.attemptCount <= this.options.failAfterAttempts
 64 | 		) {
 65 | 			if (this.options.simulateRateLimit) {
 66 | 				throw new Error('Rate limit exceeded - too many requests (429)');
 67 | 			}
 68 | 			if (this.options.simulateTimeout) {
 69 | 				throw new Error('Request timeout - ECONNRESET');
 70 | 			}
 71 | 			throw new Error('Temporary failure');
 72 | 		}
 73 | 
 74 | 		// Return successful mock response
 75 | 		return {
 76 | 			content: `Mock response to: ${prompt}`,
 77 | 			inputTokens: this.calculateTokens(prompt),
 78 | 			outputTokens: this.calculateTokens(`Mock response to: ${prompt}`),
 79 | 			finishReason: 'complete',
 80 | 			model: this.model
 81 | 		};
 82 | 	}
 83 | 
 84 | 	/**
 85 | 	 * Simple token calculation for testing
 86 | 	 */
 87 | 	calculateTokens(text: string, _model?: string): number {
 88 | 		const multiplier = this.options.tokenMultiplier || 1;
 89 | 		// Rough approximation: 1 token per 4 characters
 90 | 		return Math.ceil((text.length / 4) * multiplier);
 91 | 	}
 92 | 
 93 | 	getName(): string {
 94 | 		return 'mock';
 95 | 	}
 96 | 
 97 | 	getDefaultModel(): string {
 98 | 		return 'mock-model-v1';
 99 | 	}
100 | 
101 | 	/**
102 | 	 * Get the number of attempts made
103 | 	 */
104 | 	getAttemptCount(): number {
105 | 		return this.attemptCount;
106 | 	}
107 | 
108 | 	/**
109 | 	 * Reset attempt counter
110 | 	 */
111 | 	resetAttempts(): void {
112 | 		this.attemptCount = 0;
113 | 	}
114 | 
115 | 	// Implement remaining abstract methods
116 | 	async generateStreamingCompletion(
117 | 		prompt: string,
118 | 		_options?: AIOptions
119 | 	): AsyncIterator<Partial<AIResponse>> {
120 | 		// Simple mock implementation
121 | 		const response: Partial<AIResponse> = {
122 | 			content: `Mock streaming response to: ${prompt}`,
123 | 			provider: this.getName(),
124 | 			model: this.model
125 | 		};
126 | 
127 | 		return {
128 | 			async next() {
129 | 				return { value: response, done: true };
130 | 			}
131 | 		};
132 | 	}
133 | 
134 | 	async isAvailable(): Promise<boolean> {
135 | 		return !this.options.shouldFail;
136 | 	}
137 | 
138 | 	getProviderInfo(): ProviderInfo {
139 | 		return {
140 | 			name: 'mock',
141 | 			displayName: 'Mock Provider',
142 | 			description: 'Mock provider for testing',
143 | 			models: this.getAvailableModels(),
144 | 			defaultModel: this.getDefaultModel(),
145 | 			requiresApiKey: true,
146 | 			features: {
147 | 				streaming: true,
148 | 				functions: false,
149 | 				vision: false,
150 | 				embeddings: false
151 | 			}
152 | 		};
153 | 	}
154 | 
155 | 	getAvailableModels(): AIModel[] {
156 | 		return [
157 | 			{
158 | 				id: 'mock-model-v1',
159 | 				name: 'Mock Model v1',
160 | 				description: 'First mock model',
161 | 				contextLength: 4096,
162 | 				inputCostPer1K: 0.001,
163 | 				outputCostPer1K: 0.002,
164 | 				supportsStreaming: true
165 | 			},
166 | 			{
167 | 				id: 'mock-model-v2',
168 | 				name: 'Mock Model v2',
169 | 				description: 'Second mock model',
170 | 				contextLength: 8192,
171 | 				inputCostPer1K: 0.002,
172 | 				outputCostPer1K: 0.004,
173 | 				supportsStreaming: true
174 | 			}
175 | 		];
176 | 	}
177 | 
178 | 	async validateCredentials(): Promise<boolean> {
179 | 		return this.apiKey === 'valid-key';
180 | 	}
181 | 
182 | 	async getUsageStats(): Promise<ProviderUsageStats | null> {
183 | 		return {
184 | 			totalRequests: this.attemptCount,
185 | 			totalTokens: 1000,
186 | 			totalCost: 0.01,
187 | 			requestsToday: this.attemptCount,
188 | 			tokensToday: 1000,
189 | 			costToday: 0.01,
190 | 			averageResponseTime: 100,
191 | 			successRate: 0.9,
192 | 			lastRequestAt: new Date().toISOString()
193 | 		};
194 | 	}
195 | 
196 | 	async initialize(): Promise<void> {
197 | 		// No-op for mock
198 | 	}
199 | 
200 | 	async close(): Promise<void> {
201 | 		// No-op for mock
202 | 	}
203 | 
204 | 	// Override retry configuration for testing
205 | 	protected getMaxRetries(): number {
206 | 		return this.options.failAfterAttempts
207 | 			? this.options.failAfterAttempts + 1
208 | 			: 3;
209 | 	}
210 | }
211 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/ui/formatters/status-formatters.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Status formatter tests
  3 |  * Tests for apps/cli/src/utils/formatters/status-formatters.ts
  4 |  */
  5 | 
  6 | import { describe, expect, it } from 'vitest';
  7 | import {
  8 | 	capitalizeStatus,
  9 | 	getBriefStatusColor,
 10 | 	getBriefStatusIcon,
 11 | 	getBriefStatusWithColor
 12 | } from './status-formatters.js';
 13 | 
 14 | describe('Status Formatters', () => {
 15 | 	describe('getBriefStatusWithColor', () => {
 16 | 		it('should format draft status with gray color and circle icon', () => {
 17 | 			const result = getBriefStatusWithColor('draft', true);
 18 | 			expect(result).toContain('Draft');
 19 | 			expect(result).toContain('○');
 20 | 		});
 21 | 
 22 | 		it('should format refining status with yellow color and half-circle icon', () => {
 23 | 			const result = getBriefStatusWithColor('refining', true);
 24 | 			expect(result).toContain('Refining');
 25 | 			expect(result).toContain('◐');
 26 | 		});
 27 | 
 28 | 		it('should format aligned status with cyan color and target icon', () => {
 29 | 			const result = getBriefStatusWithColor('aligned', true);
 30 | 			expect(result).toContain('Aligned');
 31 | 			expect(result).toContain('◎');
 32 | 		});
 33 | 
 34 | 		it('should format delivering status with orange color and play icon', () => {
 35 | 			const result = getBriefStatusWithColor('delivering', true);
 36 | 			expect(result).toContain('Delivering');
 37 | 			expect(result).toContain('▶');
 38 | 		});
 39 | 
 40 | 		it('should format delivered status with blue color and diamond icon', () => {
 41 | 			const result = getBriefStatusWithColor('delivered', true);
 42 | 			expect(result).toContain('Delivered');
 43 | 			expect(result).toContain('◆');
 44 | 		});
 45 | 
 46 | 		it('should format done status with green color and checkmark icon', () => {
 47 | 			const result = getBriefStatusWithColor('done', true);
 48 | 			expect(result).toContain('Done');
 49 | 			expect(result).toContain('✓');
 50 | 		});
 51 | 
 52 | 		it('should format archived status with gray color and square icon', () => {
 53 | 			const result = getBriefStatusWithColor('archived', true);
 54 | 			expect(result).toContain('Archived');
 55 | 			expect(result).toContain('■');
 56 | 		});
 57 | 
 58 | 		it('should handle unknown status with red color and question mark', () => {
 59 | 			const result = getBriefStatusWithColor('unknown-status', true);
 60 | 			expect(result).toContain('Unknown-status');
 61 | 			expect(result).toContain('?');
 62 | 		});
 63 | 
 64 | 		it('should handle undefined status with gray color', () => {
 65 | 			const result = getBriefStatusWithColor(undefined, true);
 66 | 			expect(result).toContain('Unknown');
 67 | 			expect(result).toContain('○');
 68 | 		});
 69 | 
 70 | 		it('should use same icon for table and non-table display', () => {
 71 | 			const tableResult = getBriefStatusWithColor('done', true);
 72 | 			const nonTableResult = getBriefStatusWithColor('done', false);
 73 | 			expect(tableResult).toBe(nonTableResult);
 74 | 		});
 75 | 
 76 | 		it('should handle case-insensitive status names', () => {
 77 | 			const lowerResult = getBriefStatusWithColor('draft', true);
 78 | 			const upperResult = getBriefStatusWithColor('DRAFT', true);
 79 | 			const mixedResult = getBriefStatusWithColor('DrAfT', true);
 80 | 			expect(lowerResult).toContain('Draft');
 81 | 			expect(upperResult).toContain('Draft');
 82 | 			expect(mixedResult).toContain('Draft');
 83 | 		});
 84 | 	});
 85 | 
 86 | 	describe('getBriefStatusIcon', () => {
 87 | 		it('should return correct icon for status', () => {
 88 | 			expect(getBriefStatusIcon('draft')).toBe('○');
 89 | 			expect(getBriefStatusIcon('done')).toBe('✓');
 90 | 			expect(getBriefStatusIcon('delivering')).toBe('▶');
 91 | 		});
 92 | 
 93 | 		it('should return default icon for unknown status', () => {
 94 | 			expect(getBriefStatusIcon('unknown-status')).toBe('?');
 95 | 		});
 96 | 
 97 | 		it('should return default icon for undefined', () => {
 98 | 			expect(getBriefStatusIcon(undefined)).toBe('○');
 99 | 		});
100 | 
101 | 		it('should return same icon for table and non-table', () => {
102 | 			expect(getBriefStatusIcon('done', true)).toBe(
103 | 				getBriefStatusIcon('done', false)
104 | 			);
105 | 		});
106 | 	});
107 | 
108 | 	describe('getBriefStatusColor', () => {
109 | 		it('should return a color function', () => {
110 | 			const colorFn = getBriefStatusColor('draft');
111 | 			expect(typeof colorFn).toBe('function');
112 | 			const result = colorFn('test');
113 | 			expect(typeof result).toBe('string');
114 | 		});
115 | 
116 | 		it('should return gray color for undefined', () => {
117 | 			const colorFn = getBriefStatusColor(undefined);
118 | 			expect(typeof colorFn).toBe('function');
119 | 		});
120 | 	});
121 | 
122 | 	describe('capitalizeStatus', () => {
123 | 		it('should capitalize first letter and lowercase rest', () => {
124 | 			expect(capitalizeStatus('draft')).toBe('Draft');
125 | 			expect(capitalizeStatus('DRAFT')).toBe('Draft');
126 | 			expect(capitalizeStatus('DrAfT')).toBe('Draft');
127 | 			expect(capitalizeStatus('in-progress')).toBe('In-progress');
128 | 		});
129 | 
130 | 		it('should handle single character', () => {
131 | 			expect(capitalizeStatus('a')).toBe('A');
132 | 			expect(capitalizeStatus('A')).toBe('A');
133 | 		});
134 | 
135 | 		it('should handle empty string', () => {
136 | 			expect(capitalizeStatus('')).toBe('');
137 | 		});
138 | 	});
139 | });
140 | 
```

--------------------------------------------------------------------------------
/packages/claude-code-plugin/agents/task-executor.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: task-executor
 3 | description: Use this agent when you need to implement, complete, or work on a specific task that has been identified by the task-orchestrator or when explicitly asked to execute a particular task. This agent focuses on the actual implementation and completion of individual tasks rather than planning or orchestration. Examples: <example>Context: The task-orchestrator has identified that task 2.3 'Implement user authentication' needs to be worked on next. user: 'Let's work on the authentication task' assistant: 'I'll use the task-executor agent to implement the user authentication task that was identified.' <commentary>Since we need to actually implement a specific task rather than plan or identify tasks, use the task-executor agent.</commentary></example> <example>Context: User wants to complete a specific subtask. user: 'Please implement the JWT token validation for task 2.3.1' assistant: 'I'll launch the task-executor agent to implement the JWT token validation subtask.' <commentary>The user is asking for specific implementation work on a known task, so the task-executor is appropriate.</commentary></example> <example>Context: After reviewing the task list, implementation is needed. user: 'Now let's actually build the API endpoint for user registration' assistant: 'I'll use the task-executor agent to implement the user registration API endpoint.' <commentary>Moving from planning to execution phase requires the task-executor agent.</commentary></example>
 4 | model: sonnet
 5 | color: blue
 6 | ---
 7 | 
 8 | You are an elite implementation specialist focused on executing and completing specific tasks with precision and thoroughness. Your role is to take identified tasks and transform them into working implementations, following best practices and project standards.
 9 | 
10 | **Core Responsibilities:**
11 | 
12 | 1. **Task Analysis**: When given a task, first retrieve its full details using `task-master show <id>` to understand requirements, dependencies, and acceptance criteria.
13 | 
14 | 2. **Implementation Planning**: Before coding, briefly outline your implementation approach:
15 |    - Identify files that need to be created or modified
16 |    - Note any dependencies or prerequisites
17 |    - Consider the testing strategy defined in the task
18 | 
19 | 3. **Focused Execution**: 
20 |    - Implement one subtask at a time for clarity and traceability
21 |    - Follow the project's coding standards from CLAUDE.md if available
22 |    - Prefer editing existing files over creating new ones
23 |    - Only create files that are essential for the task completion
24 | 
25 | 4. **Progress Documentation**: 
26 |    - Use `task-master update-subtask --id=<id> --prompt="implementation notes"` to log your approach and any important decisions
27 |    - Update task status to 'in-progress' when starting: `task-master set-status --id=<id> --status=in-progress`
28 |    - Mark as 'done' only after verification: `task-master set-status --id=<id> --status=done`
29 | 
30 | 5. **Quality Assurance**:
31 |    - Implement the testing strategy specified in the task
32 |    - Verify that all acceptance criteria are met
33 |    - Check for any dependency conflicts or integration issues
34 |    - Run relevant tests before marking task as complete
35 | 
36 | 6. **Dependency Management**:
37 |    - Check task dependencies before starting implementation
38 |    - If blocked by incomplete dependencies, clearly communicate this
39 |    - Use `task-master validate-dependencies` when needed
40 | 
41 | **Implementation Workflow:**
42 | 
43 | 1. Retrieve task details and understand requirements
44 | 2. Check dependencies and prerequisites
45 | 3. Plan implementation approach
46 | 4. Update task status to in-progress
47 | 5. Implement the solution incrementally
48 | 6. Log progress and decisions in subtask updates
49 | 7. Test and verify the implementation
50 | 8. Mark task as done when complete
51 | 9. Suggest next task if appropriate
52 | 
53 | **Key Principles:**
54 | 
55 | - Focus on completing one task thoroughly before moving to the next
56 | - Maintain clear communication about what you're implementing and why
57 | - Follow existing code patterns and project conventions
58 | - Prioritize working code over extensive documentation unless docs are the task
59 | - Ask for clarification if task requirements are ambiguous
60 | - Consider edge cases and error handling in your implementations
61 | 
62 | **Integration with Task Master:**
63 | 
64 | You work in tandem with the task-orchestrator agent. While the orchestrator identifies and plans tasks, you execute them. Always use Task Master commands to:
65 | - Track your progress
66 | - Update task information
67 | - Maintain project state
68 | - Coordinate with the broader development workflow
69 | 
70 | When you complete a task, briefly summarize what was implemented and suggest whether to continue with the next task or if review/testing is needed first.
71 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/set-task-status.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * set-task-status.js
  3 |  * Direct function implementation for setting task status
  4 |  */
  5 | 
  6 | import { setTaskStatus } from '../../../../scripts/modules/task-manager.js';
  7 | import {
  8 | 	enableSilentMode,
  9 | 	disableSilentMode,
 10 | 	isSilentMode
 11 | } from '../../../../scripts/modules/utils.js';
 12 | import { nextTaskDirect } from './next-task.js';
 13 | /**
 14 |  * Direct function wrapper for setTaskStatus with error handling.
 15 |  *
 16 |  * @param {Object} args - Command arguments containing id, status, tasksJsonPath, and projectRoot.
 17 |  * @param {string} args.id - The ID of the task to update.
 18 |  * @param {string} args.status - The new status to set for the task.
 19 |  * @param {string} args.tasksJsonPath - Path to the tasks.json file.
 20 |  * @param {string} args.projectRoot - Project root path (for MCP/env fallback)
 21 |  * @param {string} args.tag - Tag for the task (optional)
 22 |  * @param {Object} log - Logger object.
 23 |  * @param {Object} context - Additional context (session)
 24 |  * @returns {Promise<Object>} - Result object with success status and data/error information.
 25 |  */
 26 | export async function setTaskStatusDirect(args, log, context = {}) {
 27 | 	// Destructure expected args, including the resolved tasksJsonPath and projectRoot
 28 | 	const { tasksJsonPath, id, status, complexityReportPath, projectRoot, tag } =
 29 | 		args;
 30 | 	const { session } = context;
 31 | 	try {
 32 | 		log.info(`Setting task status with args: ${JSON.stringify(args)}`);
 33 | 
 34 | 		// Check if tasksJsonPath was provided
 35 | 		if (!tasksJsonPath) {
 36 | 			const errorMessage = 'tasksJsonPath is required but was not provided.';
 37 | 			log.error(errorMessage);
 38 | 			return {
 39 | 				success: false,
 40 | 				error: { code: 'MISSING_ARGUMENT', message: errorMessage }
 41 | 			};
 42 | 		}
 43 | 
 44 | 		// Check required parameters (id and status)
 45 | 		if (!id) {
 46 | 			const errorMessage =
 47 | 				'No task ID specified. Please provide a task ID to update.';
 48 | 			log.error(errorMessage);
 49 | 			return {
 50 | 				success: false,
 51 | 				error: { code: 'MISSING_TASK_ID', message: errorMessage }
 52 | 			};
 53 | 		}
 54 | 
 55 | 		if (!status) {
 56 | 			const errorMessage =
 57 | 				'No status specified. Please provide a new status value.';
 58 | 			log.error(errorMessage);
 59 | 			return {
 60 | 				success: false,
 61 | 				error: { code: 'MISSING_STATUS', message: errorMessage }
 62 | 			};
 63 | 		}
 64 | 
 65 | 		// Use the provided path
 66 | 		const tasksPath = tasksJsonPath;
 67 | 
 68 | 		// Execute core setTaskStatus function
 69 | 		const taskId = id;
 70 | 		const newStatus = status;
 71 | 
 72 | 		log.info(`Setting task ${taskId} status to "${newStatus}"`);
 73 | 
 74 | 		// Call the core function with proper silent mode handling
 75 | 		enableSilentMode(); // Enable silent mode before calling core function
 76 | 		try {
 77 | 			// Call the core function
 78 | 			await setTaskStatus(tasksPath, taskId, newStatus, {
 79 | 				mcpLog: log,
 80 | 				projectRoot,
 81 | 				session,
 82 | 				tag
 83 | 			});
 84 | 
 85 | 			log.info(`Successfully set task ${taskId} status to ${newStatus}`);
 86 | 
 87 | 			// Return success data
 88 | 			const result = {
 89 | 				success: true,
 90 | 				data: {
 91 | 					message: `Successfully updated task ${taskId} status to "${newStatus}"`,
 92 | 					taskId,
 93 | 					status: newStatus,
 94 | 					tasksPath: tasksPath // Return the path used
 95 | 				}
 96 | 			};
 97 | 
 98 | 			// If the task was completed, attempt to fetch the next task
 99 | 			if (result.data.status === 'done') {
100 | 				try {
101 | 					log.info(`Attempting to fetch next task for task ${taskId}`);
102 | 					const nextResult = await nextTaskDirect(
103 | 						{
104 | 							tasksJsonPath: tasksJsonPath,
105 | 							reportPath: complexityReportPath,
106 | 							projectRoot: projectRoot,
107 | 							tag
108 | 						},
109 | 						log,
110 | 						{ session }
111 | 					);
112 | 
113 | 					if (nextResult.success) {
114 | 						log.info(
115 | 							`Successfully retrieved next task: ${nextResult.data.nextTask}`
116 | 						);
117 | 						result.data = {
118 | 							...result.data,
119 | 							nextTask: nextResult.data.nextTask,
120 | 							isNextSubtask: nextResult.data.isSubtask,
121 | 							nextSteps: nextResult.data.nextSteps
122 | 						};
123 | 					} else {
124 | 						log.warn(
125 | 							`Failed to retrieve next task: ${nextResult.error?.message || 'Unknown error'}`
126 | 						);
127 | 					}
128 | 				} catch (nextErr) {
129 | 					log.error(`Error retrieving next task: ${nextErr.message}`);
130 | 				}
131 | 			}
132 | 
133 | 			return result;
134 | 		} catch (error) {
135 | 			log.error(`Error setting task status: ${error.message}`);
136 | 			return {
137 | 				success: false,
138 | 				error: {
139 | 					code: 'SET_STATUS_ERROR',
140 | 					message: error.message || 'Unknown error setting task status'
141 | 				}
142 | 			};
143 | 		} finally {
144 | 			// ALWAYS restore normal logging in finally block
145 | 			disableSilentMode();
146 | 		}
147 | 	} catch (error) {
148 | 		// Ensure silent mode is disabled if there was an uncaught error in the outer try block
149 | 		if (isSilentMode()) {
150 | 			disableSilentMode();
151 | 		}
152 | 
153 | 		log.error(`Error setting task status: ${error.message}`);
154 | 		return {
155 | 			success: false,
156 | 			error: {
157 | 				code: 'SET_STATUS_ERROR',
158 | 				message: error.message || 'Unknown error setting task status'
159 | 			}
160 | 		};
161 | 	}
162 | }
163 | 
```

--------------------------------------------------------------------------------
/src/prompts/update-subtask.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"id": "update-subtask",
 3 | 	"version": "1.0.0",
 4 | 	"description": "Append information to a subtask by generating only new content",
 5 | 	"metadata": {
 6 | 		"author": "system",
 7 | 		"created": "2024-01-01T00:00:00Z",
 8 | 		"updated": "2024-01-01T00:00:00Z",
 9 | 		"tags": ["update", "subtask", "append", "logging"]
10 | 	},
11 | 	"parameters": {
12 | 		"parentTask": {
13 | 			"type": "object",
14 | 			"required": true,
15 | 			"description": "The parent task context"
16 | 		},
17 | 		"prevSubtask": {
18 | 			"type": "object",
19 | 			"required": false,
20 | 			"description": "The previous subtask if any"
21 | 		},
22 | 		"nextSubtask": {
23 | 			"type": "object",
24 | 			"required": false,
25 | 			"description": "The next subtask if any"
26 | 		},
27 | 		"currentDetails": {
28 | 			"type": "string",
29 | 			"required": true,
30 | 			"default": "(No existing details)",
31 | 			"description": "Current subtask details"
32 | 		},
33 | 		"updatePrompt": {
34 | 			"type": "string",
35 | 			"required": true,
36 | 			"description": "User request for what to add"
37 | 		},
38 | 		"useResearch": {
39 | 			"type": "boolean",
40 | 			"default": false,
41 | 			"description": "Use research mode"
42 | 		},
43 | 		"gatheredContext": {
44 | 			"type": "string",
45 | 			"default": "",
46 | 			"description": "Additional project context"
47 | 		},
48 | 		"hasCodebaseAnalysis": {
49 | 			"type": "boolean",
50 | 			"required": false,
51 | 			"default": false,
52 | 			"description": "Whether codebase analysis is available"
53 | 		},
54 | 		"projectRoot": {
55 | 			"type": "string",
56 | 			"required": false,
57 | 			"default": "",
58 | 			"description": "Project root path for context"
59 | 		}
60 | 	},
61 | 	"prompts": {
62 | 		"default": {
63 | 			"system": "You are an AI assistant helping to update a subtask. You will be provided with the subtask's existing details, context about its parent and sibling tasks, and a user request string.{{#if useResearch}} You have access to current best practices and latest technical information to provide research-backed updates.{{/if}}\n\nYour Goal: Based *only* on the user's request and all the provided context (including existing details if relevant to the request), GENERATE the new text content that should be added to the subtask's details.\nFocus *only* on generating the substance of the update.\n\nOutput Requirements:\n1. Return *only* the newly generated text content as a plain string. Do NOT return a JSON object or any other structured data.\n2. Your string response should NOT include any of the subtask's original details, unless the user's request explicitly asks to rephrase, summarize, or directly modify existing text.\n3. Do NOT include any timestamps, XML-like tags, markdown, or any other special formatting in your string response.\n4. Ensure the generated text is concise yet complete for the update based on the user request. Avoid conversational fillers or explanations about what you are doing (e.g., do not start with \"Okay, here's the update...\").{{#if useResearch}}\n5. Include specific libraries, versions, and current best practices relevant to the subtask implementation.\n6. Provide research-backed technical recommendations and proven approaches.{{/if}}",
64 | 			"user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before generating the subtask update:\n\n1. Use the Glob tool to explore the project structure (e.g., \"**/*.js\", \"**/*.json\", \"**/README.md\")\n2. Use the Grep tool to search for existing implementations, patterns, and technologies\n3. Use the Read tool to examine relevant files and understand current implementation\n4. Analyze the current codebase to inform your subtask update\n\nBased on your analysis:\n- Include specific file references, code patterns, or implementation details\n- Ensure suggestions align with the project's current architecture\n- Reference existing components or patterns when relevant\n- Make implementation notes specific to the codebase structure\n\nProject Root: {{projectRoot}}\n\n{{/if}}Task Context:\n\nParent Task: {{{json parentTask}}}\n{{#if prevSubtask}}Previous Subtask: {{{json prevSubtask}}}\n{{/if}}{{#if nextSubtask}}Next Subtask: {{{json nextSubtask}}}\n{{/if}}Current Subtask Details (for context only):\n{{currentDetails}}\n\nUser Request: \"{{updatePrompt}}\"\n\n{{#if useResearch}}Research and incorporate current best practices, latest stable versions, and proven approaches into your update. {{/if}}Based on the User Request and all the Task Context (including current subtask details provided above), what is the new information or text that should be appended to this subtask's details? Return ONLY this new text as a plain string.{{#if useResearch}} Include specific technical recommendations based on current industry standards.{{/if}}\n{{#if gatheredContext}}\n\n# Additional Project Context\n\n{{gatheredContext}}\n{{/if}}"
65 | 		}
66 | 	}
67 | }
68 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/commands/autopilot/next.command.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Next Command - Get next action in TDD workflow
  3 |  */
  4 | 
  5 | import { WorkflowOrchestrator } from '@tm/core';
  6 | import { Command } from 'commander';
  7 | import { getProjectRoot } from '../../utils/project-root.js';
  8 | import {
  9 | 	type AutopilotBaseOptions,
 10 | 	OutputFormatter,
 11 | 	hasWorkflowState,
 12 | 	loadWorkflowState
 13 | } from './shared.js';
 14 | 
 15 | type NextOptions = AutopilotBaseOptions;
 16 | 
 17 | /**
 18 |  * Next Command - Get next action details
 19 |  */
 20 | export class NextCommand extends Command {
 21 | 	constructor() {
 22 | 		super('next');
 23 | 
 24 | 		this.description(
 25 | 			'Get the next action to perform in the TDD workflow'
 26 | 		).action(async (options: NextOptions) => {
 27 | 			await this.execute(options);
 28 | 		});
 29 | 	}
 30 | 
 31 | 	private async execute(options: NextOptions): Promise<void> {
 32 | 		// Inherit parent options
 33 | 		const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
 34 | 
 35 | 		// Initialize mergedOptions with defaults (projectRoot will be set in try block)
 36 | 		let mergedOptions: NextOptions = {
 37 | 			...parentOpts,
 38 | 			...options,
 39 | 			projectRoot: '' // Will be set in try block
 40 | 		};
 41 | 
 42 | 		const formatter = new OutputFormatter(
 43 | 			options.json || parentOpts?.json || false
 44 | 		);
 45 | 
 46 | 		try {
 47 | 			// Resolve project root inside try block to catch any errors
 48 | 			const projectRoot = getProjectRoot(
 49 | 				options.projectRoot || parentOpts?.projectRoot
 50 | 			);
 51 | 
 52 | 			// Update mergedOptions with resolved project root
 53 | 			mergedOptions = {
 54 | 				...mergedOptions,
 55 | 				projectRoot
 56 | 			};
 57 | 			// Check for workflow state
 58 | 			const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
 59 | 			if (!hasState) {
 60 | 				formatter.error('No active workflow', {
 61 | 					suggestion: 'Start a workflow with: autopilot start <taskId>'
 62 | 				});
 63 | 				process.exit(1);
 64 | 			}
 65 | 
 66 | 			// Load state
 67 | 			const state = await loadWorkflowState(mergedOptions.projectRoot!);
 68 | 			if (!state) {
 69 | 				formatter.error('Failed to load workflow state');
 70 | 				process.exit(1);
 71 | 			}
 72 | 
 73 | 			// Restore orchestrator
 74 | 			const orchestrator = new WorkflowOrchestrator(state.context);
 75 | 			orchestrator.restoreState(state);
 76 | 
 77 | 			// Get current phase and subtask
 78 | 			const phase = orchestrator.getCurrentPhase();
 79 | 			const tddPhase = orchestrator.getCurrentTDDPhase();
 80 | 			const currentSubtask = orchestrator.getCurrentSubtask();
 81 | 
 82 | 			// Determine next action based on phase
 83 | 			let actionType: string;
 84 | 			let actionDescription: string;
 85 | 			let actionDetails: Record<string, unknown> = {};
 86 | 
 87 | 			if (phase === 'COMPLETE') {
 88 | 				formatter.success('Workflow complete', {
 89 | 					message: 'All subtasks have been completed',
 90 | 					taskId: state.context.taskId
 91 | 				});
 92 | 				return;
 93 | 			}
 94 | 
 95 | 			if (phase === 'SUBTASK_LOOP' && tddPhase) {
 96 | 				switch (tddPhase) {
 97 | 					case 'RED':
 98 | 						actionType = 'generate_test';
 99 | 						actionDescription = 'Write failing test for current subtask';
100 | 						actionDetails = {
101 | 							subtask: currentSubtask
102 | 								? {
103 | 										id: currentSubtask.id,
104 | 										title: currentSubtask.title,
105 | 										attempts: currentSubtask.attempts
106 | 									}
107 | 								: null,
108 | 							testCommand: 'npm test', // Could be customized based on config
109 | 							expectedOutcome: 'Test should fail'
110 | 						};
111 | 						break;
112 | 
113 | 					case 'GREEN':
114 | 						actionType = 'implement_code';
115 | 						actionDescription = 'Implement code to pass the failing test';
116 | 						actionDetails = {
117 | 							subtask: currentSubtask
118 | 								? {
119 | 										id: currentSubtask.id,
120 | 										title: currentSubtask.title,
121 | 										attempts: currentSubtask.attempts
122 | 									}
123 | 								: null,
124 | 							testCommand: 'npm test',
125 | 							expectedOutcome: 'All tests should pass',
126 | 							lastTestResults: state.context.lastTestResults
127 | 						};
128 | 						break;
129 | 
130 | 					case 'COMMIT':
131 | 						actionType = 'commit_changes';
132 | 						actionDescription = 'Commit the changes';
133 | 						actionDetails = {
134 | 							subtask: currentSubtask
135 | 								? {
136 | 										id: currentSubtask.id,
137 | 										title: currentSubtask.title,
138 | 										attempts: currentSubtask.attempts
139 | 									}
140 | 								: null,
141 | 							suggestion: 'Use: autopilot commit'
142 | 						};
143 | 						break;
144 | 
145 | 					default:
146 | 						actionType = 'unknown';
147 | 						actionDescription = 'Unknown TDD phase';
148 | 				}
149 | 			} else {
150 | 				actionType = 'workflow_phase';
151 | 				actionDescription = `Currently in ${phase} phase`;
152 | 			}
153 | 
154 | 			// Output next action
155 | 			const output = {
156 | 				action: actionType,
157 | 				description: actionDescription,
158 | 				phase,
159 | 				tddPhase,
160 | 				taskId: state.context.taskId,
161 | 				branchName: state.context.branchName,
162 | 				...actionDetails
163 | 			};
164 | 
165 | 			if (mergedOptions.json) {
166 | 				formatter.output(output);
167 | 			} else {
168 | 				formatter.success('Next action', output);
169 | 			}
170 | 		} catch (error) {
171 | 			formatter.error((error as Error).message);
172 | 			if (mergedOptions.verbose) {
173 | 				console.error((error as Error).stack);
174 | 			}
175 | 			process.exit(1);
176 | 		}
177 | 	}
178 | }
179 | 
```

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

```typescript
  1 | /**
  2 |  * @fileoverview Configuration Persistence Service
  3 |  * Handles saving and backup of configuration files
  4 |  */
  5 | 
  6 | import fs from 'node:fs/promises';
  7 | import path from 'node:path';
  8 | import {
  9 | 	ERROR_CODES,
 10 | 	TaskMasterError
 11 | } from '../../../common/errors/task-master-error.js';
 12 | import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
 13 | import { getLogger } from '../../../common/logger/index.js';
 14 | 
 15 | /**
 16 |  * Persistence options
 17 |  */
 18 | export interface PersistenceOptions {
 19 | 	/** Enable backup before saving */
 20 | 	createBackup?: boolean;
 21 | 	/** Maximum number of backups to keep */
 22 | 	maxBackups?: number;
 23 | 	/** Use atomic write operations */
 24 | 	atomic?: boolean;
 25 | }
 26 | 
 27 | /**
 28 |  * ConfigPersistence handles all configuration file I/O operations
 29 |  * Single responsibility: Configuration persistence
 30 |  */
 31 | export class ConfigPersistence {
 32 | 	private localConfigPath: string;
 33 | 	private backupDir: string;
 34 | 	private readonly logger = getLogger('ConfigPersistence');
 35 | 
 36 | 	constructor(projectRoot: string) {
 37 | 		this.localConfigPath = path.join(projectRoot, '.taskmaster', 'config.json');
 38 | 		this.backupDir = path.join(projectRoot, '.taskmaster', 'backups');
 39 | 	}
 40 | 
 41 | 	/**
 42 | 	 * Save configuration to file
 43 | 	 */
 44 | 	async saveConfig(
 45 | 		config: PartialConfiguration,
 46 | 		options: PersistenceOptions = {}
 47 | 	): Promise<void> {
 48 | 		const { createBackup = false, atomic = true } = options;
 49 | 
 50 | 		try {
 51 | 			// Create backup if requested
 52 | 			if (createBackup && (await this.configExists())) {
 53 | 				await this.createBackup();
 54 | 			}
 55 | 
 56 | 			// Ensure directory exists
 57 | 			const configDir = path.dirname(this.localConfigPath);
 58 | 			await fs.mkdir(configDir, { recursive: true });
 59 | 
 60 | 			const jsonContent = JSON.stringify(config, null, 2);
 61 | 
 62 | 			if (atomic) {
 63 | 				// Atomic write: write to temp file then rename
 64 | 				const tempPath = `${this.localConfigPath}.tmp`;
 65 | 				await fs.writeFile(tempPath, jsonContent, 'utf-8');
 66 | 				await fs.rename(tempPath, this.localConfigPath);
 67 | 			} else {
 68 | 				// Direct write
 69 | 				await fs.writeFile(this.localConfigPath, jsonContent, 'utf-8');
 70 | 			}
 71 | 		} catch (error) {
 72 | 			throw new TaskMasterError(
 73 | 				'Failed to save configuration',
 74 | 				ERROR_CODES.CONFIG_ERROR,
 75 | 				{ configPath: this.localConfigPath },
 76 | 				error as Error
 77 | 			);
 78 | 		}
 79 | 	}
 80 | 
 81 | 	/**
 82 | 	 * Create a backup of the current configuration
 83 | 	 */
 84 | 	private async createBackup(): Promise<string> {
 85 | 		try {
 86 | 			await fs.mkdir(this.backupDir, { recursive: true });
 87 | 
 88 | 			const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
 89 | 			const backupPath = path.join(this.backupDir, `config-${timestamp}.json`);
 90 | 
 91 | 			const configContent = await fs.readFile(this.localConfigPath, 'utf-8');
 92 | 			await fs.writeFile(backupPath, configContent, 'utf-8');
 93 | 
 94 | 			// Clean old backups
 95 | 			await this.cleanOldBackups();
 96 | 
 97 | 			return backupPath;
 98 | 		} catch (error) {
 99 | 			this.logger.warn('Failed to create backup:', error);
100 | 			throw error;
101 | 		}
102 | 	}
103 | 
104 | 	/**
105 | 	 * Clean old backup files
106 | 	 */
107 | 	private async cleanOldBackups(maxBackups = 5): Promise<void> {
108 | 		try {
109 | 			const files = await fs.readdir(this.backupDir);
110 | 			const backupFiles = files
111 | 				.filter((f) => f.startsWith('config-') && f.endsWith('.json'))
112 | 				.sort()
113 | 				.reverse();
114 | 
115 | 			// Remove old backups
116 | 			const toDelete = backupFiles.slice(maxBackups);
117 | 			for (const file of toDelete) {
118 | 				await fs.unlink(path.join(this.backupDir, file));
119 | 			}
120 | 		} catch (error) {
121 | 			this.logger.warn('Failed to clean old backups:', error);
122 | 		}
123 | 	}
124 | 
125 | 	/**
126 | 	 * Check if config file exists
127 | 	 */
128 | 	async configExists(): Promise<boolean> {
129 | 		try {
130 | 			await fs.access(this.localConfigPath);
131 | 			return true;
132 | 		} catch {
133 | 			return false;
134 | 		}
135 | 	}
136 | 
137 | 	/**
138 | 	 * Delete configuration file
139 | 	 */
140 | 	async deleteConfig(): Promise<void> {
141 | 		try {
142 | 			await fs.unlink(this.localConfigPath);
143 | 		} catch (error: any) {
144 | 			if (error.code !== 'ENOENT') {
145 | 				throw new TaskMasterError(
146 | 					'Failed to delete configuration',
147 | 					ERROR_CODES.CONFIG_ERROR,
148 | 					{ configPath: this.localConfigPath },
149 | 					error
150 | 				);
151 | 			}
152 | 		}
153 | 	}
154 | 
155 | 	/**
156 | 	 * Get list of available backups
157 | 	 */
158 | 	async getBackups(): Promise<string[]> {
159 | 		try {
160 | 			const files = await fs.readdir(this.backupDir);
161 | 			return files
162 | 				.filter((f) => f.startsWith('config-') && f.endsWith('.json'))
163 | 				.sort()
164 | 				.reverse();
165 | 		} catch {
166 | 			return [];
167 | 		}
168 | 	}
169 | 
170 | 	/**
171 | 	 * Restore from a backup
172 | 	 */
173 | 	async restoreFromBackup(backupFile: string): Promise<void> {
174 | 		const backupPath = path.join(this.backupDir, backupFile);
175 | 
176 | 		try {
177 | 			const backupContent = await fs.readFile(backupPath, 'utf-8');
178 | 			await fs.writeFile(this.localConfigPath, backupContent, 'utf-8');
179 | 		} catch (error) {
180 | 			throw new TaskMasterError(
181 | 				'Failed to restore from backup',
182 | 				ERROR_CODES.CONFIG_ERROR,
183 | 				{ backupPath },
184 | 				error as Error
185 | 			);
186 | 		}
187 | 	}
188 | }
189 | 
```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/add-subtask.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { log, readJSON, writeJSON, getCurrentTag } from '../utils.js';
  2 | import { isTaskDependentOn } from '../task-manager.js';
  3 | 
  4 | /**
  5 |  * Add a subtask to a parent task
  6 |  * @param {string} tasksPath - Path to the tasks.json file
  7 |  * @param {number|string} parentId - ID of the parent task
  8 |  * @param {number|string|null} existingTaskId - ID of an existing task to convert to subtask (optional)
  9 |  * @param {Object} newSubtaskData - Data for creating a new subtask (used if existingTaskId is null)
 10 |  * @param {boolean} generateFiles - Whether to regenerate task files after adding the subtask
 11 |  * @param {Object} context - Context object containing projectRoot and tag information
 12 |  * @param {string} context.projectRoot - Project root path
 13 |  * @param {string} context.tag - Tag for the task
 14 |  * @returns {Object} The newly created or converted subtask
 15 |  */
 16 | async function addSubtask(
 17 | 	tasksPath,
 18 | 	parentId,
 19 | 	existingTaskId = null,
 20 | 	newSubtaskData = null,
 21 | 	generateFiles = false,
 22 | 	context = {}
 23 | ) {
 24 | 	const { projectRoot, tag } = context;
 25 | 	try {
 26 | 		log('info', `Adding subtask to parent task ${parentId}...`);
 27 | 
 28 | 		// Read the existing tasks with proper context
 29 | 		const data = readJSON(tasksPath, projectRoot, tag);
 30 | 		if (!data || !data.tasks) {
 31 | 			throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
 32 | 		}
 33 | 
 34 | 		// Convert parent ID to number
 35 | 		const parentIdNum = parseInt(parentId, 10);
 36 | 
 37 | 		// Find the parent task
 38 | 		const parentTask = data.tasks.find((t) => t.id === parentIdNum);
 39 | 		if (!parentTask) {
 40 | 			throw new Error(`Parent task with ID ${parentIdNum} not found`);
 41 | 		}
 42 | 
 43 | 		// Initialize subtasks array if it doesn't exist
 44 | 		if (!parentTask.subtasks) {
 45 | 			parentTask.subtasks = [];
 46 | 		}
 47 | 
 48 | 		let newSubtask;
 49 | 
 50 | 		// Case 1: Convert an existing task to a subtask
 51 | 		if (existingTaskId !== null) {
 52 | 			const existingTaskIdNum = parseInt(existingTaskId, 10);
 53 | 
 54 | 			// Find the existing task
 55 | 			const existingTaskIndex = data.tasks.findIndex(
 56 | 				(t) => t.id === existingTaskIdNum
 57 | 			);
 58 | 			if (existingTaskIndex === -1) {
 59 | 				throw new Error(`Task with ID ${existingTaskIdNum} not found`);
 60 | 			}
 61 | 
 62 | 			const existingTask = data.tasks[existingTaskIndex];
 63 | 
 64 | 			// Check if task is already a subtask
 65 | 			if (existingTask.parentTaskId) {
 66 | 				throw new Error(
 67 | 					`Task ${existingTaskIdNum} is already a subtask of task ${existingTask.parentTaskId}`
 68 | 				);
 69 | 			}
 70 | 
 71 | 			// Check for circular dependency
 72 | 			if (existingTaskIdNum === parentIdNum) {
 73 | 				throw new Error(`Cannot make a task a subtask of itself`);
 74 | 			}
 75 | 
 76 | 			// Check if parent task is a subtask of the task we're converting
 77 | 			// This would create a circular dependency
 78 | 			if (isTaskDependentOn(data.tasks, parentTask, existingTaskIdNum)) {
 79 | 				throw new Error(
 80 | 					`Cannot create circular dependency: task ${parentIdNum} is already a subtask or dependent of task ${existingTaskIdNum}`
 81 | 				);
 82 | 			}
 83 | 
 84 | 			// Find the highest subtask ID to determine the next ID
 85 | 			const highestSubtaskId =
 86 | 				parentTask.subtasks.length > 0
 87 | 					? Math.max(...parentTask.subtasks.map((st) => st.id))
 88 | 					: 0;
 89 | 			const newSubtaskId = highestSubtaskId + 1;
 90 | 
 91 | 			// Clone the existing task to be converted to a subtask
 92 | 			newSubtask = {
 93 | 				...existingTask,
 94 | 				id: newSubtaskId,
 95 | 				parentTaskId: parentIdNum
 96 | 			};
 97 | 
 98 | 			// Add to parent's subtasks
 99 | 			parentTask.subtasks.push(newSubtask);
100 | 
101 | 			// Remove the task from the main tasks array
102 | 			data.tasks.splice(existingTaskIndex, 1);
103 | 
104 | 			log(
105 | 				'info',
106 | 				`Converted task ${existingTaskIdNum} to subtask ${parentIdNum}.${newSubtaskId}`
107 | 			);
108 | 		}
109 | 		// Case 2: Create a new subtask
110 | 		else if (newSubtaskData) {
111 | 			// Find the highest subtask ID to determine the next ID
112 | 			const highestSubtaskId =
113 | 				parentTask.subtasks.length > 0
114 | 					? Math.max(...parentTask.subtasks.map((st) => st.id))
115 | 					: 0;
116 | 			const newSubtaskId = highestSubtaskId + 1;
117 | 
118 | 			// Create the new subtask object
119 | 			newSubtask = {
120 | 				id: newSubtaskId,
121 | 				title: newSubtaskData.title,
122 | 				description: newSubtaskData.description || '',
123 | 				details: newSubtaskData.details || '',
124 | 				status: newSubtaskData.status || 'pending',
125 | 				dependencies: newSubtaskData.dependencies || [],
126 | 				parentTaskId: parentIdNum
127 | 			};
128 | 
129 | 			// Add to parent's subtasks
130 | 			parentTask.subtasks.push(newSubtask);
131 | 
132 | 			log('info', `Created new subtask ${parentIdNum}.${newSubtaskId}`);
133 | 		} else {
134 | 			throw new Error(
135 | 				'Either existingTaskId or newSubtaskData must be provided'
136 | 			);
137 | 		}
138 | 
139 | 		// Write the updated tasks back to the file with proper context
140 | 		writeJSON(tasksPath, data, projectRoot, tag);
141 | 
142 | 		// Note: Task file generation is no longer supported and has been removed
143 | 
144 | 		return newSubtask;
145 | 	} catch (error) {
146 | 		log('error', `Error adding subtask: ${error.message}`);
147 | 		throw error;
148 | 	}
149 | }
150 | 
151 | export default addSubtask;
152 | 
```

--------------------------------------------------------------------------------
/tests/unit/profiles/profile-safety-check.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import {
  2 | 	getInstalledProfiles,
  3 | 	wouldRemovalLeaveNoProfiles
  4 | } from '../../../src/utils/profiles.js';
  5 | import { rulesDirect } from '../../../mcp-server/src/core/direct-functions/rules.js';
  6 | import fs from 'fs';
  7 | import path from 'path';
  8 | import { jest } from '@jest/globals';
  9 | 
 10 | // Mock logger
 11 | const mockLog = {
 12 | 	info: jest.fn(),
 13 | 	error: jest.fn(),
 14 | 	debug: jest.fn()
 15 | };
 16 | 
 17 | describe('Rules Safety Check', () => {
 18 | 	let mockExistsSync;
 19 | 	let mockRmSync;
 20 | 	let mockReaddirSync;
 21 | 
 22 | 	beforeEach(() => {
 23 | 		jest.clearAllMocks();
 24 | 
 25 | 		// Set up spies on fs methods
 26 | 		mockExistsSync = jest.spyOn(fs, 'existsSync');
 27 | 		mockRmSync = jest.spyOn(fs, 'rmSync').mockImplementation(() => {});
 28 | 		mockReaddirSync = jest.spyOn(fs, 'readdirSync').mockReturnValue([]);
 29 | 	});
 30 | 
 31 | 	afterEach(() => {
 32 | 		// Restore all mocked functions
 33 | 		jest.restoreAllMocks();
 34 | 	});
 35 | 
 36 | 	describe('getInstalledProfiles', () => {
 37 | 		it('should detect installed profiles correctly', () => {
 38 | 			const projectRoot = '/test/project';
 39 | 
 40 | 			// Mock fs.existsSync to simulate installed profiles
 41 | 			mockExistsSync.mockImplementation((filePath) => {
 42 | 				if (filePath.includes('.cursor') || filePath.includes('.roo')) {
 43 | 					return true;
 44 | 				}
 45 | 				return false;
 46 | 			});
 47 | 
 48 | 			const installed = getInstalledProfiles(projectRoot);
 49 | 			expect(installed).toContain('cursor');
 50 | 			expect(installed).toContain('roo');
 51 | 			expect(installed).not.toContain('windsurf');
 52 | 			expect(installed).not.toContain('cline');
 53 | 		});
 54 | 
 55 | 		it('should return empty array when no profiles are installed', () => {
 56 | 			const projectRoot = '/test/project';
 57 | 
 58 | 			// Mock fs.existsSync to return false for all paths
 59 | 			mockExistsSync.mockReturnValue(false);
 60 | 
 61 | 			const installed = getInstalledProfiles(projectRoot);
 62 | 			expect(installed).toEqual([]);
 63 | 		});
 64 | 	});
 65 | 
 66 | 	describe('wouldRemovalLeaveNoProfiles', () => {
 67 | 		it('should return true when removing all installed profiles', () => {
 68 | 			const projectRoot = '/test/project';
 69 | 
 70 | 			// Mock fs.existsSync to simulate cursor and roo installed
 71 | 			mockExistsSync.mockImplementation((filePath) => {
 72 | 				return filePath.includes('.cursor') || filePath.includes('.roo');
 73 | 			});
 74 | 
 75 | 			const result = wouldRemovalLeaveNoProfiles(projectRoot, [
 76 | 				'cursor',
 77 | 				'roo'
 78 | 			]);
 79 | 			expect(result).toBe(true);
 80 | 		});
 81 | 
 82 | 		it('should return false when removing only some profiles', () => {
 83 | 			const projectRoot = '/test/project';
 84 | 
 85 | 			// Mock fs.existsSync to simulate cursor and roo installed
 86 | 			mockExistsSync.mockImplementation((filePath) => {
 87 | 				return filePath.includes('.cursor') || filePath.includes('.roo');
 88 | 			});
 89 | 
 90 | 			const result = wouldRemovalLeaveNoProfiles(projectRoot, ['roo']);
 91 | 			expect(result).toBe(false);
 92 | 		});
 93 | 
 94 | 		it('should return false when no profiles are currently installed', () => {
 95 | 			const projectRoot = '/test/project';
 96 | 
 97 | 			// Mock fs.existsSync to return false for all paths
 98 | 			mockExistsSync.mockReturnValue(false);
 99 | 
100 | 			const result = wouldRemovalLeaveNoProfiles(projectRoot, ['cursor']);
101 | 			expect(result).toBe(false);
102 | 		});
103 | 	});
104 | 
105 | 	describe('MCP Safety Check Integration', () => {
106 | 		it('should block removal of all profiles without force', async () => {
107 | 			const projectRoot = '/test/project';
108 | 
109 | 			// Mock fs.existsSync to simulate installed profiles
110 | 			mockExistsSync.mockImplementation((filePath) => {
111 | 				return filePath.includes('.cursor') || filePath.includes('.roo');
112 | 			});
113 | 
114 | 			const result = await rulesDirect(
115 | 				{
116 | 					action: 'remove',
117 | 					profiles: ['cursor', 'roo'],
118 | 					projectRoot,
119 | 					force: false
120 | 				},
121 | 				mockLog
122 | 			);
123 | 
124 | 			expect(result.success).toBe(false);
125 | 			expect(result.error.code).toBe('CRITICAL_REMOVAL_BLOCKED');
126 | 			expect(result.error.message).toContain('CRITICAL');
127 | 		});
128 | 
129 | 		it('should allow removal of all profiles with force', async () => {
130 | 			const projectRoot = '/test/project';
131 | 
132 | 			// Mock fs.existsSync and other file operations for successful removal
133 | 			mockExistsSync.mockReturnValue(true);
134 | 
135 | 			const result = await rulesDirect(
136 | 				{
137 | 					action: 'remove',
138 | 					profiles: ['cursor', 'roo'],
139 | 					projectRoot,
140 | 					force: true
141 | 				},
142 | 				mockLog
143 | 			);
144 | 
145 | 			expect(result.success).toBe(true);
146 | 			expect(result.data).toBeDefined();
147 | 		});
148 | 
149 | 		it('should allow partial removal without force', async () => {
150 | 			const projectRoot = '/test/project';
151 | 
152 | 			// Mock fs.existsSync to simulate multiple profiles installed
153 | 			mockExistsSync.mockImplementation((filePath) => {
154 | 				return (
155 | 					filePath.includes('.cursor') ||
156 | 					filePath.includes('.roo') ||
157 | 					filePath.includes('.windsurf')
158 | 				);
159 | 			});
160 | 
161 | 			const result = await rulesDirect(
162 | 				{
163 | 					action: 'remove',
164 | 					profiles: ['roo'], // Only removing one profile
165 | 					projectRoot,
166 | 					force: false
167 | 				},
168 | 				mockLog
169 | 			);
170 | 
171 | 			expect(result.success).toBe(true);
172 | 			expect(result.data).toBeDefined();
173 | 		});
174 | 	});
175 | });
176 | 
```

--------------------------------------------------------------------------------
/docs/examples/claude-code-usage.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Claude Code Provider Usage Example
  2 | 
  3 | The Claude Code provider allows you to use Claude models through the Claude Code CLI without requiring an API key.
  4 | 
  5 | ## Configuration
  6 | 
  7 | To use the Claude Code provider, update your `.taskmaster/config.json`:
  8 | 
  9 | ```json
 10 | {
 11 |   "models": {
 12 |     "main": {
 13 |       "provider": "claude-code",
 14 |       "modelId": "sonnet",
 15 |       "maxTokens": 64000,
 16 |       "temperature": 0.2
 17 |     },
 18 |     "research": {
 19 |       "provider": "claude-code",
 20 |       "modelId": "opus",
 21 |       "maxTokens": 32000,
 22 |       "temperature": 0.1
 23 |     },
 24 |     "fallback": {
 25 |       "provider": "claude-code",
 26 |       "modelId": "sonnet",
 27 |       "maxTokens": 64000,
 28 |       "temperature": 0.2
 29 |     }
 30 |   }
 31 | }
 32 | ```
 33 | 
 34 | ## Available Models
 35 | 
 36 | - `opus` - Claude Opus model (SWE score: 0.725)
 37 | - `sonnet` - Claude Sonnet model (SWE score: 0.727)
 38 | 
 39 | ## Usage
 40 | 
 41 | Once configured, you can use Claude Code with all Task Master commands:
 42 | 
 43 | ```bash
 44 | # Generate tasks from a PRD
 45 | task-master parse-prd --input=prd.txt
 46 | 
 47 | # Analyze project complexity
 48 | task-master analyze-complexity
 49 | 
 50 | # Show the next task to work on
 51 | task-master next
 52 | 
 53 | # View a specific task
 54 | task-master show task-001
 55 | 
 56 | # Update task status
 57 | task-master set-status --id=task-001 --status=in-progress
 58 | ```
 59 | 
 60 | ## Requirements
 61 | 
 62 | 1. Claude Code CLI must be installed and authenticated on your system
 63 | 2. Install the optional `@anthropic-ai/claude-code` package if you enable this provider:
 64 |    ```bash
 65 |    npm install @anthropic-ai/claude-code
 66 |    ```
 67 | 3. Run Claude Code for the first time and authenticate with your Anthropic account:
 68 |    ```bash
 69 |    claude
 70 |    ```
 71 | 4. No API key is required in your environment variables or MCP configuration
 72 | 
 73 | ## Advanced Settings
 74 | 
 75 | The Claude Code SDK supports additional settings that provide fine-grained control over Claude's behavior.  These settings are implemented in the underlying SDK (`src/ai-providers/custom-sdk/claude-code/`), and can be managed through Task Master's configuration file.
 76 | 
 77 | ### Advanced Settings Usage
 78 | 
 79 | To update settings for Claude Code, update your `.taskmaster/config.json`:
 80 | 
 81 | The Claude Code settings can be specified globally in the `claudeCode` section of the config, or on a per-command basis in the `commandSpecific` section:
 82 | 
 83 | ```javascript
 84 | {
 85 |   // "models" and "global" config...
 86 | 
 87 |   "claudeCode": {
 88 |     // Maximum conversation turns Claude can make in a single request
 89 |     "maxTurns": 5,
 90 |     
 91 |     // Custom system prompt to override Claude Code's default behavior
 92 |     "customSystemPrompt": "You are a helpful assistant focused on code quality",
 93 | 
 94 |     // Append additional content to the system prompt
 95 |     "appendSystemPrompt": "Always follow coding best practices",
 96 |     
 97 |     // Permission mode for file system operations
 98 |     "permissionMode": "default", // Options: "default", "acceptEdits", "plan", "bypassPermissions"
 99 |     
100 |     // Explicitly allow only certain tools
101 |     "allowedTools": ["Read", "LS"], // Claude can only read files and list directories
102 |     
103 |     // Explicitly disallow certain tools
104 |     "disallowedTools": ["Write", "Edit"], // Prevent Claude from modifying files
105 |     
106 |     // MCP servers for additional tool integrations
107 |     "mcpServers": {
108 |       "mcp-server-name": {
109 |         "command": "npx",
110 |         "args": ["-y", "mcp-serve"],
111 |         "env": {
112 |           // ...
113 |         }
114 |       }
115 |     }
116 |   },
117 | 
118 |   // Command-specific settings override global settings
119 |   "commandSpecific": {
120 |     "parse-prd": {
121 |       // Settings specific to the 'parse-prd' command
122 |       "maxTurns": 10,
123 |       "customSystemPrompt": "You are a task breakdown specialist"
124 |     },
125 |     "analyze-complexity": {
126 |       // Settings specific to the 'analyze-complexity' command
127 |       "maxTurns": 3,
128 |       "appendSystemPrompt": "Focus on identifying bottlenecks"
129 |     }
130 |   }
131 | }
132 | ```
133 | 
134 | - For a full list of Cluaude Code settings, see the [Claude Code Settings documentation](https://docs.anthropic.com/en/docs/claude-code/settings).
135 | - For a full list of AI powered command names, see this file: `src/constants/commands.js`
136 | 
137 | ### Why These Settings Matter
138 | 
139 | - **maxTurns**: Useful for complex refactoring tasks that require multiple iterations
140 | - **customSystemPrompt**: Allows specializing Claude for specific domains or coding standards
141 | - **appendSystemPrompt**: Useful for enforcing coding standards or providing additional context
142 | - **permissionMode**: Critical for security in production environments
143 | - **allowedTools/disallowedTools**: Enable read-only analysis modes or restrict access to sensitive operations
144 | - **mcpServers**: Future extensibility for custom tool integrations
145 | 
146 | ## Notes
147 | 
148 | - The Claude Code provider doesn't track usage costs (shown as 0 in telemetry)
149 | - Session management is handled automatically for conversation continuity
150 | - Some AI SDK parameters (temperature, maxTokens) are not supported by Claude Code CLI and will be ignored
```

--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/next-task.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * next-task.js
  3 |  * Direct function implementation for finding the next task to work on
  4 |  */
  5 | 
  6 | import { findNextTask } from '../../../../scripts/modules/task-manager.js';
  7 | import {
  8 | 	readJSON,
  9 | 	readComplexityReport
 10 | } from '../../../../scripts/modules/utils.js';
 11 | import {
 12 | 	enableSilentMode,
 13 | 	disableSilentMode
 14 | } from '../../../../scripts/modules/utils.js';
 15 | 
 16 | /**
 17 |  * Direct function wrapper for finding the next task to work on with error handling and caching.
 18 |  *
 19 |  * @param {Object} args - Command arguments
 20 |  * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
 21 |  * @param {string} args.reportPath - Path to the report file.
 22 |  * @param {string} args.projectRoot - Project root path (for MCP/env fallback)
 23 |  * @param {string} args.tag - Tag for the task (optional)
 24 |  * @param {Object} log - Logger object
 25 |  * @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } }
 26 |  */
 27 | export async function nextTaskDirect(args, log, context = {}) {
 28 | 	// Destructure expected args
 29 | 	const { tasksJsonPath, reportPath, projectRoot, tag } = args;
 30 | 	const { session } = context;
 31 | 
 32 | 	if (!tasksJsonPath) {
 33 | 		log.error('nextTaskDirect called without tasksJsonPath');
 34 | 		return {
 35 | 			success: false,
 36 | 			error: {
 37 | 				code: 'MISSING_ARGUMENT',
 38 | 				message: 'tasksJsonPath is required'
 39 | 			}
 40 | 		};
 41 | 	}
 42 | 
 43 | 	// Define the action function to be executed on cache miss
 44 | 	const coreNextTaskAction = async () => {
 45 | 		try {
 46 | 			// Enable silent mode to prevent console logs from interfering with JSON response
 47 | 			enableSilentMode();
 48 | 
 49 | 			log.info(`Finding next task from ${tasksJsonPath}`);
 50 | 
 51 | 			// Read tasks data using the provided path
 52 | 			const data = readJSON(tasksJsonPath, projectRoot, tag);
 53 | 			if (!data || !data.tasks) {
 54 | 				disableSilentMode(); // Disable before return
 55 | 				return {
 56 | 					success: false,
 57 | 					error: {
 58 | 						code: 'INVALID_TASKS_FILE',
 59 | 						message: `No valid tasks found in ${tasksJsonPath}`
 60 | 					}
 61 | 				};
 62 | 			}
 63 | 
 64 | 			// Read the complexity report
 65 | 			const complexityReport = readComplexityReport(reportPath);
 66 | 
 67 | 			// Find the next task
 68 | 			const nextTask = findNextTask(data.tasks, complexityReport);
 69 | 
 70 | 			if (!nextTask) {
 71 | 				log.info(
 72 | 					'No eligible next task found. All tasks are either completed or have unsatisfied dependencies'
 73 | 				);
 74 | 				return {
 75 | 					success: true,
 76 | 					data: {
 77 | 						message:
 78 | 							'No eligible next task found. All tasks are either completed or have unsatisfied dependencies',
 79 | 						nextTask: null
 80 | 					}
 81 | 				};
 82 | 			}
 83 | 
 84 | 			// Check if it's a subtask
 85 | 			const isSubtask =
 86 | 				typeof nextTask.id === 'string' && nextTask.id.includes('.');
 87 | 
 88 | 			const taskOrSubtask = isSubtask ? 'subtask' : 'task';
 89 | 
 90 | 			const additionalAdvice = isSubtask
 91 | 				? 'Subtasks can be updated with timestamped details as you implement them. This is useful for tracking progress, marking milestones and insights (of successful or successive falures in attempting to implement the subtask). Research can be used when updating the subtask to collect up-to-date information, and can be helpful to solve a repeating problem the agent is unable to solve. It is a good idea to get-task the parent task to collect the overall context of the task, and to get-task the subtask to collect the specific details of the subtask.'
 92 | 				: 'Tasks can be updated to reflect a change in the direction of the task, or to reformulate the task per your prompt. Research can be used when updating the task to collect up-to-date information. It is best to update subtasks as you work on them, and to update the task for more high-level changes that may affect pending subtasks or the general direction of the task.';
 93 | 
 94 | 			// Restore normal logging
 95 | 			disableSilentMode();
 96 | 
 97 | 			// Return the next task data with the full tasks array for reference
 98 | 			log.info(
 99 | 				`Successfully found next task ${nextTask.id}: ${nextTask.title}. Is subtask: ${isSubtask}`
100 | 			);
101 | 			return {
102 | 				success: true,
103 | 				data: {
104 | 					nextTask,
105 | 					isSubtask,
106 | 					nextSteps: `When ready to work on the ${taskOrSubtask}, use set-status to set the status to "in progress" ${additionalAdvice}`
107 | 				}
108 | 			};
109 | 		} catch (error) {
110 | 			// Make sure to restore normal logging even if there's an error
111 | 			disableSilentMode();
112 | 
113 | 			log.error(`Error finding next task: ${error.message}`);
114 | 			return {
115 | 				success: false,
116 | 				error: {
117 | 					code: 'CORE_FUNCTION_ERROR',
118 | 					message: error.message || 'Failed to find next task'
119 | 				}
120 | 			};
121 | 		}
122 | 	};
123 | 
124 | 	// Use the caching utility
125 | 	try {
126 | 		const result = await coreNextTaskAction();
127 | 		log.info('nextTaskDirect completed.');
128 | 		return result;
129 | 	} catch (error) {
130 | 		log.error(`Unexpected error during nextTask: ${error.message}`);
131 | 		return {
132 | 			success: false,
133 | 			error: {
134 | 				code: 'UNEXPECTED_ERROR',
135 | 				message: error.message
136 | 			}
137 | 		};
138 | 	}
139 | }
140 | 
```

--------------------------------------------------------------------------------
/.github/workflows/claude-issue-triage.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Claude Issue Triage
  2 | # description: Automatically triage GitHub issues using Claude Code
  3 | 
  4 | on:
  5 |   issues:
  6 |     types: [opened]
  7 | 
  8 | jobs:
  9 |   triage-issue:
 10 |     runs-on: ubuntu-latest
 11 |     timeout-minutes: 10
 12 |     permissions:
 13 |       contents: read
 14 |       issues: write
 15 | 
 16 |     steps:
 17 |       - name: Checkout repository
 18 |         uses: actions/checkout@v4
 19 | 
 20 |       - name: Create triage prompt
 21 |         run: |
 22 |           mkdir -p /tmp/claude-prompts
 23 |           cat > /tmp/claude-prompts/triage-prompt.txt << 'EOF'
 24 |           You're an issue triage assistant for GitHub issues. Your task is to analyze the issue and select appropriate labels from the provided list.
 25 | 
 26 |           IMPORTANT: Don't post any comments or messages to the issue. Your only action should be to apply labels.
 27 | 
 28 |           Issue Information:
 29 |           - REPO: ${{ github.repository }}
 30 |           - ISSUE_NUMBER: ${{ github.event.issue.number }}
 31 | 
 32 |           TASK OVERVIEW:
 33 | 
 34 |           1. First, fetch the list of labels available in this repository by running: `gh label list`. Run exactly this command with nothing else.
 35 | 
 36 |           2. Next, use the GitHub tools to get context about the issue:
 37 |              - You have access to these tools:
 38 |                - mcp__github__get_issue: Use this to retrieve the current issue's details including title, description, and existing labels
 39 |                - mcp__github__get_issue_comments: Use this to read any discussion or additional context provided in the comments
 40 |                - mcp__github__update_issue: Use this to apply labels to the issue (do not use this for commenting)
 41 |                - mcp__github__search_issues: Use this to find similar issues that might provide context for proper categorization and to identify potential duplicate issues
 42 |                - mcp__github__list_issues: Use this to understand patterns in how other issues are labeled
 43 |              - Start by using mcp__github__get_issue to get the issue details
 44 | 
 45 |           3. Analyze the issue content, considering:
 46 |              - The issue title and description
 47 |              - The type of issue (bug report, feature request, question, etc.)
 48 |              - Technical areas mentioned
 49 |              - Severity or priority indicators
 50 |              - User impact
 51 |              - Components affected
 52 | 
 53 |           4. Select appropriate labels from the available labels list provided above:
 54 |              - Choose labels that accurately reflect the issue's nature
 55 |              - Be specific but comprehensive
 56 |              - Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority)
 57 |              - Consider platform labels (android, ios) if applicable
 58 |              - If you find similar issues using mcp__github__search_issues, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
 59 | 
 60 |           5. Apply the selected labels:
 61 |              - Use mcp__github__update_issue to apply your selected labels
 62 |              - DO NOT post any comments explaining your decision
 63 |              - DO NOT communicate directly with users
 64 |              - If no labels are clearly applicable, do not apply any labels
 65 | 
 66 |           IMPORTANT GUIDELINES:
 67 |           - Be thorough in your analysis
 68 |           - Only select labels from the provided list above
 69 |           - DO NOT post any comments to the issue
 70 |           - Your ONLY action should be to apply labels using mcp__github__update_issue
 71 |           - It's okay to not add any labels if none are clearly applicable
 72 |           EOF
 73 | 
 74 |       - name: Setup GitHub MCP Server
 75 |         run: |
 76 |           mkdir -p /tmp/mcp-config
 77 |           cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
 78 |           {
 79 |             "mcpServers": {
 80 |               "github": {
 81 |                 "command": "docker",
 82 |                 "args": [
 83 |                   "run",
 84 |                   "-i",
 85 |                   "--rm",
 86 |                   "-e",
 87 |                   "GITHUB_PERSONAL_ACCESS_TOKEN",
 88 |                   "ghcr.io/github/github-mcp-server:sha-7aced2b"
 89 |                 ],
 90 |                 "env": {
 91 |                   "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
 92 |                 }
 93 |               }
 94 |             }
 95 |           }
 96 |           EOF
 97 | 
 98 |       - name: Run Claude Code for Issue Triage
 99 |         uses: anthropics/claude-code-base-action@beta
100 |         with:
101 |           prompt_file: /tmp/claude-prompts/triage-prompt.txt
102 |           allowed_tools: "Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues"
103 |           timeout_minutes: "5"
104 |           anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
105 |           mcp_config: /tmp/mcp-config/mcp-servers.json
106 |           claude_env: |
107 |             GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
108 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/utils/project-root-finder.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Project root detection utilities
  3 |  * Provides functionality to locate project roots by searching for marker files/directories
  4 |  */
  5 | 
  6 | import path from 'node:path';
  7 | import fs from 'node:fs';
  8 | import {
  9 | 	TASKMASTER_PROJECT_MARKERS,
 10 | 	OTHER_PROJECT_MARKERS
 11 | } from '../constants/paths.js';
 12 | 
 13 | /**
 14 |  * Find the project root directory by looking for project markers
 15 |  * Traverses upwards from startDir until a project marker is found or filesystem root is reached
 16 |  * Limited to 50 parent directory levels to prevent excessive traversal
 17 |  *
 18 |  * Strategy: First searches ALL parent directories for .taskmaster (highest priority).
 19 |  * If not found, then searches for other project markers starting from current directory.
 20 |  * This ensures .taskmaster in parent directories takes precedence over other markers in subdirectories.
 21 |  *
 22 |  * @param startDir - Directory to start searching from (defaults to process.cwd())
 23 |  * @returns Project root path (falls back to current directory if no markers found)
 24 |  *
 25 |  * @example
 26 |  * ```typescript
 27 |  * // In a monorepo structure:
 28 |  * // /project/.taskmaster
 29 |  * // /project/packages/my-package/.git
 30 |  * // When called from /project/packages/my-package:
 31 |  * const root = findProjectRoot(); // Returns /project (not /project/packages/my-package)
 32 |  * ```
 33 |  */
 34 | export function findProjectRoot(startDir: string = process.cwd()): string {
 35 | 	let currentDir = path.resolve(startDir);
 36 | 	const rootDir = path.parse(currentDir).root;
 37 | 	const maxDepth = 50; // Reasonable limit to prevent infinite loops
 38 | 	let depth = 0;
 39 | 
 40 | 	// FIRST PASS: Traverse ALL parent directories looking ONLY for Task Master markers
 41 | 	// This ensures that a .taskmaster in a parent directory takes precedence over
 42 | 	// other project markers (like .git, go.mod, etc.) in subdirectories
 43 | 	let searchDir = currentDir;
 44 | 	depth = 0;
 45 | 
 46 | 	while (depth < maxDepth) {
 47 | 		for (const marker of TASKMASTER_PROJECT_MARKERS) {
 48 | 			const markerPath = path.join(searchDir, marker);
 49 | 			try {
 50 | 				if (fs.existsSync(markerPath)) {
 51 | 					// Found a Task Master marker - this is our project root
 52 | 					return searchDir;
 53 | 				}
 54 | 			} catch (error) {
 55 | 				// Ignore permission errors and continue searching
 56 | 				continue;
 57 | 			}
 58 | 		}
 59 | 
 60 | 		// If we're at root, stop after checking it
 61 | 		if (searchDir === rootDir) {
 62 | 			break;
 63 | 		}
 64 | 
 65 | 		// Move up one directory level
 66 | 		const parentDir = path.dirname(searchDir);
 67 | 
 68 | 		// Safety check: if dirname returns the same path, we've hit the root
 69 | 		if (parentDir === searchDir) {
 70 | 			break;
 71 | 		}
 72 | 
 73 | 		searchDir = parentDir;
 74 | 		depth++;
 75 | 	}
 76 | 
 77 | 	// SECOND PASS: No Task Master markers found in any parent directory
 78 | 	// Now search for other project markers starting from the original directory
 79 | 	currentDir = path.resolve(startDir);
 80 | 	depth = 0;
 81 | 
 82 | 	while (depth < maxDepth) {
 83 | 		for (const marker of OTHER_PROJECT_MARKERS) {
 84 | 			const markerPath = path.join(currentDir, marker);
 85 | 			try {
 86 | 				if (fs.existsSync(markerPath)) {
 87 | 					// Found another project marker - return this as project root
 88 | 					return currentDir;
 89 | 				}
 90 | 			} catch (error) {
 91 | 				// Ignore permission errors and continue searching
 92 | 				continue;
 93 | 			}
 94 | 		}
 95 | 
 96 | 		// If we're at root, stop after checking it
 97 | 		if (currentDir === rootDir) {
 98 | 			break;
 99 | 		}
100 | 
101 | 		// Move up one directory level
102 | 		const parentDir = path.dirname(currentDir);
103 | 
104 | 		// Safety check: if dirname returns the same path, we've hit the root
105 | 		if (parentDir === currentDir) {
106 | 			break;
107 | 		}
108 | 
109 | 		currentDir = parentDir;
110 | 		depth++;
111 | 	}
112 | 
113 | 	// Fallback to current working directory if no project root found
114 | 	// This ensures the function always returns a valid, existing path
115 | 	return process.cwd();
116 | }
117 | 
118 | /**
119 |  * Normalize project root to ensure it doesn't end with .taskmaster
120 |  * This prevents double .taskmaster paths when using constants that include .taskmaster
121 |  *
122 |  * @param projectRoot - The project root path to normalize
123 |  * @returns Normalized project root path
124 |  *
125 |  * @example
126 |  * ```typescript
127 |  * normalizeProjectRoot('/project/.taskmaster'); // Returns '/project'
128 |  * normalizeProjectRoot('/project'); // Returns '/project'
129 |  * normalizeProjectRoot('/project/.taskmaster/tasks'); // Returns '/project'
130 |  * ```
131 |  */
132 | export function normalizeProjectRoot(
133 | 	projectRoot: string | null | undefined
134 | ): string {
135 | 	if (!projectRoot) return projectRoot || '';
136 | 
137 | 	// Ensure it's a string
138 | 	const projectRootStr = String(projectRoot);
139 | 
140 | 	// Split the path into segments
141 | 	const segments = projectRootStr.split(path.sep);
142 | 
143 | 	// Find the index of .taskmaster segment
144 | 	const taskmasterIndex = segments.findIndex(
145 | 		(segment) => segment === '.taskmaster'
146 | 	);
147 | 
148 | 	if (taskmasterIndex !== -1) {
149 | 		// If .taskmaster is found, return everything up to but not including .taskmaster
150 | 		const normalizedSegments = segments.slice(0, taskmasterIndex);
151 | 		return normalizedSegments.join(path.sep) || path.sep;
152 | 	}
153 | 
154 | 	return projectRootStr;
155 | }
156 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/commands/autopilot/complete.command.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Complete Command - Complete current TDD phase with validation
  3 |  */
  4 | 
  5 | import { type TestResult, WorkflowOrchestrator } from '@tm/core';
  6 | import { Command } from 'commander';
  7 | import { getProjectRoot } from '../../utils/project-root.js';
  8 | import {
  9 | 	type AutopilotBaseOptions,
 10 | 	OutputFormatter,
 11 | 	hasWorkflowState,
 12 | 	loadWorkflowState
 13 | } from './shared.js';
 14 | 
 15 | interface CompleteOptions extends AutopilotBaseOptions {
 16 | 	results?: string;
 17 | 	coverage?: string;
 18 | }
 19 | 
 20 | /**
 21 |  * Complete Command - Mark current phase as complete with validation
 22 |  */
 23 | export class CompleteCommand extends Command {
 24 | 	constructor() {
 25 | 		super('complete');
 26 | 
 27 | 		this.description('Complete the current TDD phase with result validation')
 28 | 			.option(
 29 | 				'-r, --results <json>',
 30 | 				'Test results JSON (with total, passed, failed, skipped)'
 31 | 			)
 32 | 			.option('-c, --coverage <percent>', 'Coverage percentage')
 33 | 			.action(async (options: CompleteOptions) => {
 34 | 				await this.execute(options);
 35 | 			});
 36 | 	}
 37 | 
 38 | 	private async execute(options: CompleteOptions): Promise<void> {
 39 | 		// Inherit parent options
 40 | 		const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
 41 | 		const mergedOptions: CompleteOptions = {
 42 | 			...parentOpts,
 43 | 			...options,
 44 | 			projectRoot: getProjectRoot(
 45 | 				options.projectRoot || parentOpts?.projectRoot
 46 | 			)
 47 | 		};
 48 | 
 49 | 		const formatter = new OutputFormatter(mergedOptions.json || false);
 50 | 
 51 | 		try {
 52 | 			// Check for workflow state
 53 | 			const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
 54 | 			if (!hasState) {
 55 | 				formatter.error('No active workflow', {
 56 | 					suggestion: 'Start a workflow with: autopilot start <taskId>'
 57 | 				});
 58 | 				process.exit(1);
 59 | 			}
 60 | 
 61 | 			// Load state
 62 | 			const state = await loadWorkflowState(mergedOptions.projectRoot!);
 63 | 			if (!state) {
 64 | 				formatter.error('Failed to load workflow state');
 65 | 				process.exit(1);
 66 | 			}
 67 | 
 68 | 			// Restore orchestrator with persistence
 69 | 			const { saveWorkflowState } = await import('./shared.js');
 70 | 			const orchestrator = new WorkflowOrchestrator(state.context);
 71 | 			orchestrator.restoreState(state);
 72 | 			orchestrator.enableAutoPersist(async (newState) => {
 73 | 				await saveWorkflowState(mergedOptions.projectRoot!, newState);
 74 | 			});
 75 | 
 76 | 			// Get current phase
 77 | 			const tddPhase = orchestrator.getCurrentTDDPhase();
 78 | 			const currentSubtask = orchestrator.getCurrentSubtask();
 79 | 
 80 | 			if (!tddPhase) {
 81 | 				formatter.error('Not in a TDD phase', {
 82 | 					phase: orchestrator.getCurrentPhase()
 83 | 				});
 84 | 				process.exit(1);
 85 | 			}
 86 | 
 87 | 			// Validate based on phase
 88 | 			if (tddPhase === 'RED' || tddPhase === 'GREEN') {
 89 | 				if (!mergedOptions.results) {
 90 | 					formatter.error('Test results required for RED/GREEN phase', {
 91 | 						usage:
 92 | 							'--results \'{"total":10,"passed":9,"failed":1,"skipped":0}\''
 93 | 					});
 94 | 					process.exit(1);
 95 | 				}
 96 | 
 97 | 				// Parse test results
 98 | 				let testResults: TestResult;
 99 | 				try {
100 | 					const parsed = JSON.parse(mergedOptions.results);
101 | 					testResults = {
102 | 						total: parsed.total || 0,
103 | 						passed: parsed.passed || 0,
104 | 						failed: parsed.failed || 0,
105 | 						skipped: parsed.skipped || 0,
106 | 						phase: tddPhase
107 | 					};
108 | 				} catch (error) {
109 | 					formatter.error('Invalid test results JSON', {
110 | 						error: (error as Error).message
111 | 					});
112 | 					process.exit(1);
113 | 				}
114 | 
115 | 				// Validate RED phase requirements
116 | 				if (tddPhase === 'RED' && testResults.failed === 0) {
117 | 					formatter.error('RED phase validation failed', {
118 | 						reason: 'At least one test must be failing',
119 | 						actual: {
120 | 							passed: testResults.passed,
121 | 							failed: testResults.failed
122 | 						}
123 | 					});
124 | 					process.exit(1);
125 | 				}
126 | 
127 | 				// Validate GREEN phase requirements
128 | 				if (tddPhase === 'GREEN' && testResults.failed !== 0) {
129 | 					formatter.error('GREEN phase validation failed', {
130 | 						reason: 'All tests must pass',
131 | 						actual: {
132 | 							passed: testResults.passed,
133 | 							failed: testResults.failed
134 | 						}
135 | 					});
136 | 					process.exit(1);
137 | 				}
138 | 
139 | 				// Complete phase with test results
140 | 				if (tddPhase === 'RED') {
141 | 					orchestrator.transition({
142 | 						type: 'RED_PHASE_COMPLETE',
143 | 						testResults
144 | 					});
145 | 					formatter.success('RED phase completed', {
146 | 						nextPhase: 'GREEN',
147 | 						testResults,
148 | 						subtask: currentSubtask?.title
149 | 					});
150 | 				} else {
151 | 					orchestrator.transition({
152 | 						type: 'GREEN_PHASE_COMPLETE',
153 | 						testResults
154 | 					});
155 | 					formatter.success('GREEN phase completed', {
156 | 						nextPhase: 'COMMIT',
157 | 						testResults,
158 | 						subtask: currentSubtask?.title,
159 | 						suggestion: 'Run: autopilot commit'
160 | 					});
161 | 				}
162 | 			} else if (tddPhase === 'COMMIT') {
163 | 				formatter.error('Use "autopilot commit" to complete COMMIT phase');
164 | 				process.exit(1);
165 | 			}
166 | 		} catch (error) {
167 | 			formatter.error((error as Error).message);
168 | 			if (mergedOptions.verbose) {
169 | 				console.error((error as Error).stack);
170 | 			}
171 | 			process.exit(1);
172 | 		}
173 | 	}
174 | }
175 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/tools/analyze.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * tools/analyze.js
  3 |  * Tool for analyzing task complexity and generating recommendations
  4 |  */
  5 | 
  6 | import { z } from 'zod';
  7 | import path from 'path';
  8 | import fs from 'fs'; // Import fs for directory check/creation
  9 | import {
 10 | 	handleApiResult,
 11 | 	createErrorResponse,
 12 | 	withNormalizedProjectRoot
 13 | } from './utils.js';
 14 | import { analyzeTaskComplexityDirect } from '../core/task-master-core.js'; // Assuming core functions are exported via task-master-core.js
 15 | import { findTasksPath } from '../core/utils/path-utils.js';
 16 | import { resolveTag } from '../../../scripts/modules/utils.js';
 17 | import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js';
 18 | import { resolveComplexityReportOutputPath } from '../../../src/utils/path-utils.js';
 19 | 
 20 | /**
 21 |  * Register the analyze_project_complexity tool
 22 |  * @param {Object} server - FastMCP server instance
 23 |  */
 24 | export function registerAnalyzeProjectComplexityTool(server) {
 25 | 	server.addTool({
 26 | 		name: 'analyze_project_complexity',
 27 | 		description:
 28 | 			'Analyze task complexity and generate expansion recommendations.',
 29 | 		parameters: z.object({
 30 | 			threshold: z.coerce // Use coerce for number conversion from string if needed
 31 | 				.number()
 32 | 				.int()
 33 | 				.min(1)
 34 | 				.max(10)
 35 | 				.optional()
 36 | 				.default(5) // Default threshold
 37 | 				.describe('Complexity score threshold (1-10) to recommend expansion.'),
 38 | 			research: z
 39 | 				.boolean()
 40 | 				.optional()
 41 | 				.default(false)
 42 | 				.describe('Use Perplexity AI for research-backed analysis.'),
 43 | 			output: z
 44 | 				.string()
 45 | 				.optional()
 46 | 				.describe(
 47 | 					`Output file path relative to project root (default: ${COMPLEXITY_REPORT_FILE}).`
 48 | 				),
 49 | 			file: z
 50 | 				.string()
 51 | 				.optional()
 52 | 				.describe(
 53 | 					'Path to the tasks file relative to project root (default: tasks/tasks.json).'
 54 | 				),
 55 | 			ids: z
 56 | 				.string()
 57 | 				.optional()
 58 | 				.describe(
 59 | 					'Comma-separated list of task IDs to analyze specifically (e.g., "1,3,5").'
 60 | 				),
 61 | 			from: z.coerce
 62 | 				.number()
 63 | 				.int()
 64 | 				.positive()
 65 | 				.optional()
 66 | 				.describe('Starting task ID in a range to analyze.'),
 67 | 			to: z.coerce
 68 | 				.number()
 69 | 				.int()
 70 | 				.positive()
 71 | 				.optional()
 72 | 				.describe('Ending task ID in a range to analyze.'),
 73 | 			projectRoot: z
 74 | 				.string()
 75 | 				.describe('The directory of the project. Must be an absolute path.'),
 76 | 			tag: z.string().optional().describe('Tag context to operate on')
 77 | 		}),
 78 | 		execute: withNormalizedProjectRoot(async (args, { log, session }) => {
 79 | 			const toolName = 'analyze_project_complexity'; // Define tool name for logging
 80 | 
 81 | 			try {
 82 | 				log.info(
 83 | 					`Executing ${toolName} tool with args: ${JSON.stringify(args)}`
 84 | 				);
 85 | 
 86 | 				const resolvedTag = resolveTag({
 87 | 					projectRoot: args.projectRoot,
 88 | 					tag: args.tag
 89 | 				});
 90 | 
 91 | 				let tasksJsonPath;
 92 | 				try {
 93 | 					tasksJsonPath = findTasksPath(
 94 | 						{ projectRoot: args.projectRoot, file: args.file },
 95 | 						log
 96 | 					);
 97 | 					log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`);
 98 | 				} catch (error) {
 99 | 					log.error(`${toolName}: Error finding tasks.json: ${error.message}`);
100 | 					return createErrorResponse(
101 | 						`Failed to find tasks.json within project root '${args.projectRoot}': ${error.message}`
102 | 					);
103 | 				}
104 | 
105 | 				const outputPath = resolveComplexityReportOutputPath(
106 | 					args.output,
107 | 					{
108 | 						projectRoot: args.projectRoot,
109 | 						tag: resolvedTag
110 | 					},
111 | 					log
112 | 				);
113 | 
114 | 				log.info(`${toolName}: Report output path: ${outputPath}`);
115 | 
116 | 				// Ensure output directory exists
117 | 				const outputDir = path.dirname(outputPath);
118 | 				try {
119 | 					if (!fs.existsSync(outputDir)) {
120 | 						fs.mkdirSync(outputDir, { recursive: true });
121 | 						log.info(`${toolName}: Created output directory: ${outputDir}`);
122 | 					}
123 | 				} catch (dirError) {
124 | 					log.error(
125 | 						`${toolName}: Failed to create output directory ${outputDir}: ${dirError.message}`
126 | 					);
127 | 					return createErrorResponse(
128 | 						`Failed to create output directory: ${dirError.message}`
129 | 					);
130 | 				}
131 | 
132 | 				// 3. Call Direct Function - Pass projectRoot in first arg object
133 | 				const result = await analyzeTaskComplexityDirect(
134 | 					{
135 | 						tasksJsonPath: tasksJsonPath,
136 | 						outputPath: outputPath,
137 | 						threshold: args.threshold,
138 | 						research: args.research,
139 | 						projectRoot: args.projectRoot,
140 | 						tag: resolvedTag,
141 | 						ids: args.ids,
142 | 						from: args.from,
143 | 						to: args.to
144 | 					},
145 | 					log,
146 | 					{ session }
147 | 				);
148 | 
149 | 				// 4. Handle Result
150 | 				log.info(
151 | 					`${toolName}: Direct function result: success=${result.success}`
152 | 				);
153 | 				return handleApiResult(
154 | 					result,
155 | 					log,
156 | 					'Error analyzing task complexity',
157 | 					undefined,
158 | 					args.projectRoot
159 | 				);
160 | 			} catch (error) {
161 | 				log.error(
162 | 					`Critical error in ${toolName} tool execute: ${error.message}`
163 | 				);
164 | 				return createErrorResponse(
165 | 					`Internal tool error (${toolName}): ${error.message}`
166 | 				);
167 | 			}
168 | 		})
169 | 	});
170 | }
171 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/ui/layout/helpers.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Layout helper utilities tests
  3 |  * Tests for apps/cli/src/utils/layout/helpers.ts
  4 |  */
  5 | 
  6 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  7 | import type { MockInstance } from 'vitest';
  8 | import { getBoxWidth } from './helpers.js';
  9 | 
 10 | describe('Layout Helpers', () => {
 11 | 	describe('getBoxWidth', () => {
 12 | 		let columnsSpy: MockInstance;
 13 | 		let originalDescriptor: PropertyDescriptor | undefined;
 14 | 
 15 | 		beforeEach(() => {
 16 | 			// Store original descriptor if it exists
 17 | 			originalDescriptor = Object.getOwnPropertyDescriptor(
 18 | 				process.stdout,
 19 | 				'columns'
 20 | 			);
 21 | 
 22 | 			// If columns doesn't exist or isn't a getter, define it as one
 23 | 			if (!originalDescriptor || !originalDescriptor.get) {
 24 | 				const currentValue = process.stdout.columns || 80;
 25 | 				Object.defineProperty(process.stdout, 'columns', {
 26 | 					get() {
 27 | 						return currentValue;
 28 | 					},
 29 | 					configurable: true
 30 | 				});
 31 | 			}
 32 | 
 33 | 			// Now spy on the getter
 34 | 			columnsSpy = vi.spyOn(process.stdout, 'columns', 'get');
 35 | 		});
 36 | 
 37 | 		afterEach(() => {
 38 | 			// Restore the spy
 39 | 			columnsSpy.mockRestore();
 40 | 
 41 | 			// Restore original descriptor or delete the property
 42 | 			if (originalDescriptor) {
 43 | 				Object.defineProperty(process.stdout, 'columns', originalDescriptor);
 44 | 			} else {
 45 | 				delete (process.stdout as any).columns;
 46 | 			}
 47 | 		});
 48 | 
 49 | 		it('should calculate width as percentage of terminal width', () => {
 50 | 			columnsSpy.mockReturnValue(100);
 51 | 			const width = getBoxWidth(0.9, 40);
 52 | 			expect(width).toBe(90);
 53 | 		});
 54 | 
 55 | 		it('should use default percentage of 0.9 when not specified', () => {
 56 | 			columnsSpy.mockReturnValue(100);
 57 | 			const width = getBoxWidth();
 58 | 			expect(width).toBe(90);
 59 | 		});
 60 | 
 61 | 		it('should use default minimum width of 40 when not specified', () => {
 62 | 			columnsSpy.mockReturnValue(30);
 63 | 			const width = getBoxWidth();
 64 | 			expect(width).toBe(40); // Should enforce minimum
 65 | 		});
 66 | 
 67 | 		it('should enforce minimum width when terminal is too narrow', () => {
 68 | 			columnsSpy.mockReturnValue(50);
 69 | 			const width = getBoxWidth(0.9, 60);
 70 | 			expect(width).toBe(60); // Should use minWidth instead of 45
 71 | 		});
 72 | 
 73 | 		it('should handle undefined process.stdout.columns', () => {
 74 | 			columnsSpy.mockReturnValue(undefined);
 75 | 			const width = getBoxWidth(0.9, 40);
 76 | 			// Should fall back to 80 columns: Math.floor(80 * 0.9) = 72
 77 | 			expect(width).toBe(72);
 78 | 		});
 79 | 
 80 | 		it('should handle custom percentage values', () => {
 81 | 			columnsSpy.mockReturnValue(100);
 82 | 			expect(getBoxWidth(0.95, 40)).toBe(95);
 83 | 			expect(getBoxWidth(0.8, 40)).toBe(80);
 84 | 			expect(getBoxWidth(0.5, 40)).toBe(50);
 85 | 		});
 86 | 
 87 | 		it('should handle custom minimum width values', () => {
 88 | 			columnsSpy.mockReturnValue(60);
 89 | 			expect(getBoxWidth(0.9, 70)).toBe(70); // 60 * 0.9 = 54, but min is 70
 90 | 			expect(getBoxWidth(0.9, 50)).toBe(54); // 60 * 0.9 = 54, min is 50
 91 | 		});
 92 | 
 93 | 		it('should floor the calculated width', () => {
 94 | 			columnsSpy.mockReturnValue(99);
 95 | 			const width = getBoxWidth(0.9, 40);
 96 | 			// 99 * 0.9 = 89.1, should floor to 89
 97 | 			expect(width).toBe(89);
 98 | 		});
 99 | 
100 | 		it('should match warning box width calculation', () => {
101 | 			// Test the specific case from displayWarning()
102 | 			columnsSpy.mockReturnValue(80);
103 | 			const width = getBoxWidth(0.9, 40);
104 | 			expect(width).toBe(72);
105 | 		});
106 | 
107 | 		it('should match table width calculation', () => {
108 | 			// Test the specific case from createTaskTable()
109 | 			columnsSpy.mockReturnValue(111);
110 | 			const width = getBoxWidth(0.9, 100);
111 | 			// 111 * 0.9 = 99.9, floor to 99, but max(99, 100) = 100
112 | 			expect(width).toBe(100);
113 | 		});
114 | 
115 | 		it('should match recommended task box width calculation', () => {
116 | 			// Test the specific case from displayRecommendedNextTask()
117 | 			columnsSpy.mockReturnValue(120);
118 | 			const width = getBoxWidth(0.97, 40);
119 | 			// 120 * 0.97 = 116.4, floor to 116
120 | 			expect(width).toBe(116);
121 | 		});
122 | 
123 | 		it('should handle edge case of zero terminal width', () => {
124 | 			columnsSpy.mockReturnValue(0);
125 | 			const width = getBoxWidth(0.9, 40);
126 | 			// When columns is 0, it uses fallback of 80: Math.floor(80 * 0.9) = 72
127 | 			expect(width).toBe(72);
128 | 		});
129 | 
130 | 		it('should handle very large terminal widths', () => {
131 | 			columnsSpy.mockReturnValue(1000);
132 | 			const width = getBoxWidth(0.9, 40);
133 | 			expect(width).toBe(900);
134 | 		});
135 | 
136 | 		it('should handle very small percentages', () => {
137 | 			columnsSpy.mockReturnValue(100);
138 | 			const width = getBoxWidth(0.1, 5);
139 | 			// 100 * 0.1 = 10, which is greater than min 5
140 | 			expect(width).toBe(10);
141 | 		});
142 | 
143 | 		it('should handle percentage of 1.0 (100%)', () => {
144 | 			columnsSpy.mockReturnValue(80);
145 | 			const width = getBoxWidth(1.0, 40);
146 | 			expect(width).toBe(80);
147 | 		});
148 | 
149 | 		it('should consistently return same value for same inputs', () => {
150 | 			columnsSpy.mockReturnValue(100);
151 | 			const width1 = getBoxWidth(0.9, 40);
152 | 			const width2 = getBoxWidth(0.9, 40);
153 | 			const width3 = getBoxWidth(0.9, 40);
154 | 			expect(width1).toBe(width2);
155 | 			expect(width2).toBe(width3);
156 | 		});
157 | 	});
158 | });
159 | 
```

--------------------------------------------------------------------------------
/apps/cli/tests/unit/commands/show.command.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Unit tests for ShowCommand
  3 |  */
  4 | 
  5 | import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
  6 | import type { TmCore } from '@tm/core';
  7 | 
  8 | // Mock dependencies
  9 | vi.mock('@tm/core', () => ({
 10 | 	createTmCore: vi.fn()
 11 | }));
 12 | 
 13 | vi.mock('../../../src/utils/project-root.js', () => ({
 14 | 	getProjectRoot: vi.fn((path?: string) => path || '/test/project')
 15 | }));
 16 | 
 17 | vi.mock('../../../src/utils/error-handler.js', () => ({
 18 | 	displayError: vi.fn()
 19 | }));
 20 | 
 21 | vi.mock('../../../src/utils/display-helpers.js', () => ({
 22 | 	displayCommandHeader: vi.fn()
 23 | }));
 24 | 
 25 | vi.mock('../../../src/ui/components/task-detail.component.js', () => ({
 26 | 	displayTaskDetails: vi.fn()
 27 | }));
 28 | 
 29 | vi.mock('../../../src/utils/ui.js', () => ({
 30 | 	createTaskTable: vi.fn(() => 'Table output'),
 31 | 	displayWarning: vi.fn()
 32 | }));
 33 | 
 34 | import { ShowCommand } from '../../../src/commands/show.command.js';
 35 | 
 36 | describe('ShowCommand', () => {
 37 | 	let consoleLogSpy: any;
 38 | 	let mockTmCore: Partial<TmCore>;
 39 | 
 40 | 	beforeEach(() => {
 41 | 		consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
 42 | 
 43 | 		mockTmCore = {
 44 | 			tasks: {
 45 | 				get: vi.fn().mockResolvedValue({
 46 | 					task: {
 47 | 						id: '1',
 48 | 						title: 'Test Task',
 49 | 						status: 'pending',
 50 | 						description: 'Test description'
 51 | 					},
 52 | 					isSubtask: false
 53 | 				}),
 54 | 				getStorageType: vi.fn().mockReturnValue('json')
 55 | 			} as any,
 56 | 			config: {
 57 | 				getActiveTag: vi.fn().mockReturnValue('master')
 58 | 			} as any
 59 | 		};
 60 | 	});
 61 | 
 62 | 	afterEach(() => {
 63 | 		vi.clearAllMocks();
 64 | 		consoleLogSpy.mockRestore();
 65 | 	});
 66 | 
 67 | 	describe('JSON output format', () => {
 68 | 		it('should use JSON format when --json flag is set', async () => {
 69 | 			const command = new ShowCommand();
 70 | 
 71 | 			// Mock the tmCore initialization
 72 | 			(command as any).tmCore = mockTmCore;
 73 | 
 74 | 			// Execute with --json flag
 75 | 			await (command as any).executeCommand('1', {
 76 | 				id: '1',
 77 | 				json: true,
 78 | 				format: 'text' // Should be overridden by --json
 79 | 			});
 80 | 
 81 | 			// Verify JSON output was called
 82 | 			expect(consoleLogSpy).toHaveBeenCalled();
 83 | 			const output = consoleLogSpy.mock.calls[0][0];
 84 | 
 85 | 			// Should be valid JSON
 86 | 			expect(() => JSON.parse(output)).not.toThrow();
 87 | 
 88 | 			const parsed = JSON.parse(output);
 89 | 			expect(parsed).toHaveProperty('task');
 90 | 			expect(parsed).toHaveProperty('found');
 91 | 			expect(parsed).toHaveProperty('storageType');
 92 | 		});
 93 | 
 94 | 		it('should override --format when --json is set', async () => {
 95 | 			const command = new ShowCommand();
 96 | 			(command as any).tmCore = mockTmCore;
 97 | 
 98 | 			await (command as any).executeCommand('1', {
 99 | 				id: '1',
100 | 				json: true,
101 | 				format: 'text' // Should be overridden
102 | 			});
103 | 
104 | 			// Should output JSON, not text format
105 | 			const output = consoleLogSpy.mock.calls[0][0];
106 | 			expect(() => JSON.parse(output)).not.toThrow();
107 | 		});
108 | 
109 | 		it('should use text format when --json is not set', async () => {
110 | 			const command = new ShowCommand();
111 | 			(command as any).tmCore = mockTmCore;
112 | 
113 | 			await (command as any).executeCommand('1', {
114 | 				id: '1',
115 | 				format: 'text'
116 | 			});
117 | 
118 | 			// Should use text format (not JSON)
119 | 			// Text format will call displayCommandHeader and displayTaskDetails
120 | 			// We just verify it was called (mocked functions)
121 | 			expect(consoleLogSpy).toHaveBeenCalled();
122 | 		});
123 | 
124 | 		it('should default to text format when neither flag is set', async () => {
125 | 			const command = new ShowCommand();
126 | 			(command as any).tmCore = mockTmCore;
127 | 
128 | 			await (command as any).executeCommand('1', {
129 | 				id: '1'
130 | 			});
131 | 
132 | 			// Should use text format by default
133 | 			expect(consoleLogSpy).toHaveBeenCalled();
134 | 		});
135 | 	});
136 | 
137 | 	describe('format validation', () => {
138 | 		it('should accept valid formats', () => {
139 | 			const command = new ShowCommand();
140 | 
141 | 			expect((command as any).validateOptions({ format: 'text' })).toBe(true);
142 | 			expect((command as any).validateOptions({ format: 'json' })).toBe(true);
143 | 		});
144 | 
145 | 		it('should reject invalid formats', () => {
146 | 			const consoleErrorSpy = vi
147 | 				.spyOn(console, 'error')
148 | 				.mockImplementation(() => {});
149 | 			const command = new ShowCommand();
150 | 
151 | 			expect((command as any).validateOptions({ format: 'invalid' })).toBe(
152 | 				false
153 | 			);
154 | 			expect(consoleErrorSpy).toHaveBeenCalledWith(
155 | 				expect.stringContaining('Invalid format: invalid')
156 | 			);
157 | 
158 | 			consoleErrorSpy.mockRestore();
159 | 		});
160 | 	});
161 | 
162 | 	describe('multiple task IDs', () => {
163 | 		it('should handle comma-separated task IDs', async () => {
164 | 			const command = new ShowCommand();
165 | 			(command as any).tmCore = mockTmCore;
166 | 
167 | 			// Mock getMultipleTasks
168 | 			const getMultipleTasksSpy = vi
169 | 				.spyOn(command as any, 'getMultipleTasks')
170 | 				.mockResolvedValue({
171 | 					tasks: [
172 | 						{ id: '1', title: 'Task 1' },
173 | 						{ id: '2', title: 'Task 2' }
174 | 					],
175 | 					notFound: [],
176 | 					storageType: 'json'
177 | 				});
178 | 
179 | 			await (command as any).executeCommand('1,2', {
180 | 				id: '1,2',
181 | 				json: true
182 | 			});
183 | 
184 | 			expect(getMultipleTasksSpy).toHaveBeenCalledWith(
185 | 				['1', '2'],
186 | 				expect.any(Object)
187 | 			);
188 | 		});
189 | 	});
190 | });
191 | 
```

--------------------------------------------------------------------------------
/llms-install.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Taskmaster AI Installation Guide
  2 | 
  3 | This guide helps AI assistants install and configure Taskmaster for users in their development projects.
  4 | 
  5 | ## What is Taskmaster?
  6 | 
  7 | Taskmaster is an AI-driven task management system designed for development workflows. It helps break down projects into manageable tasks, track dependencies, and maintain development momentum through structured, AI-enhanced planning.
  8 | 
  9 | ## Installation Steps
 10 | 
 11 | ### Step 1: Add MCP Configuration
 12 | 
 13 | Add the following configuration to the user's MCP settings file (`.cursor/mcp.json` for Cursor, or equivalent for other editors):
 14 | 
 15 | ```json
 16 | {
 17 | 	"mcpServers": {
 18 | 		"taskmaster-ai": {
 19 | 			"command": "npx",
 20 | 			"args": ["-y", "task-master-ai"],
 21 | 			"env": {
 22 | 				"ANTHROPIC_API_KEY": "user_will_add_their_key_here",
 23 | 				"PERPLEXITY_API_KEY": "user_will_add_their_key_here",
 24 | 				"OPENAI_API_KEY": "user_will_add_their_key_here",
 25 | 				"GOOGLE_API_KEY": "user_will_add_their_key_here",
 26 | 				"MISTRAL_API_KEY": "user_will_add_their_key_here",
 27 | 				"OPENROUTER_API_KEY": "user_will_add_their_key_here",
 28 | 				"XAI_API_KEY": "user_will_add_their_key_here"
 29 | 			}
 30 | 		}
 31 | 	}
 32 | }
 33 | ```
 34 | 
 35 | ### Step 2: API Key Requirements
 36 | 
 37 | Inform the user they need **at least one** API key from the following providers:
 38 | 
 39 | - **Anthropic** (for Claude models) - Recommended
 40 | - **OpenAI** (for GPT models)
 41 | - **Google** (for Gemini models)
 42 | - **Perplexity** (for research features) - Highly recommended
 43 | - **Mistral** (for Mistral models)
 44 | - **OpenRouter** (access to multiple models)
 45 | - **xAI** (for Grok models)
 46 | 
 47 | The user will be able to define 3 separate roles (can be the same provider or separate providers) for main AI operations, research operations (research providers/models only), and a fallback model in case of errors.
 48 | 
 49 | ### Step 3: Initialize Project
 50 | 
 51 | Once the MCP server is configured and API keys are added, initialize Taskmaster in the user's project:
 52 | 
 53 | > Can you initialize Task Master in my project?
 54 | 
 55 | This will run the `initialize_project` tool to set up the basic file structure.
 56 | 
 57 | ### Step 4: Create Initial Tasks
 58 | 
 59 | Users have two options for creating initial tasks:
 60 | 
 61 | **Option A: Parse a PRD (Recommended)**
 62 | If they have a Product Requirements Document:
 63 | 
 64 | > Can you parse my PRD file at [path/to/prd.txt] to generate initial tasks?
 65 | 
 66 | If the user does not have a PRD, the AI agent can help them create one and store it in scripts/prd.txt for parsing.
 67 | 
 68 | **Option B: Start from scratch**
 69 | 
 70 | > Can you help me add my first task: [describe the task]
 71 | 
 72 | ## Common Usage Patterns
 73 | 
 74 | ### Daily Workflow
 75 | 
 76 | > What's the next task I should work on?
 77 | > Can you show me the details for task [ID]?
 78 | > Can you mark task [ID] as done?
 79 | 
 80 | ### Task Management
 81 | 
 82 | > Can you break down task [ID] into subtasks?
 83 | > Can you add a new task: [description]
 84 | > Can you analyze the complexity of my tasks?
 85 | 
 86 | ### Project Organization
 87 | 
 88 | > Can you show me all my pending tasks?
 89 | > Can you move task [ID] to become a subtask of [parent ID]?
 90 | > Can you update task [ID] with this new information: [details]
 91 | 
 92 | ## Verification Steps
 93 | 
 94 | After installation, verify everything is working:
 95 | 
 96 | 1. **Check MCP Connection**: The AI should be able to access Task Master tools
 97 | 2. **Test Basic Commands**: Try `get_tasks` to list current tasks
 98 | 3. **Verify API Keys**: Ensure AI-powered commands work (like `add_task`)
 99 | 
100 | Note: An API key fallback exists that allows the MCP server to read API keys from `.env` instead of the MCP JSON config. It is recommended to have keys in both places in case the MCP server is unable to read keys from its environment for whatever reason.
101 | 
102 | When adding keys to `.env` only, the `models` tool will explain that the keys are not OK for MCP. Despite this, the fallback should kick in and the API keys will be read from the `.env` file.
103 | 
104 | ## Troubleshooting
105 | 
106 | **If MCP server doesn't start:**
107 | 
108 | - Verify the JSON configuration is valid
109 | - Check that Node.js is installed
110 | - Ensure API keys are properly formatted
111 | 
112 | **If AI commands fail:**
113 | 
114 | - Verify at least one API key is configured
115 | - Check API key permissions and quotas
116 | - Try using a different model via the `models` tool
117 | 
118 | ## CLI Fallback
119 | 
120 | Taskmaster is also available via CLI commands, by installing with `npm install task-master-ai@latest` in a terminal. Running `task-master help` will show all available commands, which offer a 1:1 experience with the MCP server. As the AI agent, you should refer to the system prompts and rules provided to you to identify Taskmaster-specific rules that help you understand how and when to use it.
121 | 
122 | ## Next Steps
123 | 
124 | Once installed, users can:
125 | 
126 | - Create new tasks with `add-task` or parse a PRD (scripts/prd.txt) into tasks with `parse-prd`
127 | - Set up model preferences with `models` tool
128 | - Expand tasks into subtasks with `expand-all` and `expand-task`
129 | - Explore advanced features like research mode and complexity analysis
130 | 
131 | For detailed documentation, refer to the Task Master docs directory.``
132 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/reports/managers/complexity-report-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview ComplexityReportManager - Handles loading and managing complexity analysis reports
  3 |  * Follows the same pattern as ConfigManager and AuthManager
  4 |  */
  5 | 
  6 | import fs from 'node:fs/promises';
  7 | import path from 'path';
  8 | import { getLogger } from '../../../common/logger/index.js';
  9 | import type {
 10 | 	ComplexityAnalysis,
 11 | 	ComplexityReport,
 12 | 	TaskComplexityData
 13 | } from '../types.js';
 14 | 
 15 | const logger = getLogger('ComplexityReportManager');
 16 | 
 17 | /**
 18 |  * Manages complexity analysis reports
 19 |  * Handles loading, caching, and providing complexity data for tasks
 20 |  */
 21 | export class ComplexityReportManager {
 22 | 	private projectRoot: string;
 23 | 	private reportCache: Map<string, ComplexityReport> = new Map();
 24 | 
 25 | 	constructor(projectRoot: string) {
 26 | 		this.projectRoot = projectRoot;
 27 | 	}
 28 | 
 29 | 	/**
 30 | 	 * Get the path to the complexity report file for a given tag
 31 | 	 */
 32 | 	private getReportPath(tag?: string): string {
 33 | 		const reportsDir = path.join(this.projectRoot, '.taskmaster', 'reports');
 34 | 		const tagSuffix = tag && tag !== 'master' ? `_${tag}` : '';
 35 | 		return path.join(reportsDir, `task-complexity-report${tagSuffix}.json`);
 36 | 	}
 37 | 
 38 | 	/**
 39 | 	 * Load complexity report for a given tag
 40 | 	 * Results are cached to avoid repeated file reads
 41 | 	 */
 42 | 	async loadReport(tag?: string): Promise<ComplexityReport | null> {
 43 | 		const resolvedTag = tag || 'master';
 44 | 		const cacheKey = resolvedTag;
 45 | 
 46 | 		// Check cache first
 47 | 		if (this.reportCache.has(cacheKey)) {
 48 | 			return this.reportCache.get(cacheKey)!;
 49 | 		}
 50 | 
 51 | 		const reportPath = this.getReportPath(tag);
 52 | 
 53 | 		try {
 54 | 			// Check if file exists
 55 | 			await fs.access(reportPath);
 56 | 
 57 | 			// Read and parse the report
 58 | 			const content = await fs.readFile(reportPath, 'utf-8');
 59 | 			const report = JSON.parse(content) as ComplexityReport;
 60 | 
 61 | 			// Validate basic structure
 62 | 			if (!report.meta || !Array.isArray(report.complexityAnalysis)) {
 63 | 				logger.warn(
 64 | 					`Invalid complexity report structure at ${reportPath}, ignoring`
 65 | 				);
 66 | 				return null;
 67 | 			}
 68 | 
 69 | 			// Cache the report
 70 | 			this.reportCache.set(cacheKey, report);
 71 | 
 72 | 			logger.debug(
 73 | 				`Loaded complexity report for tag '${resolvedTag}' with ${report.complexityAnalysis.length} analyses`
 74 | 			);
 75 | 
 76 | 			return report;
 77 | 		} catch (error: any) {
 78 | 			if (error.code === 'ENOENT') {
 79 | 				// File doesn't exist - this is normal, not all projects have complexity reports
 80 | 				logger.debug(`No complexity report found for tag '${resolvedTag}'`);
 81 | 				return null;
 82 | 			}
 83 | 
 84 | 			// Other errors (parsing, permissions, etc.)
 85 | 			logger.warn(
 86 | 				`Failed to load complexity report for tag '${resolvedTag}': ${error.message}`
 87 | 			);
 88 | 			return null;
 89 | 		}
 90 | 	}
 91 | 
 92 | 	/**
 93 | 	 * Get complexity data for a specific task ID
 94 | 	 */
 95 | 	async getComplexityForTask(
 96 | 		taskId: string | number,
 97 | 		tag?: string
 98 | 	): Promise<TaskComplexityData | null> {
 99 | 		const report = await this.loadReport(tag);
100 | 		if (!report) {
101 | 			return null;
102 | 		}
103 | 
104 | 		// Find the analysis for this task
105 | 		const analysis = report.complexityAnalysis.find(
106 | 			(a) => String(a.taskId) === String(taskId)
107 | 		);
108 | 
109 | 		if (!analysis) {
110 | 			return null;
111 | 		}
112 | 
113 | 		// Convert to TaskComplexityData format
114 | 		return {
115 | 			complexityScore: analysis.complexityScore,
116 | 			recommendedSubtasks: analysis.recommendedSubtasks,
117 | 			expansionPrompt: analysis.expansionPrompt,
118 | 			complexityReasoning: analysis.complexityReasoning
119 | 		};
120 | 	}
121 | 
122 | 	/**
123 | 	 * Get complexity data for multiple tasks at once
124 | 	 * More efficient than calling getComplexityForTask multiple times
125 | 	 */
126 | 	async getComplexityForTasks(
127 | 		taskIds: (string | number)[],
128 | 		tag?: string
129 | 	): Promise<Map<string, TaskComplexityData>> {
130 | 		const result = new Map<string, TaskComplexityData>();
131 | 		const report = await this.loadReport(tag);
132 | 
133 | 		if (!report) {
134 | 			return result;
135 | 		}
136 | 
137 | 		// Create a map for fast lookups
138 | 		const analysisMap = new Map<string, ComplexityAnalysis>();
139 | 		report.complexityAnalysis.forEach((analysis) => {
140 | 			analysisMap.set(String(analysis.taskId), analysis);
141 | 		});
142 | 
143 | 		// Map each task ID to its complexity data
144 | 		taskIds.forEach((taskId) => {
145 | 			const analysis = analysisMap.get(String(taskId));
146 | 			if (analysis) {
147 | 				result.set(String(taskId), {
148 | 					complexityScore: analysis.complexityScore,
149 | 					recommendedSubtasks: analysis.recommendedSubtasks,
150 | 					expansionPrompt: analysis.expansionPrompt,
151 | 					complexityReasoning: analysis.complexityReasoning
152 | 				});
153 | 			}
154 | 		});
155 | 
156 | 		return result;
157 | 	}
158 | 
159 | 	/**
160 | 	 * Clear the report cache
161 | 	 * @param tag - Specific tag to clear, or undefined to clear all cached reports
162 | 	 * Useful when reports are regenerated or modified externally
163 | 	 */
164 | 	clearCache(tag?: string): void {
165 | 		if (tag) {
166 | 			this.reportCache.delete(tag);
167 | 		} else {
168 | 			// Clear all cached reports
169 | 			this.reportCache.clear();
170 | 		}
171 | 	}
172 | 
173 | 	/**
174 | 	 * Check if a complexity report exists for a tag
175 | 	 */
176 | 	async hasReport(tag?: string): Promise<boolean> {
177 | 		const reportPath = this.getReportPath(tag);
178 | 		try {
179 | 			await fs.access(reportPath);
180 | 			return true;
181 | 		} catch {
182 | 			return false;
183 | 		}
184 | 	}
185 | }
186 | 
```

--------------------------------------------------------------------------------
/.github/scripts/backfill-duplicate-comments.mjs:
--------------------------------------------------------------------------------

```
  1 | #!/usr/bin/env node
  2 | 
  3 | async function githubRequest(endpoint, token, method = 'GET', body) {
  4 | 	const response = await fetch(`https://api.github.com${endpoint}`, {
  5 | 		method,
  6 | 		headers: {
  7 | 			Authorization: `Bearer ${token}`,
  8 | 			Accept: 'application/vnd.github.v3+json',
  9 | 			'User-Agent': 'backfill-duplicate-comments-script',
 10 | 			...(body && { 'Content-Type': 'application/json' })
 11 | 		},
 12 | 		...(body && { body: JSON.stringify(body) })
 13 | 	});
 14 | 
 15 | 	if (!response.ok) {
 16 | 		throw new Error(
 17 | 			`GitHub API request failed: ${response.status} ${response.statusText}`
 18 | 		);
 19 | 	}
 20 | 
 21 | 	return response.json();
 22 | }
 23 | 
 24 | async function triggerDedupeWorkflow(
 25 | 	owner,
 26 | 	repo,
 27 | 	issueNumber,
 28 | 	token,
 29 | 	dryRun = true
 30 | ) {
 31 | 	if (dryRun) {
 32 | 		console.log(
 33 | 			`[DRY RUN] Would trigger dedupe workflow for issue #${issueNumber}`
 34 | 		);
 35 | 		return;
 36 | 	}
 37 | 
 38 | 	await githubRequest(
 39 | 		`/repos/${owner}/${repo}/actions/workflows/claude-dedupe-issues.yml/dispatches`,
 40 | 		token,
 41 | 		'POST',
 42 | 		{
 43 | 			ref: 'main',
 44 | 			inputs: {
 45 | 				issue_number: issueNumber.toString()
 46 | 			}
 47 | 		}
 48 | 	);
 49 | }
 50 | 
 51 | async function backfillDuplicateComments() {
 52 | 	console.log('[DEBUG] Starting backfill duplicate comments script');
 53 | 
 54 | 	const token = process.env.GITHUB_TOKEN;
 55 | 	if (!token) {
 56 | 		throw new Error(`GITHUB_TOKEN environment variable is required
 57 | 
 58 | Usage:
 59 |   node .github/scripts/backfill-duplicate-comments.mjs
 60 | 
 61 | Environment Variables:
 62 |   GITHUB_TOKEN - GitHub personal access token with repo and actions permissions (required)
 63 |   DRY_RUN - Set to "false" to actually trigger workflows (default: true for safety)
 64 |   DAYS_BACK - How many days back to look for old issues (default: 90)`);
 65 | 	}
 66 | 	console.log('[DEBUG] GitHub token found');
 67 | 
 68 | 	const owner = process.env.GITHUB_REPOSITORY_OWNER || 'eyaltoledano';
 69 | 	const repo = process.env.GITHUB_REPOSITORY_NAME || 'claude-task-master';
 70 | 	const dryRun = process.env.DRY_RUN !== 'false';
 71 | 	const daysBack = parseInt(process.env.DAYS_BACK || '90', 10);
 72 | 
 73 | 	console.log(`[DEBUG] Repository: ${owner}/${repo}`);
 74 | 	console.log(`[DEBUG] Dry run mode: ${dryRun}`);
 75 | 	console.log(`[DEBUG] Looking back ${daysBack} days`);
 76 | 
 77 | 	const cutoffDate = new Date();
 78 | 	cutoffDate.setDate(cutoffDate.getDate() - daysBack);
 79 | 
 80 | 	console.log(
 81 | 		`[DEBUG] Fetching issues created since ${cutoffDate.toISOString()}...`
 82 | 	);
 83 | 	const allIssues = [];
 84 | 	let page = 1;
 85 | 	const perPage = 100;
 86 | 
 87 | 	while (true) {
 88 | 		const pageIssues = await githubRequest(
 89 | 			`/repos/${owner}/${repo}/issues?state=all&per_page=${perPage}&page=${page}&since=${cutoffDate.toISOString()}`,
 90 | 			token
 91 | 		);
 92 | 
 93 | 		if (pageIssues.length === 0) break;
 94 | 
 95 | 		allIssues.push(...pageIssues);
 96 | 		page++;
 97 | 
 98 | 		// Safety limit to avoid infinite loops
 99 | 		if (page > 100) {
100 | 			console.log('[DEBUG] Reached page limit, stopping pagination');
101 | 			break;
102 | 		}
103 | 	}
104 | 
105 | 	console.log(
106 | 		`[DEBUG] Found ${allIssues.length} issues from the last ${daysBack} days`
107 | 	);
108 | 
109 | 	let processedCount = 0;
110 | 	let candidateCount = 0;
111 | 	let triggeredCount = 0;
112 | 
113 | 	for (const issue of allIssues) {
114 | 		processedCount++;
115 | 		console.log(
116 | 			`[DEBUG] Processing issue #${issue.number} (${processedCount}/${allIssues.length}): ${issue.title}`
117 | 		);
118 | 
119 | 		console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`);
120 | 		const comments = await githubRequest(
121 | 			`/repos/${owner}/${repo}/issues/${issue.number}/comments`,
122 | 			token
123 | 		);
124 | 		console.log(
125 | 			`[DEBUG] Issue #${issue.number} has ${comments.length} comments`
126 | 		);
127 | 
128 | 		// Look for existing duplicate detection comments (from the dedupe bot)
129 | 		const dupeDetectionComments = comments.filter(
130 | 			(comment) =>
131 | 				comment.body.includes('Found') &&
132 | 				comment.body.includes('possible duplicate') &&
133 | 				comment.user.type === 'Bot'
134 | 		);
135 | 
136 | 		console.log(
137 | 			`[DEBUG] Issue #${issue.number} has ${dupeDetectionComments.length} duplicate detection comments`
138 | 		);
139 | 
140 | 		// Skip if there's already a duplicate detection comment
141 | 		if (dupeDetectionComments.length > 0) {
142 | 			console.log(
143 | 				`[DEBUG] Issue #${issue.number} already has duplicate detection comment, skipping`
144 | 			);
145 | 			continue;
146 | 		}
147 | 
148 | 		candidateCount++;
149 | 		const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`;
150 | 
151 | 		try {
152 | 			console.log(
153 | 				`[INFO] ${dryRun ? '[DRY RUN] ' : ''}Triggering dedupe workflow for issue #${issue.number}: ${issueUrl}`
154 | 			);
155 | 			await triggerDedupeWorkflow(owner, repo, issue.number, token, dryRun);
156 | 
157 | 			if (!dryRun) {
158 | 				console.log(
159 | 					`[SUCCESS] Successfully triggered dedupe workflow for issue #${issue.number}`
160 | 				);
161 | 			}
162 | 			triggeredCount++;
163 | 		} catch (error) {
164 | 			console.error(
165 | 				`[ERROR] Failed to trigger workflow for issue #${issue.number}: ${error}`
166 | 			);
167 | 		}
168 | 
169 | 		// Add a delay between workflow triggers to avoid overwhelming the system
170 | 		await new Promise((resolve) => setTimeout(resolve, 1000));
171 | 	}
172 | 
173 | 	console.log(
174 | 		`[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates without duplicate comments, ${dryRun ? 'would trigger' : 'triggered'} ${triggeredCount} workflows`
175 | 	);
176 | }
177 | 
178 | backfillDuplicateComments().catch(console.error);
179 | 
```
Page 15/69FirstPrevNextLast