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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/models.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * models.js
  3 |  * Direct function for managing AI model configurations via MCP
  4 |  */
  5 | 
  6 | import {
  7 | 	getModelConfiguration,
  8 | 	getAvailableModelsList,
  9 | 	setModel
 10 | } from '../../../../scripts/modules/task-manager/models.js';
 11 | import {
 12 | 	enableSilentMode,
 13 | 	disableSilentMode
 14 | } from '../../../../scripts/modules/utils.js';
 15 | import { createLogWrapper } from '../../tools/utils.js';
 16 | import { CUSTOM_PROVIDERS_ARRAY } from '../../../../src/constants/providers.js';
 17 | 
 18 | // Define supported roles for model setting
 19 | const MODEL_ROLES = ['main', 'research', 'fallback'];
 20 | 
 21 | /**
 22 |  * Determine provider hint from custom provider flags
 23 |  * @param {Object} args - Arguments containing provider flags
 24 |  * @returns {string|undefined} Provider hint or undefined if no custom provider flag is set
 25 |  */
 26 | function getProviderHint(args) {
 27 | 	return CUSTOM_PROVIDERS_ARRAY.find((provider) => args[provider]);
 28 | }
 29 | 
 30 | /**
 31 |  * Handle setting models for different roles
 32 |  * @param {Object} args - Arguments containing role-specific model IDs
 33 |  * @param {Object} context - Context object with session, mcpLog, projectRoot
 34 |  * @returns {Object|null} Result if a model was set, null if no model setting was requested
 35 |  */
 36 | async function handleModelSetting(args, context) {
 37 | 	for (const role of MODEL_ROLES) {
 38 | 		const roleKey = `set${role.charAt(0).toUpperCase() + role.slice(1)}`; // setMain, setResearch, setFallback
 39 | 
 40 | 		if (args[roleKey]) {
 41 | 			const providerHint = getProviderHint(args);
 42 | 
 43 | 			return await setModel(role, args[roleKey], {
 44 | 				...context,
 45 | 				providerHint
 46 | 			});
 47 | 		}
 48 | 	}
 49 | 	return null; // No model setting was requested
 50 | }
 51 | 
 52 | /**
 53 |  * Get or update model configuration
 54 |  * @param {Object} args - Arguments passed by the MCP tool
 55 |  * @param {Object} log - MCP logger
 56 |  * @param {Object} context - MCP context (contains session)
 57 |  * @returns {Object} Result object with success, data/error fields
 58 |  */
 59 | export async function modelsDirect(args, log, context = {}) {
 60 | 	const { session } = context;
 61 | 	const { projectRoot } = args; // Extract projectRoot from args
 62 | 
 63 | 	// Create a logger wrapper that the core functions can use
 64 | 	const mcpLog = createLogWrapper(log);
 65 | 
 66 | 	log.info(`Executing models_direct with args: ${JSON.stringify(args)}`);
 67 | 	log.info(`Using project root: ${projectRoot}`);
 68 | 
 69 | 	// Validate flags: only one custom provider flag can be used simultaneously
 70 | 	const customProviderFlags = CUSTOM_PROVIDERS_ARRAY.filter(
 71 | 		(provider) => args[provider]
 72 | 	);
 73 | 
 74 | 	if (customProviderFlags.length > 1) {
 75 | 		log.error(
 76 | 			'Error: Cannot use multiple custom provider flags simultaneously.'
 77 | 		);
 78 | 		return {
 79 | 			success: false,
 80 | 			error: {
 81 | 				code: 'INVALID_ARGS',
 82 | 				message:
 83 | 					'Cannot use multiple custom provider flags simultaneously. Choose only one: openrouter, ollama, bedrock, azure, or vertex.'
 84 | 			}
 85 | 		};
 86 | 	}
 87 | 
 88 | 	try {
 89 | 		enableSilentMode();
 90 | 
 91 | 		try {
 92 | 			// Check for the listAvailableModels flag
 93 | 			if (args.listAvailableModels === true) {
 94 | 				return await getAvailableModelsList({
 95 | 					session,
 96 | 					mcpLog,
 97 | 					projectRoot
 98 | 				});
 99 | 			}
100 | 
101 | 			// Handle setting any model role using unified function
102 | 			const modelContext = { session, mcpLog, projectRoot };
103 | 			const modelSetResult = await handleModelSetting(args, modelContext);
104 | 			if (modelSetResult) {
105 | 				return modelSetResult;
106 | 			}
107 | 
108 | 			// Default action: get current configuration
109 | 			return await getModelConfiguration({
110 | 				session,
111 | 				mcpLog,
112 | 				projectRoot
113 | 			});
114 | 		} finally {
115 | 			disableSilentMode();
116 | 		}
117 | 	} catch (error) {
118 | 		log.error(`Error in models_direct: ${error.message}`);
119 | 		return {
120 | 			success: false,
121 | 			error: {
122 | 				code: 'DIRECT_FUNCTION_ERROR',
123 | 				message: error.message,
124 | 				details: error.stack
125 | 			}
126 | 		};
127 | 	}
128 | }
129 | 
```

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

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

```javascript
  1 | /**
  2 |  * update-tasks.js
  3 |  * Direct function implementation for updating tasks based on new context
  4 |  */
  5 | 
  6 | import path from 'path';
  7 | import { updateTasks } from '../../../../scripts/modules/task-manager.js';
  8 | import { createLogWrapper } from '../../tools/utils.js';
  9 | import {
 10 | 	enableSilentMode,
 11 | 	disableSilentMode
 12 | } from '../../../../scripts/modules/utils.js';
 13 | 
 14 | /**
 15 |  * Direct function wrapper for updating tasks based on new context.
 16 |  *
 17 |  * @param {Object} args - Command arguments containing projectRoot, from, prompt, research options.
 18 |  * @param {string} args.from - The ID of the task to update.
 19 |  * @param {string} args.prompt - The prompt to update the task with.
 20 |  * @param {boolean} args.research - Whether to use research mode.
 21 |  * @param {string} args.tasksJsonPath - Path to the tasks.json file.
 22 |  * @param {string} args.projectRoot - Project root path (for MCP/env fallback)
 23 |  * @param {string} args.tag - Tag for the task (optional)
 24 |  * @param {Object} log - Logger object.
 25 |  * @param {Object} context - Context object containing session data.
 26 |  * @returns {Promise<Object>} - Result object with success status and data/error information.
 27 |  */
 28 | export async function updateTasksDirect(args, log, context = {}) {
 29 | 	const { session } = context;
 30 | 	const { from, prompt, research, tasksJsonPath, projectRoot, tag } = args;
 31 | 
 32 | 	// Create the standard logger wrapper
 33 | 	const logWrapper = createLogWrapper(log);
 34 | 
 35 | 	// --- Input Validation ---
 36 | 	if (!projectRoot) {
 37 | 		logWrapper.error('updateTasksDirect requires a projectRoot argument.');
 38 | 		return {
 39 | 			success: false,
 40 | 			error: {
 41 | 				code: 'MISSING_ARGUMENT',
 42 | 				message: 'projectRoot is required.'
 43 | 			}
 44 | 		};
 45 | 	}
 46 | 
 47 | 	if (!from) {
 48 | 		logWrapper.error('updateTasksDirect called without from ID');
 49 | 		return {
 50 | 			success: false,
 51 | 			error: {
 52 | 				code: 'MISSING_ARGUMENT',
 53 | 				message: 'Starting task ID (from) is required'
 54 | 			}
 55 | 		};
 56 | 	}
 57 | 
 58 | 	if (!prompt) {
 59 | 		logWrapper.error('updateTasksDirect called without prompt');
 60 | 		return {
 61 | 			success: false,
 62 | 			error: {
 63 | 				code: 'MISSING_ARGUMENT',
 64 | 				message: 'Update prompt is required'
 65 | 			}
 66 | 		};
 67 | 	}
 68 | 
 69 | 	logWrapper.info(
 70 | 		`Updating tasks via direct function. From: ${from}, Research: ${research}, File: ${tasksJsonPath}, ProjectRoot: ${projectRoot}`
 71 | 	);
 72 | 
 73 | 	enableSilentMode(); // Enable silent mode
 74 | 	try {
 75 | 		// Call the core updateTasks function
 76 | 		const result = await updateTasks(
 77 | 			tasksJsonPath,
 78 | 			from,
 79 | 			prompt,
 80 | 			research,
 81 | 			{
 82 | 				session,
 83 | 				mcpLog: logWrapper,
 84 | 				projectRoot,
 85 | 				tag
 86 | 			},
 87 | 			'json'
 88 | 		);
 89 | 
 90 | 		if (result && result.success && Array.isArray(result.updatedTasks)) {
 91 | 			logWrapper.success(
 92 | 				`Successfully updated ${result.updatedTasks.length} tasks.`
 93 | 			);
 94 | 			return {
 95 | 				success: true,
 96 | 				data: {
 97 | 					message: `Successfully updated ${result.updatedTasks.length} tasks.`,
 98 | 					tasksPath: tasksJsonPath,
 99 | 					updatedCount: result.updatedTasks.length,
100 | 					telemetryData: result.telemetryData,
101 | 					tagInfo: result.tagInfo
102 | 				}
103 | 			};
104 | 		} else {
105 | 			// Handle case where core function didn't return expected success structure
106 | 			logWrapper.error(
107 | 				'Core updateTasks function did not return a successful structure.'
108 | 			);
109 | 			return {
110 | 				success: false,
111 | 				error: {
112 | 					code: 'CORE_FUNCTION_ERROR',
113 | 					message:
114 | 						result?.message ||
115 | 						'Core function failed to update tasks or returned unexpected result.'
116 | 				}
117 | 			};
118 | 		}
119 | 	} catch (error) {
120 | 		logWrapper.error(`Error executing core updateTasks: ${error.message}`);
121 | 		return {
122 | 			success: false,
123 | 			error: {
124 | 				code: 'UPDATE_TASKS_CORE_ERROR',
125 | 				message: error.message || 'Unknown error updating tasks'
126 | 			}
127 | 		};
128 | 	} finally {
129 | 		disableSilentMode(); // Ensure silent mode is disabled
130 | 	}
131 | }
132 | 
```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/clear-subtasks.js:
--------------------------------------------------------------------------------

```javascript
  1 | import path from 'path';
  2 | import chalk from 'chalk';
  3 | import boxen from 'boxen';
  4 | import Table from 'cli-table3';
  5 | 
  6 | import { log, readJSON, writeJSON, truncate, isSilentMode } from '../utils.js';
  7 | import { displayBanner } from '../ui.js';
  8 | 
  9 | /**
 10 |  * Clear subtasks from specified tasks
 11 |  * @param {string} tasksPath - Path to the tasks.json file
 12 |  * @param {string} taskIds - Task IDs to clear subtasks from
 13 |  * @param {Object} context - Context object containing projectRoot and tag
 14 |  * @param {string} [context.projectRoot] - Project root path
 15 |  * @param {string} [context.tag] - Tag for the task
 16 |  */
 17 | function clearSubtasks(tasksPath, taskIds, context = {}) {
 18 | 	const { projectRoot, tag } = context;
 19 | 	log('info', `Reading tasks from ${tasksPath}...`);
 20 | 	const data = readJSON(tasksPath, projectRoot, tag);
 21 | 	if (!data || !data.tasks) {
 22 | 		log('error', 'No valid tasks found.');
 23 | 		process.exit(1);
 24 | 	}
 25 | 
 26 | 	if (!isSilentMode()) {
 27 | 		console.log(
 28 | 			boxen(chalk.white.bold('Clearing Subtasks'), {
 29 | 				padding: 1,
 30 | 				borderColor: 'blue',
 31 | 				borderStyle: 'round',
 32 | 				margin: { top: 1, bottom: 1 }
 33 | 			})
 34 | 		);
 35 | 	}
 36 | 
 37 | 	// Handle multiple task IDs (comma-separated)
 38 | 	const taskIdArray = taskIds.split(',').map((id) => id.trim());
 39 | 	let clearedCount = 0;
 40 | 
 41 | 	// Create a summary table for the cleared subtasks
 42 | 	const summaryTable = new Table({
 43 | 		head: [
 44 | 			chalk.cyan.bold('Task ID'),
 45 | 			chalk.cyan.bold('Task Title'),
 46 | 			chalk.cyan.bold('Subtasks Cleared')
 47 | 		],
 48 | 		colWidths: [10, 50, 20],
 49 | 		style: { head: [], border: [] }
 50 | 	});
 51 | 
 52 | 	taskIdArray.forEach((taskId) => {
 53 | 		const id = parseInt(taskId, 10);
 54 | 		if (Number.isNaN(id)) {
 55 | 			log('error', `Invalid task ID: ${taskId}`);
 56 | 			return;
 57 | 		}
 58 | 
 59 | 		const task = data.tasks.find((t) => t.id === id);
 60 | 		if (!task) {
 61 | 			log('error', `Task ${id} not found`);
 62 | 			return;
 63 | 		}
 64 | 
 65 | 		if (!task.subtasks || task.subtasks.length === 0) {
 66 | 			log('info', `Task ${id} has no subtasks to clear`);
 67 | 			summaryTable.push([
 68 | 				id.toString(),
 69 | 				truncate(task.title, 47),
 70 | 				chalk.yellow('No subtasks')
 71 | 			]);
 72 | 			return;
 73 | 		}
 74 | 
 75 | 		const subtaskCount = task.subtasks.length;
 76 | 		task.subtasks = [];
 77 | 		clearedCount++;
 78 | 		log('info', `Cleared ${subtaskCount} subtasks from task ${id}`);
 79 | 
 80 | 		summaryTable.push([
 81 | 			id.toString(),
 82 | 			truncate(task.title, 47),
 83 | 			chalk.green(`${subtaskCount} subtasks cleared`)
 84 | 		]);
 85 | 	});
 86 | 
 87 | 	if (clearedCount > 0) {
 88 | 		writeJSON(tasksPath, data, projectRoot, tag);
 89 | 
 90 | 		// Show summary table
 91 | 		if (!isSilentMode()) {
 92 | 			console.log(
 93 | 				boxen(chalk.white.bold('Subtask Clearing Summary:'), {
 94 | 					padding: { left: 2, right: 2, top: 0, bottom: 0 },
 95 | 					margin: { top: 1, bottom: 0 },
 96 | 					borderColor: 'blue',
 97 | 					borderStyle: 'round'
 98 | 				})
 99 | 			);
100 | 			console.log(summaryTable.toString());
101 | 		}
102 | 
103 | 		// Success message
104 | 		if (!isSilentMode()) {
105 | 			console.log(
106 | 				boxen(
107 | 					chalk.green(
108 | 						`Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)`
109 | 					),
110 | 					{
111 | 						padding: 1,
112 | 						borderColor: 'green',
113 | 						borderStyle: 'round',
114 | 						margin: { top: 1 }
115 | 					}
116 | 				)
117 | 			);
118 | 
119 | 			// Next steps suggestion
120 | 			console.log(
121 | 				boxen(
122 | 					chalk.white.bold('Next Steps:') +
123 | 						'\n\n' +
124 | 						`${chalk.cyan('1.')} Run ${chalk.yellow('task-master expand --id=<id>')} to generate new subtasks\n` +
125 | 						`${chalk.cyan('2.')} Run ${chalk.yellow('task-master list --with-subtasks')} to verify changes`,
126 | 					{
127 | 						padding: 1,
128 | 						borderColor: 'cyan',
129 | 						borderStyle: 'round',
130 | 						margin: { top: 1 }
131 | 					}
132 | 				)
133 | 			);
134 | 		}
135 | 	} else {
136 | 		if (!isSilentMode()) {
137 | 			console.log(
138 | 				boxen(chalk.yellow('No subtasks were cleared'), {
139 | 					padding: 1,
140 | 					borderColor: 'yellow',
141 | 					borderStyle: 'round',
142 | 					margin: { top: 1 }
143 | 				})
144 | 			);
145 | 		}
146 | 	}
147 | }
148 | 
149 | export default clearSubtasks;
150 | 
```

--------------------------------------------------------------------------------
/.taskmaster/docs/prd-tm-start.txt:
--------------------------------------------------------------------------------

```
 1 | <context>
 2 | # Overview
 3 | Add a new CLI command: `task-master start <task_id>` (alias: `tm start <task_id>`). This command hard-codes `claude-code` as the executor, fetches task details, builds a standardized prompt, runs claude-code, shows the result, checks for git changes, and auto-marks the task as done if successful.
 4 | 
 5 | We follow the Commander class pattern, reuse task retrieval from `show` command flow. Extremely minimal for 1-hour hackathon timeline.
 6 | 
 7 | # Core Features
 8 | - `start` command (Commander class style) 
 9 | - Hard-coded executor: `claude-code`
