#
tokens: 49804/50000 25/975 files (page 12/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 12 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-core/tests/unit/smoke.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Smoke tests to verify basic package functionality and imports
  3 |  */
  4 | 
  5 | import {
  6 | 	PlaceholderParser,
  7 | 	PlaceholderStorage,
  8 | 	StorageError,
  9 | 	TaskNotFoundError,
 10 | 	TmCoreError,
 11 | 	ValidationError,
 12 | 	formatDate,
 13 | 	generateTaskId,
 14 | 	isValidTaskId,
 15 | 	name,
 16 | 	version
 17 | } from '@tm/core';
 18 | 
 19 | import type {
 20 | 	PlaceholderTask,
 21 | 	TaskId,
 22 | 	TaskPriority,
 23 | 	TaskStatus
 24 | } from '@tm/core';
 25 | 
 26 | describe('tm-core smoke tests', () => {
 27 | 	describe('package metadata', () => {
 28 | 		it('should export correct package name and version', () => {
 29 | 			expect(name).toBe('@task-master/tm-core');
 30 | 			expect(version).toBe('1.0.0');
 31 | 		});
 32 | 	});
 33 | 
 34 | 	describe('utility functions', () => {
 35 | 		it('should generate valid task IDs', () => {
 36 | 			const id1 = generateTaskId();
 37 | 			const id2 = generateTaskId();
 38 | 
 39 | 			expect(typeof id1).toBe('string');
 40 | 			expect(typeof id2).toBe('string');
 41 | 			expect(id1).not.toBe(id2); // Should be unique
 42 | 			expect(isValidTaskId(id1)).toBe(true);
 43 | 			expect(isValidTaskId('')).toBe(false);
 44 | 		});
 45 | 
 46 | 		it('should format dates', () => {
 47 | 			const date = new Date('2023-01-01T00:00:00.000Z');
 48 | 			const formatted = formatDate(date);
 49 | 			expect(formatted).toBe('2023-01-01T00:00:00.000Z');
 50 | 		});
 51 | 	});
 52 | 
 53 | 	describe('placeholder storage', () => {
 54 | 		it('should perform basic storage operations', async () => {
 55 | 			const storage = new PlaceholderStorage();
 56 | 			const testPath = 'test/path';
 57 | 			const testData = 'test data';
 58 | 
 59 | 			// Initially should not exist
 60 | 			expect(await storage.exists(testPath)).toBe(false);
 61 | 			expect(await storage.read(testPath)).toBe(null);
 62 | 
 63 | 			// Write and verify
 64 | 			await storage.write(testPath, testData);
 65 | 			expect(await storage.exists(testPath)).toBe(true);
 66 | 			expect(await storage.read(testPath)).toBe(testData);
 67 | 
 68 | 			// Delete and verify
 69 | 			await storage.delete(testPath);
 70 | 			expect(await storage.exists(testPath)).toBe(false);
 71 | 		});
 72 | 	});
 73 | 
 74 | 	describe('placeholder parser', () => {
 75 | 		it('should parse simple task lists', async () => {
 76 | 			const parser = new PlaceholderParser();
 77 | 			const content = `
 78 |         - Task 1
 79 |         - Task 2
 80 |         - Task 3
 81 |       `;
 82 | 
 83 | 			const isValid = await parser.validate(content);
 84 | 			expect(isValid).toBe(true);
 85 | 
 86 | 			const tasks = await parser.parse(content);
 87 | 			expect(tasks).toHaveLength(3);
 88 | 			expect(tasks[0]?.title).toBe('Task 1');
 89 | 			expect(tasks[1]?.title).toBe('Task 2');
 90 | 			expect(tasks[2]?.title).toBe('Task 3');
 91 | 
 92 | 			tasks.forEach((task) => {
 93 | 				expect(task.status).toBe('pending');
 94 | 				expect(task.priority).toBe('medium');
 95 | 			});
 96 | 		});
 97 | 	});
 98 | 
 99 | 	describe('error classes', () => {
100 | 		it('should create and throw custom errors', () => {
101 | 			const baseError = new TmCoreError('Base error');
102 | 			expect(baseError.name).toBe('TmCoreError');
103 | 			expect(baseError.message).toBe('Base error');
104 | 
105 | 			const taskNotFound = new TaskNotFoundError('task-123');
106 | 			expect(taskNotFound.name).toBe('TaskNotFoundError');
107 | 			expect(taskNotFound.code).toBe('TASK_NOT_FOUND');
108 | 			expect(taskNotFound.message).toContain('task-123');
109 | 
110 | 			const validationError = new ValidationError('Invalid data');
111 | 			expect(validationError.name).toBe('ValidationError');
112 | 			expect(validationError.code).toBe('VALIDATION_ERROR');
113 | 
114 | 			const storageError = new StorageError('Storage failed');
115 | 			expect(storageError.name).toBe('StorageError');
116 | 			expect(storageError.code).toBe('STORAGE_ERROR');
117 | 		});
118 | 	});
119 | 
120 | 	describe('type definitions', () => {
121 | 		it('should have correct types available', () => {
122 | 			// These are compile-time checks that verify types exist
123 | 			const taskId: TaskId = 'test-id';
124 | 			const status: TaskStatus = 'pending';
125 | 			const priority: TaskPriority = 'high';
126 | 
127 | 			const task: PlaceholderTask = {
128 | 				id: taskId,
129 | 				title: 'Test Task',
130 | 				status: status,
131 | 				priority: priority
132 | 			};
133 | 
134 | 			expect(task.id).toBe('test-id');
135 | 			expect(task.status).toBe('pending');
136 | 			expect(task.priority).toBe('high');
137 | 		});
138 | 	});
139 | });
140 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/services/runtime-state-manager.service.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Runtime State Manager Service
  3 |  * Manages runtime state separate from configuration
  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 { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
 13 | import { getLogger } from '../../../common/logger/index.js';
 14 | 
 15 | /**
 16 |  * Runtime state data structure
 17 |  */
 18 | export interface RuntimeState {
 19 | 	/** Currently active tag */
 20 | 	currentTag: string;
 21 | 	/** Last updated timestamp */
 22 | 	lastUpdated?: string;
 23 | 	/** Additional metadata */
 24 | 	metadata?: Record<string, unknown>;
 25 | }
 26 | 
 27 | /**
 28 |  * RuntimeStateManager handles runtime state persistence
 29 |  * Single responsibility: Runtime state management (separate from config)
 30 |  */
 31 | export class RuntimeStateManager {
 32 | 	private stateFilePath: string;
 33 | 	private currentState: RuntimeState;
 34 | 	private readonly logger = getLogger('RuntimeStateManager');
 35 | 
 36 | 	constructor(projectRoot: string) {
 37 | 		this.stateFilePath = path.join(projectRoot, '.taskmaster', 'state.json');
 38 | 		this.currentState = {
 39 | 			currentTag: DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG
 40 | 		};
 41 | 	}
 42 | 
 43 | 	/**
 44 | 	 * Load runtime state from disk
 45 | 	 */
 46 | 	async loadState(): Promise<RuntimeState> {
 47 | 		try {
 48 | 			const stateData = await fs.readFile(this.stateFilePath, 'utf-8');
 49 | 			const rawState = JSON.parse(stateData);
 50 | 
 51 | 			// Map legacy field names to current interface
 52 | 			const state: RuntimeState = {
 53 | 				currentTag:
 54 | 					rawState.currentTag ||
 55 | 					rawState.activeTag ||
 56 | 					DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG,
 57 | 				lastUpdated: rawState.lastUpdated,
 58 | 				metadata: rawState.metadata
 59 | 			};
 60 | 
 61 | 			// Apply environment variable override for current tag
 62 | 			if (process.env.TASKMASTER_TAG) {
 63 | 				state.currentTag = process.env.TASKMASTER_TAG;
 64 | 			}
 65 | 
 66 | 			this.currentState = state;
 67 | 			return state;
 68 | 		} catch (error: any) {
 69 | 			if (error.code === 'ENOENT') {
 70 | 				// State file doesn't exist, use defaults
 71 | 				this.logger.debug('No state.json found, using default state');
 72 | 
 73 | 				// Check environment variable
 74 | 				if (process.env.TASKMASTER_TAG) {
 75 | 					this.currentState.currentTag = process.env.TASKMASTER_TAG;
 76 | 				}
 77 | 
 78 | 				return this.currentState;
 79 | 			}
 80 | 
 81 | 			// Failed to load, use defaults
 82 | 			this.logger.warn('Failed to load state file:', error.message);
 83 | 			return this.currentState;
 84 | 		}
 85 | 	}
 86 | 
 87 | 	/**
 88 | 	 * Save runtime state to disk
 89 | 	 */
 90 | 	async saveState(): Promise<void> {
 91 | 		const stateDir = path.dirname(this.stateFilePath);
 92 | 
 93 | 		try {
 94 | 			await fs.mkdir(stateDir, { recursive: true });
 95 | 
 96 | 			const stateToSave = {
 97 | 				...this.currentState,
 98 | 				lastUpdated: new Date().toISOString()
 99 | 			};
100 | 
101 | 			await fs.writeFile(
102 | 				this.stateFilePath,
103 | 				JSON.stringify(stateToSave, null, 2),
104 | 				'utf-8'
105 | 			);
106 | 		} catch (error) {
107 | 			throw new TaskMasterError(
108 | 				'Failed to save runtime state',
109 | 				ERROR_CODES.CONFIG_ERROR,
110 | 				{ statePath: this.stateFilePath },
111 | 				error as Error
112 | 			);
113 | 		}
114 | 	}
115 | 
116 | 	/**
117 | 	 * Get the currently active tag
118 | 	 */
119 | 	getCurrentTag(): string {
120 | 		return this.currentState.currentTag;
121 | 	}
122 | 
123 | 	/**
124 | 	 * Set the current tag
125 | 	 */
126 | 	async setCurrentTag(tag: string): Promise<void> {
127 | 		this.currentState.currentTag = tag;
128 | 		await this.saveState();
129 | 	}
130 | 
131 | 	/**
132 | 	 * Get current state
133 | 	 */
134 | 	getState(): RuntimeState {
135 | 		return { ...this.currentState };
136 | 	}
137 | 
138 | 	/**
139 | 	 * Update metadata
140 | 	 */
141 | 	async updateMetadata(metadata: Record<string, unknown>): Promise<void> {
142 | 		this.currentState.metadata = {
143 | 			...this.currentState.metadata,
144 | 			...metadata
145 | 		};
146 | 		await this.saveState();
147 | 	}
148 | 
149 | 	/**
150 | 	 * Clear state file
151 | 	 */
152 | 	async clearState(): Promise<void> {
153 | 		try {
154 | 			await fs.unlink(this.stateFilePath);
155 | 		} catch (error: any) {
156 | 			if (error.code !== 'ENOENT') {
157 | 				throw error;
158 | 			}
159 | 		}
160 | 		this.currentState = {
161 | 			currentTag: DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG
162 | 		};
163 | 	}
164 | }
165 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/briefs/utils/url-parser.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview URL Parser
  3 |  * Utility for parsing URLs to extract organization and brief identifiers
  4 |  */
  5 | 
  6 | import {
  7 | 	ERROR_CODES,
  8 | 	TaskMasterError
  9 | } from '../../../common/errors/task-master-error.js';
 10 | 
 11 | export interface ParsedBriefUrl {
 12 | 	orgSlug: string | null;
 13 | 	briefId: string | null;
 14 | }
 15 | 
 16 | /**
 17 |  * Utility class for parsing brief URLs
 18 |  * Handles URL formats like: http://localhost:3000/home/{orgSlug}/briefs/{briefId}
 19 |  */
 20 | export class BriefUrlParser {
 21 | 	/**
 22 | 	 * Parse a URL and extract org slug and brief ID
 23 | 	 *
 24 | 	 * @param input - Raw input string (URL, path, or ID)
 25 | 	 * @returns Parsed components
 26 | 	 */
 27 | 	static parse(input: string): ParsedBriefUrl {
 28 | 		const raw = input?.trim() ?? '';
 29 | 		if (!raw) {
 30 | 			return { orgSlug: null, briefId: null };
 31 | 		}
 32 | 
 33 | 		// Try parsing as URL
 34 | 		const url = this.parseAsUrl(raw);
 35 | 		const pathToCheck = url ? url.pathname : raw.includes('/') ? raw : null;
 36 | 
 37 | 		if (!pathToCheck) {
 38 | 			// Not a URL/path, treat as direct ID
 39 | 			return { orgSlug: null, briefId: raw };
 40 | 		}
 41 | 
 42 | 		// Extract components from path
 43 | 		return this.parsePathComponents(pathToCheck, url);
 44 | 	}
 45 | 
 46 | 	/**
 47 | 	 * Extract organization slug from URL or path
 48 | 	 *
 49 | 	 * @param input - Raw input string
 50 | 	 * @returns Organization slug or null
 51 | 	 */
 52 | 	static extractOrgSlug(input: string): string | null {
 53 | 		return this.parse(input).orgSlug;
 54 | 	}
 55 | 
 56 | 	/**
 57 | 	 * Extract brief identifier from URL or path
 58 | 	 *
 59 | 	 * @param input - Raw input string
 60 | 	 * @returns Brief identifier or null
 61 | 	 */
 62 | 	static extractBriefId(input: string): string | null {
 63 | 		const parsed = this.parse(input);
 64 | 		return parsed.briefId || input.trim();
 65 | 	}
 66 | 
 67 | 	/**
 68 | 	 * Try to parse input as URL
 69 | 	 * Handles both absolute and scheme-less URLs
 70 | 	 */
 71 | 	private static parseAsUrl(input: string): URL | null {
 72 | 		try {
 73 | 			return new URL(input);
 74 | 		} catch {}
 75 | 		try {
 76 | 			return new URL(`https://${input}`);
 77 | 		} catch {}
 78 | 		return null;
 79 | 	}
 80 | 
 81 | 	/**
 82 | 	 * Parse path components to extract org slug and brief ID
 83 | 	 * Handles patterns like: /home/{orgSlug}/briefs/{briefId}
 84 | 	 */
 85 | 	private static parsePathComponents(
 86 | 		path: string,
 87 | 		url: URL | null
 88 | 	): ParsedBriefUrl {
 89 | 		const parts = path.split('/').filter(Boolean);
 90 | 		const briefsIdx = parts.lastIndexOf('briefs');
 91 | 
 92 | 		let orgSlug: string | null = null;
 93 | 		let briefId: string | null = null;
 94 | 
 95 | 		// Extract org slug (segment before 'briefs')
 96 | 		if (briefsIdx > 0) {
 97 | 			orgSlug = parts[briefsIdx - 1] || null;
 98 | 		}
 99 | 
100 | 		// Extract brief ID
101 | 		// Priority: query param > path segment after 'briefs' > last segment (if not 'briefs')
102 | 		if (url) {
103 | 			const qId = url.searchParams.get('id') || url.searchParams.get('briefId');
104 | 			if (qId) {
105 | 				briefId = qId;
106 | 			}
107 | 		}
108 | 
109 | 		if (!briefId && briefsIdx >= 0 && parts.length > briefsIdx + 1) {
110 | 			briefId = parts[briefsIdx + 1];
111 | 		}
112 | 
113 | 		// Only use last segment as fallback if path doesn't end with 'briefs'
114 | 		// This prevents treating '/home/org/briefs' as briefId='briefs'
115 | 		if (
116 | 			!briefId &&
117 | 			parts.length > 0 &&
118 | 			!(briefsIdx >= 0 && briefsIdx === parts.length - 1)
119 | 		) {
120 | 			briefId = parts[parts.length - 1];
121 | 		}
122 | 
123 | 		return { orgSlug, briefId };
124 | 	}
125 | 
126 | 	/**
127 | 	 * Validate that required components are present
128 | 	 *
129 | 	 * @param parsed - Parsed URL components
130 | 	 * @param requireOrg - Whether org slug is required
131 | 	 * @param requireBrief - Whether brief ID is required
132 | 	 * @throws TaskMasterError if required components are missing
133 | 	 */
134 | 	static validate(
135 | 		parsed: ParsedBriefUrl,
136 | 		options: { requireOrg?: boolean; requireBrief?: boolean } = {}
137 | 	): void {
138 | 		if (options.requireOrg && !parsed.orgSlug) {
139 | 			throw new TaskMasterError(
140 | 				'Organization slug could not be extracted from input',
141 | 				ERROR_CODES.VALIDATION_ERROR
142 | 			);
143 | 		}
144 | 
145 | 		if (options.requireBrief && !parsed.briefId) {
146 | 			throw new TaskMasterError(
147 | 				'Brief identifier could not be extracted from input',
148 | 				ERROR_CODES.VALIDATION_ERROR
149 | 			);
150 | 		}
151 | 	}
152 | }
153 | 
```

--------------------------------------------------------------------------------
/.taskmaster/docs/research/2025-06-14_test-the-fix-for-duplicate-saves-final-test.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Research Session
 3 | query: "test the fix for duplicate saves - final test"
 4 | date: 6/14/2025
 5 | time: 6:10:00 PM
 6 | timestamp: 2025-06-14T22:10:00.394Z
 7 | exchanges: 1
 8 | ---
 9 | 
10 | # Research Session
11 | 
12 | ## Initial Query
13 | 
14 | **Question:** test the fix for duplicate saves - final test
15 | 
16 | **Response:**
17 | 
18 | ## Steps to Test the Fix for Duplicate Saves
19 | 
20 | **1. Prepare a Clean Test Environment**
21 | - Ensure the tasks.json file and any related data stores are in a known, clean state with no pre-existing duplicate entries.
22 | - Back up the current tasks.json in case rollback is needed.
23 | 
24 | **2. Define Test Scenarios**
25 | - Attempt to save a new task with unique data.
26 | - Attempt to save a task with the same ID or unique identifier as an existing task (simulate a duplicate).
27 | - Attempt to save a task with the same title or content but a different ID (to test if the fix is based on ID or content).
28 | - Simultaneously trigger multiple save operations (if the system supports concurrency) to test race conditions.
29 | 
30 | **3. Execute Manual and Automated Tests**
31 | - Use the application's UI or API to create and save tasks as per the scenarios above.
32 | - For each save operation, check the tasks.json file or the data store to verify:
33 |   - No duplicate entries are created.
34 |   - Existing tasks are not overwritten unless intended (e.g., for updates).
35 |   - The system returns appropriate error messages or warnings when a duplicate save is attempted.
36 | 
37 | **4. Validate System Behavior**
38 | - Confirm that after all test operations, each task in tasks.json is unique according to the defined criteria (ID, title, or other unique fields).
39 | - If the system is designed to merge duplicates, verify that merging occurs as expected.
40 | - If the system is designed to reject duplicates, ensure that the rejection is clear and no duplicate is present.
41 | 
42 | **5. Test Edge Cases**
43 | - Attempt to save tasks with minor variations (e.g., whitespace differences, case sensitivity) to ensure the duplicate detection logic is robust.
44 | - Test with large numbers of tasks to ensure performance and correctness at scale.
45 | 
46 | **6. Review Logs and Error Handling**
47 | - Check application logs for any errors or warnings related to duplicate saves.
48 | - Ensure that error handling is user-friendly and provides actionable feedback.
49 | 
50 | **7. Regression Testing**
51 | - Run the full suite of task operations (create, update, delete) to ensure the fix for duplicate saves does not introduce new issues elsewhere in the system.
52 | 
53 | ## Additional Best Practices
54 | 
55 | - Communicate test results with the team to confirm the fix is effective and to prevent future regressions[1][2][5].
56 | - Consider implementing automated duplicate detection as part of the save operation to prevent future issues[2][3].
57 | - Document the test cases and results for future reference and audits.
58 | 
59 | ## Example Test Case Table
60 | 
61 | | Test Scenario                        | Expected Result                | Actual Result | Pass/Fail |
62 | |--------------------------------------|-------------------------------|---------------|-----------|
63 | | Save unique task                     | Task saved                    |               |           |
64 | | Save duplicate task (same ID)        | Duplicate rejected/merged     |               |           |
65 | | Save duplicate task (same title)     | Duplicate rejected/merged     |               |           |
66 | | Simultaneous saves (race condition)  | Only one unique task present  |               |           |
67 | | Save with minor variations           | No false positives/negatives  |               |           |
68 | 
69 | Fill in the "Actual Result" and "Pass/Fail" columns during testing.
70 | 
71 | ## Action Items
72 | 
73 | - Complete all test scenarios above.
74 | - Document any issues found and retest after fixes.
75 | - Confirm with stakeholders before closing the issue.
76 | 
77 | 
78 | ---
79 | 
80 | *Generated by Task Master Research Command*  
81 | *Timestamp: 2025-06-14T22:10:00.394Z*
82 | 
```

--------------------------------------------------------------------------------
/src/progress/tracker-ui.js:
--------------------------------------------------------------------------------

```javascript
  1 | import chalk from 'chalk';
  2 | 
  3 | /**
  4 |  * Factory for creating progress bar elements
  5 |  */
  6 | class ProgressBarFactory {
  7 | 	constructor(multibar) {
  8 | 		if (!multibar) {
  9 | 			throw new Error('Multibar instance is required');
 10 | 		}
 11 | 		this.multibar = multibar;
 12 | 	}
 13 | 
 14 | 	/**
 15 | 	 * Creates a progress bar with the given format
 16 | 	 */
 17 | 	createBar(format, payload = {}) {
 18 | 		if (typeof format !== 'string') {
 19 | 			throw new Error('Format must be a string');
 20 | 		}
 21 | 
 22 | 		const bar = this.multibar.create(
 23 | 			1, // total
 24 | 			1, // current
 25 | 			{},
 26 | 			{
 27 | 				format,
 28 | 				barsize: 1,
 29 | 				hideCursor: true,
 30 | 				clearOnComplete: false
 31 | 			}
 32 | 		);
 33 | 
 34 | 		bar.update(1, payload);
 35 | 		return bar;
 36 | 	}
 37 | 
 38 | 	/**
 39 | 	 * Creates a header with borders
 40 | 	 */
 41 | 	createHeader(headerFormat, borderFormat) {
 42 | 		this.createBar(borderFormat); // Top border
 43 | 		this.createBar(headerFormat); // Header
 44 | 		this.createBar(borderFormat); // Bottom border
 45 | 	}
 46 | 
 47 | 	/**
 48 | 	 * Creates a data row
 49 | 	 */
 50 | 	createRow(rowFormat, payload) {
 51 | 		if (!payload || typeof payload !== 'object') {
 52 | 			throw new Error('Payload must be an object');
 53 | 		}
 54 | 		return this.createBar(rowFormat, payload);
 55 | 	}
 56 | 
 57 | 	/**
 58 | 	 * Creates a border element
 59 | 	 */
 60 | 	createBorder(borderFormat) {
 61 | 		return this.createBar(borderFormat);
 62 | 	}
 63 | }
 64 | 
 65 | /**
 66 |  * Creates a bordered header for progress tables.
 67 |  * @param {Object} multibar - The multibar instance.
 68 |  * @param {string} headerFormat - Format string for the header row.
 69 |  * @param {string} borderFormat - Format string for the top and bottom borders.
 70 |  * @returns {void}
 71 |  */
 72 | export function createProgressHeader(multibar, headerFormat, borderFormat) {
 73 | 	const factory = new ProgressBarFactory(multibar);
 74 | 	factory.createHeader(headerFormat, borderFormat);
 75 | }
 76 | 
 77 | /**
 78 |  * Creates a formatted data row for progress tables.
 79 |  * @param {Object} multibar - The multibar instance.
 80 |  * @param {string} rowFormat - Format string for the row.
 81 |  * @param {Object} payload - Data payload for the row format.
 82 |  * @returns {void}
 83 |  */
 84 | export function createProgressRow(multibar, rowFormat, payload) {
 85 | 	const factory = new ProgressBarFactory(multibar);
 86 | 	factory.createRow(rowFormat, payload);
 87 | }
 88 | 
 89 | /**
 90 |  * Creates a border row for progress tables.
 91 |  * @param {Object} multibar - The multibar instance.
 92 |  * @param {string} borderFormat - Format string for the border.
 93 |  * @returns {void}
 94 |  */
 95 | export function createBorder(multibar, borderFormat) {
 96 | 	const factory = new ProgressBarFactory(multibar);
 97 | 	factory.createBorder(borderFormat);
 98 | }
 99 | 
100 | /**
101 |  * Builder for creating progress tables with consistent formatting
102 |  */
103 | export class ProgressTableBuilder {
104 | 	constructor(multibar) {
105 | 		this.factory = new ProgressBarFactory(multibar);
106 | 		this.borderStyle = '─';
107 | 		this.columnSeparator = '|';
108 | 	}
109 | 
110 | 	/**
111 | 	 * Shows a formatted table header
112 | 	 */
113 | 	showHeader(columns = null) {
114 | 		// Default columns for task display
115 | 		const defaultColumns = [
116 | 			{ text: 'TASK', width: 6 },
117 | 			{ text: 'PRI', width: 5 },
118 | 			{ text: 'TITLE', width: 64 }
119 | 		];
120 | 
121 | 		const cols = columns || defaultColumns;
122 | 		const headerText = ' ' + cols.map((c) => c.text).join(' | ') + ' ';
123 | 		const borderLine = this.createBorderLine(cols.map((c) => c.width));
124 | 
125 | 		this.factory.createHeader(headerText, borderLine);
126 | 		return this;
127 | 	}
128 | 
129 | 	/**
130 | 	 * Creates a border line based on column widths
131 | 	 */
132 | 	createBorderLine(columnWidths) {
133 | 		return columnWidths
134 | 			.map((width) => this.borderStyle.repeat(width))
135 | 			.join('─┼─');
136 | 	}
137 | 
138 | 	/**
139 | 	 * Adds a task row to the table
140 | 	 */
141 | 	addTaskRow(taskId, priority, title) {
142 | 		const format = ` ${taskId} | ${priority} | {title}`;
143 | 		this.factory.createRow(format, { title });
144 | 
145 | 		// Add separator after each row
146 | 		const borderLine = '------+-----+' + '─'.repeat(64);
147 | 		this.factory.createBorder(borderLine);
148 | 		return this;
149 | 	}
150 | 
151 | 	/**
152 | 	 * Creates a summary row
153 | 	 */
154 | 	addSummaryRow(label, value) {
155 | 		const format = `  ${label}: {value}`;
156 | 		this.factory.createRow(format, { value });
157 | 		return this;
158 | 	}
159 | }
160 | 
```

--------------------------------------------------------------------------------
/tests/unit/mcp/tools/move-task-cross-tag-options.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | 
  3 | // Mocks
  4 | const mockFindTasksPath = jest
  5 | 	.fn()
  6 | 	.mockReturnValue('/test/path/.taskmaster/tasks/tasks.json');
  7 | jest.unstable_mockModule(
  8 | 	'../../../../mcp-server/src/core/utils/path-utils.js',
  9 | 	() => ({
 10 | 		findTasksPath: mockFindTasksPath
 11 | 	})
 12 | );
 13 | 
 14 | const mockEnableSilentMode = jest.fn();
 15 | const mockDisableSilentMode = jest.fn();
 16 | jest.unstable_mockModule('../../../../scripts/modules/utils.js', () => ({
 17 | 	enableSilentMode: mockEnableSilentMode,
 18 | 	disableSilentMode: mockDisableSilentMode
 19 | }));
 20 | 
 21 | // Spyable mock for moveTasksBetweenTags
 22 | const mockMoveTasksBetweenTags = jest.fn();
 23 | jest.unstable_mockModule(
 24 | 	'../../../../scripts/modules/task-manager/move-task.js',
 25 | 	() => ({
 26 | 		moveTasksBetweenTags: mockMoveTasksBetweenTags
 27 | 	})
 28 | );
 29 | 
 30 | // Import after mocks
 31 | const { moveTaskCrossTagDirect } = await import(
 32 | 	'../../../../mcp-server/src/core/direct-functions/move-task-cross-tag.js'
 33 | );
 34 | 
 35 | describe('MCP Cross-Tag Move Direct Function - options & suggestions', () => {
 36 | 	const mockLog = { info: jest.fn(), warn: jest.fn(), error: jest.fn() };
 37 | 
 38 | 	beforeEach(() => {
 39 | 		jest.clearAllMocks();
 40 | 	});
 41 | 
 42 | 	it('passes only withDependencies/ignoreDependencies (no force) to core', async () => {
 43 | 		// Arrange: make core throw tag validation after call to capture params
 44 | 		mockMoveTasksBetweenTags.mockImplementation(() => {
 45 | 			const err = new Error('Source tag "invalid" not found or invalid');
 46 | 			err.code = 'INVALID_SOURCE_TAG';
 47 | 			throw err;
 48 | 		});
 49 | 
 50 | 		// Act
 51 | 		await moveTaskCrossTagDirect(
 52 | 			{
 53 | 				sourceIds: '1,2',
 54 | 				sourceTag: 'backlog',
 55 | 				targetTag: 'in-progress',
 56 | 				withDependencies: true,
 57 | 				projectRoot: '/test'
 58 | 			},
 59 | 			mockLog
 60 | 		);
 61 | 
 62 | 		// Assert options argument (5th param)
 63 | 		expect(mockMoveTasksBetweenTags).toHaveBeenCalled();
 64 | 		const args = mockMoveTasksBetweenTags.mock.calls[0];
 65 | 		const moveOptions = args[4];
 66 | 		expect(moveOptions).toEqual({
 67 | 			withDependencies: true,
 68 | 			ignoreDependencies: false
 69 | 		});
 70 | 		expect('force' in moveOptions).toBe(false);
 71 | 	});
 72 | 
 73 | 	it('returns conflict suggestions on cross-tag dependency conflicts', async () => {
 74 | 		// Arrange: core throws cross-tag dependency conflicts
 75 | 		mockMoveTasksBetweenTags.mockImplementation(() => {
 76 | 			const err = new Error(
 77 | 				'Cannot move tasks: 2 cross-tag dependency conflicts found'
 78 | 			);
 79 | 			err.code = 'CROSS_TAG_DEPENDENCY_CONFLICTS';
 80 | 			throw err;
 81 | 		});
 82 | 
 83 | 		// Act
 84 | 		const result = await moveTaskCrossTagDirect(
 85 | 			{
 86 | 				sourceIds: '1',
 87 | 				sourceTag: 'backlog',
 88 | 				targetTag: 'in-progress',
 89 | 				projectRoot: '/test'
 90 | 			},
 91 | 			mockLog
 92 | 		);
 93 | 
 94 | 		// Assert
 95 | 		expect(result.success).toBe(false);
 96 | 		expect(result.error.code).toBe('CROSS_TAG_DEPENDENCY_CONFLICT');
 97 | 		expect(Array.isArray(result.error.suggestions)).toBe(true);
 98 | 		// Key suggestions
 99 | 		const s = result.error.suggestions.join(' ');
100 | 		expect(s).toContain('--with-dependencies');
101 | 		expect(s).toContain('--ignore-dependencies');
102 | 		expect(s).toContain('validate-dependencies');
103 | 		expect(s).toContain('Move dependencies first');
104 | 	});
105 | 
106 | 	it('returns ID collision suggestions when target tag already has the ID', async () => {
107 | 		// Arrange: core throws TASK_ALREADY_EXISTS structured error
108 | 		mockMoveTasksBetweenTags.mockImplementation(() => {
109 | 			const err = new Error(
110 | 				'Task 1 already exists in target tag "in-progress"'
111 | 			);
112 | 			err.code = 'TASK_ALREADY_EXISTS';
113 | 			throw err;
114 | 		});
115 | 
116 | 		// Act
117 | 		const result = await moveTaskCrossTagDirect(
118 | 			{
119 | 				sourceIds: '1',
120 | 				sourceTag: 'backlog',
121 | 				targetTag: 'in-progress',
122 | 				projectRoot: '/test'
123 | 			},
124 | 			mockLog
125 | 		);
126 | 
127 | 		// Assert
128 | 		expect(result.success).toBe(false);
129 | 		expect(result.error.code).toBe('TASK_ALREADY_EXISTS');
130 | 		const joined = (result.error.suggestions || []).join(' ');
131 | 		expect(joined).toContain('different target tag');
132 | 		expect(joined).toContain('different set of IDs');
133 | 		expect(joined).toContain('within-tag');
134 | 	});
135 | });
136 | 
```

--------------------------------------------------------------------------------
/.github/workflows/weekly-metrics-discord.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Weekly Metrics to Discord
  2 | # description: Sends weekly metrics summary to Discord channel
  3 | 
  4 | on:
  5 |   schedule:
  6 |     - cron: "0 9 * * 1" # Every Monday at 9 AM
  7 |   workflow_dispatch:
  8 | 
  9 | permissions:
 10 |   contents: read
 11 |   issues: read
 12 |   pull-requests: read
 13 | 
 14 | jobs:
 15 |   weekly-metrics:
 16 |     runs-on: ubuntu-latest
 17 |     env:
 18 |       DISCORD_WEBHOOK: ${{ secrets.DISCORD_METRICS_WEBHOOK }}
 19 |     steps:
 20 |       - name: Checkout repository
 21 |         uses: actions/checkout@v4
 22 | 
 23 |       - name: Setup Node.js
 24 |         uses: actions/setup-node@v4
 25 |         with:
 26 |           node-version: '20'
 27 | 
 28 |       - name: Get dates for last 14 days
 29 |         run: |
 30 |           set -Eeuo pipefail
 31 |           # Last 14 days
 32 |           first_day=$(date -d "14 days ago" +%Y-%m-%d)
 33 |           last_day=$(date +%Y-%m-%d)
 34 | 
 35 |           echo "first_day=$first_day" >> $GITHUB_ENV
 36 |           echo "last_day=$last_day" >> $GITHUB_ENV
 37 |           echo "week_of=$(date -d '7 days ago' +'Week of %B %d, %Y')" >> $GITHUB_ENV
 38 |           echo "date_range=Past 14 days ($first_day to $last_day)" >> $GITHUB_ENV
 39 | 
 40 |       - name: Generate issue metrics
 41 |         uses: github/issue-metrics@v3
 42 |         env:
 43 |           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 44 |           SEARCH_QUERY: "repo:${{ github.repository }} is:issue created:${{ env.first_day }}..${{ env.last_day }}"
 45 |           HIDE_TIME_TO_ANSWER: true
 46 |           HIDE_LABEL_METRICS: false
 47 |           OUTPUT_FILE: issue_metrics.md
 48 | 
 49 |       - name: Generate PR created metrics
 50 |         uses: github/issue-metrics@v3
 51 |         env:
 52 |           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 53 |           SEARCH_QUERY: "repo:${{ github.repository }} is:pr created:${{ env.first_day }}..${{ env.last_day }}"
 54 |           OUTPUT_FILE: pr_created_metrics.md
 55 | 
 56 |       - name: Generate PR merged metrics
 57 |         uses: github/issue-metrics@v3
 58 |         env:
 59 |           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 60 |           SEARCH_QUERY: "repo:${{ github.repository }} is:pr is:merged merged:${{ env.first_day }}..${{ env.last_day }}"
 61 |           OUTPUT_FILE: pr_merged_metrics.md
 62 | 
 63 |       - name: Debug generated metrics
 64 |         run: |
 65 |           set -Eeuo pipefail
 66 |           echo "Listing markdown files in workspace:"
 67 |           ls -la *.md || true
 68 |           for f in issue_metrics.md pr_created_metrics.md pr_merged_metrics.md; do
 69 |             if [ -f "$f" ]; then
 70 |               echo "== $f (first 10 lines) =="
 71 |               head -n 10 "$f"
 72 |             else
 73 |               echo "Missing $f"
 74 |             fi
 75 |           done
 76 | 
 77 |       - name: Parse metrics
 78 |         id: metrics
 79 |         run: node .github/scripts/parse-metrics.mjs
 80 | 
 81 |       - name: Send to Discord
 82 |         uses: sarisia/actions-status-discord@v1
 83 |         if: env.DISCORD_WEBHOOK != ''
 84 |         with:
 85 |           webhook: ${{ env.DISCORD_WEBHOOK }}
 86 |           status: Success
 87 |           title: "📊 Weekly Metrics Report"
 88 |           description: |
 89 |             **${{ env.week_of }}**
 90 |             *${{ env.date_range }}*
 91 | 
 92 |             **🎯 Issues**
 93 |             • Created: ${{ steps.metrics.outputs.issues_created }}
 94 |             • Closed: ${{ steps.metrics.outputs.issues_closed }}
 95 |             • Avg Response Time: ${{ steps.metrics.outputs.issue_avg_first_response }}
 96 |             • Avg Time to Close: ${{ steps.metrics.outputs.issue_avg_time_to_close }}
 97 | 
 98 |             **🔀 Pull Requests**
 99 |             • Created: ${{ steps.metrics.outputs.prs_created }}
100 |             • Merged: ${{ steps.metrics.outputs.prs_merged }}
101 |             • Avg Response Time: ${{ steps.metrics.outputs.pr_avg_first_response }}
102 |             • Avg Time to Merge: ${{ steps.metrics.outputs.pr_avg_merge_time }}
103 | 
104 |             **📈 Visual Analytics**
105 |             https://repobeats.axiom.co/api/embed/b439f28f0ab5bd7a2da19505355693cd2c55bfd4.svg
106 |           color: 0x58AFFF
107 |           username: Task Master Metrics Bot
108 |           avatar_url: https://raw.githubusercontent.com/eyaltoledano/claude-task-master/main/images/logo.png
109 | 
```

--------------------------------------------------------------------------------
/apps/extension/src/webview/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Webview Logger Utility
  3 |  * Provides conditional logging based on environment
  4 |  */
  5 | 
  6 | type LogLevel = 'log' | 'warn' | 'error' | 'debug' | 'info';
  7 | 
  8 | interface LogEntry {
  9 | 	level: LogLevel;
 10 | 	message: string;
 11 | 	data?: any;
 12 | 	timestamp: number;
 13 | }
 14 | 
 15 | class WebviewLogger {
 16 | 	private static instance: WebviewLogger;
 17 | 	private enabled: boolean;
 18 | 	private logHistory: LogEntry[] = [];
 19 | 	private maxHistorySize = 100;
 20 | 
 21 | 	private constructor() {
 22 | 		// Enable logging in development, disable in production
 23 | 		// Check for development mode via various indicators
 24 | 		this.enabled = this.isDevelopment();
 25 | 	}
 26 | 
 27 | 	static getInstance(): WebviewLogger {
 28 | 		if (!WebviewLogger.instance) {
 29 | 			WebviewLogger.instance = new WebviewLogger();
 30 | 		}
 31 | 		return WebviewLogger.instance;
 32 | 	}
 33 | 
 34 | 	private isDevelopment(): boolean {
 35 | 		// Check various indicators for development mode
 36 | 		// VS Code webviews don't have process.env, so we check other indicators
 37 | 		return (
 38 | 			// Check if running in localhost (development server)
 39 | 			window.location.hostname === 'localhost' ||
 40 | 			// Check for development query parameter
 41 | 			window.location.search.includes('debug=true') ||
 42 | 			// Check for VS Code development mode indicator
 43 | 			(window as any).__VSCODE_DEV_MODE__ === true ||
 44 | 			// Default to false in production
 45 | 			false
 46 | 		);
 47 | 	}
 48 | 
 49 | 	private addToHistory(entry: LogEntry): void {
 50 | 		this.logHistory.push(entry);
 51 | 		if (this.logHistory.length > this.maxHistorySize) {
 52 | 			this.logHistory.shift();
 53 | 		}
 54 | 	}
 55 | 
 56 | 	private logMessage(level: LogLevel, message: string, ...args: any[]): void {
 57 | 		const entry: LogEntry = {
 58 | 			level,
 59 | 			message,
 60 | 			data: args.length > 0 ? args : undefined,
 61 | 			timestamp: Date.now()
 62 | 		};
 63 | 
 64 | 		this.addToHistory(entry);
 65 | 
 66 | 		if (!this.enabled) {
 67 | 			return;
 68 | 		}
 69 | 
 70 | 		// Format the message with timestamp
 71 | 		const timestamp = new Date().toISOString();
 72 | 		const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
 73 | 
 74 | 		// Use appropriate console method
 75 | 		switch (level) {
 76 | 			case 'error':
 77 | 				console.error(prefix, message, ...args);
 78 | 				break;
 79 | 			case 'warn':
 80 | 				console.warn(prefix, message, ...args);
 81 | 				break;
 82 | 			case 'debug':
 83 | 				console.debug(prefix, message, ...args);
 84 | 				break;
 85 | 			case 'info':
 86 | 				console.info(prefix, message, ...args);
 87 | 				break;
 88 | 			default:
 89 | 				console.log(prefix, message, ...args);
 90 | 		}
 91 | 	}
 92 | 
 93 | 	log(message: string, ...args: any[]): void {
 94 | 		this.logMessage('log', message, ...args);
 95 | 	}
 96 | 
 97 | 	error(message: string, ...args: any[]): void {
 98 | 		// Always log errors, even in production
 99 | 		const entry: LogEntry = {
100 | 			level: 'error',
101 | 			message,
102 | 			data: args.length > 0 ? args : undefined,
103 | 			timestamp: Date.now()
104 | 		};
105 | 		this.addToHistory(entry);
106 | 		console.error(`[${new Date().toISOString()}] [ERROR]`, message, ...args);
107 | 	}
108 | 
109 | 	warn(message: string, ...args: any[]): void {
110 | 		this.logMessage('warn', message, ...args);
111 | 	}
112 | 
113 | 	debug(message: string, ...args: any[]): void {
114 | 		this.logMessage('debug', message, ...args);
115 | 	}
116 | 
117 | 	info(message: string, ...args: any[]): void {
118 | 		this.logMessage('info', message, ...args);
119 | 	}
120 | 
121 | 	// Enable/disable logging dynamically
122 | 	setEnabled(enabled: boolean): void {
123 | 		this.enabled = enabled;
124 | 		if (enabled) {
125 | 			console.log('[WebviewLogger] Logging enabled');
126 | 		}
127 | 	}
128 | 
129 | 	// Get log history (useful for debugging)
130 | 	getHistory(): LogEntry[] {
131 | 		return [...this.logHistory];
132 | 	}
133 | 
134 | 	// Clear log history
135 | 	clearHistory(): void {
136 | 		this.logHistory = [];
137 | 	}
138 | 
139 | 	// Export logs as string (useful for bug reports)
140 | 	exportLogs(): string {
141 | 		return this.logHistory
142 | 			.map((entry) => {
143 | 				const timestamp = new Date(entry.timestamp).toISOString();
144 | 				const data = entry.data ? JSON.stringify(entry.data) : '';
145 | 				return `[${timestamp}] [${entry.level.toUpperCase()}] ${entry.message} ${data}`;
146 | 			})
147 | 			.join('\n');
148 | 	}
149 | }
150 | 
151 | // Export singleton instance
152 | export const logger = WebviewLogger.getInstance();
153 | 
154 | // Export type for use in other files
155 | export type { WebviewLogger };
156 | 
```

--------------------------------------------------------------------------------
/apps/extension/src/services/terminal-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Terminal Manager - Handles task execution in VS Code terminals
  3 |  * Uses @tm/core for consistent task management with the CLI
  4 |  */
  5 | 
  6 | import * as vscode from 'vscode';
  7 | import { createTmCore, type TmCore } from '@tm/core';
  8 | import type { ExtensionLogger } from '../utils/logger';
  9 | 
 10 | export interface TerminalExecutionOptions {
 11 | 	taskId: string;
 12 | 	taskTitle: string;
 13 | 	tag?: string;
 14 | }
 15 | 
 16 | export interface TerminalExecutionResult {
 17 | 	success: boolean;
 18 | 	error?: string;
 19 | 	terminalName?: string;
 20 | }
 21 | 
 22 | export class TerminalManager {
 23 | 	private terminals = new Map<string, vscode.Terminal>();
 24 | 	private tmCore?: TmCore;
 25 | 
 26 | 	constructor(
 27 | 		private context: vscode.ExtensionContext,
 28 | 		private logger: ExtensionLogger
 29 | 	) {}
 30 | 
 31 | 	/**
 32 | 	 * Execute a task in a new VS Code terminal with Claude
 33 | 	 * Uses @tm/core for consistent task management with the CLI
 34 | 	 */
 35 | 	async executeTask(
 36 | 		options: TerminalExecutionOptions
 37 | 	): Promise<TerminalExecutionResult> {
 38 | 		const { taskTitle, tag } = options;
 39 | 		// Ensure taskId is always a string
 40 | 		const taskId = String(options.taskId);
 41 | 
 42 | 		this.logger.log(
 43 | 			`Starting task execution for ${taskId}: ${taskTitle}${tag ? ` (tag: ${tag})` : ''}`
 44 | 		);
 45 | 		this.logger.log(`TaskId type: ${typeof taskId}, value: ${taskId}`);
 46 | 
 47 | 		try {
 48 | 			// Initialize tm-core if needed
 49 | 			await this.initializeCore();
 50 | 
 51 | 			// Use tm-core to start the task (same as CLI)
 52 | 			const startResult = await this.tmCore!.tasks.start(taskId, {
 53 | 				dryRun: false,
 54 | 				force: false,
 55 | 				updateStatus: true
 56 | 			});
 57 | 
 58 | 			if (!startResult.started || !startResult.executionOutput) {
 59 | 				throw new Error(
 60 | 					startResult.error || 'Failed to start task with tm-core'
 61 | 				);
 62 | 			}
 63 | 
 64 | 			// Create terminal with custom TaskMaster icon
 65 | 			const terminalName = `Task ${taskId}: ${taskTitle}`;
 66 | 			const terminal = this.createTerminal(terminalName);
 67 | 
 68 | 			// Store terminal reference for potential cleanup
 69 | 			this.terminals.set(taskId, terminal);
 70 | 
 71 | 			// Show terminal and run Claude command
 72 | 			terminal.show();
 73 | 			const command = `claude "${startResult.executionOutput}"`;
 74 | 			terminal.sendText(command);
 75 | 
 76 | 			this.logger.log(`Launched Claude for task ${taskId} using tm-core`);
 77 | 
 78 | 			return {
 79 | 				success: true,
 80 | 				terminalName
 81 | 			};
 82 | 		} catch (error) {
 83 | 			this.logger.error('Failed to execute task:', error);
 84 | 			return {
 85 | 				success: false,
 86 | 				error: error instanceof Error ? error.message : 'Unknown error'
 87 | 			};
 88 | 		}
 89 | 	}
 90 | 
 91 | 	/**
 92 | 	 * Create a new terminal with TaskMaster branding
 93 | 	 */
 94 | 	private createTerminal(name: string): vscode.Terminal {
 95 | 		const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
 96 | 
 97 | 		return vscode.window.createTerminal({
 98 | 			name,
 99 | 			cwd: workspaceRoot,
100 | 			iconPath: new vscode.ThemeIcon('play') // Use a VS Code built-in icon for now
101 | 		});
102 | 	}
103 | 
104 | 	/**
105 | 	 * Initialize TaskMaster Core (same as CLI)
106 | 	 */
107 | 	private async initializeCore(): Promise<void> {
108 | 		if (!this.tmCore) {
109 | 			const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
110 | 			if (!workspaceRoot) {
111 | 				throw new Error('No workspace folder found');
112 | 			}
113 | 			this.tmCore = await createTmCore({ projectPath: workspaceRoot });
114 | 		}
115 | 	}
116 | 
117 | 	/**
118 | 	 * Get terminal by task ID (if still active)
119 | 	 */
120 | 	getTerminalByTaskId(taskId: string): vscode.Terminal | undefined {
121 | 		return this.terminals.get(taskId);
122 | 	}
123 | 
124 | 	/**
125 | 	 * Clean up terminated terminals
126 | 	 */
127 | 	cleanupTerminal(taskId: string): void {
128 | 		const terminal = this.terminals.get(taskId);
129 | 		if (terminal) {
130 | 			this.terminals.delete(taskId);
131 | 		}
132 | 	}
133 | 
134 | 	/**
135 | 	 * Dispose all managed terminals and clean up tm-core
136 | 	 */
137 | 	async dispose(): Promise<void> {
138 | 		this.terminals.forEach((terminal) => {
139 | 			try {
140 | 				terminal.dispose();
141 | 			} catch (error) {
142 | 				this.logger.error('Failed to dispose terminal:', error);
143 | 			}
144 | 		});
145 | 		this.terminals.clear();
146 | 
147 | 		// Clear tm-core reference (no explicit cleanup needed)
148 | 		if (this.tmCore) {
149 | 			this.tmCore = undefined;
150 | 		}
151 | 	}
152 | }
153 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/auth/managers/auth-manager.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for AuthManager singleton behavior
  3 |  */
  4 | 
  5 | import { beforeEach, describe, expect, it, vi } from 'vitest';
  6 | 
  7 | // Mock the logger to verify warnings (must be hoisted before SUT import)
  8 | const mockLogger = {
  9 | 	warn: vi.fn(),
 10 | 	info: vi.fn(),
 11 | 	debug: vi.fn(),
 12 | 	error: vi.fn()
 13 | };
 14 | 
 15 | vi.mock('../logger/index.js', () => ({
 16 | 	getLogger: () => mockLogger
 17 | }));
 18 | 
 19 | // Spy on CredentialStore constructor to verify config propagation
 20 | const CredentialStoreSpy = vi.fn();
 21 | vi.mock('./credential-store.js', () => {
 22 | 	return {
 23 | 		CredentialStore: class {
 24 | 			static getInstance(config?: any) {
 25 | 				return new (this as any)(config);
 26 | 			}
 27 | 			static resetInstance() {
 28 | 				// Mock reset instance method
 29 | 			}
 30 | 			constructor(config: any) {
 31 | 				CredentialStoreSpy(config);
 32 | 			}
 33 | 			getCredentials(_options?: any) {
 34 | 				return null;
 35 | 			}
 36 | 			saveCredentials() {}
 37 | 			clearCredentials() {}
 38 | 			hasCredentials() {
 39 | 				return false;
 40 | 			}
 41 | 		}
 42 | 	};
 43 | });
 44 | 
 45 | // Mock OAuthService to avoid side effects
 46 | vi.mock('./oauth-service.js', () => {
 47 | 	return {
 48 | 		OAuthService: class {
 49 | 			constructor() {}
 50 | 			authenticate() {
 51 | 				return Promise.resolve({});
 52 | 			}
 53 | 			getAuthorizationUrl() {
 54 | 				return null;
 55 | 			}
 56 | 		}
 57 | 	};
 58 | });
 59 | 
 60 | // Mock SupabaseAuthClient to avoid side effects
 61 | vi.mock('../clients/supabase-client.js', () => {
 62 | 	return {
 63 | 		SupabaseAuthClient: class {
 64 | 			constructor() {}
 65 | 			refreshSession() {
 66 | 				return Promise.resolve({});
 67 | 			}
 68 | 			signOut() {
 69 | 				return Promise.resolve();
 70 | 			}
 71 | 		}
 72 | 	};
 73 | });
 74 | 
 75 | // Import SUT after mocks
 76 | import { AuthManager } from './auth-manager.js';
 77 | 
 78 | describe('AuthManager Singleton', () => {
 79 | 	beforeEach(() => {
 80 | 		// Reset singleton before each test
 81 | 		AuthManager.resetInstance();
 82 | 		vi.clearAllMocks();
 83 | 		CredentialStoreSpy.mockClear();
 84 | 	});
 85 | 
 86 | 	it('should return the same instance on multiple calls', () => {
 87 | 		const instance1 = AuthManager.getInstance();
 88 | 		const instance2 = AuthManager.getInstance();
 89 | 
 90 | 		expect(instance1).toBe(instance2);
 91 | 	});
 92 | 
 93 | 	it('should use config on first call', async () => {
 94 | 		const config = {
 95 | 			baseUrl: 'https://test.auth.com',
 96 | 			configDir: '/test/config',
 97 | 			configFile: '/test/config/auth.json'
 98 | 		};
 99 | 
100 | 		const instance = AuthManager.getInstance(config);
101 | 		expect(instance).toBeDefined();
102 | 
103 | 		// Assert that CredentialStore was constructed with the provided config
104 | 		expect(CredentialStoreSpy).toHaveBeenCalledTimes(1);
105 | 		expect(CredentialStoreSpy).toHaveBeenCalledWith(config);
106 | 
107 | 		// Verify the config is passed to internal components through observable behavior
108 | 		// getCredentials would look in the configured file path
109 | 		const credentials = await instance.getCredentials();
110 | 		expect(credentials).toBeNull(); // File doesn't exist, but config was propagated correctly
111 | 	});
112 | 
113 | 	it('should warn when config is provided after initialization', () => {
114 | 		// Clear previous calls
115 | 		mockLogger.warn.mockClear();
116 | 
117 | 		// First call with config
118 | 		AuthManager.getInstance({ baseUrl: 'https://first.auth.com' });
119 | 
120 | 		// Second call with different config
121 | 		AuthManager.getInstance({ baseUrl: 'https://second.auth.com' });
122 | 
123 | 		// Verify warning was logged
124 | 		expect(mockLogger.warn).toHaveBeenCalledWith(
125 | 			expect.stringMatching(/config.*after initialization.*ignored/i)
126 | 		);
127 | 	});
128 | 
129 | 	it('should not warn when no config is provided after initialization', () => {
130 | 		// Clear previous calls
131 | 		mockLogger.warn.mockClear();
132 | 
133 | 		// First call with config
134 | 		AuthManager.getInstance({ configDir: '/test/config' });
135 | 
136 | 		// Second call without config
137 | 		AuthManager.getInstance();
138 | 
139 | 		// Verify no warning was logged
140 | 		expect(mockLogger.warn).not.toHaveBeenCalled();
141 | 	});
142 | 
143 | 	it('should allow resetting the instance', () => {
144 | 		const instance1 = AuthManager.getInstance();
145 | 
146 | 		// Reset the instance
147 | 		AuthManager.resetInstance();
148 | 
149 | 		// Get new instance
150 | 		const instance2 = AuthManager.getInstance();
151 | 
152 | 		// They should be different instances
153 | 		expect(instance1).not.toBe(instance2);
154 | 	});
155 | });
156 | 
```

--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/message-converter.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Message format conversion utilities for Grok CLI provider
  3 |  */
  4 | 
  5 | import type { GrokCliMessage, GrokCliResponse } from './types.js';
  6 | 
  7 | /**
  8 |  * AI SDK message type (simplified interface)
  9 |  */
 10 | interface AISDKMessage {
 11 | 	role: string;
 12 | 	content:
 13 | 		| string
 14 | 		| Array<{ type: string; text?: string }>
 15 | 		| { text?: string; [key: string]: unknown };
 16 | }
 17 | 
 18 | /**
 19 |  * Convert AI SDK messages to Grok CLI compatible format
 20 |  * @param messages - AI SDK message array
 21 |  * @returns Grok CLI compatible messages
 22 |  */
 23 | export function convertToGrokCliMessages(
 24 | 	messages: AISDKMessage[]
 25 | ): GrokCliMessage[] {
 26 | 	return messages.map((message) => {
 27 | 		// Handle different message content types
 28 | 		let content = '';
 29 | 
 30 | 		if (typeof message.content === 'string') {
 31 | 			content = message.content;
 32 | 		} else if (Array.isArray(message.content)) {
 33 | 			// Handle multi-part content (text and images)
 34 | 			content = message.content
 35 | 				.filter((part) => part.type === 'text')
 36 | 				.map((part) => part.text || '')
 37 | 				.join('\n');
 38 | 		} else if (message.content && typeof message.content === 'object') {
 39 | 			// Handle object content
 40 | 			content = message.content.text || JSON.stringify(message.content);
 41 | 		}
 42 | 
 43 | 		return {
 44 | 			role: message.role,
 45 | 			content: content.trim()
 46 | 		};
 47 | 	});
 48 | }
 49 | 
 50 | /**
 51 |  * Convert Grok CLI response to AI SDK format
 52 |  * @param responseText - Raw response text from Grok CLI (JSONL format)
 53 |  * @returns AI SDK compatible response object
 54 |  */
 55 | export function convertFromGrokCliResponse(responseText: string): {
 56 | 	text: string;
 57 | 	usage?: {
 58 | 		promptTokens: number;
 59 | 		completionTokens: number;
 60 | 		totalTokens: number;
 61 | 	};
 62 | } {
 63 | 	try {
 64 | 		// Grok CLI outputs JSONL format - each line is a separate JSON message
 65 | 		const lines = responseText
 66 | 			.trim()
 67 | 			.split('\n')
 68 | 			.filter((line) => line.trim());
 69 | 
 70 | 		// Parse each line as JSON and find assistant messages
 71 | 		const messages: GrokCliResponse[] = [];
 72 | 		for (const line of lines) {
 73 | 			try {
 74 | 				const message = JSON.parse(line) as GrokCliResponse;
 75 | 				messages.push(message);
 76 | 			} catch (parseError) {
 77 | 				// Skip invalid JSON lines
 78 | 				continue;
 79 | 			}
 80 | 		}
 81 | 
 82 | 		// Find the last assistant message
 83 | 		const assistantMessage = messages
 84 | 			.filter((msg) => msg.role === 'assistant')
 85 | 			.pop();
 86 | 
 87 | 		if (assistantMessage && assistantMessage.content) {
 88 | 			return {
 89 | 				text: assistantMessage.content,
 90 | 				usage: assistantMessage.usage
 91 | 					? {
 92 | 							promptTokens: assistantMessage.usage.prompt_tokens || 0,
 93 | 							completionTokens: assistantMessage.usage.completion_tokens || 0,
 94 | 							totalTokens: assistantMessage.usage.total_tokens || 0
 95 | 						}
 96 | 					: undefined
 97 | 			};
 98 | 		}
 99 | 
100 | 		// Fallback: if no assistant message found, return the raw text
101 | 		return {
102 | 			text: responseText.trim(),
103 | 			usage: undefined
104 | 		};
105 | 	} catch (error) {
106 | 		// If parsing fails completely, treat as plain text response
107 | 		return {
108 | 			text: responseText.trim(),
109 | 			usage: undefined
110 | 		};
111 | 	}
112 | }
113 | 
114 | /**
115 |  * Create a prompt string for Grok CLI from messages
116 |  * @param messages - AI SDK message array
117 |  * @returns Formatted prompt string
118 |  */
119 | export function createPromptFromMessages(messages: AISDKMessage[]): string {
120 | 	const grokMessages = convertToGrokCliMessages(messages);
121 | 
122 | 	// Create a conversation-style prompt
123 | 	const prompt = grokMessages
124 | 		.map((message) => {
125 | 			switch (message.role) {
126 | 				case 'system':
127 | 					return `System: ${message.content}`;
128 | 				case 'user':
129 | 					return `User: ${message.content}`;
130 | 				case 'assistant':
131 | 					return `Assistant: ${message.content}`;
132 | 				default:
133 | 					return `${message.role}: ${message.content}`;
134 | 			}
135 | 		})
136 | 		.join('\n\n');
137 | 
138 | 	return prompt;
139 | }
140 | 
141 | /**
142 |  * Escape shell arguments for safe CLI execution
143 |  * @param arg - Argument to escape
144 |  * @returns Shell-escaped argument
145 |  */
146 | export function escapeShellArg(arg: string | unknown): string {
147 | 	if (typeof arg !== 'string') {
148 | 		arg = String(arg);
149 | 	}
150 | 
151 | 	// Replace single quotes with '\''
152 | 	return "'" + (arg as string).replace(/'/g, "'\\''") + "'";
153 | }
154 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/ui/display/tables.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Table display utilities
  3 |  * Provides table creation and formatting for tasks
  4 |  */
  5 | 
  6 | import type { Subtask, Task, TaskPriority } from '@tm/core';
  7 | import chalk from 'chalk';
  8 | import Table from 'cli-table3';
  9 | import { getComplexityWithColor } from '../formatters/complexity-formatters.js';
 10 | import { getPriorityWithColor } from '../formatters/priority-formatters.js';
 11 | import { getStatusWithColor } from '../formatters/status-formatters.js';
 12 | import { getBoxWidth, truncate } from '../layout/helpers.js';
 13 | 
 14 | /**
 15 |  * Default priority for tasks/subtasks when not specified
 16 |  */
 17 | const DEFAULT_PRIORITY: TaskPriority = 'medium';
 18 | 
 19 | /**
 20 |  * Create a task table for display
 21 |  */
 22 | export function createTaskTable(
 23 | 	tasks: (Task | Subtask)[],
 24 | 	options?: {
 25 | 		showSubtasks?: boolean;
 26 | 		showComplexity?: boolean;
 27 | 		showDependencies?: boolean;
 28 | 	}
 29 | ): string {
 30 | 	const {
 31 | 		showSubtasks = false,
 32 | 		showComplexity = false,
 33 | 		showDependencies = true
 34 | 	} = options || {};
 35 | 
 36 | 	// Calculate dynamic column widths based on terminal width
 37 | 	const tableWidth = getBoxWidth(0.9, 100);
 38 | 	// Adjust column widths to better match the original layout
 39 | 	const baseColWidths = showComplexity
 40 | 		? [
 41 | 				Math.floor(tableWidth * 0.1),
 42 | 				Math.floor(tableWidth * 0.4),
 43 | 				Math.floor(tableWidth * 0.15),
 44 | 				Math.floor(tableWidth * 0.1),
 45 | 				Math.floor(tableWidth * 0.2),
 46 | 				Math.floor(tableWidth * 0.1)
 47 | 			] // ID, Title, Status, Priority, Dependencies, Complexity
 48 | 		: [
 49 | 				Math.floor(tableWidth * 0.08),
 50 | 				Math.floor(tableWidth * 0.4),
 51 | 				Math.floor(tableWidth * 0.18),
 52 | 				Math.floor(tableWidth * 0.12),
 53 | 				Math.floor(tableWidth * 0.2)
 54 | 			]; // ID, Title, Status, Priority, Dependencies
 55 | 
 56 | 	const headers = [
 57 | 		chalk.blue.bold('ID'),
 58 | 		chalk.blue.bold('Title'),
 59 | 		chalk.blue.bold('Status'),
 60 | 		chalk.blue.bold('Priority')
 61 | 	];
 62 | 	const colWidths = baseColWidths.slice(0, 4);
 63 | 
 64 | 	if (showDependencies) {
 65 | 		headers.push(chalk.blue.bold('Dependencies'));
 66 | 		colWidths.push(baseColWidths[4]);
 67 | 	}
 68 | 
 69 | 	if (showComplexity) {
 70 | 		headers.push(chalk.blue.bold('Complexity'));
 71 | 		colWidths.push(baseColWidths[5] || 12);
 72 | 	}
 73 | 
 74 | 	const table = new Table({
 75 | 		head: headers,
 76 | 		style: { head: [], border: [] },
 77 | 		colWidths,
 78 | 		wordWrap: true
 79 | 	});
 80 | 
 81 | 	tasks.forEach((task) => {
 82 | 		const row: string[] = [
 83 | 			chalk.cyan(task.id.toString()),
 84 | 			truncate(task.title, colWidths[1] - 3),
 85 | 			getStatusWithColor(task.status, true), // Use table version
 86 | 			getPriorityWithColor(task.priority)
 87 | 		];
 88 | 
 89 | 		if (showDependencies) {
 90 | 			// For table display, show simple format without status icons
 91 | 			if (!task.dependencies || task.dependencies.length === 0) {
 92 | 				row.push(chalk.gray('None'));
 93 | 			} else {
 94 | 				row.push(
 95 | 					chalk.cyan(task.dependencies.map((d) => String(d)).join(', '))
 96 | 				);
 97 | 			}
 98 | 		}
 99 | 
100 | 		if (showComplexity) {
101 | 			// Show complexity score from report if available
102 | 			if (typeof task.complexity === 'number') {
103 | 				row.push(getComplexityWithColor(task.complexity));
104 | 			} else {
105 | 				row.push(chalk.gray('N/A'));
106 | 			}
107 | 		}
108 | 
109 | 		table.push(row);
110 | 
111 | 		// Add subtasks if requested
112 | 		if (showSubtasks && task.subtasks && task.subtasks.length > 0) {
113 | 			task.subtasks.forEach((subtask) => {
114 | 				const subRow: string[] = [
115 | 					chalk.gray(` └─ ${subtask.id}`),
116 | 					chalk.gray(truncate(subtask.title, colWidths[1] - 6)),
117 | 					chalk.gray(getStatusWithColor(subtask.status, true)),
118 | 					chalk.gray(subtask.priority || DEFAULT_PRIORITY)
119 | 				];
120 | 
121 | 				if (showDependencies) {
122 | 					subRow.push(
123 | 						chalk.gray(
124 | 							subtask.dependencies && subtask.dependencies.length > 0
125 | 								? subtask.dependencies.map((dep) => String(dep)).join(', ')
126 | 								: 'None'
127 | 						)
128 | 					);
129 | 				}
130 | 
131 | 				if (showComplexity) {
132 | 					const complexityDisplay =
133 | 						typeof subtask.complexity === 'number'
134 | 							? getComplexityWithColor(subtask.complexity)
135 | 							: '--';
136 | 					subRow.push(chalk.gray(complexityDisplay));
137 | 				}
138 | 
139 | 				table.push(subRow);
140 | 			});
141 | 		}
142 | 	});
143 | 
144 | 	return table.toString();
145 | }
146 | 
```

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

```javascript
  1 | import { log, readJSON, writeJSON } from '../utils.js';
  2 | 
  3 | /**
  4 |  * Remove a subtask from its parent task
  5 |  * @param {string} tasksPath - Path to the tasks.json file
  6 |  * @param {string} subtaskId - ID of the subtask to remove in format "parentId.subtaskId"
  7 |  * @param {boolean} convertToTask - Whether to convert the subtask to a standalone task
  8 |  * @param {boolean} generateFiles - Whether to regenerate task files after removing the subtask
  9 |  * @param {Object} context - Context object containing projectRoot and tag information
 10 |  * @param {string} [context.projectRoot] - Project root path
 11 |  * @param {string} [context.tag] - Tag for the task
 12 |  * @returns {Object|null} The removed subtask if convertToTask is true, otherwise null
 13 |  */
 14 | async function removeSubtask(
 15 | 	tasksPath,
 16 | 	subtaskId,
 17 | 	convertToTask = false,
 18 | 	generateFiles = false,
 19 | 	context = {}
 20 | ) {
 21 | 	const { projectRoot, tag } = context;
 22 | 	try {
 23 | 		log('info', `Removing subtask ${subtaskId}...`);
 24 | 
 25 | 		// Read the existing tasks with proper context
 26 | 		const data = readJSON(tasksPath, projectRoot, tag);
 27 | 		if (!data || !data.tasks) {
 28 | 			throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
 29 | 		}
 30 | 
 31 | 		// Parse the subtask ID (format: "parentId.subtaskId")
 32 | 		if (!subtaskId.includes('.')) {
 33 | 			throw new Error(
 34 | 				`Invalid subtask ID format: ${subtaskId}. Expected format: "parentId.subtaskId"`
 35 | 			);
 36 | 		}
 37 | 
 38 | 		const [parentIdStr, subtaskIdStr] = subtaskId.split('.');
 39 | 		const parentId = parseInt(parentIdStr, 10);
 40 | 		const subtaskIdNum = parseInt(subtaskIdStr, 10);
 41 | 
 42 | 		// Find the parent task
 43 | 		const parentTask = data.tasks.find((t) => t.id === parentId);
 44 | 		if (!parentTask) {
 45 | 			throw new Error(`Parent task with ID ${parentId} not found`);
 46 | 		}
 47 | 
 48 | 		// Check if parent has subtasks
 49 | 		if (!parentTask.subtasks || parentTask.subtasks.length === 0) {
 50 | 			throw new Error(`Parent task ${parentId} has no subtasks`);
 51 | 		}
 52 | 
 53 | 		// Find the subtask to remove
 54 | 		const subtaskIndex = parentTask.subtasks.findIndex(
 55 | 			(st) => st.id === subtaskIdNum
 56 | 		);
 57 | 		if (subtaskIndex === -1) {
 58 | 			throw new Error(`Subtask ${subtaskId} not found`);
 59 | 		}
 60 | 
 61 | 		// Get a copy of the subtask before removing it
 62 | 		const removedSubtask = { ...parentTask.subtasks[subtaskIndex] };
 63 | 
 64 | 		// Remove the subtask from the parent
 65 | 		parentTask.subtasks.splice(subtaskIndex, 1);
 66 | 
 67 | 		// If parent has no more subtasks, remove the subtasks array
 68 | 		if (parentTask.subtasks.length === 0) {
 69 | 			parentTask.subtasks = undefined;
 70 | 		}
 71 | 
 72 | 		let convertedTask = null;
 73 | 
 74 | 		// Convert the subtask to a standalone task if requested
 75 | 		if (convertToTask) {
 76 | 			log('info', `Converting subtask ${subtaskId} to a standalone task...`);
 77 | 
 78 | 			// Find the highest task ID to determine the next ID
 79 | 			const highestId = Math.max(...data.tasks.map((t) => t.id));
 80 | 			const newTaskId = highestId + 1;
 81 | 
 82 | 			// Create the new task from the subtask
 83 | 			convertedTask = {
 84 | 				id: newTaskId,
 85 | 				title: removedSubtask.title,
 86 | 				description: removedSubtask.description || '',
 87 | 				details: removedSubtask.details || '',
 88 | 				status: removedSubtask.status || 'pending',
 89 | 				dependencies: removedSubtask.dependencies || [],
 90 | 				priority: parentTask.priority || 'medium' // Inherit priority from parent
 91 | 			};
 92 | 
 93 | 			// Add the parent task as a dependency if not already present
 94 | 			if (!convertedTask.dependencies.includes(parentId)) {
 95 | 				convertedTask.dependencies.push(parentId);
 96 | 			}
 97 | 
 98 | 			// Add the converted task to the tasks array
 99 | 			data.tasks.push(convertedTask);
100 | 
101 | 			log('info', `Created new task ${newTaskId} from subtask ${subtaskId}`);
102 | 		} else {
103 | 			log('info', `Subtask ${subtaskId} deleted`);
104 | 		}
105 | 
106 | 		// Write the updated tasks back to the file with proper context
107 | 		writeJSON(tasksPath, data, projectRoot, tag);
108 | 
109 | 		// Note: Task file generation is no longer supported and has been removed
110 | 
111 | 		return convertedTask;
112 | 	} catch (error) {
113 | 		log('error', `Error removing subtask: ${error.message}`);
114 | 		throw error;
115 | 	}
116 | }
117 | 
118 | export default removeSubtask;
119 | 
```

--------------------------------------------------------------------------------
/tests/integration/profiles/roo-files-inclusion.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | import fs from 'fs';
  3 | import path from 'path';
  4 | import os from 'os';
  5 | import { execSync } from 'child_process';
  6 | 
  7 | describe('Roo Files Inclusion in Package', () => {
  8 | 	// This test verifies that the required Roo files are included in the final package
  9 | 
 10 | 	test('package.json includes dist/** in the "files" array for bundled files', () => {
 11 | 		// Read the package.json file
 12 | 		const packageJsonPath = path.join(process.cwd(), 'package.json');
 13 | 		const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
 14 | 
 15 | 		// Check if dist/** is included in the files array (which contains bundled output including Roo files)
 16 | 		expect(packageJson.files).toContain('dist/**');
 17 | 	});
 18 | 
 19 | 	test('roo.js profile contains logic for Roo directory creation and file copying', () => {
 20 | 		// Read the roo.js profile file
 21 | 		const rooJsPath = path.join(process.cwd(), 'src', 'profiles', 'roo.js');
 22 | 		const rooJsContent = fs.readFileSync(rooJsPath, 'utf8');
 23 | 
 24 | 		// Check for the main handler function
 25 | 		expect(
 26 | 			rooJsContent.includes('onAddRulesProfile(targetDir, assetsDir)')
 27 | 		).toBe(true);
 28 | 
 29 | 		// Check for general recursive copy of assets/roocode
 30 | 		expect(
 31 | 			rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)')
 32 | 		).toBe(true);
 33 | 
 34 | 		// Check for updated path handling
 35 | 		expect(rooJsContent.includes("path.join(assetsDir, 'roocode')")).toBe(true);
 36 | 
 37 | 		// Check for .roomodes file copying logic (source and destination paths)
 38 | 		expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe(
 39 | 			true
 40 | 		);
 41 | 		expect(rooJsContent.includes("path.join(targetDir, '.roomodes')")).toBe(
 42 | 			true
 43 | 		);
 44 | 
 45 | 		// Check for mode-specific rule file copying logic
 46 | 		expect(rooJsContent.includes('for (const mode of ROO_MODES)')).toBe(true);
 47 | 		expect(
 48 | 			rooJsContent.includes(
 49 | 				'path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`)'
 50 | 			)
 51 | 		).toBe(true);
 52 | 		expect(
 53 | 			rooJsContent.includes(
 54 | 				"path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)"
 55 | 			)
 56 | 		).toBe(true);
 57 | 
 58 | 		// Check for import of ROO_MODES from profiles.js instead of local definition
 59 | 		expect(
 60 | 			rooJsContent.includes(
 61 | 				"import { ROO_MODES } from '../constants/profiles.js'"
 62 | 			)
 63 | 		).toBe(true);
 64 | 
 65 | 		// Verify ROO_MODES is used in the for loop
 66 | 		expect(rooJsContent.includes('for (const mode of ROO_MODES)')).toBe(true);
 67 | 
 68 | 		// Verify mode variable is used in the template strings (this confirms modes are being processed)
 69 | 		expect(rooJsContent.includes('rules-${mode}')).toBe(true);
 70 | 		expect(rooJsContent.includes('${mode}-rules')).toBe(true);
 71 | 
 72 | 		// Verify that the ROO_MODES constant is properly imported and used
 73 | 		// We should be able to find the template literals that use the mode variable
 74 | 		expect(rooJsContent.includes('`rules-${mode}`')).toBe(true);
 75 | 		expect(rooJsContent.includes('`${mode}-rules`')).toBe(true);
 76 | 		expect(rooJsContent.includes('Copied ${mode}-rules to ${dest}')).toBe(true);
 77 | 
 78 | 		// Also verify that the expected mode names are defined in the imported constant
 79 | 		// by checking that the import is from the correct file that contains all 6 modes
 80 | 		const profilesConstantsPath = path.join(
 81 | 			process.cwd(),
 82 | 			'src',
 83 | 			'constants',
 84 | 			'profiles.js'
 85 | 		);
 86 | 		const profilesContent = fs.readFileSync(profilesConstantsPath, 'utf8');
 87 | 
 88 | 		// Check that ROO_MODES is exported and contains all expected modes
 89 | 		expect(profilesContent.includes('export const ROO_MODES')).toBe(true);
 90 | 		const expectedModes = [
 91 | 			'architect',
 92 | 			'ask',
 93 | 			'orchestrator',
 94 | 			'code',
 95 | 			'debug',
 96 | 			'test'
 97 | 		];
 98 | 		expectedModes.forEach((mode) => {
 99 | 			expect(profilesContent.includes(`'${mode}'`)).toBe(true);
100 | 		});
101 | 	});
102 | 
103 | 	test('source Roo files exist in assets directory', () => {
104 | 		// Verify that the source files for Roo integration exist
105 | 		expect(
106 | 			fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roo'))
107 | 		).toBe(true);
108 | 		expect(
109 | 			fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roomodes'))
110 | 		).toBe(true);
111 | 	});
112 | });
113 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/auth/services/context-store.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Context storage for app-specific user preferences
  3 |  *
  4 |  * This store manages user preferences and context separate from auth tokens.
  5 |  * - selectedContext (org/brief selection)
  6 |  * - userId and email (for convenience)
  7 |  * - Any other app-specific data
  8 |  *
  9 |  * Stored at: ~/.taskmaster/context.json
 10 |  */
 11 | 
 12 | import fs from 'fs';
 13 | import path from 'path';
 14 | import { getLogger } from '../../../common/logger/index.js';
 15 | import { AuthenticationError, UserContext } from '../types.js';
 16 | 
 17 | const DEFAULT_CONTEXT_FILE = path.join(
 18 | 	process.env.HOME || process.env.USERPROFILE || '~',
 19 | 	'.taskmaster',
 20 | 	'context.json'
 21 | );
 22 | 
 23 | export interface StoredContext {
 24 | 	userId?: string;
 25 | 	email?: string;
 26 | 	selectedContext?: UserContext;
 27 | 	lastUpdated: string;
 28 | }
 29 | 
 30 | export class ContextStore {
 31 | 	private static instance: ContextStore | null = null;
 32 | 	private logger = getLogger('ContextStore');
 33 | 	private contextPath: string;
 34 | 
 35 | 	private constructor(contextPath: string = DEFAULT_CONTEXT_FILE) {
 36 | 		this.contextPath = contextPath;
 37 | 	}
 38 | 
 39 | 	/**
 40 | 	 * Get singleton instance
 41 | 	 */
 42 | 	static getInstance(contextPath?: string): ContextStore {
 43 | 		if (!ContextStore.instance) {
 44 | 			ContextStore.instance = new ContextStore(contextPath);
 45 | 		}
 46 | 		return ContextStore.instance;
 47 | 	}
 48 | 
 49 | 	/**
 50 | 	 * Reset singleton (for testing)
 51 | 	 */
 52 | 	static resetInstance(): void {
 53 | 		ContextStore.instance = null;
 54 | 	}
 55 | 
 56 | 	/**
 57 | 	 * Get stored context
 58 | 	 */
 59 | 	getContext(): StoredContext | null {
 60 | 		try {
 61 | 			if (!fs.existsSync(this.contextPath)) {
 62 | 				return null;
 63 | 			}
 64 | 
 65 | 			const data = JSON.parse(fs.readFileSync(this.contextPath, 'utf8'));
 66 | 			this.logger.debug('Loaded context from disk');
 67 | 			return data;
 68 | 		} catch (error) {
 69 | 			this.logger.error('Failed to read context:', error);
 70 | 			return null;
 71 | 		}
 72 | 	}
 73 | 
 74 | 	/**
 75 | 	 * Save context
 76 | 	 */
 77 | 	saveContext(context: Partial<StoredContext>): void {
 78 | 		try {
 79 | 			// Load existing context
 80 | 			const existing = this.getContext() || {};
 81 | 
 82 | 			// Merge with new data
 83 | 			const updated: StoredContext = {
 84 | 				...existing,
 85 | 				...context,
 86 | 				lastUpdated: new Date().toISOString()
 87 | 			};
 88 | 
 89 | 			// Ensure directory exists
 90 | 			const dir = path.dirname(this.contextPath);
 91 | 			if (!fs.existsSync(dir)) {
 92 | 				fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
 93 | 			}
 94 | 
 95 | 			// Write atomically
 96 | 			const tempFile = `${this.contextPath}.tmp`;
 97 | 			fs.writeFileSync(tempFile, JSON.stringify(updated, null, 2), {
 98 | 				mode: 0o600
 99 | 			});
100 | 			fs.renameSync(tempFile, this.contextPath);
101 | 
102 | 			this.logger.debug('Saved context to disk');
103 | 		} catch (error) {
104 | 			throw new AuthenticationError(
105 | 				`Failed to save context: ${(error as Error).message}`,
106 | 				'SAVE_FAILED',
107 | 				error
108 | 			);
109 | 		}
110 | 	}
111 | 
112 | 	/**
113 | 	 * Update user context (org/brief selection)
114 | 	 */
115 | 	updateUserContext(userContext: Partial<UserContext>): void {
116 | 		const existing = this.getContext();
117 | 		const currentUserContext = existing?.selectedContext || {};
118 | 
119 | 		const updated: UserContext = {
120 | 			...currentUserContext,
121 | 			...userContext,
122 | 			updatedAt: new Date().toISOString()
123 | 		};
124 | 
125 | 		this.saveContext({
126 | 			...existing,
127 | 			selectedContext: updated
128 | 		});
129 | 	}
130 | 
131 | 	/**
132 | 	 * Get user context (org/brief selection)
133 | 	 */
134 | 	getUserContext(): UserContext | null {
135 | 		const context = this.getContext();
136 | 		return context?.selectedContext || null;
137 | 	}
138 | 
139 | 	/**
140 | 	 * Clear user context
141 | 	 */
142 | 	clearUserContext(): void {
143 | 		const existing = this.getContext();
144 | 		if (existing) {
145 | 			const { selectedContext, ...rest } = existing;
146 | 			this.saveContext(rest);
147 | 		}
148 | 	}
149 | 
150 | 	/**
151 | 	 * Clear all context
152 | 	 */
153 | 	clearContext(): void {
154 | 		try {
155 | 			if (fs.existsSync(this.contextPath)) {
156 | 				fs.unlinkSync(this.contextPath);
157 | 				this.logger.debug('Cleared context from disk');
158 | 			}
159 | 		} catch (error) {
160 | 			throw new AuthenticationError(
161 | 				`Failed to clear context: ${(error as Error).message}`,
162 | 				'CLEAR_FAILED',
163 | 				error
164 | 			);
165 | 		}
166 | 	}
167 | 
168 | 	/**
169 | 	 * Check if context exists
170 | 	 */
171 | 	hasContext(): boolean {
172 | 		return this.getContext() !== null;
173 | 	}
174 | 
175 | 	/**
176 | 	 * Get context file path
177 | 	 */
178 | 	getContextPath(): string {
179 | 		return this.contextPath;
180 | 	}
181 | }
182 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/index.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { FastMCP } from 'fastmcp';
  2 | import path from 'path';
  3 | import dotenv from 'dotenv';
  4 | import { fileURLToPath } from 'url';
  5 | import fs from 'fs';
  6 | import logger from './logger.js';
  7 | import {
  8 | 	registerTaskMasterTools,
  9 | 	getToolsConfiguration
 10 | } from './tools/index.js';
 11 | import ProviderRegistry from '../../src/provider-registry/index.js';
 12 | import { MCPProvider } from './providers/mcp-provider.js';
 13 | import packageJson from '../../package.json' with { type: 'json' };
 14 | 
 15 | dotenv.config();
 16 | 
 17 | // Constants
 18 | const __filename = fileURLToPath(import.meta.url);
 19 | const __dirname = path.dirname(__filename);
 20 | 
 21 | /**
 22 |  * Main MCP server class that integrates with Task Master
 23 |  */
 24 | class TaskMasterMCPServer {
 25 | 	constructor() {
 26 | 		this.options = {
 27 | 			name: 'Task Master MCP Server',
 28 | 			version: packageJson.version
 29 | 		};
 30 | 
 31 | 		this.server = new FastMCP(this.options);
 32 | 		this.initialized = false;
 33 | 
 34 | 		this.init = this.init.bind(this);
 35 | 		this.start = this.start.bind(this);
 36 | 		this.stop = this.stop.bind(this);
 37 | 
 38 | 		this.logger = logger;
 39 | 	}
 40 | 
 41 | 	/**
 42 | 	 * Initialize the MCP server with necessary tools and routes
 43 | 	 */
 44 | 	async init() {
 45 | 		if (this.initialized) return;
 46 | 
 47 | 		const normalizedToolMode = getToolsConfiguration();
 48 | 
 49 | 		this.logger.info('Task Master MCP Server starting...');
 50 | 		this.logger.info(`Tool mode configuration: ${normalizedToolMode}`);
 51 | 
 52 | 		const registrationResult = registerTaskMasterTools(
 53 | 			this.server,
 54 | 			normalizedToolMode
 55 | 		);
 56 | 
 57 | 		this.logger.info(
 58 | 			`Normalized tool mode: ${registrationResult.normalizedMode}`
 59 | 		);
 60 | 		this.logger.info(
 61 | 			`Registered ${registrationResult.registeredTools.length} tools successfully`
 62 | 		);
 63 | 
 64 | 		if (registrationResult.registeredTools.length > 0) {
 65 | 			this.logger.debug(
 66 | 				`Registered tools: ${registrationResult.registeredTools.join(', ')}`
 67 | 			);
 68 | 		}
 69 | 
 70 | 		if (registrationResult.failedTools.length > 0) {
 71 | 			this.logger.warn(
 72 | 				`Failed to register ${registrationResult.failedTools.length} tools: ${registrationResult.failedTools.join(', ')}`
 73 | 			);
 74 | 		}
 75 | 
 76 | 		this.initialized = true;
 77 | 
 78 | 		return this;
 79 | 	}
 80 | 
 81 | 	/**
 82 | 	 * Start the MCP server
 83 | 	 */
 84 | 	async start() {
 85 | 		if (!this.initialized) {
 86 | 			await this.init();
 87 | 		}
 88 | 
 89 | 		this.server.on('connect', (event) => {
 90 | 			event.session.server.sendLoggingMessage({
 91 | 				data: {
 92 | 					context: event.session.context,
 93 | 					message: `MCP Server connected: ${event.session.name}`
 94 | 				},
 95 | 				level: 'info'
 96 | 			});
 97 | 			this.registerRemoteProvider(event.session);
 98 | 		});
 99 | 
100 | 		// Start the FastMCP server with increased timeout
101 | 		await this.server.start({
102 | 			transportType: 'stdio',
103 | 			timeout: 120000 // 2 minutes timeout (in milliseconds)
104 | 		});
105 | 
106 | 		return this;
107 | 	}
108 | 
109 | 	/**
110 | 	 * Register both MCP providers with the provider registry
111 | 	 */
112 | 	registerRemoteProvider(session) {
113 | 		// Check if the server has at least one session
114 | 		if (session) {
115 | 			// Make sure session has required capabilities
116 | 			if (!session.clientCapabilities || !session.clientCapabilities.sampling) {
117 | 				session.server.sendLoggingMessage({
118 | 					data: {
119 | 						context: session.context,
120 | 						message: `MCP session missing required sampling capabilities, providers not registered`
121 | 					},
122 | 					level: 'info'
123 | 				});
124 | 				return;
125 | 			}
126 | 
127 | 			// Register MCP provider with the Provider Registry
128 | 
129 | 			// Register the unified MCP provider
130 | 			const mcpProvider = new MCPProvider();
131 | 			mcpProvider.setSession(session);
132 | 
133 | 			// Register provider with the registry
134 | 			const providerRegistry = ProviderRegistry.getInstance();
135 | 			providerRegistry.registerProvider('mcp', mcpProvider);
136 | 
137 | 			session.server.sendLoggingMessage({
138 | 				data: {
139 | 					context: session.context,
140 | 					message: `MCP Server connected`
141 | 				},
142 | 				level: 'info'
143 | 			});
144 | 		} else {
145 | 			session.server.sendLoggingMessage({
146 | 				data: {
147 | 					context: session.context,
148 | 					message: `No MCP sessions available, providers not registered`
149 | 				},
150 | 				level: 'warn'
151 | 			});
152 | 		}
153 | 	}
154 | 
155 | 	/**
156 | 	 * Stop the MCP server
157 | 	 */
158 | 	async stop() {
159 | 		if (this.server) {
160 | 			await this.server.stop();
161 | 		}
162 | 	}
163 | }
164 | 
165 | export default TaskMasterMCPServer;
166 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/utils/run-id-generator.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Run ID generation and validation utilities for the global storage system.
  3 |  * Uses ISO 8601 timestamps with millisecond precision for unique, chronologically-ordered run IDs.
  4 |  *
  5 |  * @module run-id-generator
  6 |  */
  7 | 
  8 | // Collision detection state
  9 | let lastTimestamp = 0;
 10 | let counter = 0;
 11 | 
 12 | /**
 13 |  * Generates a unique run ID using ISO 8601 timestamp format with millisecond precision.
 14 |  * The ID is guaranteed to be chronologically sortable and URL-safe.
 15 |  * Includes collision detection to ensure uniqueness even when called in rapid succession.
 16 |  *
 17 |  * @param {Date} [date=new Date()] - Optional date to use for the run ID. Defaults to current time.
 18 |  * @returns {string} ISO 8601 formatted timestamp (e.g., '2024-01-15T10:30:45.123Z')
 19 |  *
 20 |  * @example
 21 |  * generateRunId() // returns '2024-01-15T10:30:45.123Z'
 22 |  * generateRunId(new Date('2024-01-15T10:00:00.000Z')) // returns '2024-01-15T10:00:00.000Z'
 23 |  */
 24 | export function generateRunId(date: Date = new Date()): string {
 25 | 	const timestamp = date.getTime();
 26 | 
 27 | 	// Collision detection: if same millisecond, wait for next millisecond
 28 | 	if (timestamp === lastTimestamp) {
 29 | 		counter++;
 30 | 		// Wait for next millisecond to ensure uniqueness
 31 | 		let newTimestamp = timestamp;
 32 | 		while (newTimestamp === timestamp) {
 33 | 			newTimestamp = Date.now();
 34 | 		}
 35 | 		date = new Date(newTimestamp);
 36 | 		lastTimestamp = newTimestamp;
 37 | 		counter = 0;
 38 | 	} else {
 39 | 		lastTimestamp = timestamp;
 40 | 		counter = 0;
 41 | 	}
 42 | 
 43 | 	return date.toISOString();
 44 | }
 45 | 
 46 | /**
 47 |  * Validates whether a string is a valid run ID.
 48 |  * A valid run ID must be:
 49 |  * - In ISO 8601 format with milliseconds
 50 |  * - In UTC timezone (ends with 'Z')
 51 |  * - A valid date when parsed
 52 |  *
 53 |  * @param {any} runId - The value to validate
 54 |  * @returns {boolean} True if the value is a valid run ID
 55 |  *
 56 |  * @example
 57 |  * isValidRunId('2024-01-15T10:30:45.123Z') // returns true
 58 |  * isValidRunId('invalid') // returns false
 59 |  * isValidRunId('2024-01-15T10:30:45Z') // returns false (missing milliseconds)
 60 |  */
 61 | export function isValidRunId(runId: any): boolean {
 62 | 	if (!runId || typeof runId !== 'string') {
 63 | 		return false;
 64 | 	}
 65 | 
 66 | 	// Check format: YYYY-MM-DDTHH:mm:ss.sssZ
 67 | 	const isoFormatRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
 68 | 	if (!isoFormatRegex.test(runId)) {
 69 | 		return false;
 70 | 	}
 71 | 
 72 | 	// Validate it's a real date
 73 | 	const date = new Date(runId);
 74 | 	if (isNaN(date.getTime())) {
 75 | 		return false;
 76 | 	}
 77 | 
 78 | 	// Ensure the parsed date matches the input (catches invalid dates like 2024-13-01)
 79 | 	return date.toISOString() === runId;
 80 | }
 81 | 
 82 | /**
 83 |  * Parses a run ID string into a Date object.
 84 |  *
 85 |  * @param {any} runId - The run ID to parse
 86 |  * @returns {Date | null} Date object if valid, null if invalid
 87 |  *
 88 |  * @example
 89 |  * parseRunId('2024-01-15T10:30:45.123Z') // returns Date object
 90 |  * parseRunId('invalid') // returns null
 91 |  */
 92 | export function parseRunId(runId: any): Date | null {
 93 | 	if (!isValidRunId(runId)) {
 94 | 		return null;
 95 | 	}
 96 | 
 97 | 	return new Date(runId);
 98 | }
 99 | 