10 | - Standardized prompt designed for minimal changes following existing patterns
11 | - Shows claude-code output (no streaming)
12 | - Git status check for success detection
13 | - Auto-mark task done if successful
14 | 
15 | # User Experience
16 | ```
17 | task-master start 12
18 | ```
19 | 1) Fetches Task #12 details
20 | 2) Builds standardized prompt with task context
21 | 3) Runs claude-code with the prompt
22 | 4) Shows output
23 | 5) Checks git status for changes
24 | 6) Auto-marks task done if changes detected
25 | </context>
26 | 
27 | <PRD>
28 | # Technical Architecture
29 | 
30 | - Command pattern:
31 |   - Create `apps/cli/src/commands/start.command.ts` modeled on [list.command.ts](mdc:apps/cli/src/commands/list.command.ts) and task lookup from [show.command.ts](mdc:apps/cli/src/commands/show.command.ts)
32 | 
33 | - Task retrieval:
34 |   - Use `@tm/core` via `createTaskMasterCore` to get task by ID
35 |   - Extract: id, title, description, details
36 | 
37 | - Executor (ultra-simple approach):
38 |   - Execute `claude "full prompt here"` command directly
39 |   - The prompt tells Claude to first run `tm show <task_id>` to get task details
40 |   - Then tells Claude to implement the code changes
41 |   - This opens Claude CLI interface naturally in the current terminal
42 |   - No subprocess management needed - just execute the command
43 | 
44 | - Execution flow:
45 |   1) Validate `<task_id>` exists; exit with error if not
46 |   2) Build standardized prompt that includes instructions to run `tm show <task_id>`
47 |   3) Execute `claude "prompt"` command directly in terminal
48 |   4) Claude CLI opens, runs `tm show`, then implements changes
49 |   5) After Claude session ends, run `git status --porcelain` to detect changes
50 |   6) If changes detected, auto-run `task-master set-status --id=<task_id> --status=done`
51 | 
52 | - Success criteria:
53 |   - Success = exit code 0 AND git shows modified/created files
54 |   - Print changed file paths; warn if no changes detected
55 | 
56 | # Development Roadmap
57 | 
58 | MVP (ship in ~1 hour):
59 | 1) Implement `start.command.ts` (Commander class), parse `<task_id>`
60 | 2) Validate task exists via tm-core 
61 | 3) Build prompt that tells Claude to run `tm show <task_id>` then implement
62 | 4) Execute `claude "prompt"` command, then check git status and auto-mark done
63 | 
64 | # Risks and Mitigations
65 | - Executor availability: Error clearly if `claude-code` provider fails
66 | - False success: Git-change heuristic acceptable for hackathon MVP
67 | 
68 | # Appendix
69 | 
70 | **Standardized Prompt Template:**
71 | ```
72 | You are an AI coding assistant with access to this repository's codebase.
73 | 
74 | First, run this command to get the task details:
75 | tm show <task_id>
76 | 
77 | Then implement the task with these requirements:
78 | - Make the SMALLEST number of code changes possible
79 | - Follow ALL existing patterns in the codebase (you have access to analyze the code)
80 | - Do NOT over-engineer the solution
81 | - Use existing files/functions/patterns wherever possible
82 | - When complete, print: COMPLETED: <brief summary of changes>
83 | 
84 | Begin by running tm show <task_id> to understand what needs to be implemented.
85 | ```
86 | 
87 | **Key References:**
88 | - [list.command.ts](mdc:apps/cli/src/commands/list.command.ts) - Command structure
89 | - [show.command.ts](mdc:apps/cli/src/commands/show.command.ts) - Task validation
90 | - Node.js `child_process.exec()` - For executing `claude "prompt"` command
91 | </PRD>
```

--------------------------------------------------------------------------------
/packages/tm-core/src/subpath-exports.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Test file documenting subpath export usage
  3 |  * This demonstrates how consumers can use granular imports for better tree-shaking
  4 |  */
  5 | 
  6 | import { describe, it, expect } from 'vitest';
  7 | 
  8 | describe('Subpath Exports', () => {
  9 | 	it('should allow importing from auth subpath', async () => {
 10 | 		// Instead of: import { AuthManager } from '@tm/core';
 11 | 		// Use: import { AuthManager } from '@tm/core/auth';
 12 | 		const authModule = await import('./auth');
 13 | 		expect(authModule.AuthManager).toBeDefined();
 14 | 		expect(authModule.AuthenticationError).toBeDefined();
 15 | 	});
 16 | 
 17 | 	it('should allow importing from storage subpath', async () => {
 18 | 		// Instead of: import { FileStorage } from '@tm/core';
 19 | 		// Use: import { FileStorage } from '@tm/core/storage';
 20 | 		const storageModule = await import('./storage');
 21 | 		expect(storageModule.FileStorage).toBeDefined();
 22 | 		expect(storageModule.ApiStorage).toBeDefined();
 23 | 		expect(storageModule.StorageFactory).toBeDefined();
 24 | 	});
 25 | 
 26 | 	it('should allow importing from config subpath', async () => {
 27 | 		// Instead of: import { ConfigManager } from '@tm/core';
 28 | 		// Use: import { ConfigManager } from '@tm/core/config';
 29 | 		const configModule = await import('./config');
 30 | 		expect(configModule.ConfigManager).toBeDefined();
 31 | 	});
 32 | 
 33 | 	it('should allow importing from errors subpath', async () => {
 34 | 		// Instead of: import { TaskMasterError } from '@tm/core';
 35 | 		// Use: import { TaskMasterError } from '@tm/core/errors';
 36 | 		const errorsModule = await import('./errors');
 37 | 		expect(errorsModule.TaskMasterError).toBeDefined();
 38 | 		expect(errorsModule.ERROR_CODES).toBeDefined();
 39 | 	});
 40 | 
 41 | 	it('should allow importing from logger subpath', async () => {
 42 | 		// Instead of: import { getLogger } from '@tm/core';
 43 | 		// Use: import { getLogger } from '@tm/core/logger';
 44 | 		const loggerModule = await import('./logger');
 45 | 		expect(loggerModule.getLogger).toBeDefined();
 46 | 		expect(loggerModule.createLogger).toBeDefined();
 47 | 	});
 48 | 
 49 | 	it('should allow importing from providers subpath', async () => {
 50 | 		// Instead of: import { BaseProvider } from '@tm/core';
 51 | 		// Use: import { BaseProvider } from '@tm/core/providers';
 52 | 		const providersModule = await import('./providers');
 53 | 		expect(providersModule.BaseProvider).toBeDefined();
 54 | 	});
 55 | 
 56 | 	it('should allow importing from services subpath', async () => {
 57 | 		// Instead of: import { TaskService } from '@tm/core';
 58 | 		// Use: import { TaskService } from '@tm/core/services';
 59 | 		const servicesModule = await import('./services');
 60 | 		expect(servicesModule.TaskService).toBeDefined();
 61 | 	});
 62 | 
 63 | 	it('should allow importing from utils subpath', async () => {
 64 | 		// Instead of: import { generateId } from '@tm/core';
 65 | 		// Use: import { generateId } from '@tm/core/utils';
 66 | 		const utilsModule = await import('./utils');
 67 | 		expect(utilsModule.generateId).toBeDefined();
 68 | 	});
 69 | });
 70 | 
 71 | /**
 72 |  * Usage Examples for Consumers:
 73 |  *
 74 |  * 1. Import only authentication (smaller bundle):
 75 |  * ```typescript
 76 |  * import { AuthManager, AuthenticationError } from '@tm/core/auth';
 77 |  * ```
 78 |  *
 79 |  * 2. Import only storage (no auth code bundled):
 80 |  * ```typescript
 81 |  * import { FileStorage, StorageFactory } from '@tm/core/storage';
 82 |  * ```
 83 |  *
 84 |  * 3. Import only errors (minimal bundle):
 85 |  * ```typescript
 86 |  * import { TaskMasterError, ERROR_CODES } from '@tm/core/errors';
 87 |  * ```
 88 |  *
 89 |  * 4. Still support convenience imports (larger bundle but better DX):
 90 |  * ```typescript
 91 |  * import { AuthManager, FileStorage, TaskMasterError } from '@tm/core';
 92 |  * ```
 93 |  *
 94 |  * Benefits:
 95 |  * - Better tree-shaking: unused modules are not bundled
 96 |  * - Clearer dependencies: explicit about what parts of the library you use
 97 |  * - Faster builds: bundlers can optimize better with granular imports
 98 |  * - Smaller bundles: especially important for browser/edge deployments
 99 |  */
100 | 
```

--------------------------------------------------------------------------------
/src/prompts/research.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"id": "research",
 3 | 	"version": "1.0.0",
 4 | 	"description": "Perform AI-powered research with project context",
 5 | 	"metadata": {
 6 | 		"author": "system",
 7 | 		"created": "2024-01-01T00:00:00Z",
 8 | 		"updated": "2024-01-01T00:00:00Z",
 9 | 		"tags": ["research", "context-aware", "information-gathering"]
10 | 	},
11 | 	"parameters": {
12 | 		"query": {
13 | 			"type": "string",
14 | 			"required": true,
15 | 			"description": "Research query"
16 | 		},
17 | 		"gatheredContext": {
18 | 			"type": "string",
19 | 			"default": "",
20 | 			"description": "Gathered project context"
21 | 		},
22 | 		"detailLevel": {
23 | 			"type": "string",
24 | 			"enum": ["low", "medium", "high"],
25 | 			"default": "medium",
26 | 			"description": "Level of detail for the response"
27 | 		},
28 | 		"projectInfo": {
29 | 			"type": "object",
30 | 			"description": "Project information",
31 | 			"properties": {
32 | 				"root": {
33 | 					"type": "string",
34 | 					"description": "Project root path"
35 | 				},
36 | 				"taskCount": {
37 | 					"type": "number",
38 | 					"description": "Number of related tasks"
39 | 				},
40 | 				"fileCount": {
41 | 					"type": "number",
42 | 					"description": "Number of related files"
43 | 				}
44 | 			}
45 | 		}
46 | 	},
47 | 	"prompts": {
48 | 		"default": {
49 | 			"system": "You are an expert AI research assistant helping with a software development project. You have access to project context including tasks, files, and project structure.\n\nYour role is to provide comprehensive, accurate, and actionable research responses based on the user's query and the provided project context.\n{{#if (eq detailLevel \"low\")}}\n**Response Style: Concise & Direct**\n- Provide brief, focused answers (2-4 paragraphs maximum)\n- Focus on the most essential information\n- Use bullet points for key takeaways\n- Avoid lengthy explanations unless critical\n- Skip pleasantries, introductions, and conclusions\n- No phrases like \"Based on your project context\" or \"I'll provide guidance\"\n- No summary outros or alignment statements\n- Get straight to the actionable information\n- Use simple, direct language - users want info, not explanation{{/if}}{{#if (eq detailLevel \"medium\")}}\n**Response Style: Balanced & Comprehensive**\n- Provide thorough but well-structured responses (4-8 paragraphs)\n- Include relevant examples and explanations\n- Balance depth with readability\n- Use headings and bullet points for organization{{/if}}{{#if (eq detailLevel \"high\")}}\n**Response Style: Detailed & Exhaustive**\n- Provide comprehensive, in-depth analysis (8+ paragraphs)\n- Include multiple perspectives and approaches\n- Provide detailed examples, code snippets, and step-by-step guidance\n- Cover edge cases and potential pitfalls\n- Use clear structure with headings, subheadings, and lists{{/if}}\n\n**Guidelines:**\n- Always consider the project context when formulating responses\n- Reference specific tasks, files, or project elements when relevant\n- Provide actionable insights that can be applied to the project\n- If the query relates to existing project tasks, suggest how the research applies to those tasks\n- Use markdown formatting for better readability\n- Be precise and avoid speculation unless clearly marked as such\n{{#if (eq detailLevel \"low\")}}\n**For LOW detail level specifically:**\n- Start immediately with the core information\n- No introductory phrases or context acknowledgments\n- No concluding summaries or project alignment statements\n- Focus purely on facts, steps, and actionable items{{/if}}",
50 | 			"user": "# Research Query\n\n{{query}}\n{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}\n{{/if}}\n\n# Instructions\n\nPlease research and provide a {{detailLevel}}-detail response to the query above. Consider the project context provided and make your response as relevant and actionable as possible for this specific project."
51 | 		}
52 | 	}
53 | }
54 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/storage/file-storage/file-operations.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview File operations with atomic writes and locking
  3 |  */
  4 | 
  5 | import { promises as fs } from 'node:fs';
  6 | import type { FileStorageData } from './format-handler.js';
  7 | 
  8 | /**
  9 |  * Handles atomic file operations with locking mechanism
 10 |  */
 11 | export class FileOperations {
 12 | 	private fileLocks: Map<string, Promise<void>> = new Map();
 13 | 
 14 | 	/**
 15 | 	 * Read and parse JSON file
 16 | 	 */
 17 | 	async readJson(filePath: string): Promise<any> {
 18 | 		try {
 19 | 			const content = await fs.readFile(filePath, 'utf-8');
 20 | 			return JSON.parse(content);
 21 | 		} catch (error: any) {
 22 | 			if (error.code === 'ENOENT') {
 23 | 				throw error; // Re-throw ENOENT for caller to handle
 24 | 			}
 25 | 			if (error instanceof SyntaxError) {
 26 | 				throw new Error(`Invalid JSON in file ${filePath}: ${error.message}`);
 27 | 			}
 28 | 			throw new Error(`Failed to read file ${filePath}: ${error.message}`);
 29 | 		}
 30 | 	}
 31 | 
 32 | 	/**
 33 | 	 * Write JSON file with atomic operation and locking
 34 | 	 */
 35 | 	async writeJson(
 36 | 		filePath: string,
 37 | 		data: FileStorageData | any
 38 | 	): Promise<void> {
 39 | 		// Use file locking to prevent concurrent writes
 40 | 		const lockKey = filePath;
 41 | 		const existingLock = this.fileLocks.get(lockKey);
 42 | 
 43 | 		if (existingLock) {
 44 | 			await existingLock;
 45 | 		}
 46 | 
 47 | 		const lockPromise = this.performAtomicWrite(filePath, data);
 48 | 		this.fileLocks.set(lockKey, lockPromise);
 49 | 
 50 | 		try {
 51 | 			await lockPromise;
 52 | 		} finally {
 53 | 			this.fileLocks.delete(lockKey);
 54 | 		}
 55 | 	}
 56 | 
 57 | 	/**
 58 | 	 * Perform atomic write operation using temporary file
 59 | 	 */
 60 | 	private async performAtomicWrite(filePath: string, data: any): Promise<void> {
 61 | 		const tempPath = `${filePath}.tmp`;
 62 | 
 63 | 		try {
 64 | 			// Write to temp file first
 65 | 			const content = JSON.stringify(data, null, 2);
 66 | 			await fs.writeFile(tempPath, content, 'utf-8');
 67 | 
 68 | 			// Atomic rename
 69 | 			await fs.rename(tempPath, filePath);
 70 | 		} catch (error: any) {
 71 | 			// Clean up temp file if it exists
 72 | 			try {
 73 | 				await fs.unlink(tempPath);
 74 | 			} catch {
 75 | 				// Ignore cleanup errors
 76 | 			}
 77 | 
 78 | 			throw new Error(`Failed to write file ${filePath}: ${error.message}`);
 79 | 		}
 80 | 	}
 81 | 
 82 | 	/**
 83 | 	 * Check if file exists
 84 | 	 */
 85 | 	async exists(filePath: string): Promise<boolean> {
 86 | 		try {
 87 | 			await fs.access(filePath, fs.constants.F_OK);
 88 | 			return true;
 89 | 		} catch {
 90 | 			return false;
 91 | 		}
 92 | 	}
 93 | 
 94 | 	/**
 95 | 	 * Get file stats
 96 | 	 */
 97 | 	async getStats(filePath: string) {
 98 | 		return fs.stat(filePath);
 99 | 	}
100 | 
101 | 	/**
102 | 	 * Read directory contents
103 | 	 */
104 | 	async readDir(dirPath: string): Promise<string[]> {
105 | 		return fs.readdir(dirPath);
106 | 	}
107 | 
108 | 	/**
109 | 	 * Create directory recursively
110 | 	 */
111 | 	async ensureDir(dirPath: string): Promise<void> {
112 | 		try {
113 | 			await fs.mkdir(dirPath, { recursive: true });
114 | 		} catch (error: any) {
115 | 			throw new Error(
116 | 				`Failed to create directory ${dirPath}: ${error.message}`
117 | 			);
118 | 		}
119 | 	}
120 | 
121 | 	/**
122 | 	 * Delete file
123 | 	 */
124 | 	async deleteFile(filePath: string): Promise<void> {
125 | 		try {
126 | 			await fs.unlink(filePath);
127 | 		} catch (error: any) {
128 | 			if (error.code !== 'ENOENT') {
129 | 				throw new Error(`Failed to delete file ${filePath}: ${error.message}`);
130 | 			}
131 | 		}
132 | 	}
133 | 
134 | 	/**
135 | 	 * Rename/move file
136 | 	 */
137 | 	async moveFile(oldPath: string, newPath: string): Promise<void> {
138 | 		try {
139 | 			await fs.rename(oldPath, newPath);
140 | 		} catch (error: any) {
141 | 			throw new Error(
142 | 				`Failed to move file from ${oldPath} to ${newPath}: ${error.message}`
143 | 			);
144 | 		}
145 | 	}
146 | 
147 | 	/**
148 | 	 * Copy file
149 | 	 */
150 | 	async copyFile(srcPath: string, destPath: string): Promise<void> {
151 | 		try {
152 | 			await fs.copyFile(srcPath, destPath);
153 | 		} catch (error: any) {
154 | 			throw new Error(
155 | 				`Failed to copy file from ${srcPath} to ${destPath}: ${error.message}`
156 | 			);
157 | 		}
158 | 	}
159 | 
160 | 	/**
161 | 	 * Clean up all pending file operations
162 | 	 */
163 | 	async cleanup(): Promise<void> {
164 | 		const locks = Array.from(this.fileLocks.values());
165 | 		if (locks.length > 0) {
166 | 			await Promise.all(locks);
167 | 		}
168 | 		this.fileLocks.clear();
169 | 	}
170 | }
171 | 
```

--------------------------------------------------------------------------------
/apps/extension/src/webview/components/TagDropdown.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import React, { useState, useEffect, useRef } from 'react';
  2 | 
  3 | interface TagDropdownProps {
  4 | 	currentTag: string;
  5 | 	availableTags: string[];
  6 | 	onTagSwitch: (tagName: string) => Promise<void>;
  7 | 	sendMessage: (message: any) => Promise<any>;
  8 | 	dispatch: React.Dispatch<any>;
  9 | }
 10 | 
 11 | export const TagDropdown: React.FC<TagDropdownProps> = ({
 12 | 	currentTag,
 13 | 	availableTags,
 14 | 	onTagSwitch,
 15 | 	sendMessage,
 16 | 	dispatch
 17 | }) => {
 18 | 	const [isOpen, setIsOpen] = useState(false);
 19 | 	const [isLoading, setIsLoading] = useState(false);
 20 | 	const dropdownRef = useRef<HTMLDivElement>(null);
 21 | 
 22 | 	// Fetch tags when component mounts
 23 | 	useEffect(() => {
 24 | 		fetchTags();
 25 | 	}, []);
 26 | 
 27 | 	// Handle click outside to close dropdown
 28 | 	useEffect(() => {
 29 | 		const handleClickOutside = (event: MouseEvent) => {
 30 | 			if (
 31 | 				dropdownRef.current &&
 32 | 				!dropdownRef.current.contains(event.target as Node)
 33 | 			) {
 34 | 				setIsOpen(false);
 35 | 			}
 36 | 		};
 37 | 
 38 | 		if (isOpen) {
 39 | 			document.addEventListener('mousedown', handleClickOutside);
 40 | 			return () => {
 41 | 				document.removeEventListener('mousedown', handleClickOutside);
 42 | 			};
 43 | 		}
 44 | 	}, [isOpen]);
 45 | 
 46 | 	const fetchTags = async () => {
 47 | 		try {
 48 | 			const result = await sendMessage({ type: 'getTags' });
 49 | 
 50 | 			if (result?.tags && result?.currentTag) {
 51 | 				const tagNames = result.tags.map((tag: any) => tag.name || tag);
 52 | 				dispatch({
 53 | 					type: 'SET_TAG_DATA',
 54 | 					payload: {
 55 | 						currentTag: result.currentTag,
 56 | 						availableTags: tagNames
 57 | 					}
 58 | 				});
 59 | 			}
 60 | 		} catch (error) {
 61 | 			console.error('Failed to fetch tags:', error);
 62 | 		}
 63 | 	};
 64 | 
 65 | 	const handleTagSwitch = async (tagName: string) => {
 66 | 		if (tagName === currentTag) {
 67 | 			setIsOpen(false);
 68 | 			return;
 69 | 		}
 70 | 
 71 | 		setIsLoading(true);
 72 | 		try {
 73 | 			await onTagSwitch(tagName);
 74 | 			dispatch({ type: 'SET_CURRENT_TAG', payload: tagName });
 75 | 			setIsOpen(false);
 76 | 		} catch (error) {
 77 | 			console.error('Failed to switch tag:', error);
 78 | 		} finally {
 79 | 			setIsLoading(false);
 80 | 		}
 81 | 	};
 82 | 
 83 | 	return (
 84 | 		<div className="relative" ref={dropdownRef}>
 85 | 			<button
 86 | 				onClick={() => setIsOpen(!isOpen)}
 87 | 				disabled={isLoading}
 88 | 				className="flex items-center gap-2 px-3 py-1.5 text-sm bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded hover:bg-vscode-list-hoverBackground transition-colors"
 89 | 			>
 90 | 				<span className="font-medium">{currentTag}</span>
 91 | 				<svg
 92 | 					className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''}`}
 93 | 					fill="none"
 94 | 					stroke="currentColor"
 95 | 					viewBox="0 0 24 24"
 96 | 				>
 97 | 					<path
 98 | 						strokeLinecap="round"
 99 | 						strokeLinejoin="round"