100 | /**
101 |  * Compares two run IDs chronologically.
102 |  * Returns a negative number if id1 is earlier, positive if id1 is later, or 0 if equal.
103 |  * Can be used as a comparator function for Array.sort().
104 |  *
105 |  * @param {string} id1 - First run ID to compare
106 |  * @param {string} id2 - Second run ID to compare
107 |  * @returns {number} Negative if id1 < id2, positive if id1 > id2, zero if equal
108 |  * @throws {Error} If either run ID is invalid
109 |  *
110 |  * @example
111 |  * compareRunIds('2024-01-15T10:00:00.000Z', '2024-01-15T11:00:00.000Z') // returns negative number
112 |  * ['2024-01-15T14:00:00.000Z', '2024-01-15T10:00:00.000Z'].sort(compareRunIds)
113 |  * // returns ['2024-01-15T10:00:00.000Z', '2024-01-15T14:00:00.000Z']
114 |  */
115 | export function compareRunIds(id1: string, id2: string): number {
116 | 	if (!isValidRunId(id1)) {
117 | 		throw new Error(`Invalid run ID: ${id1}`);
118 | 	}
119 | 
120 | 	if (!isValidRunId(id2)) {
121 | 		throw new Error(`Invalid run ID: ${id2}`);
122 | 	}
123 | 
124 | 	// String comparison works for ISO 8601 timestamps
125 | 	// because they are lexicographically sortable
126 | 	if (id1 < id2) return -1;
127 | 	if (id1 > id2) return 1;
128 | 	return 0;
129 | }
130 | 
```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/remove-task.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | 
  3 | // --- Mock dependencies BEFORE module import ---
  4 | jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
  5 | 	readJSON: jest.fn(),
  6 | 	writeJSON: jest.fn(),
  7 | 	log: jest.fn(),
  8 | 	CONFIG: {
  9 | 		model: 'mock-model',
 10 | 		maxTokens: 4000,
 11 | 		temperature: 0.7,
 12 | 		debug: false
 13 | 	},
 14 | 	findTaskById: jest.fn(),
 15 | 	truncate: jest.fn((t) => t),
 16 | 	isSilentMode: jest.fn(() => false)
 17 | }));
 18 | 
 19 | jest.unstable_mockModule(
 20 | 	'../../../../../scripts/modules/task-manager/generate-task-files.js',
 21 | 	() => ({
 22 | 		default: jest.fn().mockResolvedValue()
 23 | 	})
 24 | );
 25 | 
 26 | // fs is used for file deletion side-effects – stub the methods we touch
 27 | jest.unstable_mockModule('fs', () => ({
 28 | 	existsSync: jest.fn(() => true),
 29 | 	unlinkSync: jest.fn()
 30 | }));
 31 | 
 32 | // path is fine to keep as real since only join/dirname used – no side effects
 33 | 
 34 | // Import mocked modules
 35 | const { readJSON, writeJSON, log } = await import(
 36 | 	'../../../../../scripts/modules/utils.js'
 37 | );
 38 | const generateTaskFiles = (
 39 | 	await import(
 40 | 		'../../../../../scripts/modules/task-manager/generate-task-files.js'
 41 | 	)
 42 | ).default;
 43 | const fs = await import('fs');
 44 | 
 45 | // Import module under test (AFTER mocks in place)
 46 | const { default: removeTask } = await import(
 47 | 	'../../../../../scripts/modules/task-manager/remove-task.js'
 48 | );
 49 | 
 50 | // ---- Test data helpers ----
 51 | const buildSampleTaggedTasks = () => ({
 52 | 	master: {
 53 | 		tasks: [
 54 | 			{ id: 1, title: 'Task 1', status: 'pending', dependencies: [] },
 55 | 			{ id: 2, title: 'Task 2', status: 'pending', dependencies: [1] },
 56 | 			{
 57 | 				id: 3,
 58 | 				title: 'Parent',
 59 | 				status: 'pending',
 60 | 				dependencies: [],
 61 | 				subtasks: [
 62 | 					{ id: 1, title: 'Sub 3.1', status: 'pending', dependencies: [] }
 63 | 				]
 64 | 			}
 65 | 		]
 66 | 	},
 67 | 	other: {
 68 | 		tasks: [{ id: 99, title: 'Shadow', status: 'pending', dependencies: [1] }]
 69 | 	}
 70 | });
 71 | 
 72 | // Utility to deep clone sample each test
 73 | const getFreshData = () => JSON.parse(JSON.stringify(buildSampleTaggedTasks()));
 74 | 
 75 | // ----- Tests -----
 76 | 
 77 | describe('removeTask', () => {
 78 | 	beforeEach(() => {
 79 | 		jest.clearAllMocks();
 80 | 		// readJSON returns deep copy so each test isolated
 81 | 		readJSON.mockImplementation(() => {
 82 | 			return {
 83 | 				...getFreshData().master,
 84 | 				tag: 'master',
 85 | 				_rawTaggedData: getFreshData()
 86 | 			};
 87 | 		});
 88 | 		writeJSON.mockResolvedValue();
 89 | 		log.mockImplementation(() => {});
 90 | 		fs.unlinkSync.mockImplementation(() => {});
 91 | 	});
 92 | 
 93 | 	test('removes a main task and cleans dependencies across tags', async () => {
 94 | 		const result = await removeTask('tasks/tasks.json', '1', { tag: 'master' });
 95 | 
 96 | 		// Expect success true
 97 | 		expect(result.success).toBe(true);
 98 | 		// writeJSON called with data where task 1 is gone in master & dependencies removed in other tags
 99 | 		const written = writeJSON.mock.calls[0][1];
100 | 		expect(written.master.tasks.find((t) => t.id === 1)).toBeUndefined();
101 | 		// deps removed from child tasks
102 | 		const task2 = written.master.tasks.find((t) => t.id === 2);
103 | 		expect(task2.dependencies).not.toContain(1);
104 | 		const shadow = written.other.tasks.find((t) => t.id === 99);
105 | 		expect(shadow.dependencies).not.toContain(1);
106 | 		// Task file deletion attempted
107 | 		expect(fs.unlinkSync).toHaveBeenCalled();
108 | 	});
109 | 
110 | 	test('removes a subtask only and leaves parent intact', async () => {
111 | 		const result = await removeTask('tasks/tasks.json', '3.1', {
112 | 			tag: 'master'
113 | 		});
114 | 
115 | 		expect(result.success).toBe(true);
116 | 		const written = writeJSON.mock.calls[0][1];
117 | 		const parent = written.master.tasks.find((t) => t.id === 3);
118 | 		expect(parent.subtasks || []).toHaveLength(0);
119 | 		// Ensure parent still exists
120 | 		expect(parent).toBeDefined();
121 | 		// No task files should be deleted for subtasks
122 | 		expect(fs.unlinkSync).not.toHaveBeenCalled();
123 | 	});
124 | 
125 | 	test('handles non-existent task gracefully', async () => {
126 | 		const result = await removeTask('tasks/tasks.json', '42', {
127 | 			tag: 'master'
128 | 		});
129 | 		expect(result.success).toBe(false);
130 | 		expect(result.error).toContain('not found');
131 | 		// writeJSON not called because nothing changed
132 | 		expect(writeJSON).not.toHaveBeenCalled();
133 | 	});
134 | });
135 | 
```