100 | 						strokeWidth={2}
101 | 						d="M19 9l-7 7-7-7"
102 | 					/>
103 | 				</svg>
104 | 			</button>
105 | 
106 | 			{isOpen && (
107 | 				<div className="absolute top-full mt-1 right-0 bg-background border border-vscode-dropdown-border rounded shadow-lg z-50 min-w-[200px] py-1">
108 | 					{availableTags.map((tag) => (
109 | 						<button
110 | 							key={tag}
111 | 							onClick={() => handleTagSwitch(tag)}
112 | 							className={`w-full text-left px-3 py-2 text-sm transition-colors flex items-center justify-between group
113 | 								${
114 | 									tag === currentTag
115 | 										? 'bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground'
116 | 										: 'hover:bg-vscode-list-hoverBackground text-vscode-dropdown-foreground'
117 | 								}`}
118 | 						>
119 | 							<span className="truncate pr-2">{tag}</span>
120 | 							{tag === currentTag && (
121 | 								<svg
122 | 									className="w-4 h-4 flex-shrink-0 text-vscode-textLink-foreground"
123 | 									fill="none"
124 | 									stroke="currentColor"
125 | 									viewBox="0 0 24 24"
126 | 								>
127 | 									<path
128 | 										strokeLinecap="round"
129 | 										strokeLinejoin="round"
130 | 										strokeWidth={2}
131 | 										d="M5 13l4 4L19 7"
132 | 									/>
133 | 								</svg>
134 | 							)}
135 | 						</button>
136 | 					))}
137 | 				</div>
138 | 			)}
139 | 		</div>
140 | 	);
141 | };
142 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/auth/auth-manager.test.ts:
--------------------------------------------------------------------------------

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

--------------------------------------------------------------------------------
/tests/integration/profiles/opencode-init-functionality.test.js:
--------------------------------------------------------------------------------

```javascript
 1 | import fs from 'fs';
 2 | import path from 'path';
 3 | import { opencodeProfile } from '../../../src/profiles/opencode.js';
 4 | 
 5 | describe('OpenCode Profile Initialization Functionality', () => {
 6 | 	let opencodeProfileContent;
 7 | 
 8 | 	beforeAll(() => {
 9 | 		const opencodeJsPath = path.join(
10 | 			process.cwd(),
11 | 			'src',
12 | 			'profiles',
13 | 			'opencode.js'
14 | 		);
15 | 		opencodeProfileContent = fs.readFileSync(opencodeJsPath, 'utf8');
16 | 	});
17 | 
18 | 	test('opencode.js has correct asset-only profile configuration', () => {
19 | 		// Check for explicit, non-default values in the source file
20 | 		expect(opencodeProfileContent).toContain("name: 'opencode'");
21 | 		expect(opencodeProfileContent).toContain("displayName: 'OpenCode'");
22 | 		expect(opencodeProfileContent).toContain("url: 'opencode.ai'");
23 | 		expect(opencodeProfileContent).toContain("docsUrl: 'opencode.ai/docs/'");
24 | 		expect(opencodeProfileContent).toContain("profileDir: '.'"); // non-default
25 | 		expect(opencodeProfileContent).toContain("rulesDir: '.'"); // non-default
26 | 		expect(opencodeProfileContent).toContain("mcpConfigName: 'opencode.json'"); // non-default
27 | 		expect(opencodeProfileContent).toContain('includeDefaultRules: false'); // non-default
28 | 		expect(opencodeProfileContent).toContain("'AGENTS.md': 'AGENTS.md'");
29 | 
30 | 		// Check the final computed properties on the profile object
31 | 		expect(opencodeProfile.profileName).toBe('opencode');
32 | 		expect(opencodeProfile.displayName).toBe('OpenCode');
33 | 		expect(opencodeProfile.profileDir).toBe('.');
34 | 		expect(opencodeProfile.rulesDir).toBe('.');
35 | 		expect(opencodeProfile.mcpConfig).toBe(true); // computed from mcpConfigName
36 | 		expect(opencodeProfile.mcpConfigName).toBe('opencode.json');
37 | 		expect(opencodeProfile.mcpConfigPath).toBe('opencode.json'); // computed
38 | 		expect(opencodeProfile.includeDefaultRules).toBe(false);
39 | 		expect(opencodeProfile.fileMap['AGENTS.md']).toBe('AGENTS.md');
40 | 	});
41 | 
42 | 	test('opencode.js has lifecycle functions for MCP config transformation', () => {
43 | 		expect(opencodeProfileContent).toContain(
44 | 			'function onPostConvertRulesProfile'
45 | 		);
46 | 		expect(opencodeProfileContent).toContain('function onRemoveRulesProfile');
47 | 		expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
48 | 	});
49 | 
50 | 	test('opencode.js handles opencode.json transformation in lifecycle functions', () => {
51 | 		expect(opencodeProfileContent).toContain('opencode.json');
52 | 		expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
53 | 		expect(opencodeProfileContent).toContain('$schema');
54 | 		expect(opencodeProfileContent).toContain('mcpServers');
55 | 		expect(opencodeProfileContent).toContain('mcp');
56 | 	});
57 | 
58 | 	test('opencode.js has proper error handling in lifecycle functions', () => {
59 | 		expect(opencodeProfileContent).toContain('try {');
60 | 		expect(opencodeProfileContent).toContain('} catch (error) {');
61 | 		expect(opencodeProfileContent).toContain('log(');
62 | 	});
63 | 
64 | 	test('opencode.js uses custom MCP config name', () => {
65 | 		// OpenCode uses opencode.json instead of mcp.json
66 | 		expect(opencodeProfileContent).toContain("mcpConfigName: 'opencode.json'");
67 | 		// Should not contain mcp.json as a config value (comments are OK)
68 | 		expect(opencodeProfileContent).not.toMatch(
69 | 			/mcpConfigName:\s*['"]mcp\.json['"]/
70 | 		);
71 | 	});
72 | 
73 | 	test('opencode.js has transformation logic for OpenCode format', () => {
74 | 		// Check for transformation function
75 | 		expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
76 | 
77 | 		// Check for specific transformation logic
78 | 		expect(opencodeProfileContent).toContain('mcpServers');
79 | 		expect(opencodeProfileContent).toContain('command');
80 | 		expect(opencodeProfileContent).toContain('args');
81 | 		expect(opencodeProfileContent).toContain('environment');
82 | 		expect(opencodeProfileContent).toContain('enabled');
83 | 		expect(opencodeProfileContent).toContain('type');
84 | 	});
85 | });
86 | 
```

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

```javascript
  1 | /**
  2 |  * remove-task.js
  3 |  * Direct function implementation for removing a task
  4 |  */
  5 | 
  6 | import {
  7 | 	removeTask,
  8 | 	taskExists
  9 | } from '../../../../scripts/modules/task-manager.js';
 10 | import {
 11 | 	enableSilentMode,
 12 | 	disableSilentMode,
 13 | 	readJSON
 14 | } from '../../../../scripts/modules/utils.js';
 15 | 
 16 | /**
 17 |  * Direct function wrapper for removeTask with error handling.
 18 |  * Supports removing multiple tasks at once with comma-separated IDs.
 19 |  *
 20 |  * @param {Object} args - Command arguments
 21 |  * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
 22 |  * @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple).
 23 |  * @param {string} args.projectRoot - Project root path (for MCP/env fallback)
 24 |  * @param {string} args.tag - Tag for the task (optional)
 25 |  * @param {Object} log - Logger object
 26 |  * @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } }
 27 |  */
 28 | export async function removeTaskDirect(args, log, context = {}) {
 29 | 	// Destructure expected args
 30 | 	const { tasksJsonPath, id, projectRoot, tag } = args;
 31 | 	const { session } = context;
 32 | 	try {
 33 | 		// Check if tasksJsonPath was provided
 34 | 		if (!tasksJsonPath) {
 35 | 			log.error('removeTaskDirect called without tasksJsonPath');
 36 | 			return {
 37 | 				success: false,
 38 | 				error: {
 39 | 					code: 'MISSING_ARGUMENT',
 40 | 					message: 'tasksJsonPath is required'
 41 | 				}
 42 | 			};
 43 | 		}
 44 | 
 45 | 		// Validate task ID parameter
 46 | 		if (!id) {
 47 | 			log.error('Task ID is required');
 48 | 			return {
 49 | 				success: false,
 50 | 				error: {
 51 | 					code: 'INPUT_VALIDATION_ERROR',
 52 | 					message: 'Task ID is required'
 53 | 				}
 54 | 			};
 55 | 		}
 56 | 
 57 | 		// Split task IDs if comma-separated
 58 | 		const taskIdArray = id.split(',').map((taskId) => taskId.trim());
 59 | 
 60 | 		log.info(
 61 | 			`Removing ${taskIdArray.length} task(s) with ID(s): ${taskIdArray.join(', ')} from ${tasksJsonPath}${tag ? ` in tag '${tag}'` : ''}`
 62 | 		);
 63 | 
 64 | 		// Validate all task IDs exist before proceeding
 65 | 		const data = readJSON(tasksJsonPath, projectRoot, tag);
 66 | 		if (!data || !data.tasks) {
 67 | 			return {
 68 | 				success: false,
 69 | 				error: {
 70 | 					code: 'INVALID_TASKS_FILE',
 71 | 					message: `No valid tasks found in ${tasksJsonPath}${tag ? ` for tag '${tag}'` : ''}`
 72 | 				}
 73 | 			};
 74 | 		}
 75 | 
 76 | 		const invalidTasks = taskIdArray.filter(
 77 | 			(taskId) => !taskExists(data.tasks, taskId)
 78 | 		);
 79 | 
 80 | 		if (invalidTasks.length > 0) {
 81 | 			return {
 82 | 				success: false,
 83 | 				error: {
 84 | 					code: 'INVALID_TASK_ID',
 85 | 					message: `The following tasks were not found${tag ? ` in tag '${tag}'` : ''}: ${invalidTasks.join(', ')}`
 86 | 				}
 87 | 			};
 88 | 		}
 89 | 
 90 | 		// Enable silent mode to prevent console logs from interfering with JSON response
 91 | 		enableSilentMode();
 92 | 
 93 | 		try {
 94 | 			// Call removeTask with proper context including tag
 95 | 			const result = await removeTask(tasksJsonPath, id, {
 96 | 				projectRoot,
 97 | 				tag
 98 | 			});
 99 | 
100 | 			if (!result.success) {
101 | 				return {
102 | 					success: false,
103 | 					error: {
104 | 						code: 'REMOVE_TASK_ERROR',
105 | 						message: result.error || 'Failed to remove tasks'
106 | 					}
107 | 				};
108 | 			}
109 | 
110 | 			log.info(`Successfully removed ${result.removedTasks.length} task(s)`);
111 | 
112 | 			return {
113 | 				success: true,
114 | 				data: {
115 | 					totalTasks: taskIdArray.length,
116 | 					successful: result.removedTasks.length,
117 | 					failed: taskIdArray.length - result.removedTasks.length,
118 | 					removedTasks: result.removedTasks,
119 | 					message: result.message,
120 | 					tasksPath: tasksJsonPath,
121 | 					tag
122 | 				}
123 | 			};
124 | 		} finally {
125 | 			// Restore normal logging
126 | 			disableSilentMode();
127 | 		}
128 | 	} catch (error) {
129 | 		// Ensure silent mode is disabled even if an outer error occurs
130 | 		disableSilentMode();
131 | 
132 | 		// Catch any unexpected errors
133 | 		log.error(`Unexpected error in removeTaskDirect: ${error.message}`);
134 | 		return {
135 | 			success: false,
136 | 			error: {
137 | 				code: 'UNEXPECTED_ERROR',
138 | 				message: error.message
139 | 			}
140 | 		};
141 | 	}
142 | }
143 | 
```