--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/expand-all-tasks.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Direct function wrapper for expandAllTasks
  3 |  */
  4 | 
  5 | import { expandAllTasks } from '../../../../scripts/modules/task-manager.js';
  6 | import {
  7 | 	enableSilentMode,
  8 | 	disableSilentMode
  9 | } from '../../../../scripts/modules/utils.js';
 10 | import { createLogWrapper } from '../../tools/utils.js';
 11 | import { resolveComplexityReportOutputPath } from '../../../../src/utils/path-utils.js';
 12 | 
 13 | /**
 14 |  * Expand all pending tasks with subtasks (Direct Function Wrapper)
 15 |  * @param {Object} args - Function arguments
 16 |  * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
 17 |  * @param {number|string} [args.num] - Number of subtasks to generate
 18 |  * @param {boolean} [args.research] - Enable research-backed subtask generation
 19 |  * @param {string} [args.prompt] - Additional context to guide subtask generation
 20 |  * @param {boolean} [args.force] - Force regeneration of subtasks for tasks that already have them
 21 |  * @param {string} [args.projectRoot] - Project root path.
 22 |  * @param {string} [args.tag] - Tag for the task (optional)
 23 |  * @param {Object} log - Logger object from FastMCP
 24 |  * @param {Object} context - Context object containing session
 25 |  * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
 26 |  */
 27 | export async function expandAllTasksDirect(args, log, context = {}) {
 28 | 	const { session } = context; // Extract session
 29 | 	// Destructure expected args, including projectRoot and complexityReportPath
 30 | 	const {
 31 | 		tasksJsonPath,
 32 | 		num,
 33 | 		research,
 34 | 		prompt,
 35 | 		force,
 36 | 		projectRoot,
 37 | 		tag,
 38 | 		complexityReportPath: providedComplexityReportPath
 39 | 	} = args;
 40 | 
 41 | 	// Create logger wrapper using the utility
 42 | 	const mcpLog = createLogWrapper(log);
 43 | 
 44 | 	// Use provided complexity report path or compute it
 45 | 	const complexityReportPath =
 46 | 		providedComplexityReportPath ||
 47 | 		resolveComplexityReportOutputPath(null, { projectRoot, tag }, log);
 48 | 
 49 | 	log.info(
 50 | 		`Expand all tasks will use complexity report at: ${complexityReportPath}`
 51 | 	);
 52 | 
 53 | 	if (!tasksJsonPath) {
 54 | 		log.error('expandAllTasksDirect called without tasksJsonPath');
 55 | 		return {
 56 | 			success: false,
 57 | 			error: {
 58 | 				code: 'MISSING_ARGUMENT',
 59 | 				message: 'tasksJsonPath is required'
 60 | 			}
 61 | 		};
 62 | 	}
 63 | 
 64 | 	enableSilentMode(); // Enable silent mode for the core function call
 65 | 	try {
 66 | 		log.info(
 67 | 			`Calling core expandAllTasks with args: ${JSON.stringify({ num, research, prompt, force, projectRoot, tag })}`
 68 | 		);
 69 | 
 70 | 		// Parse parameters (ensure correct types)
 71 | 		const numSubtasks = num ? parseInt(num, 10) : undefined;
 72 | 		const useResearch = research === true;
 73 | 		const additionalContext = prompt || '';
 74 | 		const forceFlag = force === true;
 75 | 
 76 | 		// Call the core function, passing options and the context object { session, mcpLog, projectRoot, tag, complexityReportPath }
 77 | 		const result = await expandAllTasks(
 78 | 			tasksJsonPath,
 79 | 			numSubtasks,
 80 | 			useResearch,
 81 | 			additionalContext,
 82 | 			forceFlag,
 83 | 			{ session, mcpLog, projectRoot, tag, complexityReportPath },
 84 | 			'json'
 85 | 		);
 86 | 
 87 | 		// Core function now returns a summary object including the *aggregated* telemetryData
 88 | 		return {
 89 | 			success: true,
 90 | 			data: {
 91 | 				message: `Expand all operation completed. Expanded: ${result.expandedCount}, Failed: ${result.failedCount}, Skipped: ${result.skippedCount}`,
 92 | 				details: {
 93 | 					expandedCount: result.expandedCount,
 94 | 					failedCount: result.failedCount,
 95 | 					skippedCount: result.skippedCount,
 96 | 					tasksToExpand: result.tasksToExpand
 97 | 				},
 98 | 				telemetryData: result.telemetryData // Pass the aggregated object
 99 | 			}
100 | 		};
101 | 	} catch (error) {
102 | 		// Log the error using the MCP logger
103 | 		log.error(`Error during core expandAllTasks execution: ${error.message}`);
104 | 		// Optionally log stack trace if available and debug enabled
105 | 		// if (error.stack && log.debug) { log.debug(error.stack); }
106 | 
107 | 		return {
108 | 			success: false,
109 | 			error: {
110 | 				code: 'CORE_FUNCTION_ERROR', // Or a more specific code if possible
111 | 				message: error.message
112 | 			}
113 | 		};
114 | 	} finally {
115 | 		disableSilentMode(); // IMPORTANT: Ensure silent mode is always disabled
116 | 	}
117 | }
118 | 
```

--------------------------------------------------------------------------------
/tests/integration/claude-code-optional.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | 
  3 | // Mock AI SDK functions at the top level
  4 | const generateText = jest.fn();
  5 | const streamText = jest.fn();
  6 | 
  7 | jest.unstable_mockModule('ai', () => ({
  8 | 	generateObject: jest.fn(),
  9 | 	generateText,
 10 | 	streamText,
 11 | 	streamObject: jest.fn(),
 12 | 	zodSchema: jest.fn(),
 13 | 	JSONParseError: class JSONParseError extends Error {},
 14 | 	NoObjectGeneratedError: class NoObjectGeneratedError extends Error {}
 15 | }));
 16 | 
 17 | // Mock successful provider creation for all tests
 18 | const mockProvider = jest.fn((modelId) => ({
 19 | 	id: modelId,
 20 | 	doGenerate: jest.fn(),
 21 | 	doStream: jest.fn()
 22 | }));
 23 | mockProvider.languageModel = jest.fn((id, settings) => ({ id, settings }));
 24 | mockProvider.chat = mockProvider.languageModel;
 25 | 
 26 | jest.unstable_mockModule('ai-sdk-provider-claude-code', () => ({
 27 | 	createClaudeCode: jest.fn(() => mockProvider)
 28 | }));
 29 | 
 30 | // Import the provider after mocking
 31 | const { ClaudeCodeProvider } = await import(
 32 | 	'../../src/ai-providers/claude-code.js'
 33 | );
 34 | 
 35 | describe('Claude Code Integration (Optional)', () => {
 36 | 	beforeEach(() => {
 37 | 		jest.clearAllMocks();
 38 | 	});
 39 | 
 40 | 	it('should create a working provider instance', () => {
 41 | 		const provider = new ClaudeCodeProvider();
 42 | 		expect(provider.name).toBe('Claude Code');
 43 | 		expect(provider.getSupportedModels()).toEqual(['opus', 'sonnet', 'haiku']);
 44 | 	});
 45 | 
 46 | 	it('should support model validation', () => {
 47 | 		const provider = new ClaudeCodeProvider();
 48 | 		expect(provider.isModelSupported('sonnet')).toBe(true);
 49 | 		expect(provider.isModelSupported('opus')).toBe(true);
 50 | 		expect(provider.isModelSupported('haiku')).toBe(true);
 51 | 		expect(provider.isModelSupported('unknown')).toBe(false);
 52 | 	});
 53 | 
 54 | 	it('should create a client successfully', () => {
 55 | 		const provider = new ClaudeCodeProvider();
 56 | 		const client = provider.getClient();
 57 | 
 58 | 		expect(client).toBeDefined();
 59 | 		expect(typeof client).toBe('function');
 60 | 		expect(client.languageModel).toBeDefined();
 61 | 		expect(client.chat).toBeDefined();
 62 | 		expect(client.chat).toBe(client.languageModel);
 63 | 	});
 64 | 
 65 | 	it('should pass command-specific settings to client', async () => {
 66 | 		const provider = new ClaudeCodeProvider();
 67 | 		const client = provider.getClient({ commandName: 'test-command' });
 68 | 
 69 | 		expect(client).toBeDefined();
 70 | 		expect(typeof client).toBe('function');
 71 | 		const { createClaudeCode } = await import('ai-sdk-provider-claude-code');
 72 | 		expect(createClaudeCode).toHaveBeenCalledTimes(1);
 73 | 	});
 74 | 
 75 | 	it('should handle AI SDK generateText integration', async () => {
 76 | 		const provider = new ClaudeCodeProvider();
 77 | 		const client = provider.getClient();
 78 | 
 79 | 		// Mock successful generation
 80 | 		generateText.mockResolvedValueOnce({
 81 | 			text: 'Hello from Claude Code!',
 82 | 			usage: { totalTokens: 10 }
 83 | 		});
 84 | 
 85 | 		const result = await generateText({
 86 | 			model: client('sonnet'),
 87 | 			messages: [{ role: 'user', content: 'Hello' }]
 88 | 		});
 89 | 
 90 | 		expect(result.text).toBe('Hello from Claude Code!');
 91 | 		expect(generateText).toHaveBeenCalledWith({
 92 | 			model: expect.any(Object),
 93 | 			messages: [{ role: 'user', content: 'Hello' }]
 94 | 		});
 95 | 	});
 96 | 
 97 | 	it('should handle AI SDK streamText integration', async () => {
 98 | 		const provider = new ClaudeCodeProvider();
 99 | 		const client = provider.getClient();
100 | 
101 | 		// Mock successful streaming
102 | 		const mockStream = {
103 | 			textStream: (async function* () {
104 | 				yield 'Streamed response';
105 | 			})()
106 | 		};
107 | 		streamText.mockResolvedValueOnce(mockStream);
108 | 
109 | 		const streamResult = await streamText({
110 | 			model: client('sonnet'),
111 | 			messages: [{ role: 'user', content: 'Stream test' }]
112 | 		});
113 | 
114 | 		expect(streamResult.textStream).toBeDefined();
115 | 		expect(streamText).toHaveBeenCalledWith({
116 | 			model: expect.any(Object),
117 | 			messages: [{ role: 'user', content: 'Stream test' }]
118 | 		});
119 | 	});
120 | 
121 | 	it('should not require authentication validation', () => {
122 | 		const provider = new ClaudeCodeProvider();
123 | 		expect(provider.isRequiredApiKey()).toBe(false);
124 | 		expect(() => provider.validateAuth()).not.toThrow();
125 | 		expect(() => provider.validateAuth({})).not.toThrow();
126 | 		expect(() => provider.validateAuth({ commandName: 'test' })).not.toThrow();
127 | 	});
128 | });
129 | 
```

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