--------------------------------------------------------------------------------
/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 | 
```

--------------------------------------------------------------------------------
/.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 | 
```

--------------------------------------------------------------------------------
/apps/docs/getting-started/quick-start/configuration-quick.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Configuration
  3 | sidebarTitle: "Configuration"
  4 | 
  5 | ---
  6 | 
  7 | Before getting started with Task Master, you'll need to set up your API keys. There are a couple of ways to do this depending on whether you're using the CLI or working inside MCP. It's also a good time to start getting familiar with the other configuration options available — even if you don’t need to adjust them yet, knowing what’s possible will help down the line.
  8 | 
  9 | ## API Key Setup
 10 | 
 11 | Task Master uses environment variables to securely store provider API keys and optional endpoint URLs.
 12 | 
 13 | ### MCP Usage: mcp.json file
 14 | 
 15 | For MCP/Cursor usage: Configure keys in the env section of your .cursor/mcp.json file.
 16 | 
 17 | ```java .env lines icon="java"
 18 | {
 19 | 	"mcpServers": {
 20 | 		"task-master-ai": {
 21 | 			"command": "npx",
 22 | 			"args": ["-y", "task-master-ai"],
 23 | 			"env": {
 24 | 				"ANTHROPIC_API_KEY": "ANTHROPIC_API_KEY_HERE",
 25 | 				"PERPLEXITY_API_KEY": "PERPLEXITY_API_KEY_HERE",
 26 | 				"OPENAI_API_KEY": "OPENAI_API_KEY_HERE",
 27 | 				"GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE",
 28 | 				"XAI_API_KEY": "XAI_API_KEY_HERE",
 29 | 				"OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE",
 30 | 				"MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE",
 31 | 				"AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE",
 32 | 				"OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE",
 33 | 				"GITHUB_API_KEY": "GITHUB_API_KEY_HERE"
 34 | 			}
 35 | 		}
 36 | 	}
 37 | }
 38 | ```
 39 | 
 40 | ### CLI Usage: `.env` File
 41 | 
 42 | Create a `.env` file in your project root and include the keys for the providers you plan to use:
 43 | 
 44 | 
 45 | 
 46 | ```java .env lines icon="java"
 47 | # Required API keys for providers configured in .taskmaster/config.json
 48 | ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
 49 | PERPLEXITY_API_KEY=pplx-your-key-here
 50 | # OPENAI_API_KEY=sk-your-key-here
 51 | # GOOGLE_API_KEY=AIzaSy...
 52 | # AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
 53 | # etc.
 54 | 
 55 | # Optional Endpoint Overrides
 56 | # Use a specific provider's base URL, e.g., for an OpenAI-compatible API
 57 | # OPENAI_BASE_URL=https://api.third-party.com/v1
 58 | #
 59 | # Azure OpenAI Configuration
 60 | # AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/ or https://your-endpoint-name.cognitiveservices.azure.com/openai/deployments
 61 | # OLLAMA_BASE_URL=http://custom-ollama-host:11434/api
 62 | 
 63 | # Google Vertex AI Configuration (Required if using 'vertex' provider)
 64 | # VERTEX_PROJECT_ID=your-gcp-project-id
 65 | ```
 66 | 
 67 | ## What Else Can Be Configured?
 68 | 
 69 | The main configuration file (`.taskmaster/config.json`) allows you to control nearly every aspect of Task Master’s behavior. Here’s a high-level look at what you can customize:
 70 | 
 71 | <Tip>
 72 | You don’t need to configure everything up front. Most settings can be left as defaults or updated later as your workflow evolves.
 73 | </Tip>
 74 | 
 75 | <Accordion title="View Configuration Options">
 76 | 
 77 | ### Models and Providers
 78 | - Role-based model setup: `main`, `research`, `fallback`
 79 | - Provider selection (Anthropic, OpenAI, Perplexity, etc.)
 80 | - Model IDs per role
 81 | - Temperature, max tokens, and other generation settings
 82 | - Custom base URLs for OpenAI-compatible APIs
 83 | 
 84 | ### Global Settings
 85 | - `logLevel`: Logging verbosity
 86 | - `debug`: Enable/disable debug mode
 87 | - `projectName`: Optional name for your project
 88 | - `defaultTag`: Default tag for task grouping
 89 | - `defaultSubtasks`: Number of subtasks to auto-generate
 90 | - `defaultPriority`: Priority level for new tasks
 91 | 
 92 | ### API Endpoint Overrides
 93 | - `ollamaBaseURL`: Custom Ollama server URL
 94 | - `azureBaseURL`: Global Azure endpoint
 95 | - `vertexProjectId`: Google Vertex AI project ID
 96 | - `vertexLocation`: Region for Vertex AI models
 97 | 
 98 | ### Tag and Git Integration
 99 | - Default tag context per project
100 | - Support for task isolation by tag
101 | - Manual tag creation from Git branches
102 | 
103 | ### State Management
104 | - Active tag tracking
105 | - Migration state
106 | - Last tag switch timestamp
107 | 
108 | </Accordion>
109 | 
110 | <Note>
111 | For advanced configuration options and detailed customization, see our [Advanced Configuration Guide](/docs/best-practices/configuration-advanced) page.
112 | </Note>
```

--------------------------------------------------------------------------------
/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 | 
```

--------------------------------------------------------------------------------
/src/ai-providers/custom-sdk/claude-code/message-converter.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * @fileoverview Converts AI SDK prompt format to Claude Code message format
  3 |  */
  4 | 
  5 | /**
  6 |  * Convert AI SDK prompt to Claude Code messages format
  7 |  * @param {Array} prompt - AI SDK prompt array
  8 |  * @param {Object} [mode] - Generation mode
  9 |  * @param {string} mode.type - Mode type ('regular', 'object-json', 'object-tool')
 10 |  * @returns {{messagesPrompt: string, systemPrompt?: string}}
 11 |  */
 12 | export function convertToClaudeCodeMessages(prompt, mode) {
 13 | 	const messages = [];
 14 | 	let systemPrompt;
 15 | 
 16 | 	for (const message of prompt) {
 17 | 		switch (message.role) {
 18 | 			case 'system':
 19 | 				systemPrompt = message.content;
 20 | 				break;
 21 | 
 22 | 			case 'user':
 23 | 				if (typeof message.content === 'string') {
 24 | 					messages.push(message.content);
 25 | 				} else {
 26 | 					// Handle multi-part content
 27 | 					const textParts = message.content
 28 | 						.filter((part) => part.type === 'text')
 29 | 						.map((part) => part.text)
 30 | 						.join('\n');
 31 | 
 32 | 					if (textParts) {
 33 | 						messages.push(textParts);
 34 | 					}
 35 | 
 36 | 					// Note: Image parts are not supported by Claude Code CLI
 37 | 					const imageParts = message.content.filter(
 38 | 						(part) => part.type === 'image'
 39 | 					);
 40 | 					if (imageParts.length > 0) {
 41 | 						console.warn(
 42 | 							'Claude Code CLI does not support image inputs. Images will be ignored.'
 43 | 						);
 44 | 					}
 45 | 				}
 46 | 				break;
 47 | 
 48 | 			case 'assistant':
 49 | 				if (typeof message.content === 'string') {
 50 | 					messages.push(`Assistant: ${message.content}`);
 51 | 				} else {
 52 | 					const textParts = message.content
 53 | 						.filter((part) => part.type === 'text')
 54 | 						.map((part) => part.text)
 55 | 						.join('\n');
 56 | 
 57 | 					if (textParts) {
 58 | 						messages.push(`Assistant: ${textParts}`);
 59 | 					}
 60 | 
 61 | 					// Handle tool calls if present
 62 | 					const toolCalls = message.content.filter(
 63 | 						(part) => part.type === 'tool-call'
 64 | 					);
 65 | 					if (toolCalls.length > 0) {
 66 | 						// For now, we'll just note that tool calls were made
 67 | 						messages.push(`Assistant: [Tool calls made]`);
 68 | 					}
 69 | 				}
 70 | 				break;
 71 | 
 72 | 			case 'tool':
 73 | 				// Tool results could be included in the conversation
 74 | 				messages.push(
 75 | 					`Tool Result (${message.content[0].toolName}): ${JSON.stringify(
 76 | 						message.content[0].result
 77 | 					)}`
 78 | 				);
 79 | 				break;
 80 | 		}
 81 | 	}
 82 | 
 83 | 	// For the SDK, we need to provide a single prompt string
 84 | 	// Format the conversation history properly
 85 | 
 86 | 	// Combine system prompt with messages
 87 | 	let finalPrompt = '';
 88 | 
 89 | 	// Add system prompt at the beginning if present
 90 | 	if (systemPrompt) {
 91 | 		finalPrompt = systemPrompt;
 92 | 	}
 93 | 
 94 | 	if (messages.length === 0) {
 95 | 		return { messagesPrompt: finalPrompt, systemPrompt };
 96 | 	}
 97 | 
 98 | 	// Format messages
 99 | 	const formattedMessages = [];
100 | 	for (let i = 0; i < messages.length; i++) {
101 | 		const msg = messages[i];
102 | 		// Check if this is a user or assistant message based on content
103 | 		if (msg.startsWith('Assistant:') || msg.startsWith('Tool Result')) {
104 | 			formattedMessages.push(msg);
105 | 		} else {
106 | 			// User messages
107 | 			formattedMessages.push(`Human: ${msg}`);
108 | 		}
109 | 	}
110 | 
111 | 	// Combine system prompt with messages
112 | 	if (finalPrompt) {
113 | 		finalPrompt = finalPrompt + '\n\n' + formattedMessages.join('\n\n');
114 | 	} else {
115 | 		finalPrompt = formattedMessages.join('\n\n');
116 | 	}
117 | 
118 | 	// For JSON mode, add explicit instruction to ensure JSON output
119 | 	if (mode?.type === 'object-json') {
120 | 		// Make the JSON instruction even more explicit
121 | 		finalPrompt = `${finalPrompt}
122 | 
123 | CRITICAL INSTRUCTION: You MUST respond with ONLY valid JSON. Follow these rules EXACTLY:
124 | 1. Start your response with an opening brace {
125 | 2. End your response with a closing brace }
126 | 3. Do NOT include any text before the opening brace
127 | 4. Do NOT include any text after the closing brace
128 | 5. Do NOT use markdown code blocks or backticks
129 | 6. Do NOT include explanations or commentary
130 | 7. The ENTIRE response must be valid JSON that can be parsed with JSON.parse()
131 | 
132 | Begin your response with { and end with }`;
133 | 	}
134 | 
135 | 	return {
136 | 		messagesPrompt: finalPrompt,
137 | 		systemPrompt
138 | 	};
139 | }
140 | 
```

--------------------------------------------------------------------------------
/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 | 
```

--------------------------------------------------------------------------------
/src/profiles/roo.js:
--------------------------------------------------------------------------------

```javascript
  1 | // Roo Code conversion profile for rule-transformer
  2 | import path from 'path';
  3 | import fs from 'fs';
  4 | import { isSilentMode, log } from '../../scripts/modules/utils.js';
  5 | import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
  6 | import { ROO_MODES } from '../constants/profiles.js';
  7 | 
  8 | // Lifecycle functions for Roo profile
  9 | function onAddRulesProfile(targetDir, assetsDir) {
 10 | 	// Use the provided assets directory to find the roocode directory
 11 | 	const sourceDir = path.join(assetsDir, 'roocode');
 12 | 
 13 | 	if (!fs.existsSync(sourceDir)) {
 14 | 		log('error', `[Roo] Source directory does not exist: ${sourceDir}`);
 15 | 		return;
 16 | 	}
 17 | 
 18 | 	copyRecursiveSync(sourceDir, targetDir);
 19 | 	log('debug', `[Roo] Copied roocode directory to ${targetDir}`);
 20 | 
 21 | 	const rooModesDir = path.join(sourceDir, '.roo');
 22 | 
 23 | 	// Copy .roomodes to project root
 24 | 	const roomodesSrc = path.join(sourceDir, '.roomodes');
 25 | 	const roomodesDest = path.join(targetDir, '.roomodes');
 26 | 	if (fs.existsSync(roomodesSrc)) {
 27 | 		try {
 28 | 			fs.copyFileSync(roomodesSrc, roomodesDest);
 29 | 			log('debug', `[Roo] Copied .roomodes to ${roomodesDest}`);
 30 | 		} catch (err) {
 31 | 			log('error', `[Roo] Failed to copy .roomodes: ${err.message}`);
 32 | 		}
 33 | 	}
 34 | 
 35 | 	for (const mode of ROO_MODES) {
 36 | 		const src = path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`);
 37 | 		const dest = path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`);
 38 | 		if (fs.existsSync(src)) {
 39 | 			try {
 40 | 				const destDir = path.dirname(dest);
 41 | 				if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
 42 | 				fs.copyFileSync(src, dest);
 43 | 				log('debug', `[Roo] Copied ${mode}-rules to ${dest}`);
 44 | 			} catch (err) {
 45 | 				log('error', `[Roo] Failed to copy ${src} to ${dest}: ${err.message}`);
 46 | 			}
 47 | 		}
 48 | 	}
 49 | }
 50 | 
 51 | function copyRecursiveSync(src, dest) {
 52 | 	const exists = fs.existsSync(src);
 53 | 	const stats = exists && fs.statSync(src);
 54 | 	const isDirectory = exists && stats.isDirectory();
 55 | 	if (isDirectory) {
 56 | 		if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
 57 | 		fs.readdirSync(src).forEach((childItemName) => {
 58 | 			copyRecursiveSync(
 59 | 				path.join(src, childItemName),
 60 | 				path.join(dest, childItemName)
 61 | 			);
 62 | 		});
 63 | 	} else {
 64 | 		fs.copyFileSync(src, dest);
 65 | 	}
 66 | }
 67 | 
 68 | function onRemoveRulesProfile(targetDir) {
 69 | 	const roomodesPath = path.join(targetDir, '.roomodes');
 70 | 	if (fs.existsSync(roomodesPath)) {
 71 | 		try {
 72 | 			fs.rmSync(roomodesPath, { force: true });
 73 | 			log('debug', `[Roo] Removed .roomodes from ${roomodesPath}`);
 74 | 		} catch (err) {
 75 | 			log('error', `[Roo] Failed to remove .roomodes: ${err.message}`);
 76 | 		}
 77 | 	}
 78 | 
 79 | 	const rooDir = path.join(targetDir, '.roo');
 80 | 	if (fs.existsSync(rooDir)) {
 81 | 		fs.readdirSync(rooDir).forEach((entry) => {
 82 | 			if (entry.startsWith('rules-')) {
 83 | 				const modeDir = path.join(rooDir, entry);
 84 | 				try {
 85 | 					fs.rmSync(modeDir, { recursive: true, force: true });
 86 | 					log('debug', `[Roo] Removed ${entry} directory from ${modeDir}`);
 87 | 				} catch (err) {
 88 | 					log('error', `[Roo] Failed to remove ${modeDir}: ${err.message}`);
 89 | 				}
 90 | 			}
 91 | 		});
 92 | 		if (fs.readdirSync(rooDir).length === 0) {
 93 | 			try {
 94 | 				fs.rmSync(rooDir, { recursive: true, force: true });
 95 | 				log('debug', `[Roo] Removed empty .roo directory from ${rooDir}`);
 96 | 			} catch (err) {
 97 | 				log('error', `[Roo] Failed to remove .roo directory: ${err.message}`);
 98 | 			}
 99 | 		}
100 | 	}
101 | }
102 | 
103 | function onPostConvertRulesProfile(targetDir, assetsDir) {
104 | 	onAddRulesProfile(targetDir, assetsDir);
105 | }
106 | 
107 | // Create and export roo profile using the base factory
108 | export const rooProfile = createProfile({
109 | 	name: 'roo',
110 | 	displayName: 'Roo Code',
111 | 	url: 'roocode.com',
112 | 	docsUrl: 'docs.roocode.com',
113 | 	toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE,
114 | 	onAdd: onAddRulesProfile,
115 | 	onRemove: onRemoveRulesProfile,
116 | 	onPostConvert: onPostConvertRulesProfile
117 | });
118 | 
119 | // Export lifecycle functions separately to avoid naming conflicts
120 | export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };
121 | 
```

--------------------------------------------------------------------------------
/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 | 
```

--------------------------------------------------------------------------------
/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 { createTaskMasterCore, type TaskMasterCore } 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?: TaskMasterCore;
 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!.startTask(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 createTaskMasterCore({ 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 | 		if (this.tmCore) {
148 | 			try {
149 | 				await this.tmCore.close();
150 | 				this.tmCore = undefined;
151 | 			} catch (error) {
152 | 				this.logger.error('Failed to close tm-core:', error);
153 | 			}
154 | 		}
155 | 	}
156 | }
157 | 
```