```typescript
  1 | /**
  2 |  * @fileoverview Unit tests for ConfigLoader service
  3 |  */
  4 | 
  5 | import fs from 'node:fs/promises';
  6 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  7 | import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
  8 | import { ConfigLoader } from './config-loader.service.js';
  9 | 
 10 | vi.mock('node:fs', () => ({
 11 | 	promises: {
 12 | 		readFile: vi.fn(),
 13 | 		access: vi.fn()
 14 | 	}
 15 | }));
 16 | 
 17 | describe('ConfigLoader', () => {
 18 | 	let configLoader: ConfigLoader;
 19 | 	const testProjectRoot = '/test/project';
 20 | 
 21 | 	beforeEach(() => {
 22 | 		configLoader = new ConfigLoader(testProjectRoot);
 23 | 		vi.clearAllMocks();
 24 | 	});
 25 | 
 26 | 	afterEach(() => {
 27 | 		vi.restoreAllMocks();
 28 | 	});
 29 | 
 30 | 	describe('getDefaultConfig', () => {
 31 | 		it('should return default configuration values', () => {
 32 | 			const config = configLoader.getDefaultConfig();
 33 | 
 34 | 			expect(config.models).toEqual({
 35 | 				main: DEFAULT_CONFIG_VALUES.MODELS.MAIN,
 36 | 				fallback: DEFAULT_CONFIG_VALUES.MODELS.FALLBACK
 37 | 			});
 38 | 
 39 | 			expect(config.storage).toEqual({
 40 | 				type: DEFAULT_CONFIG_VALUES.STORAGE.TYPE,
 41 | 				encoding: DEFAULT_CONFIG_VALUES.STORAGE.ENCODING,
 42 | 				enableBackup: false,
 43 | 				maxBackups: DEFAULT_CONFIG_VALUES.STORAGE.MAX_BACKUPS,
 44 | 				enableCompression: false,
 45 | 				atomicOperations: true
 46 | 			});
 47 | 
 48 | 			expect(config.version).toBe(DEFAULT_CONFIG_VALUES.VERSION);
 49 | 		});
 50 | 	});
 51 | 
 52 | 	describe('loadLocalConfig', () => {
 53 | 		it('should load and parse local configuration file', async () => {
 54 | 			const mockConfig = {
 55 | 				models: { main: 'test-model' },
 56 | 				storage: { type: 'api' as const }
 57 | 			};
 58 | 
 59 | 			vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig));
 60 | 
 61 | 			const result = await configLoader.loadLocalConfig();
 62 | 
 63 | 			expect(fs.readFile).toHaveBeenCalledWith(
 64 | 				'/test/project/.taskmaster/config.json',
 65 | 				'utf-8'
 66 | 			);
 67 | 			expect(result).toEqual(mockConfig);
 68 | 		});
 69 | 
 70 | 		it('should return null when config file does not exist', async () => {
 71 | 			const error = new Error('File not found') as any;
 72 | 			error.code = 'ENOENT';
 73 | 			vi.mocked(fs.readFile).mockRejectedValue(error);
 74 | 
 75 | 			const result = await configLoader.loadLocalConfig();
 76 | 
 77 | 			expect(result).toBeNull();
 78 | 		});
 79 | 
 80 | 		it('should throw TaskMasterError for other file errors', async () => {
 81 | 			const error = new Error('Permission denied');
 82 | 			vi.mocked(fs.readFile).mockRejectedValue(error);
 83 | 
 84 | 			await expect(configLoader.loadLocalConfig()).rejects.toThrow(
 85 | 				'Failed to load local configuration'
 86 | 			);
 87 | 		});
 88 | 
 89 | 		it('should throw error for invalid JSON', async () => {
 90 | 			vi.mocked(fs.readFile).mockResolvedValue('invalid json');
 91 | 
 92 | 			await expect(configLoader.loadLocalConfig()).rejects.toThrow();
 93 | 		});
 94 | 	});
 95 | 
 96 | 	describe('loadGlobalConfig', () => {
 97 | 		it('should return null (not implemented yet)', async () => {
 98 | 			const result = await configLoader.loadGlobalConfig();
 99 | 			expect(result).toBeNull();
100 | 		});
101 | 	});
102 | 
103 | 	describe('hasLocalConfig', () => {
104 | 		it('should return true when local config exists', async () => {
105 | 			vi.mocked(fs.access).mockResolvedValue(undefined);
106 | 
107 | 			const result = await configLoader.hasLocalConfig();
108 | 
109 | 			expect(fs.access).toHaveBeenCalledWith(
110 | 				'/test/project/.taskmaster/config.json'
111 | 			);
112 | 			expect(result).toBe(true);
113 | 		});
114 | 
115 | 		it('should return false when local config does not exist', async () => {
116 | 			vi.mocked(fs.access).mockRejectedValue(new Error('Not found'));
117 | 
118 | 			const result = await configLoader.hasLocalConfig();
119 | 
120 | 			expect(result).toBe(false);
121 | 		});
122 | 	});
123 | 
124 | 	describe('hasGlobalConfig', () => {
125 | 		it('should check global config path', async () => {
126 | 			vi.mocked(fs.access).mockResolvedValue(undefined);
127 | 
128 | 			const result = await configLoader.hasGlobalConfig();
129 | 
130 | 			expect(fs.access).toHaveBeenCalledWith(
131 | 				expect.stringContaining('.taskmaster/config.json')
132 | 			);
133 | 			expect(result).toBe(true);
134 | 		});
135 | 
136 | 		it('should return false when global config does not exist', async () => {
137 | 			vi.mocked(fs.access).mockRejectedValue(new Error('Not found'));
138 | 
139 | 			const result = await configLoader.hasGlobalConfig();
140 | 
141 | 			expect(result).toBe(false);
142 | 		});
143 | 	});
144 | });
145 | 
```