--------------------------------------------------------------------------------
/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 | 
```

--------------------------------------------------------------------------------
/tests/unit/prompts/expand-task-prompt.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | import { PromptManager } from '../../../scripts/modules/prompt-manager.js';
  3 | 
  4 | describe('expand-task prompt template', () => {
  5 | 	let promptManager;
  6 | 
  7 | 	beforeEach(() => {
  8 | 		promptManager = new PromptManager();
  9 | 	});
 10 | 
 11 | 	const testTask = {
 12 | 		id: 1,
 13 | 		title: 'Setup AWS Infrastructure',
 14 | 		description: 'Provision core AWS services',
 15 | 		details: 'Create VPC, subnets, and security groups'
 16 | 	};
 17 | 
 18 | 	const baseParams = {
 19 | 		task: testTask,
 20 | 		subtaskCount: 3,
 21 | 		nextSubtaskId: 1,
 22 | 		additionalContext: '',
 23 | 		complexityReasoningContext: '',
 24 | 		gatheredContext: '',
 25 | 		useResearch: false,
 26 | 		expansionPrompt: undefined
 27 | 	};
 28 | 
 29 | 	test('default variant includes task context', () => {
 30 | 		const { userPrompt } = promptManager.loadPrompt(
 31 | 			'expand-task',
 32 | 			baseParams,
 33 | 			'default'
 34 | 		);
 35 | 
 36 | 		expect(userPrompt).toContain(testTask.title);
 37 | 		expect(userPrompt).toContain(testTask.description);
 38 | 		expect(userPrompt).toContain(testTask.details);
 39 | 		expect(userPrompt).toContain('Task ID: 1');
 40 | 	});
 41 | 
 42 | 	test('research variant includes task context', () => {
 43 | 		const params = { ...baseParams, useResearch: true };
 44 | 		const { userPrompt } = promptManager.loadPrompt(
 45 | 			'expand-task',
 46 | 			params,
 47 | 			'research'
 48 | 		);
 49 | 
 50 | 		expect(userPrompt).toContain(testTask.title);
 51 | 		expect(userPrompt).toContain(testTask.description);
 52 | 		expect(userPrompt).toContain(testTask.details);
 53 | 		expect(userPrompt).toContain('Parent Task:');
 54 | 		expect(userPrompt).toContain('ID: 1');
 55 | 	});
 56 | 
 57 | 	test('complexity-report variant includes task context', () => {
 58 | 		const params = {
 59 | 			...baseParams,
 60 | 			expansionPrompt: 'Focus on security best practices',
 61 | 			complexityReasoningContext: 'High complexity due to security requirements'
 62 | 		};
 63 | 		const { userPrompt } = promptManager.loadPrompt(
 64 | 			'expand-task',
 65 | 			params,
 66 | 			'complexity-report'
 67 | 		);
 68 | 
 69 | 		// The fix ensures task context is included
 70 | 		expect(userPrompt).toContain('Parent Task:');
 71 | 		expect(userPrompt).toContain(`ID: ${testTask.id}`);
 72 | 		expect(userPrompt).toContain(`Title: ${testTask.title}`);
 73 | 		expect(userPrompt).toContain(`Description: ${testTask.description}`);
 74 | 		expect(userPrompt).toContain(`Current details: ${testTask.details}`);
 75 | 
 76 | 		// Also includes the expansion prompt
 77 | 		expect(userPrompt).toContain('Expansion Guidance:');
 78 | 		expect(userPrompt).toContain(params.expansionPrompt);
 79 | 		expect(userPrompt).toContain(params.complexityReasoningContext);
 80 | 	});
 81 | 
 82 | 	test('all variants request JSON format with subtasks array', () => {
 83 | 		const variants = ['default', 'research', 'complexity-report'];
 84 | 
 85 | 		variants.forEach((variant) => {
 86 | 			const params =
 87 | 				variant === 'complexity-report'
 88 | 					? { ...baseParams, expansionPrompt: 'test' }
 89 | 					: baseParams;
 90 | 
 91 | 			const { systemPrompt, userPrompt } = promptManager.loadPrompt(
 92 | 				'expand-task',
 93 | 				params,
 94 | 				variant
 95 | 			);
 96 | 			const combined = systemPrompt + userPrompt;
 97 | 
 98 | 			expect(combined.toLowerCase()).toContain('subtasks');
 99 | 			expect(combined).toContain('JSON');
100 | 		});
101 | 	});
102 | 
103 | 	test('complexity-report variant fails without task context regression test', () => {
104 | 		// This test ensures we don't regress to the old behavior where
105 | 		// complexity-report variant only used expansionPrompt without task context
106 | 		const params = {
107 | 			...baseParams,
108 | 			expansionPrompt: 'Generic expansion prompt'
109 | 		};
110 | 
111 | 		const { userPrompt } = promptManager.loadPrompt(
112 | 			'expand-task',
113 | 			params,
114 | 			'complexity-report'
115 | 		);
116 | 
117 | 		// Count occurrences of task-specific content
118 | 		const titleOccurrences = (
119 | 			userPrompt.match(new RegExp(testTask.title, 'g')) || []
120 | 		).length;
121 | 		const descriptionOccurrences = (
122 | 			userPrompt.match(new RegExp(testTask.description, 'g')) || []
123 | 		).length;
124 | 
125 | 		// Should have at least one occurrence of title and description
126 | 		expect(titleOccurrences).toBeGreaterThanOrEqual(1);
127 | 		expect(descriptionOccurrences).toBeGreaterThanOrEqual(1);
128 | 
129 | 		// Should not be ONLY the expansion prompt
130 | 		expect(userPrompt.length).toBeGreaterThan(
131 | 			params.expansionPrompt.length + 100
132 | 		);
133 | 	});
134 | });
135 | 
```

--------------------------------------------------------------------------------
/src/ai-providers/custom-sdk/grok-cli/errors.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * @fileoverview Error handling utilities for Grok CLI provider
  3 |  */
  4 | 
  5 | import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';
  6 | 
  7 | /**
  8 |  * @typedef {import('./types.js').GrokCliErrorMetadata} GrokCliErrorMetadata
  9 |  */
 10 | 
 11 | /**
 12 |  * Create an API call error with Grok CLI specific metadata
 13 |  * @param {Object} params - Error parameters
 14 |  * @param {string} params.message - Error message
 15 |  * @param {string} [params.code] - Error code
 16 |  * @param {number} [params.exitCode] - Process exit code
 17 |  * @param {string} [params.stderr] - Standard error output
 18 |  * @param {string} [params.stdout] - Standard output
 19 |  * @param {string} [params.promptExcerpt] - Excerpt of the prompt
 20 |  * @param {boolean} [params.isRetryable=false] - Whether the error is retryable
 21 |  * @returns {APICallError}
 22 |  */
 23 | export function createAPICallError({
 24 | 	message,
 25 | 	code,
 26 | 	exitCode,
 27 | 	stderr,
 28 | 	stdout,
 29 | 	promptExcerpt,
 30 | 	isRetryable = false
 31 | }) {
 32 | 	/** @type {GrokCliErrorMetadata} */
 33 | 	const metadata = {
 34 | 		code,
 35 | 		exitCode,
 36 | 		stderr,
 37 | 		stdout,
 38 | 		promptExcerpt
 39 | 	};
 40 | 
 41 | 	return new APICallError({
 42 | 		message,
 43 | 		isRetryable,
 44 | 		url: 'grok-cli://command',
 45 | 		requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
 46 | 		data: metadata
 47 | 	});
 48 | }
 49 | 
 50 | /**
 51 |  * Create an authentication error
 52 |  * @param {Object} params - Error parameters
 53 |  * @param {string} params.message - Error message
 54 |  * @returns {LoadAPIKeyError}
 55 |  */
 56 | export function createAuthenticationError({ message }) {
 57 | 	return new LoadAPIKeyError({
 58 | 		message:
 59 | 			message ||
 60 | 			'Authentication failed. Please ensure Grok CLI is properly configured with API key.'
 61 | 	});
 62 | }
 63 | 
 64 | /**
 65 |  * Create a timeout error
 66 |  * @param {Object} params - Error parameters
 67 |  * @param {string} params.message - Error message
 68 |  * @param {string} [params.promptExcerpt] - Excerpt of the prompt
 69 |  * @param {number} params.timeoutMs - Timeout in milliseconds
 70 |  * @returns {APICallError}
 71 |  */
 72 | export function createTimeoutError({ message, promptExcerpt, timeoutMs }) {
 73 | 	/** @type {GrokCliErrorMetadata & { timeoutMs: number }} */
 74 | 	const metadata = {
 75 | 		code: 'TIMEOUT',
 76 | 		promptExcerpt,
 77 | 		timeoutMs
 78 | 	};
 79 | 
 80 | 	return new APICallError({
 81 | 		message,
 82 | 		isRetryable: true,
 83 | 		url: 'grok-cli://command',
 84 | 		requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
 85 | 		data: metadata
 86 | 	});
 87 | }
 88 | 
 89 | /**
 90 |  * Create a CLI installation error
 91 |  * @param {Object} params - Error parameters
 92 |  * @param {string} [params.message] - Error message
 93 |  * @returns {APICallError}
 94 |  */
 95 | export function createInstallationError({ message }) {
 96 | 	return new APICallError({
 97 | 		message:
 98 | 			message ||
 99 | 			'Grok CLI is not installed or not found in PATH. Please install with: npm install -g @vibe-kit/grok-cli',
100 | 		isRetryable: false,
101 | 		url: 'grok-cli://installation'
102 | 	});
103 | }
104 | 
105 | /**
106 |  * Check if an error is an authentication error
107 |  * @param {unknown} error - Error to check
108 |  * @returns {boolean}
109 |  */
110 | export function isAuthenticationError(error) {
111 | 	if (error instanceof LoadAPIKeyError) return true;
112 | 	if (
113 | 		error instanceof APICallError &&
114 | 		/** @type {GrokCliErrorMetadata} */ (error.data)?.exitCode === 401
115 | 	)
116 | 		return true;
117 | 	return false;
118 | }
119 | 
120 | /**
121 |  * Check if an error is a timeout error
122 |  * @param {unknown} error - Error to check
123 |  * @returns {boolean}
124 |  */
125 | export function isTimeoutError(error) {
126 | 	if (
127 | 		error instanceof APICallError &&
128 | 		/** @type {GrokCliErrorMetadata} */ (error.data)?.code === 'TIMEOUT'
129 | 	)
130 | 		return true;
131 | 	return false;
132 | }
133 | 
134 | /**
135 |  * Check if an error is an installation error
136 |  * @param {unknown} error - Error to check
137 |  * @returns {boolean}
138 |  */
139 | export function isInstallationError(error) {
140 | 	if (error instanceof APICallError && error.url === 'grok-cli://installation')
141 | 		return true;
142 | 	return false;
143 | }
144 | 
145 | /**
146 |  * Get error metadata from an error
147 |  * @param {unknown} error - Error to extract metadata from
148 |  * @returns {GrokCliErrorMetadata|undefined}
149 |  */
150 | export function getErrorMetadata(error) {
151 | 	if (error instanceof APICallError && error.data) {
152 | 		return /** @type {GrokCliErrorMetadata} */ (error.data);
153 | 	}
154 | 	return undefined;
155 | }
156 | 
```

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

```typescript
  1 | /**
  2 |  * @fileoverview Unit tests for ConfigLoader service
  3 |  */
  4 | 
  5 | import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
  6 | import { promises as fs } from 'node:fs';
  7 | import { ConfigLoader } from './config-loader.service.js';
  8 | import { DEFAULT_CONFIG_VALUES } from '../../interfaces/configuration.interface.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 | 
```
Page 10/52FirstPrevNextLast