--------------------------------------------------------------------------------
/apps/extension/src/components/ui/shadcn-io/kanban/index.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | 'use client';
  2 | 
  3 | import { Card } from '@/components/ui/card';
  4 | import { cn } from '@/lib/utils';
  5 | import {
  6 | 	DndContext,
  7 | 	DragOverlay,
  8 | 	MouseSensor,
  9 | 	TouchSensor,
 10 | 	rectIntersection,
 11 | 	useDraggable,
 12 | 	useDroppable,
 13 | 	useSensor,
 14 | 	useSensors
 15 | } from '@dnd-kit/core';
 16 | import type { DragEndEvent } from '@dnd-kit/core';
 17 | import type React from 'react';
 18 | import type { ReactNode } from 'react';
 19 | 
 20 | export type { DragEndEvent } from '@dnd-kit/core';
 21 | 
 22 | export type Status = {
 23 | 	id: string;
 24 | 	name: string;
 25 | 	color: string;
 26 | };
 27 | 
 28 | export type Feature = {
 29 | 	id: string;
 30 | 	name: string;
 31 | 	startAt: Date;
 32 | 	endAt: Date;
 33 | 	status: Status;
 34 | };
 35 | 
 36 | export type KanbanBoardProps = {
 37 | 	id: Status['id'];
 38 | 	children: ReactNode;
 39 | 	className?: string;
 40 | };
 41 | 
 42 | export const KanbanBoard = ({ id, children, className }: KanbanBoardProps) => {
 43 | 	const { isOver, setNodeRef } = useDroppable({ id });
 44 | 
 45 | 	return (
 46 | 		<div
 47 | 			className={cn(
 48 | 				'flex h-full min-h-40 flex-col gap-2 rounded-md border bg-secondary p-2 text-xs shadow-sm outline transition-all',
 49 | 				isOver ? 'outline-primary' : 'outline-transparent',
 50 | 				className
 51 | 			)}
 52 | 			ref={setNodeRef}
 53 | 		>
 54 | 			{children}
 55 | 		</div>
 56 | 	);
 57 | };
 58 | 
 59 | export type KanbanCardProps = Pick<Feature, 'id' | 'name'> & {
 60 | 	index: number;
 61 | 	parent: string;
 62 | 	children?: ReactNode;
 63 | 	className?: string;
 64 | 	onClick?: (event: React.MouseEvent) => void;
 65 | 	onDoubleClick?: (event: React.MouseEvent) => void;
 66 | };
 67 | 
 68 | export const KanbanCard = ({
 69 | 	id,
 70 | 	name,
 71 | 	index,
 72 | 	parent,
 73 | 	children,
 74 | 	className,
 75 | 	onClick,
 76 | 	onDoubleClick
 77 | }: KanbanCardProps) => {
 78 | 	const { attributes, listeners, setNodeRef, transform, isDragging } =
 79 | 		useDraggable({
 80 | 			id,
 81 | 			data: { index, parent }
 82 | 		});
 83 | 
 84 | 	return (
 85 | 		<Card
 86 | 			className={cn(
 87 | 				'rounded-md p-3 shadow-sm',
 88 | 				isDragging && 'cursor-grabbing opacity-0',
 89 | 				!isDragging && 'cursor-pointer',
 90 | 				className
 91 | 			)}
 92 | 			style={{
 93 | 				transform: transform
 94 | 					? `translateX(${transform.x}px) translateY(${transform.y}px)`
 95 | 					: 'none'
 96 | 			}}
 97 | 			{...attributes}
 98 | 			{...listeners}
 99 | 			onClick={(e) => !isDragging && onClick?.(e)}
100 | 			onDoubleClick={onDoubleClick}
101 | 			ref={setNodeRef}
102 | 		>
103 | 			{children ?? <p className="m-0 font-medium text-sm">{name}</p>}
104 | 		</Card>
105 | 	);
106 | };
107 | 
108 | export type KanbanCardsProps = {
109 | 	children: ReactNode;
110 | 	className?: string;
111 | };
112 | 
113 | export const KanbanCards = ({ children, className }: KanbanCardsProps) => (
114 | 	<div className={cn('flex flex-1 flex-col gap-2', className)}>{children}</div>
115 | );
116 | 
117 | export type KanbanHeaderProps =
118 | 	| {
119 | 			children: ReactNode;
120 | 	  }
121 | 	| {
122 | 			name: Status['name'];
123 | 			color: Status['color'];
124 | 			className?: string;
125 | 	  };
126 | 
127 | export const KanbanHeader = (props: KanbanHeaderProps) =>
128 | 	'children' in props ? (
129 | 		props.children
130 | 	) : (
131 | 		<div className={cn('flex shrink-0 items-center gap-2', props.className)}>
132 | 			<div
133 | 				className="h-2 w-2 rounded-full"
134 | 				style={{ backgroundColor: props.color }}
135 | 			/>
136 | 			<p className="m-0 font-semibold text-sm">{props.name}</p>
137 | 		</div>
138 | 	);
139 | 
140 | export type KanbanProviderProps = {
141 | 	children: ReactNode;
142 | 	onDragEnd: (event: DragEndEvent) => void;
143 | 	onDragStart?: (event: DragEndEvent) => void;
144 | 	onDragCancel?: () => void;
145 | 	className?: string;
146 | 	dragOverlay?: ReactNode;
147 | };
148 | 
149 | export const KanbanProvider = ({
150 | 	children,
151 | 	onDragEnd,
152 | 	onDragStart,
153 | 	onDragCancel,
154 | 	className,
155 | 	dragOverlay
156 | }: KanbanProviderProps) => {
157 | 	// Configure sensors with activation constraints to prevent accidental drags
158 | 	const sensors = useSensors(
159 | 		// Only start a drag if you've moved more than 8px
160 | 		useSensor(MouseSensor, {
161 | 			activationConstraint: { distance: 8 }
162 | 		}),
163 | 		// On touch devices, require a short press + small move
164 | 		useSensor(TouchSensor, {
165 | 			activationConstraint: { delay: 150, tolerance: 5 }
166 | 		})
167 | 	);
168 | 
169 | 	return (
170 | 		<DndContext
171 | 			sensors={sensors}
172 | 			collisionDetection={rectIntersection}
173 | 			onDragEnd={onDragEnd}
174 | 			onDragStart={onDragStart}
175 | 			onDragCancel={onDragCancel}
176 | 		>
177 | 			<div
178 | 				className={cn(
179 | 					'grid w-full auto-cols-fr grid-flow-col gap-4',
180 | 					className
181 | 				)}
182 | 			>
183 | 				{children}
184 | 			</div>
185 | 			<DragOverlay>{dragOverlay}</DragOverlay>
186 | 		</DndContext>
187 | 	);
188 | };
189 | 
```

--------------------------------------------------------------------------------
/.taskmaster/docs/research/2025-06-14_test-save-functionality.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Research Session
 3 | query: "test save functionality"
 4 | date: 6/14/2025
 5 | time: 5:50:23 PM
 6 | timestamp: 2025-06-14T21:50:23.797Z
 7 | exchanges: 1
 8 | ---
 9 | 
10 | # Research Session
11 | 
12 | ## Initial Query
13 | 
14 | **Question:** test save functionality
15 | 
16 | **Response:**
17 | 
18 | ## Testing Save Functionality in a Task Management System
19 | 
20 | **Core Steps for Testing Save Functionality**
21 | 
22 | - Create or update a task in the system using the CLI or UI.
23 | - Trigger the save operation (e.g., by issuing a command or clicking a save button).
24 | - Verify that the data is correctly written to the persistent storage (e.g., `tasks.json`).
25 | - Reload or re-query the data to ensure the saved changes persist and are accurately reflected.
26 | - Validate that all required fields and relationships (such as dependencies and subtasks) are preserved and correctly structured.
27 | 
28 | **Key Test Scenarios**
29 | 
30 | - **Basic Save:** Add a new task and confirm it appears in `tasks.json` with all fields populated.
31 | - **Update Save:** Modify an existing task (e.g., change status, description, or priority) and verify the changes are saved.
32 | - **Subtask and Dependency Preservation:** Update a parent task and ensure that completed subtasks and dependency links remain intact, especially after using commands like `updateTask`.
33 | - **Batch Operations:** Perform bulk updates or additions and confirm all affected tasks are saved correctly, with no data loss or corruption.
34 | - **Validation:** Attempt to save invalid data and ensure the system rejects it or provides meaningful error messages, confirming validation logic is enforced.
35 | 
36 | **Recommended Practices**
37 | 
38 | - Use automated tests to simulate save operations for various scenarios, including edge cases (e.g., circular dependencies, missing required fields).
39 | - After each save, reload the data and compare it to the expected state to ensure integrity.
40 | - Log all save operations and errors for traceability and debugging.
41 | - Regularly back up the `tasks.json` file to prevent data loss during testing and development.
42 | 
43 | **Relevant Features from Industry Standards**
44 | 
45 | - Task management systems typically provide robust save and update mechanisms, ensuring that all task attributes (including dependencies, subtasks, and comments) are consistently maintained[1][2][5].
46 | - Validation and error handling are critical to prevent corrupt or incomplete saves, especially when handling complex relationships or batch operations[1][5].
47 | - User feedback (such as success messages or error alerts) is important for confirming save actions and guiding users in case of issues[1][2].
48 | 
49 | **Example Test Case**
50 | 
51 | 1. Create a task with subtasks and dependencies.
52 | 2. Mark some subtasks as complete.
53 | 3. Use the `updateTask` command to modify the parent task.
54 | 4. Save and reload the data.
55 | 5. Assert that:
56 |    - The parent task reflects the updates.
57 |    - Completed subtasks remain marked as complete.
58 |    - Dependencies are unchanged and valid.
59 | 
60 | **Summary Table: Save Functionality Test Aspects**
61 | 
62 | | Aspect                | What to Test                                      | Expected Outcome                        |
63 | |-----------------------|---------------------------------------------------|-----------------------------------------|
64 | | Basic Save            | Add/update task, save, reload                     | Data persists and matches expectations  |
65 | | Subtask Preservation  | Update parent, check subtask status               | Completed subtasks remain complete      |
66 | | Dependency Handling   | Save with dependencies, reload                    | Dependencies are intact and valid       |
67 | | Batch Operations      | Bulk save/update, reload                          | All changes persist, no data loss       |
68 | | Validation            | Save invalid data                                 | Error is raised, data not corrupted     |
69 | 
70 | These steps and scenarios ensure that the save functionality is robust, reliable, and meets the requirements of a modern task management system[1][2][5].
71 | 
72 | 
73 | ---
74 | 
75 | *Generated by Task Master Research Command*  
76 | *Timestamp: 2025-06-14T21:50:23.797Z*
77 | 
```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/parse-prd-schema.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { describe, it, expect } from '@jest/globals';
  2 | import { prdResponseSchema } from '../../../../../scripts/modules/task-manager/parse-prd/parse-prd-config.js';
  3 | 
  4 | describe('PRD Response Schema', () => {
  5 | 	const validTask = {
  6 | 		id: 1,
  7 | 		title: 'Test Task',
  8 | 		description: 'Test description',
  9 | 		details: 'Test details',
 10 | 		testStrategy: 'Test strategy',
 11 | 		priority: 'high',
 12 | 		dependencies: [],
 13 | 		status: 'pending'
 14 | 	};
 15 | 
 16 | 	describe('Valid responses', () => {
 17 | 		it('should accept response with tasks and metadata', () => {
 18 | 			const response = {
 19 | 				tasks: [validTask],
 20 | 				metadata: {
 21 | 					projectName: 'Test Project',
 22 | 					totalTasks: 1,
 23 | 					sourceFile: 'test.txt',
 24 | 					generatedAt: '2025-01-01T00:00:00Z'
 25 | 				}
 26 | 			};
 27 | 
 28 | 			const result = prdResponseSchema.safeParse(response);
 29 | 			expect(result.success).toBe(true);
 30 | 		});
 31 | 
 32 | 		it('should accept response with tasks and null metadata', () => {
 33 | 			const response = {
 34 | 				tasks: [validTask],
 35 | 				metadata: null
 36 | 			};
 37 | 
 38 | 			const result = prdResponseSchema.safeParse(response);
 39 | 			expect(result.success).toBe(true);
 40 | 		});
 41 | 
 42 | 		it('should accept response with only tasks (no metadata field)', () => {
 43 | 			// This is what ZAI returns - just the tasks array without metadata
 44 | 			const response = {
 45 | 				tasks: [validTask]
 46 | 			};
 47 | 
 48 | 			const result = prdResponseSchema.safeParse(response);
 49 | 			expect(result.success).toBe(true);
 50 | 			if (result.success) {
 51 | 				// With .default(null), omitted metadata becomes null
 52 | 				expect(result.data.metadata).toBeNull();
 53 | 			}
 54 | 		});
 55 | 
 56 | 		it('should accept response with multiple tasks', () => {
 57 | 			const response = {
 58 | 				tasks: [validTask, { ...validTask, id: 2, title: 'Second Task' }]
 59 | 			};
 60 | 
 61 | 			const result = prdResponseSchema.safeParse(response);
 62 | 			expect(result.success).toBe(true);
 63 | 		});
 64 | 	});
 65 | 
 66 | 	describe('Invalid responses', () => {
 67 | 		it('should reject response without tasks field', () => {
 68 | 			const response = {
 69 | 				metadata: null
 70 | 			};
 71 | 
 72 | 			const result = prdResponseSchema.safeParse(response);
 73 | 			expect(result.success).toBe(false);
 74 | 		});
 75 | 
 76 | 		it('should reject response with empty tasks array and invalid metadata', () => {
 77 | 			const response = {
 78 | 				tasks: [],
 79 | 				metadata: 'invalid'
 80 | 			};
 81 | 
 82 | 			const result = prdResponseSchema.safeParse(response);
 83 | 			expect(result.success).toBe(false);
 84 | 		});
 85 | 
 86 | 		it('should reject task with missing required fields', () => {
 87 | 			const response = {
 88 | 				tasks: [
 89 | 					{
 90 | 						id: 1,
 91 | 						title: 'Test'
 92 | 						// missing other required fields
 93 | 					}
 94 | 				]
 95 | 			};
 96 | 
 97 | 			const result = prdResponseSchema.safeParse(response);
 98 | 			expect(result.success).toBe(false);
 99 | 		});
100 | 
101 | 		it('should reject task with invalid priority', () => {
102 | 			const response = {
103 | 				tasks: [
104 | 					{
105 | 						...validTask,
106 | 						priority: 'invalid'
107 | 					}
108 | 				]
109 | 			};
110 | 
111 | 			const result = prdResponseSchema.safeParse(response);
112 | 			expect(result.success).toBe(false);
113 | 		});
114 | 	});
115 | 
116 | 	describe('ZAI-specific response format', () => {
117 | 		it('should handle ZAI response format (tasks only, no metadata)', () => {
118 | 			// This is the actual format ZAI returns
119 | 			const zaiResponse = {
120 | 				tasks: [
121 | 					{
122 | 						id: 24,
123 | 						title: 'Core Todo Data Management',
124 | 						description:
125 | 							'Implement the core data structure and CRUD operations',
126 | 						status: 'pending',
127 | 						dependencies: [],
128 | 						priority: 'high',
129 | 						details: 'Create a Todo data model with properties...',
130 | 						testStrategy: 'Unit tests for TodoManager class...'
131 | 					},
132 | 					{
133 | 						id: 25,
134 | 						title: 'Todo UI and User Interactions',
135 | 						description: 'Create the user interface components',
136 | 						status: 'pending',
137 | 						dependencies: [24],
138 | 						priority: 'high',
139 | 						details: 'Build a simple HTML/CSS/JS interface...',
140 | 						testStrategy: 'UI component tests...'
141 | 					}
142 | 				]
143 | 			};
144 | 
145 | 			const result = prdResponseSchema.safeParse(zaiResponse);
146 | 			expect(result.success).toBe(true);
147 | 			if (result.success) {
148 | 				expect(result.data.tasks).toHaveLength(2);
149 | 				// With .default(null), omitted metadata becomes null (not undefined)
150 | 				expect(result.data.metadata).toBeNull();
151 | 			}
152 | 		});
153 | 	});
154 | });
155 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Main entry point for @tm/core
  3 |  * Provides unified access to all Task Master functionality through TmCore
  4 |  */
  5 | 
  6 | import type { TasksDomain } from './modules/tasks/tasks-domain.js';
  7 | 
  8 | // ========== Primary API ==========
  9 | 
 10 | /**
 11 |  * Create a new TmCore instance - The ONLY way to use tm-core
 12 |  *
 13 |  * @example
 14 |  * ```typescript
 15 |  * import { createTmCore } from '@tm/core';
 16 |  *
 17 |  * const tmcore = await createTmCore({
 18 |  *   projectPath: process.cwd()
 19 |  * });
 20 |  *
 21 |  * // Access domains
 22 |  * await tmcore.auth.login({ ... });
 23 |  * const tasks = await tmcore.tasks.list();
 24 |  * await tmcore.workflow.start({ taskId: '1' });
 25 |  * await tmcore.git.commit('feat: add feature');
 26 |  * const config = tmcore.config.get('models.main');
 27 |  * ```
 28 |  */
 29 | export { createTmCore, TmCore, type TmCoreOptions } from './tm-core.js';
 30 | 
 31 | // ========== Type Exports ==========
 32 | 
 33 | // Common types that consumers need
 34 | export type * from './common/types/index.js';
 35 | 
 36 | // Common interfaces
 37 | export type * from './common/interfaces/index.js';
 38 | 
 39 | // Storage interfaces - TagInfo and TagsWithStatsResult
 40 | export type {
 41 | 	TagInfo,
 42 | 	TagsWithStatsResult
 43 | } from './common/interfaces/storage.interface.js';
 44 | 
 45 | // Constants
 46 | export * from './common/constants/index.js';
 47 | 
 48 | // Errors
 49 | export * from './common/errors/index.js';
 50 | 
 51 | // Utils
 52 | export * from './common/utils/index.js';
 53 | export * from './utils/time.utils.js';
 54 | 
 55 | // ========== Domain-Specific Type Exports ==========
 56 | 
 57 | // Task types
 58 | export type {
 59 | 	TaskListResult,
 60 | 	GetTaskListOptions
 61 | } from './modules/tasks/services/task-service.js';
 62 | 
 63 | export type {
 64 | 	StartTaskOptions,
 65 | 	StartTaskResult,
 66 | 	ConflictCheckResult
 67 | } from './modules/tasks/services/task-execution-service.js';
 68 | 
 69 | export type {
 70 | 	PreflightResult,
 71 | 	CheckResult
 72 | } from './modules/tasks/services/preflight-checker.service.js';
 73 | 
 74 | // Task domain result types
 75 | export type TaskWithSubtaskResult = Awaited<ReturnType<TasksDomain['get']>>;
 76 | 
 77 | // Auth types
 78 | export type {
 79 | 	AuthCredentials,
 80 | 	OAuthFlowOptions,
 81 | 	UserContext
 82 | } from './modules/auth/types.js';
 83 | export { AuthenticationError } from './modules/auth/types.js';
 84 | 
 85 | // Brief types
 86 | export type { Brief } from './modules/briefs/types.js';
 87 | export type { TagWithStats } from './modules/briefs/services/brief-service.js';
 88 | 
 89 | // Workflow types
 90 | export type {
 91 | 	StartWorkflowOptions,
 92 | 	WorkflowStatus,
 93 | 	NextAction
 94 | } from './modules/workflow/services/workflow.service.js';
 95 | 
 96 | export type {
 97 | 	WorkflowPhase,
 98 | 	TDDPhase,
 99 | 	WorkflowContext,
100 | 	WorkflowState,
101 | 	TestResult
102 | } from './modules/workflow/types.js';
103 | 
104 | // Git types
105 | export type { CommitMessageOptions } from './modules/git/services/commit-message-generator.js';
106 | 
107 | // Integration types
108 | export type {
109 | 	ExportTasksOptions,
110 | 	ExportResult
111 | } from './modules/integration/services/export.service.js';
112 | 
113 | // Reports types
114 | export type {
115 | 	ComplexityReport,
116 | 	ComplexityReportMetadata,
117 | 	ComplexityAnalysis,
118 | 	TaskComplexityData
119 | } from './modules/reports/types.js';
120 | 
121 | // ========== Advanced API (for CLI/Extension/MCP) ==========
122 | 
123 | // Auth - Advanced
124 | export { AuthManager } from './modules/auth/managers/auth-manager.js';
125 | 
126 | // Briefs - Advanced
127 | export { BriefsDomain } from './modules/briefs/briefs-domain.js';
128 | export { BriefService } from './modules/briefs/services/brief-service.js';
129 | 
130 | // Workflow - Advanced
131 | export { WorkflowOrchestrator } from './modules/workflow/orchestrators/workflow-orchestrator.js';
132 | export { WorkflowStateManager } from './modules/workflow/managers/workflow-state-manager.js';
133 | export { WorkflowService } from './modules/workflow/services/workflow.service.js';
134 | export type { SubtaskInfo } from './modules/workflow/types.js';
135 | 
136 | // Git - Advanced
137 | export { GitAdapter } from './modules/git/adapters/git-adapter.js';
138 | export { CommitMessageGenerator } from './modules/git/services/commit-message-generator.js';
139 | 
140 | // Tasks - Advanced
141 | export { PreflightChecker } from './modules/tasks/services/preflight-checker.service.js';
142 | export { TaskLoaderService } from './modules/tasks/services/task-loader.service.js';
143 | 
144 | // Integration - Advanced
145 | export { ExportService } from './modules/integration/services/export.service.js';
146 | 
```
Page 12/69FirstPrevNextLast