This is page 27 of 69. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── commands
│ │ └── dedupe.md
│ └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│ └── marketplace.json
├── .coderabbit.yaml
├── .cursor
│ ├── mcp.json
│ └── rules
│ ├── ai_providers.mdc
│ ├── ai_services.mdc
│ ├── architecture.mdc
│ ├── changeset.mdc
│ ├── commands.mdc
│ ├── context_gathering.mdc
│ ├── cursor_rules.mdc
│ ├── dependencies.mdc
│ ├── dev_workflow.mdc
│ ├── git_workflow.mdc
│ ├── glossary.mdc
│ ├── mcp.mdc
│ ├── new_features.mdc
│ ├── self_improve.mdc
│ ├── tags.mdc
│ ├── taskmaster.mdc
│ ├── tasks.mdc
│ ├── telemetry.mdc
│ ├── test_workflow.mdc
│ ├── tests.mdc
│ ├── ui.mdc
│ └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── enhancements---feature-requests.md
│ │ └── feedback.md
│ ├── PULL_REQUEST_TEMPLATE
│ │ ├── bugfix.md
│ │ ├── config.yml
│ │ ├── feature.md
│ │ └── integration.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── scripts
│ │ ├── auto-close-duplicates.mjs
│ │ ├── backfill-duplicate-comments.mjs
│ │ ├── check-pre-release-mode.mjs
│ │ ├── parse-metrics.mjs
│ │ ├── release.mjs
│ │ ├── tag-extension.mjs
│ │ ├── utils.mjs
│ │ └── validate-changesets.mjs
│ └── workflows
│ ├── auto-close-duplicates.yml
│ ├── backfill-duplicate-comments.yml
│ ├── ci.yml
│ ├── claude-dedupe-issues.yml
│ ├── claude-docs-trigger.yml
│ ├── claude-docs-updater.yml
│ ├── claude-issue-triage.yml
│ ├── claude.yml
│ ├── extension-ci.yml
│ ├── extension-release.yml
│ ├── log-issue-events.yml
│ ├── pre-release.yml
│ ├── release-check.yml
│ ├── release.yml
│ ├── update-models-md.yml
│ └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│ ├── hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── settings
│ │ └── mcp.json
│ └── steering
│ ├── dev_workflow.md
│ ├── kiro_rules.md
│ ├── self_improve.md
│ ├── taskmaster_hooks_workflow.md
│ └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│ ├── CLAUDE.md
│ ├── config.json
│ ├── docs
│ │ ├── autonomous-tdd-git-workflow.md
│ │ ├── MIGRATION-ROADMAP.md
│ │ ├── prd-tm-start.txt
│ │ ├── prd.txt
│ │ ├── README.md
│ │ ├── research
│ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│ │ │ ├── 2025-06-14_test-save-functionality.md
│ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│ │ ├── task-template-importing-prd.txt
│ │ ├── tdd-workflow-phase-0-spike.md
│ │ ├── tdd-workflow-phase-1-core-rails.md
│ │ ├── tdd-workflow-phase-1-orchestrator.md
│ │ ├── tdd-workflow-phase-2-pr-resumability.md
│ │ ├── tdd-workflow-phase-3-extensibility-guardrails.md
│ │ ├── test-prd.txt
│ │ └── tm-core-phase-1.txt
│ ├── reports
│ │ ├── task-complexity-report_autonomous-tdd-git-workflow.json
│ │ ├── task-complexity-report_cc-kiro-hooks.json
│ │ ├── task-complexity-report_tdd-phase-1-core-rails.json
│ │ ├── task-complexity-report_tdd-workflow-phase-0.json
│ │ ├── task-complexity-report_test-prd-tag.json
│ │ ├── task-complexity-report_tm-core-phase-1.json
│ │ ├── task-complexity-report.json
│ │ └── tm-core-complexity.json
│ ├── state.json
│ ├── tasks
│ │ ├── task_001_tm-start.txt
│ │ ├── task_002_tm-start.txt
│ │ ├── task_003_tm-start.txt
│ │ ├── task_004_tm-start.txt
│ │ ├── task_007_tm-start.txt
│ │ └── tasks.json
│ └── templates
│ ├── example_prd_rpg.md
│ └── example_prd.md
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── apps
│ ├── cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── command-registry.ts
│ │ │ ├── commands
│ │ │ │ ├── auth.command.ts
│ │ │ │ ├── autopilot
│ │ │ │ │ ├── abort.command.ts
│ │ │ │ │ ├── commit.command.ts
│ │ │ │ │ ├── complete.command.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next.command.ts
│ │ │ │ │ ├── resume.command.ts
│ │ │ │ │ ├── shared.ts
│ │ │ │ │ ├── start.command.ts
│ │ │ │ │ └── status.command.ts
│ │ │ │ ├── briefs.command.ts
│ │ │ │ ├── context.command.ts
│ │ │ │ ├── export.command.ts
│ │ │ │ ├── list.command.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── custom-providers.ts
│ │ │ │ │ ├── fetchers.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompts.ts
│ │ │ │ │ ├── setup.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── next.command.ts
│ │ │ │ ├── set-status.command.ts
│ │ │ │ ├── show.command.ts
│ │ │ │ ├── start.command.ts
│ │ │ │ └── tags.command.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── model-management.ts
│ │ │ ├── types
│ │ │ │ └── tag-management.d.ts
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── cardBox.component.ts
│ │ │ │ │ ├── dashboard.component.ts
│ │ │ │ │ ├── header.component.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next-task.component.ts
│ │ │ │ │ ├── suggested-steps.component.ts
│ │ │ │ │ └── task-detail.component.ts
│ │ │ │ ├── display
│ │ │ │ │ ├── messages.ts
│ │ │ │ │ └── tables.ts
│ │ │ │ ├── formatters
│ │ │ │ │ ├── complexity-formatters.ts
│ │ │ │ │ ├── dependency-formatters.ts
│ │ │ │ │ ├── priority-formatters.ts
│ │ │ │ │ ├── status-formatters.spec.ts
│ │ │ │ │ └── status-formatters.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── layout
│ │ │ │ ├── helpers.spec.ts
│ │ │ │ └── helpers.ts
│ │ │ └── utils
│ │ │ ├── auth-helpers.ts
│ │ │ ├── auto-update.ts
│ │ │ ├── brief-selection.ts
│ │ │ ├── display-helpers.ts
│ │ │ ├── error-handler.ts
│ │ │ ├── index.ts
│ │ │ ├── project-root.ts
│ │ │ ├── task-status.ts
│ │ │ ├── ui.spec.ts
│ │ │ └── ui.ts
│ │ ├── tests
│ │ │ ├── integration
│ │ │ │ └── commands
│ │ │ │ └── autopilot
│ │ │ │ └── workflow.test.ts
│ │ │ └── unit
│ │ │ ├── commands
│ │ │ │ ├── autopilot
│ │ │ │ │ └── shared.test.ts
│ │ │ │ ├── list.command.spec.ts
│ │ │ │ └── show.command.spec.ts
│ │ │ └── ui
│ │ │ └── dashboard.component.spec.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── docs
│ │ ├── archive
│ │ │ ├── ai-client-utils-example.mdx
│ │ │ ├── ai-development-workflow.mdx
│ │ │ ├── command-reference.mdx
│ │ │ ├── configuration.mdx
│ │ │ ├── cursor-setup.mdx
│ │ │ ├── examples.mdx
│ │ │ └── Installation.mdx
│ │ ├── best-practices
│ │ │ ├── advanced-tasks.mdx
│ │ │ ├── configuration-advanced.mdx
│ │ │ └── index.mdx
│ │ ├── capabilities
│ │ │ ├── cli-root-commands.mdx
│ │ │ ├── index.mdx
│ │ │ ├── mcp.mdx
│ │ │ ├── rpg-method.mdx
│ │ │ └── task-structure.mdx
│ │ ├── CHANGELOG.md
│ │ ├── command-reference.mdx
│ │ ├── configuration.mdx
│ │ ├── docs.json
│ │ ├── favicon.svg
│ │ ├── getting-started
│ │ │ ├── api-keys.mdx
│ │ │ ├── contribute.mdx
│ │ │ ├── faq.mdx
│ │ │ └── quick-start
│ │ │ ├── configuration-quick.mdx
│ │ │ ├── execute-quick.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── moving-forward.mdx
│ │ │ ├── prd-quick.mdx
│ │ │ ├── quick-start.mdx
│ │ │ ├── requirements.mdx
│ │ │ ├── rules-quick.mdx
│ │ │ └── tasks-quick.mdx
│ │ ├── introduction.mdx
│ │ ├── licensing.md
│ │ ├── logo
│ │ │ ├── dark.svg
│ │ │ ├── light.svg
│ │ │ └── task-master-logo.png
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── style.css
│ │ ├── tdd-workflow
│ │ │ ├── ai-agent-integration.mdx
│ │ │ └── quickstart.mdx
│ │ ├── vercel.json
│ │ └── whats-new.mdx
│ ├── extension
│ │ ├── .vscodeignore
│ │ ├── assets
│ │ │ ├── banner.png
│ │ │ ├── icon-dark.svg
│ │ │ ├── icon-light.svg
│ │ │ ├── icon.png
│ │ │ ├── screenshots
│ │ │ │ ├── kanban-board.png
│ │ │ │ └── task-details.png
│ │ │ └── sidebar-icon.svg
│ │ ├── CHANGELOG.md
│ │ ├── components.json
│ │ ├── docs
│ │ │ ├── extension-CI-setup.md
│ │ │ └── extension-development-guide.md
│ │ ├── esbuild.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── package.mjs
│ │ ├── package.publish.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── components
│ │ │ │ ├── ConfigView.tsx
│ │ │ │ ├── constants.ts
│ │ │ │ ├── TaskDetails
│ │ │ │ │ ├── AIActionsSection.tsx
│ │ │ │ │ ├── DetailsSection.tsx
│ │ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ │ ├── SubtasksSection.tsx
│ │ │ │ │ ├── TaskMetadataSidebar.tsx
│ │ │ │ │ └── useTaskDetails.ts
│ │ │ │ ├── TaskDetailsView.tsx
│ │ │ │ ├── TaskMasterLogo.tsx
│ │ │ │ └── ui
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── breadcrumb.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── collapsible.tsx
│ │ │ │ ├── CollapsibleSection.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── scroll-area.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── shadcn-io
│ │ │ │ │ └── kanban
│ │ │ │ │ └── index.tsx
│ │ │ │ └── textarea.tsx
│ │ │ ├── extension.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── utils.ts
│ │ │ ├── services
│ │ │ │ ├── config-service.ts
│ │ │ │ ├── error-handler.ts
│ │ │ │ ├── notification-preferences.ts
│ │ │ │ ├── polling-service.ts
│ │ │ │ ├── polling-strategies.ts
│ │ │ │ ├── sidebar-webview-manager.ts
│ │ │ │ ├── task-repository.ts
│ │ │ │ ├── terminal-manager.ts
│ │ │ │ └── webview-manager.ts
│ │ │ ├── test
│ │ │ │ └── extension.test.ts
│ │ │ ├── utils
│ │ │ │ ├── configManager.ts
│ │ │ │ ├── connectionManager.ts
│ │ │ │ ├── errorHandler.ts
│ │ │ │ ├── event-emitter.ts
│ │ │ │ ├── logger.ts
│ │ │ │ ├── mcpClient.ts
│ │ │ │ ├── notificationPreferences.ts
│ │ │ │ └── task-master-api
│ │ │ │ ├── cache
│ │ │ │ │ └── cache-manager.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mcp-client.ts
│ │ │ │ ├── transformers
│ │ │ │ │ └── task-transformer.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ └── webview
│ │ │ ├── App.tsx
│ │ │ ├── components
│ │ │ │ ├── AppContent.tsx
│ │ │ │ ├── EmptyState.tsx
│ │ │ │ ├── ErrorBoundary.tsx
│ │ │ │ ├── PollingStatus.tsx
│ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ ├── SidebarView.tsx
│ │ │ │ ├── TagDropdown.tsx
│ │ │ │ ├── TaskCard.tsx
│ │ │ │ ├── TaskEditModal.tsx
│ │ │ │ ├── TaskMasterKanban.tsx
│ │ │ │ ├── ToastContainer.tsx
│ │ │ │ └── ToastNotification.tsx
│ │ │ ├── constants
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ └── VSCodeContext.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useTaskQueries.ts
│ │ │ │ ├── useVSCodeMessages.ts
│ │ │ │ └── useWebviewHeight.ts
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── providers
│ │ │ │ └── QueryProvider.tsx
│ │ │ ├── reducers
│ │ │ │ └── appReducer.ts
│ │ │ ├── sidebar.tsx
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ └── utils
│ │ │ ├── logger.ts
│ │ │ └── toast.ts
│ │ └── tsconfig.json
│ └── mcp
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── tools
│ │ ├── autopilot
│ │ │ ├── abort.tool.ts
│ │ │ ├── commit.tool.ts
│ │ │ ├── complete.tool.ts
│ │ │ ├── finalize.tool.ts
│ │ │ ├── index.ts
│ │ │ ├── next.tool.ts
│ │ │ ├── resume.tool.ts
│ │ │ ├── start.tool.ts
│ │ │ └── status.tool.ts
│ │ ├── README-ZOD-V3.md
│ │ └── tasks
│ │ ├── get-task.tool.ts
│ │ ├── get-tasks.tool.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── assets
│ ├── .windsurfrules
│ ├── AGENTS.md
│ ├── claude
│ │ └── TM_COMMANDS_GUIDE.md
│ ├── config.json
│ ├── env.example
│ ├── example_prd_rpg.txt
│ ├── example_prd.txt
│ ├── GEMINI.md
│ ├── gitignore
│ ├── kiro-hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── roocode
│ │ ├── .roo
│ │ │ ├── rules-architect
│ │ │ │ └── architect-rules
│ │ │ ├── rules-ask
│ │ │ │ └── ask-rules
│ │ │ ├── rules-code
│ │ │ │ └── code-rules
│ │ │ ├── rules-debug
│ │ │ │ └── debug-rules
│ │ │ ├── rules-orchestrator
│ │ │ │ └── orchestrator-rules
│ │ │ └── rules-test
│ │ │ └── test-rules
│ │ └── .roomodes
│ ├── rules
│ │ ├── cursor_rules.mdc
│ │ ├── dev_workflow.mdc
│ │ ├── self_improve.mdc
│ │ ├── taskmaster_hooks_workflow.mdc
│ │ └── taskmaster.mdc
│ └── scripts_README.md
├── bin
│ └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE_CODE_PLUGIN.md
├── CLAUDE.md
├── context
│ ├── chats
│ │ ├── add-task-dependencies-1.md
│ │ └── max-min-tokens.txt.md
│ ├── fastmcp-core.txt
│ ├── fastmcp-docs.txt
│ ├── MCP_INTEGRATION.md
│ ├── mcp-js-sdk-docs.txt
│ ├── mcp-protocol-repo.txt
│ ├── mcp-protocol-schema-03262025.json
│ └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│ ├── claude-code-integration.md
│ ├── CLI-COMMANDER-PATTERN.md
│ ├── command-reference.md
│ ├── configuration.md
│ ├── contributor-docs
│ │ ├── testing-roo-integration.md
│ │ └── worktree-setup.md
│ ├── cross-tag-task-movement.md
│ ├── examples
│ │ ├── claude-code-usage.md
│ │ └── codex-cli-usage.md
│ ├── examples.md
│ ├── licensing.md
│ ├── mcp-provider-guide.md
│ ├── mcp-provider.md
│ ├── migration-guide.md
│ ├── models.md
│ ├── providers
│ │ ├── codex-cli.md
│ │ └── gemini-cli.md
│ ├── README.md
│ ├── scripts
│ │ └── models-json-to-markdown.js
│ ├── task-structure.md
│ └── tutorial.md
├── images
│ ├── hamster-hiring.png
│ └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│ ├── server.js
│ └── src
│ ├── core
│ │ ├── __tests__
│ │ │ └── context-manager.test.js
│ │ ├── context-manager.js
│ │ ├── direct-functions
│ │ │ ├── add-dependency.js
│ │ │ ├── add-subtask.js
│ │ │ ├── add-tag.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── cache-stats.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── complexity-report.js
│ │ │ ├── copy-tag.js
│ │ │ ├── create-tag-from-branch.js
│ │ │ ├── delete-tag.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── fix-dependencies.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── initialize-project.js
│ │ │ ├── list-tags.js
│ │ │ ├── models.js
│ │ │ ├── move-task-cross-tag.js
│ │ │ ├── move-task.js
│ │ │ ├── next-task.js
│ │ │ ├── parse-prd.js
│ │ │ ├── remove-dependency.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── rename-tag.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── rules.js
│ │ │ ├── scope-down.js
│ │ │ ├── scope-up.js
│ │ │ ├── set-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ ├── update-tasks.js
│ │ │ ├── use-tag.js
│ │ │ └── validate-dependencies.js
│ │ ├── task-master-core.js
│ │ └── utils
│ │ ├── env-utils.js
│ │ └── path-utils.js
│ ├── custom-sdk
│ │ ├── errors.js
│ │ ├── index.js
│ │ ├── json-extractor.js
│ │ ├── language-model.js
│ │ ├── message-converter.js
│ │ └── schema-converter.js
│ ├── index.js
│ ├── logger.js
│ ├── providers
│ │ └── mcp-provider.js
│ └── tools
│ ├── add-dependency.js
│ ├── add-subtask.js
│ ├── add-tag.js
│ ├── add-task.js
│ ├── analyze.js
│ ├── clear-subtasks.js
│ ├── complexity-report.js
│ ├── copy-tag.js
│ ├── delete-tag.js
│ ├── expand-all.js
│ ├── expand-task.js
│ ├── fix-dependencies.js
│ ├── generate.js
│ ├── get-operation-status.js
│ ├── index.js
│ ├── initialize-project.js
│ ├── list-tags.js
│ ├── models.js
│ ├── move-task.js
│ ├── next-task.js
│ ├── parse-prd.js
│ ├── README-ZOD-V3.md
│ ├── remove-dependency.js
│ ├── remove-subtask.js
│ ├── remove-task.js
│ ├── rename-tag.js
│ ├── research.js
│ ├── response-language.js
│ ├── rules.js
│ ├── scope-down.js
│ ├── scope-up.js
│ ├── set-task-status.js
│ ├── tool-registry.js
│ ├── update-subtask.js
│ ├── update-task.js
│ ├── update.js
│ ├── use-tag.js
│ ├── utils.js
│ └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│ ├── ai-sdk-provider-grok-cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── errors.test.ts
│ │ │ ├── errors.ts
│ │ │ ├── grok-cli-language-model.ts
│ │ │ ├── grok-cli-provider.test.ts
│ │ │ ├── grok-cli-provider.ts
│ │ │ ├── index.ts
│ │ │ ├── json-extractor.test.ts
│ │ │ ├── json-extractor.ts
│ │ │ ├── message-converter.test.ts
│ │ │ ├── message-converter.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── build-config
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ └── tsdown.base.ts
│ │ └── tsconfig.json
│ ├── claude-code-plugin
│ │ ├── .claude-plugin
│ │ │ └── plugin.json
│ │ ├── .gitignore
│ │ ├── agents
│ │ │ ├── task-checker.md
│ │ │ ├── task-executor.md
│ │ │ └── task-orchestrator.md
│ │ ├── CHANGELOG.md
│ │ ├── commands
│ │ │ ├── add-dependency.md
│ │ │ ├── add-subtask.md
│ │ │ ├── add-task.md
│ │ │ ├── analyze-complexity.md
│ │ │ ├── analyze-project.md
│ │ │ ├── auto-implement-tasks.md
│ │ │ ├── command-pipeline.md
│ │ │ ├── complexity-report.md
│ │ │ ├── convert-task-to-subtask.md
│ │ │ ├── expand-all-tasks.md
│ │ │ ├── expand-task.md
│ │ │ ├── fix-dependencies.md
│ │ │ ├── generate-tasks.md
│ │ │ ├── help.md
│ │ │ ├── init-project-quick.md
│ │ │ ├── init-project.md
│ │ │ ├── install-taskmaster.md
│ │ │ ├── learn.md
│ │ │ ├── list-tasks-by-status.md
│ │ │ ├── list-tasks-with-subtasks.md
│ │ │ ├── list-tasks.md
│ │ │ ├── next-task.md
│ │ │ ├── parse-prd-with-research.md
│ │ │ ├── parse-prd.md
│ │ │ ├── project-status.md
│ │ │ ├── quick-install-taskmaster.md
│ │ │ ├── remove-all-subtasks.md
│ │ │ ├── remove-dependency.md
│ │ │ ├── remove-subtask.md
│ │ │ ├── remove-subtasks.md
│ │ │ ├── remove-task.md
│ │ │ ├── setup-models.md
│ │ │ ├── show-task.md
│ │ │ ├── smart-workflow.md
│ │ │ ├── sync-readme.md
│ │ │ ├── tm-main.md
│ │ │ ├── to-cancelled.md
│ │ │ ├── to-deferred.md
│ │ │ ├── to-done.md
│ │ │ ├── to-in-progress.md
│ │ │ ├── to-pending.md
│ │ │ ├── to-review.md
│ │ │ ├── update-single-task.md
│ │ │ ├── update-task.md
│ │ │ ├── update-tasks-from-id.md
│ │ │ ├── validate-dependencies.md
│ │ │ └── view-models.md
│ │ ├── mcp.json
│ │ └── package.json
│ ├── tm-bridge
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── add-tag-bridge.ts
│ │ │ ├── bridge-types.ts
│ │ │ ├── bridge-utils.ts
│ │ │ ├── expand-bridge.ts
│ │ │ ├── index.ts
│ │ │ ├── tags-bridge.ts
│ │ │ ├── update-bridge.ts
│ │ │ └── use-tag-bridge.ts
│ │ └── tsconfig.json
│ └── tm-core
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docs
│ │ └── listTasks-architecture.md
│ ├── package.json
│ ├── POC-STATUS.md
│ ├── README.md
│ ├── src
│ │ ├── common
│ │ │ ├── constants
│ │ │ │ ├── index.ts
│ │ │ │ ├── paths.ts
│ │ │ │ └── providers.ts
│ │ │ ├── errors
│ │ │ │ ├── index.ts
│ │ │ │ └── task-master-error.ts
│ │ │ ├── interfaces
│ │ │ │ ├── configuration.interface.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── storage.interface.ts
│ │ │ ├── logger
│ │ │ │ ├── factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── logger.spec.ts
│ │ │ │ └── logger.ts
│ │ │ ├── mappers
│ │ │ │ ├── TaskMapper.test.ts
│ │ │ │ └── TaskMapper.ts
│ │ │ ├── types
│ │ │ │ ├── database.types.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── legacy.ts
│ │ │ │ └── repository-types.ts
│ │ │ └── utils
│ │ │ ├── git-utils.ts
│ │ │ ├── id-generator.ts
│ │ │ ├── index.ts
│ │ │ ├── path-helpers.ts
│ │ │ ├── path-normalizer.spec.ts
│ │ │ ├── path-normalizer.ts
│ │ │ ├── project-root-finder.spec.ts
│ │ │ ├── project-root-finder.ts
│ │ │ ├── run-id-generator.spec.ts
│ │ │ └── run-id-generator.ts
│ │ ├── index.ts
│ │ ├── modules
│ │ │ ├── ai
│ │ │ │ ├── index.ts
│ │ │ │ ├── interfaces
│ │ │ │ │ └── ai-provider.interface.ts
│ │ │ │ └── providers
│ │ │ │ ├── base-provider.ts
│ │ │ │ └── index.ts
│ │ │ ├── auth
│ │ │ │ ├── auth-domain.spec.ts
│ │ │ │ ├── auth-domain.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── auth-manager.spec.ts
│ │ │ │ │ └── auth-manager.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── context-store.ts
│ │ │ │ │ ├── oauth-service.ts
│ │ │ │ │ ├── organization.service.ts
│ │ │ │ │ ├── supabase-session-storage.spec.ts
│ │ │ │ │ └── supabase-session-storage.ts
│ │ │ │ └── types.ts
│ │ │ ├── briefs
│ │ │ │ ├── briefs-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── brief-service.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils
│ │ │ │ └── url-parser.ts
│ │ │ ├── commands
│ │ │ │ └── index.ts
│ │ │ ├── config
│ │ │ │ ├── config-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── config-manager.spec.ts
│ │ │ │ │ └── config-manager.ts
│ │ │ │ └── services
│ │ │ │ ├── config-loader.service.spec.ts
│ │ │ │ ├── config-loader.service.ts
│ │ │ │ ├── config-merger.service.spec.ts
│ │ │ │ ├── config-merger.service.ts
│ │ │ │ ├── config-persistence.service.spec.ts
│ │ │ │ ├── config-persistence.service.ts
│ │ │ │ ├── environment-config-provider.service.spec.ts
│ │ │ │ ├── environment-config-provider.service.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── runtime-state-manager.service.spec.ts
│ │ │ │ └── runtime-state-manager.service.ts
│ │ │ ├── dependencies
│ │ │ │ └── index.ts
│ │ │ ├── execution
│ │ │ │ ├── executors
│ │ │ │ │ ├── base-executor.ts
│ │ │ │ │ ├── claude-executor.ts
│ │ │ │ │ └── executor-factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── executor-service.ts
│ │ │ │ └── types.ts
│ │ │ ├── git
│ │ │ │ ├── adapters
│ │ │ │ │ ├── git-adapter.test.ts
│ │ │ │ │ └── git-adapter.ts
│ │ │ │ ├── git-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── services
│ │ │ │ ├── branch-name-generator.spec.ts
│ │ │ │ ├── branch-name-generator.ts
│ │ │ │ ├── commit-message-generator.test.ts
│ │ │ │ ├── commit-message-generator.ts
│ │ │ │ ├── scope-detector.test.ts
│ │ │ │ ├── scope-detector.ts
│ │ │ │ ├── template-engine.test.ts
│ │ │ │ └── template-engine.ts
│ │ │ ├── integration
│ │ │ │ ├── clients
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── supabase-client.ts
│ │ │ │ ├── integration-domain.ts
│ │ │ │ └── services
│ │ │ │ ├── export.service.ts
│ │ │ │ ├── task-expansion.service.ts
│ │ │ │ └── task-retrieval.service.ts
│ │ │ ├── reports
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ └── complexity-report-manager.ts
│ │ │ │ └── types.ts
│ │ │ ├── storage
│ │ │ │ ├── adapters
│ │ │ │ │ ├── activity-logger.ts
│ │ │ │ │ ├── api-storage.ts
│ │ │ │ │ └── file-storage
│ │ │ │ │ ├── file-operations.ts
│ │ │ │ │ ├── file-storage.ts
│ │ │ │ │ ├── format-handler.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── path-resolver.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── storage-factory.ts
│ │ │ │ └── utils
│ │ │ │ └── api-client.ts
│ │ │ ├── tasks
│ │ │ │ ├── entities
│ │ │ │ │ └── task.entity.ts
│ │ │ │ ├── parser
│ │ │ │ │ └── index.ts
│ │ │ │ ├── repositories
│ │ │ │ │ ├── supabase
│ │ │ │ │ │ ├── dependency-fetcher.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── supabase-repository.ts
│ │ │ │ │ └── task-repository.interface.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── preflight-checker.service.ts
│ │ │ │ │ ├── tag.service.ts
│ │ │ │ │ ├── task-execution-service.ts
│ │ │ │ │ ├── task-loader.service.ts
│ │ │ │ │ └── task-service.ts
│ │ │ │ └── tasks-domain.ts
│ │ │ ├── ui
│ │ │ │ └── index.ts
│ │ │ └── workflow
│ │ │ ├── managers
│ │ │ │ ├── workflow-state-manager.spec.ts
│ │ │ │ └── workflow-state-manager.ts
│ │ │ ├── orchestrators
│ │ │ │ ├── workflow-orchestrator.test.ts
│ │ │ │ └── workflow-orchestrator.ts
│ │ │ ├── services
│ │ │ │ ├── test-result-validator.test.ts
│ │ │ │ ├── test-result-validator.ts
│ │ │ │ ├── test-result-validator.types.ts
│ │ │ │ ├── workflow-activity-logger.ts
│ │ │ │ └── workflow.service.ts
│ │ │ ├── types.ts
│ │ │ └── workflow-domain.ts
│ │ ├── subpath-exports.test.ts
│ │ ├── tm-core.ts
│ │ └── utils
│ │ └── time.utils.ts
│ ├── tests
│ │ ├── auth
│ │ │ └── auth-refresh.test.ts
│ │ ├── integration
│ │ │ ├── auth-token-refresh.test.ts
│ │ │ ├── list-tasks.test.ts
│ │ │ └── storage
│ │ │ └── activity-logger.test.ts
│ │ ├── mocks
│ │ │ └── mock-provider.ts
│ │ ├── setup.ts
│ │ └── unit
│ │ ├── base-provider.test.ts
│ │ ├── executor.test.ts
│ │ └── smoke.test.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│ ├── create-worktree.sh
│ ├── dev.js
│ ├── init.js
│ ├── list-worktrees.sh
│ ├── modules
│ │ ├── ai-services-unified.js
│ │ ├── bridge-utils.js
│ │ ├── commands.js
│ │ ├── config-manager.js
│ │ ├── dependency-manager.js
│ │ ├── index.js
│ │ ├── prompt-manager.js
│ │ ├── supported-models.json
│ │ ├── sync-readme.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── find-next-task.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── is-task-dependent.js
│ │ │ ├── list-tasks.js
│ │ │ ├── migrate.js
│ │ │ ├── models.js
│ │ │ ├── move-task.js
│ │ │ ├── parse-prd
│ │ │ │ ├── index.js
│ │ │ │ ├── parse-prd-config.js
│ │ │ │ ├── parse-prd-helpers.js
│ │ │ │ ├── parse-prd-non-streaming.js
│ │ │ │ ├── parse-prd-streaming.js
│ │ │ │ └── parse-prd.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── scope-adjustment.js
│ │ │ ├── set-task-status.js
│ │ │ ├── tag-management.js
│ │ │ ├── task-exists.js
│ │ │ ├── update-single-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ └── update-tasks.js
│ │ ├── task-manager.js
│ │ ├── ui.js
│ │ ├── update-config-tokens.js
│ │ ├── utils
│ │ │ ├── contextGatherer.js
│ │ │ ├── fuzzyTaskSearch.js
│ │ │ └── git-utils.js
│ │ └── utils.js
│ ├── task-complexity-report.json
│ ├── test-claude-errors.js
│ └── test-claude.js
├── sonar-project.properties
├── src
│ ├── ai-providers
│ │ ├── anthropic.js
│ │ ├── azure.js
│ │ ├── base-provider.js
│ │ ├── bedrock.js
│ │ ├── claude-code.js
│ │ ├── codex-cli.js
│ │ ├── gemini-cli.js
│ │ ├── google-vertex.js
│ │ ├── google.js
│ │ ├── grok-cli.js
│ │ ├── groq.js
│ │ ├── index.js
│ │ ├── lmstudio.js
│ │ ├── ollama.js
│ │ ├── openai-compatible.js
│ │ ├── openai.js
│ │ ├── openrouter.js
│ │ ├── perplexity.js
│ │ ├── xai.js
│ │ ├── zai-coding.js
│ │ └── zai.js
│ ├── constants
│ │ ├── commands.js
│ │ ├── paths.js
│ │ ├── profiles.js
│ │ ├── rules-actions.js
│ │ ├── task-priority.js
│ │ └── task-status.js
│ ├── profiles
│ │ ├── amp.js
│ │ ├── base-profile.js
│ │ ├── claude.js
│ │ ├── cline.js
│ │ ├── codex.js
│ │ ├── cursor.js
│ │ ├── gemini.js
│ │ ├── index.js
│ │ ├── kilo.js
│ │ ├── kiro.js
│ │ ├── opencode.js
│ │ ├── roo.js
│ │ ├── trae.js
│ │ ├── vscode.js
│ │ ├── windsurf.js
│ │ └── zed.js
│ ├── progress
│ │ ├── base-progress-tracker.js
│ │ ├── cli-progress-factory.js
│ │ ├── parse-prd-tracker.js
│ │ ├── progress-tracker-builder.js
│ │ └── tracker-ui.js
│ ├── prompts
│ │ ├── add-task.json
│ │ ├── analyze-complexity.json
│ │ ├── expand-task.json
│ │ ├── parse-prd.json
│ │ ├── README.md
│ │ ├── research.json
│ │ ├── schemas
│ │ │ ├── parameter.schema.json
│ │ │ ├── prompt-template.schema.json
│ │ │ ├── README.md
│ │ │ └── variant.schema.json
│ │ ├── update-subtask.json
│ │ ├── update-task.json
│ │ └── update-tasks.json
│ ├── provider-registry
│ │ └── index.js
│ ├── schemas
│ │ ├── add-task.js
│ │ ├── analyze-complexity.js
│ │ ├── base-schemas.js
│ │ ├── expand-task.js
│ │ ├── parse-prd.js
│ │ ├── registry.js
│ │ ├── update-subtask.js
│ │ ├── update-task.js
│ │ └── update-tasks.js
│ ├── task-master.js
│ ├── ui
│ │ ├── confirm.js
│ │ ├── indicators.js
│ │ └── parse-prd.js
│ └── utils
│ ├── asset-resolver.js
│ ├── create-mcp-config.js
│ ├── format.js
│ ├── getVersion.js
│ ├── logger-utils.js
│ ├── manage-gitignore.js
│ ├── path-utils.js
│ ├── profiles.js
│ ├── rule-transformer.js
│ ├── stream-parser.js
│ └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│ ├── e2e
│ │ ├── e2e_helpers.sh
│ │ ├── parse_llm_output.cjs
│ │ ├── run_e2e.sh
│ │ ├── run_fallback_verification.sh
│ │ └── test_llm_analysis.sh
│ ├── fixtures
│ │ ├── .taskmasterconfig
│ │ ├── sample-claude-response.js
│ │ ├── sample-prd.txt
│ │ └── sample-tasks.js
│ ├── helpers
│ │ └── tool-counts.js
│ ├── integration
│ │ ├── claude-code-error-handling.test.js
│ │ ├── claude-code-optional.test.js
│ │ ├── cli
│ │ │ ├── commands.test.js
│ │ │ ├── complex-cross-tag-scenarios.test.js
│ │ │ └── move-cross-tag.test.js
│ │ ├── manage-gitignore.test.js
│ │ ├── mcp-server
│ │ │ └── direct-functions.test.js
│ │ ├── move-task-cross-tag.integration.test.js
│ │ ├── move-task-simple.integration.test.js
│ │ ├── profiles
│ │ │ ├── amp-init-functionality.test.js
│ │ │ ├── claude-init-functionality.test.js
│ │ │ ├── cline-init-functionality.test.js
│ │ │ ├── codex-init-functionality.test.js
│ │ │ ├── cursor-init-functionality.test.js
│ │ │ ├── gemini-init-functionality.test.js
│ │ │ ├── opencode-init-functionality.test.js
│ │ │ ├── roo-files-inclusion.test.js
│ │ │ ├── roo-init-functionality.test.js
│ │ │ ├── rules-files-inclusion.test.js
│ │ │ ├── trae-init-functionality.test.js
│ │ │ ├── vscode-init-functionality.test.js
│ │ │ └── windsurf-init-functionality.test.js
│ │ └── providers
│ │ └── temperature-support.test.js
│ ├── manual
│ │ ├── progress
│ │ │ ├── parse-prd-analysis.js
│ │ │ ├── test-parse-prd.js
│ │ │ └── TESTING_GUIDE.md
│ │ └── prompts
│ │ ├── prompt-test.js
│ │ └── README.md
│ ├── README.md
│ ├── setup.js
│ └── unit
│ ├── ai-providers
│ │ ├── base-provider.test.js
│ │ ├── claude-code.test.js
│ │ ├── codex-cli.test.js
│ │ ├── gemini-cli.test.js
│ │ ├── lmstudio.test.js
│ │ ├── mcp-components.test.js
│ │ ├── openai-compatible.test.js
│ │ ├── openai.test.js
│ │ ├── provider-registry.test.js
│ │ ├── zai-coding.test.js
│ │ ├── zai-provider.test.js
│ │ ├── zai-schema-introspection.test.js
│ │ └── zai.test.js
│ ├── ai-services-unified.test.js
│ ├── commands.test.js
│ ├── config-manager.test.js
│ ├── config-manager.test.mjs
│ ├── dependency-manager.test.js
│ ├── init.test.js
│ ├── initialize-project.test.js
│ ├── kebab-case-validation.test.js
│ ├── manage-gitignore.test.js
│ ├── mcp
│ │ └── tools
│ │ ├── __mocks__
│ │ │ └── move-task.js
│ │ ├── add-task.test.js
│ │ ├── analyze-complexity.test.js
│ │ ├── expand-all.test.js
│ │ ├── get-tasks.test.js
│ │ ├── initialize-project.test.js
│ │ ├── move-task-cross-tag-options.test.js
│ │ ├── move-task-cross-tag.test.js
│ │ ├── remove-task.test.js
│ │ └── tool-registration.test.js
│ ├── mcp-providers
│ │ ├── mcp-components.test.js
│ │ └── mcp-provider.test.js
│ ├── parse-prd.test.js
│ ├── profiles
│ │ ├── amp-integration.test.js
│ │ ├── claude-integration.test.js
│ │ ├── cline-integration.test.js
│ │ ├── codex-integration.test.js
│ │ ├── cursor-integration.test.js
│ │ ├── gemini-integration.test.js
│ │ ├── kilo-integration.test.js
│ │ ├── kiro-integration.test.js
│ │ ├── mcp-config-validation.test.js
│ │ ├── opencode-integration.test.js
│ │ ├── profile-safety-check.test.js
│ │ ├── roo-integration.test.js
│ │ ├── rule-transformer-cline.test.js
│ │ ├── rule-transformer-cursor.test.js
│ │ ├── rule-transformer-gemini.test.js
│ │ ├── rule-transformer-kilo.test.js
│ │ ├── rule-transformer-kiro.test.js
│ │ ├── rule-transformer-opencode.test.js
│ │ ├── rule-transformer-roo.test.js
│ │ ├── rule-transformer-trae.test.js
│ │ ├── rule-transformer-vscode.test.js
│ │ ├── rule-transformer-windsurf.test.js
│ │ ├── rule-transformer-zed.test.js
│ │ ├── rule-transformer.test.js
│ │ ├── selective-profile-removal.test.js
│ │ ├── subdirectory-support.test.js
│ │ ├── trae-integration.test.js
│ │ ├── vscode-integration.test.js
│ │ ├── windsurf-integration.test.js
│ │ └── zed-integration.test.js
│ ├── progress
│ │ └── base-progress-tracker.test.js
│ ├── prompt-manager.test.js
│ ├── prompts
│ │ ├── expand-task-prompt.test.js
│ │ └── prompt-migration.test.js
│ ├── scripts
│ │ └── modules
│ │ ├── commands
│ │ │ ├── move-cross-tag.test.js
│ │ │ └── README.md
│ │ ├── dependency-manager
│ │ │ ├── circular-dependencies.test.js
│ │ │ ├── cross-tag-dependencies.test.js
│ │ │ └── fix-dependencies-command.test.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.test.js
│ │ │ ├── add-task.test.js
│ │ │ ├── analyze-task-complexity.test.js
│ │ │ ├── clear-subtasks.test.js
│ │ │ ├── complexity-report-tag-isolation.test.js
│ │ │ ├── expand-all-tasks.test.js
│ │ │ ├── expand-task.test.js
│ │ │ ├── find-next-task.test.js
│ │ │ ├── generate-task-files.test.js
│ │ │ ├── list-tasks.test.js
│ │ │ ├── models-baseurl.test.js
│ │ │ ├── move-task-cross-tag.test.js
│ │ │ ├── move-task.test.js
│ │ │ ├── parse-prd-schema.test.js
│ │ │ ├── parse-prd.test.js
│ │ │ ├── remove-subtask.test.js
│ │ │ ├── remove-task.test.js
│ │ │ ├── research.test.js
│ │ │ ├── scope-adjustment.test.js
│ │ │ ├── set-task-status.test.js
│ │ │ ├── setup.js
│ │ │ ├── update-single-task-status.test.js
│ │ │ ├── update-subtask-by-id.test.js
│ │ │ ├── update-task-by-id.test.js
│ │ │ └── update-tasks.test.js
│ │ ├── ui
│ │ │ └── cross-tag-error-display.test.js
│ │ └── utils-tag-aware-paths.test.js
│ ├── task-finder.test.js
│ ├── task-manager
│ │ ├── clear-subtasks.test.js
│ │ ├── move-task.test.js
│ │ ├── tag-boundary.test.js
│ │ └── tag-management.test.js
│ ├── task-master.test.js
│ ├── ui
│ │ └── indicators.test.js
│ ├── ui.test.js
│ ├── utils-strip-ansi.test.js
│ └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
├── turbo.json
└── update-task-migration-plan.md
```
# Files
--------------------------------------------------------------------------------
/apps/extension/src/components/ConfigView.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { ArrowLeft, RefreshCw, Settings } from 'lucide-react';
2 | import type React from 'react';
3 | import { useEffect, useState, useCallback } from 'react';
4 | import { Badge } from './ui/badge';
5 | import { Button } from './ui/button';
6 | import {
7 | Card,
8 | CardContent,
9 | CardDescription,
10 | CardHeader,
11 | CardTitle
12 | } from './ui/card';
13 | import { ScrollArea } from './ui/scroll-area';
14 | import { Separator } from './ui/separator';
15 |
16 | interface ModelConfig {
17 | provider: string;
18 | modelId: string;
19 | maxTokens: number;
20 | temperature: number;
21 | }
22 |
23 | interface ConfigData {
24 | models?: {
25 | main?: ModelConfig;
26 | research?: ModelConfig;
27 | fallback?: ModelConfig;
28 | };
29 | global?: {
30 | defaultNumTasks?: number;
31 | defaultSubtasks?: number;
32 | defaultPriority?: string;
33 | projectName?: string;
34 | responseLanguage?: string;
35 | };
36 | }
37 |
38 | interface ConfigViewProps {
39 | sendMessage: (message: any) => Promise<any>;
40 | onNavigateBack: () => void;
41 | }
42 |
43 | export const ConfigView: React.FC<ConfigViewProps> = ({
44 | sendMessage,
45 | onNavigateBack
46 | }) => {
47 | const [config, setConfig] = useState<ConfigData | null>(null);
48 | const [loading, setLoading] = useState(true);
49 | const [error, setError] = useState<string | null>(null);
50 |
51 | const loadConfig = useCallback(async () => {
52 | setLoading(true);
53 | setError(null);
54 | try {
55 | const response = await sendMessage({ type: 'getConfig' });
56 | setConfig(response);
57 | } catch (err) {
58 | setError('Failed to load configuration');
59 | console.error('Error loading config:', err);
60 | } finally {
61 | setLoading(false);
62 | }
63 | }, [sendMessage]);
64 |
65 | useEffect(() => {
66 | loadConfig();
67 | }, [loadConfig]);
68 |
69 | const modelLabels = {
70 | main: {
71 | label: 'Main Model',
72 | icon: '🤖',
73 | description: 'Primary model for task generation'
74 | },
75 | research: {
76 | label: 'Research Model',
77 | icon: '🔍',
78 | description: 'Model for research-backed operations'
79 | },
80 | fallback: {
81 | label: 'Fallback Model',
82 | icon: '🔄',
83 | description: 'Backup model if primary fails'
84 | }
85 | };
86 |
87 | return (
88 | <div className="flex flex-col h-full bg-vscode-editor-background">
89 | {/* Header */}
90 | <div className="flex items-center justify-between px-4 py-3 border-b border-vscode-border">
91 | <div className="flex items-center gap-3">
92 | <Button
93 | variant="ghost"
94 | size="icon"
95 | onClick={onNavigateBack}
96 | className="h-8 w-8"
97 | >
98 | <ArrowLeft className="h-4 w-4" />
99 | </Button>
100 | <div className="flex items-center gap-2">
101 | <Settings className="w-5 h-5" />
102 | <h1 className="text-lg font-semibold">Task Master Configuration</h1>
103 | </div>
104 | </div>
105 | <Button
106 | variant="ghost"
107 | size="icon"
108 | onClick={loadConfig}
109 | className="h-8 w-8"
110 | >
111 | <RefreshCw className="h-4 w-4" />
112 | </Button>
113 | </div>
114 |
115 | {/* Content */}
116 | <ScrollArea className="flex-1 overflow-hidden">
117 | <div className="p-6 pb-12">
118 | {loading ? (
119 | <div className="flex items-center justify-center py-8">
120 | <RefreshCw className="w-6 h-6 animate-spin text-vscode-foreground/50" />
121 | </div>
122 | ) : error ? (
123 | <div className="text-red-500 text-center py-8">{error}</div>
124 | ) : config ? (
125 | <div className="space-y-6 max-w-4xl mx-auto">
126 | {/* Models Section */}
127 | <Card>
128 | <CardHeader>
129 | <CardTitle>AI Models</CardTitle>
130 | <CardDescription>
131 | Models configured for different Task Master operations
132 | </CardDescription>
133 | </CardHeader>
134 | <CardContent className="space-y-4">
135 | {config.models &&
136 | Object.entries(config.models).map(([key, modelConfig]) => {
137 | const label =
138 | modelLabels[key as keyof typeof modelLabels];
139 | if (!label || !modelConfig) return null;
140 |
141 | return (
142 | <div key={key} className="space-y-2">
143 | <div className="flex items-center gap-2">
144 | <span className="text-lg">{label.icon}</span>
145 | <div>
146 | <h4 className="font-medium">{label.label}</h4>
147 | <p className="text-xs text-vscode-foreground/60">
148 | {label.description}
149 | </p>
150 | </div>
151 | </div>
152 | <div className="bg-vscode-input/20 rounded-md p-3 space-y-1">
153 | <div className="flex justify-between">
154 | <span className="text-sm text-vscode-foreground/80">
155 | Provider:
156 | </span>
157 | <Badge variant="secondary">
158 | {modelConfig.provider}
159 | </Badge>
160 | </div>
161 | <div className="flex justify-between">
162 | <span className="text-sm text-vscode-foreground/80">
163 | Model:
164 | </span>
165 | <code className="text-xs font-mono bg-vscode-input/30 px-2 py-1 rounded">
166 | {modelConfig.modelId}
167 | </code>
168 | </div>
169 | <div className="flex justify-between">
170 | <span className="text-sm text-vscode-foreground/80">
171 | Max Tokens:
172 | </span>
173 | <span className="text-sm">
174 | {modelConfig.maxTokens.toLocaleString()}
175 | </span>
176 | </div>
177 | <div className="flex justify-between">
178 | <span className="text-sm text-vscode-foreground/80">
179 | Temperature:
180 | </span>
181 | <span className="text-sm">
182 | {modelConfig.temperature}
183 | </span>
184 | </div>
185 | </div>
186 | </div>
187 | );
188 | })}
189 | </CardContent>
190 | </Card>
191 |
192 | {/* Task Defaults Section */}
193 | {config.global && (
194 | <Card>
195 | <CardHeader>
196 | <CardTitle>Task Defaults</CardTitle>
197 | <CardDescription>
198 | Default values for new tasks and subtasks
199 | </CardDescription>
200 | </CardHeader>
201 | <CardContent>
202 | <div className="space-y-3">
203 | <div className="flex justify-between items-center">
204 | <span className="text-sm font-medium">
205 | Default Number of Tasks
206 | </span>
207 | <Badge variant="outline">
208 | {config.global.defaultNumTasks || 10}
209 | </Badge>
210 | </div>
211 | <Separator />
212 | <div className="flex justify-between items-center">
213 | <span className="text-sm font-medium">
214 | Default Number of Subtasks
215 | </span>
216 | <Badge variant="outline">
217 | {config.global.defaultSubtasks || 5}
218 | </Badge>
219 | </div>
220 | <Separator />
221 | <div className="flex justify-between items-center">
222 | <span className="text-sm font-medium">
223 | Default Priority
224 | </span>
225 | <Badge
226 | variant={
227 | config.global.defaultPriority === 'high'
228 | ? 'destructive'
229 | : config.global.defaultPriority === 'low'
230 | ? 'secondary'
231 | : 'default'
232 | }
233 | >
234 | {config.global.defaultPriority || 'medium'}
235 | </Badge>
236 | </div>
237 | {config.global.projectName && (
238 | <>
239 | <Separator />
240 | <div className="flex justify-between items-center">
241 | <span className="text-sm font-medium">
242 | Project Name
243 | </span>
244 | <span className="text-sm text-vscode-foreground/80">
245 | {config.global.projectName}
246 | </span>
247 | </div>
248 | </>
249 | )}
250 | {config.global.responseLanguage && (
251 | <>
252 | <Separator />
253 | <div className="flex justify-between items-center">
254 | <span className="text-sm font-medium">
255 | Response Language
256 | </span>
257 | <span className="text-sm text-vscode-foreground/80">
258 | {config.global.responseLanguage}
259 | </span>
260 | </div>
261 | </>
262 | )}
263 | </div>
264 | </CardContent>
265 | </Card>
266 | )}
267 |
268 | {/* Info Card */}
269 | <Card>
270 | <CardContent className="pt-6">
271 | <p className="text-sm text-vscode-foreground/60">
272 | To modify these settings, go to{' '}
273 | <code className="bg-vscode-input/30 px-1 py-0.5 rounded">
274 | .taskmaster/config.json
275 | </code>{' '}
276 | and modify them, or use the MCP.
277 | </p>
278 | </CardContent>
279 | </Card>
280 | </div>
281 | ) : (
282 | <div className="text-center py-8 text-vscode-foreground/50">
283 | No configuration found. Please run `task-master init` in your
284 | project.
285 | </div>
286 | )}
287 | </div>
288 | </ScrollArea>
289 | </div>
290 | );
291 | };
292 |
```
--------------------------------------------------------------------------------
/src/prompts/expand-task.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "id": "expand-task",
3 | "version": "1.0.0",
4 | "description": "Break down a task into detailed subtasks",
5 | "metadata": {
6 | "author": "system",
7 | "created": "2024-01-01T00:00:00Z",
8 | "updated": "2024-01-01T00:00:00Z",
9 | "tags": ["expansion", "subtasks", "breakdown"]
10 | },
11 | "parameters": {
12 | "subtaskCount": {
13 | "type": "number",
14 | "required": true,
15 | "description": "Number of subtasks to generate"
16 | },
17 | "task": {
18 | "type": "object",
19 | "required": true,
20 | "description": "The task to expand"
21 | },
22 | "nextSubtaskId": {
23 | "type": "number",
24 | "required": true,
25 | "description": "Starting ID for new subtasks"
26 | },
27 | "useResearch": {
28 | "type": "boolean",
29 | "default": false,
30 | "description": "Use research mode"
31 | },
32 | "expansionPrompt": {
33 | "type": "string",
34 | "required": false,
35 | "description": "Expansion prompt from complexity report"
36 | },
37 | "additionalContext": {
38 | "type": "string",
39 | "required": false,
40 | "default": "",
41 | "description": "Additional context for task expansion"
42 | },
43 | "complexityReasoningContext": {
44 | "type": "string",
45 | "required": false,
46 | "default": "",
47 | "description": "Complexity analysis reasoning context"
48 | },
49 | "gatheredContext": {
50 | "type": "string",
51 | "required": false,
52 | "default": "",
53 | "description": "Gathered project context"
54 | },
55 | "hasCodebaseAnalysis": {
56 | "type": "boolean",
57 | "required": false,
58 | "default": false,
59 | "description": "Whether codebase analysis is available"
60 | },
61 | "projectRoot": {
62 | "type": "string",
63 | "required": false,
64 | "default": "",
65 | "description": "Project root path for context"
66 | }
67 | },
68 | "prompts": {
69 | "complexity-report": {
70 | "condition": "expansionPrompt",
71 | "system": "You are an AI assistant helping with task breakdown. Generate {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks based on the provided prompt and context.\n\nIMPORTANT: Your response MUST be a JSON object with a \"subtasks\" property containing an array of subtask objects. Each subtask must include ALL of the following fields:\n- id: MUST be sequential integers starting EXACTLY from {{nextSubtaskId}}. First subtask id={{nextSubtaskId}}, second id={{nextSubtaskId}}+1, etc. DO NOT use any other numbering pattern!\n- title: A clear, actionable title (5-200 characters)\n- description: A detailed description (minimum 10 characters)\n- dependencies: An array of task IDs this subtask depends on (can be empty [])\n- details: Implementation details (minimum 20 characters)\n- status: Must be \"pending\" for new subtasks\n- testStrategy: Testing approach (can be null)\n\nYou may optionally include a \"metadata\" object. Do not include any other top-level properties.",
72 |
73 | "user": "Break down the following task:\n\nParent Task:\nID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}\n\n{{expansionPrompt}}{{#if additionalContext}}\n\n{{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\n\n{{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nGenerate {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks. CRITICAL: Use sequential IDs starting from {{nextSubtaskId}} (first={{nextSubtaskId}}, second={{nextSubtaskId}}+1, etc.)."
74 | },
75 | "research": {
76 | "condition": "useResearch === true && !expansionPrompt",
77 | "system": "You are an AI assistant with research capabilities analyzing and breaking down software development tasks.\n\nIMPORTANT: Your response MUST be a JSON object with a \"subtasks\" property containing an array of subtask objects. Each subtask must include ALL of the following fields:\n- id: MUST be sequential integers starting EXACTLY from {{nextSubtaskId}}. First subtask id={{nextSubtaskId}}, second id={{nextSubtaskId}}+1, etc. DO NOT use any other numbering pattern!\n- title: A clear, actionable title (5-200 characters)\n- description: A detailed description (minimum 10 characters)\n- dependencies: An array of task IDs this subtask depends on (can be empty [])\n- details: Implementation details (minimum 20 characters)\n- status: Must be \"pending\" for new subtasks\n- testStrategy: Testing approach (can be null)\n\nYou may optionally include a \"metadata\" object. Do not include any other top-level properties.",
78 | "user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before generating subtasks:\n\n1. Use the Glob tool to explore relevant files for this task (e.g., \"**/*.js\", \"src/**/*.ts\")\n2. Use the Grep tool to search for existing implementations related to this task\n3. Use the Read tool to examine files that would be affected by this task\n4. Understand the current implementation state and patterns used\n\nBased on your analysis:\n- Identify existing code that relates to this task\n- Understand patterns and conventions to follow\n- Generate subtasks that integrate smoothly with existing code\n- Ensure subtasks are specific and actionable based on the actual codebase\n\nProject Root: {{projectRoot}}\n\n{{/if}}Analyze the following task and break it down into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks. Each subtask should be actionable and well-defined.\n\nParent Task:\nID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nConsider this context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nCRITICAL: You MUST use sequential IDs starting from {{nextSubtaskId}}. The first subtask MUST have id={{nextSubtaskId}}, the second MUST have id={{nextSubtaskId}}+1, and so on. Do NOT use parent task ID in subtask numbering!"
79 | },
80 | "default": {
81 | "system": "You are an AI assistant helping with task breakdown for software development. Break down high-level tasks into specific, actionable subtasks that can be implemented sequentially.\n\nIMPORTANT: Your response MUST be a JSON object with a \"subtasks\" property containing an array of subtask objects. Each subtask must include ALL of the following fields:\n- id: MUST be sequential integers starting EXACTLY from {{nextSubtaskId}}. First subtask id={{nextSubtaskId}}, second id={{nextSubtaskId}}+1, etc. DO NOT use any other numbering pattern!\n- title: A clear, actionable title (5-200 characters)\n- description: A detailed description (minimum 10 characters)\n- dependencies: An array of task IDs this subtask depends on (can be empty [])\n- details: Implementation details (minimum 20 characters)\n- status: Must be \"pending\" for new subtasks\n- testStrategy: Testing approach (can be null)\n\nYou may optionally include a \"metadata\" object. Do not include any other top-level properties.",
82 | "user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before generating subtasks:\n\n1. Use the Glob tool to explore relevant files for this task (e.g., \"**/*.js\", \"src/**/*.ts\")\n2. Use the Grep tool to search for existing implementations related to this task\n3. Use the Read tool to examine files that would be affected by this task\n4. Understand the current implementation state and patterns used\n\nBased on your analysis:\n- Identify existing code that relates to this task\n- Understand patterns and conventions to follow\n- Generate subtasks that integrate smoothly with existing code\n- Ensure subtasks are specific and actionable based on the actual codebase\n\nProject Root: {{projectRoot}}\n\n{{/if}}Break down this task into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks:\n\nTask ID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nAdditional context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nCRITICAL: You MUST use sequential IDs starting from {{nextSubtaskId}}. The first subtask MUST have id={{nextSubtaskId}}, the second MUST have id={{nextSubtaskId}}+1, and so on. Do NOT use parent task ID in subtask numbering!"
83 | }
84 | }
85 | }
86 |
```
--------------------------------------------------------------------------------
/.taskmaster/reports/task-complexity-report_tdd-phase-1-core-rails.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "meta": {
3 | "generatedAt": "2025-10-09T12:47:27.960Z",
4 | "tasksAnalyzed": 10,
5 | "totalTasks": 10,
6 | "analysisCount": 10,
7 | "thresholdScore": 5,
8 | "projectName": "Taskmaster",
9 | "usedResearch": false
10 | },
11 | "complexityAnalysis": [
12 | {
13 | "taskId": 1,
14 | "taskTitle": "Design and Implement Global Storage System",
15 | "complexityScore": 7,
16 | "recommendedSubtasks": 6,
17 | "expansionPrompt": "Break down the global storage system implementation into: 1) Path normalization utilities with cross-platform support, 2) Run ID generation and validation, 3) Manifest.json structure and management, 4) Activity.jsonl append-only logging, 5) State.json mutable checkpoint handling, and 6) Directory structure creation and cleanup. Focus on robust error handling, atomic operations, and isolation between different runs.",
18 | "reasoning": "Complex system requiring cross-platform path handling, multiple file formats (JSON/JSONL), atomic operations, and state management. The existing codebase shows sophisticated file operations infrastructure but this extends beyond current patterns. Implementation involves filesystem operations, concurrency concerns, and data integrity."
19 | },
20 | {
21 | "taskId": 2,
22 | "taskTitle": "Build GitAdapter with Safety Checks",
23 | "complexityScore": 8,
24 | "recommendedSubtasks": 7,
25 | "expansionPrompt": "Decompose GitAdapter into: 1) Git repository detection and validation, 2) Working tree status checking with detailed reporting, 3) Branch operations (create, checkout, list) with safety guards, 4) Commit operations with metadata embedding, 5) Default branch detection and protection logic, 6) Push operations with conflict handling, and 7) Branch name generation from patterns. Emphasize safety checks, confirmation gates, and comprehensive error messages.",
26 | "reasoning": "High complexity due to git operations safety requirements, multiple git commands integration, error handling for various git states, and safety mechanisms. The PRD emphasizes never allowing commits on default branch and requiring clean working tree - critical safety features that need robust implementation."
27 | },
28 | {
29 | "taskId": 3,
30 | "taskTitle": "Implement Test Result Validator",
31 | "complexityScore": 5,
32 | "recommendedSubtasks": 4,
33 | "expansionPrompt": "Split test validation into: 1) Input validation and schema definition for test results, 2) RED phase validation logic (ensuring failures exist), 3) GREEN phase validation logic (ensuring all tests pass), and 4) Coverage threshold validation with configurable limits. Include comprehensive validation messages and suggestions for common failure scenarios.",
34 | "reasoning": "Moderate complexity focused on business logic validation. The validator is framework-agnostic (only validates reported numbers), has clear validation rules, and well-defined input/output. The existing codebase shows validation patterns that can be leveraged."
35 | },
36 | {
37 | "taskId": 4,
38 | "taskTitle": "Develop WorkflowOrchestrator State Machine",
39 | "complexityScore": 9,
40 | "recommendedSubtasks": 8,
41 | "expansionPrompt": "Structure the orchestrator into: 1) State machine definition and transitions (Preflight → BranchSetup → SubtaskLoop → Finalize), 2) Event emission system with comprehensive event types, 3) State persistence and recovery mechanisms, 4) Phase coordination and validation, 5) Subtask iteration and progress tracking, 6) Error handling and recovery strategies, 7) Resume functionality from checkpoints, and 8) Integration points for Git, Test, and other adapters.",
42 | "reasoning": "Very high complexity as the central coordination component. Must orchestrate multiple adapters, handle state transitions, event emission, persistence, and recovery. The state machine needs to be robust, resumable, and coordinate all other components. Critical for the entire workflow's reliability."
43 | },
44 | {
45 | "taskId": 5,
46 | "taskTitle": "Create Enhanced Commit Message Generator",
47 | "complexityScore": 4,
48 | "recommendedSubtasks": 3,
49 | "expansionPrompt": "Organize commit message generation into: 1) Template parsing and variable substitution with configurable templates, 2) Scope detection from changed files with intelligent categorization, and 3) Metadata embedding (task context, test results, coverage) with conventional commits compliance. Ensure messages are parseable and contain all required task metadata.",
50 | "reasoning": "Relatively straightforward text processing and template system. The conventional commits format is well-defined, and the metadata requirements are clear. The existing package.json shows commander dependency for CLI patterns that can be leveraged."
51 | },
52 | {
53 | "taskId": 6,
54 | "taskTitle": "Implement Subtask TDD Loop",
55 | "complexityScore": 8,
56 | "recommendedSubtasks": 6,
57 | "expansionPrompt": "Break down the TDD loop into: 1) RED phase orchestration with test generation coordination, 2) GREEN phase orchestration with implementation guidance, 3) COMMIT phase with file staging and commit creation, 4) Attempt tracking and maximum retry logic, 5) Phase transition validation and state updates, and 6) Activity logging for all phase transitions. Focus on robust state management and clear error recovery paths.",
58 | "reasoning": "High complexity due to coordinating multiple phases, state transitions, retry logic, and integration with multiple adapters (Git, Test, State). This is the core workflow execution engine requiring careful orchestration and error handling."
59 | },
60 | {
61 | "taskId": 7,
62 | "taskTitle": "Build CLI Commands for AI Agent Orchestration",
63 | "complexityScore": 6,
64 | "recommendedSubtasks": 5,
65 | "expansionPrompt": "Structure CLI commands into: 1) Command registration and argument parsing setup, 2) `start` and `resume` commands with initialization logic, 3) `next` and `status` commands with JSON output formatting, 4) `complete` command with result validation integration, and 5) `commit` and `abort` commands with git operation coordination. Ensure consistent JSON output for machine parsing and comprehensive error handling.",
66 | "reasoning": "Moderate complexity leveraging existing CLI infrastructure. The codebase shows commander usage patterns and CLI structure. Main complexity is in JSON output formatting, argument validation, and integration with the orchestrator component."
67 | },
68 | {
69 | "taskId": 8,
70 | "taskTitle": "Develop MCP Tools for AI Agent Integration",
71 | "complexityScore": 6,
72 | "recommendedSubtasks": 5,
73 | "expansionPrompt": "Organize MCP tools into: 1) Tool schema definition and parameter validation, 2) `autopilot_start` and `autopilot_resume` tool implementation, 3) `autopilot_next` and `autopilot_status` tools with context provision, 4) `autopilot_complete_phase` tool with validation integration, and 5) `autopilot_commit` tool with git operations. Ensure parity with CLI functionality and proper error handling.",
74 | "reasoning": "Moderate complexity building on existing MCP infrastructure. The codebase shows extensive MCP tooling patterns. Main work is adapting CLI functionality to MCP interface patterns and ensuring consistent behavior between CLI and MCP interfaces."
75 | },
76 | {
77 | "taskId": 9,
78 | "taskTitle": "Write AI Agent Integration Documentation and Templates",
79 | "complexityScore": 2,
80 | "recommendedSubtasks": 2,
81 | "expansionPrompt": "Structure documentation into: 1) Comprehensive workflow documentation with step-by-step examples, command usage, and integration patterns, and 2) Template creation for CLAUDE.md integration, example prompts, and troubleshooting guides. Focus on clear examples and practical integration guidance.",
82 | "reasoning": "Low complexity documentation task. Requires understanding of the implemented system but primarily involves writing clear instructions and examples. The existing codebase shows good documentation patterns that can be followed."
83 | },
84 | {
85 | "taskId": 10,
86 | "taskTitle": "Implement Configuration System and Project Hygiene",
87 | "complexityScore": 5,
88 | "recommendedSubtasks": 4,
89 | "expansionPrompt": "Structure configuration into: 1) Configuration schema definition with comprehensive validation using ajv, 2) Default configuration setup and loading mechanisms, 3) Gitignore management and project directory hygiene rules, and 4) Configuration validation and error reporting. Ensure configurations are validated on startup and provide clear error messages for invalid settings.",
90 | "reasoning": "Moderate complexity involving schema validation, file operations, and configuration management. The package.json shows ajv dependency is available. Configuration systems require careful validation and user-friendly error reporting, but follow established patterns."
91 | }
92 | ]
93 | }
94 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/auth/services/organization.service.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview Organization and Brief management service
3 | * Handles fetching and managing organizations and briefs from the API
4 | */
5 |
6 | import type { SupabaseClient } from '@supabase/supabase-js';
7 | import {
8 | ERROR_CODES,
9 | TaskMasterError
10 | } from '../../../common/errors/task-master-error.js';
11 | import { getLogger } from '../../../common/logger/index.js';
12 | import type { Database } from '../../../common/types/database.types.js';
13 | import type { Brief } from '../../briefs/types.js';
14 |
15 | /**
16 | * Organization data structure
17 | */
18 | export interface Organization {
19 | id: string;
20 | name: string;
21 | slug: string;
22 | }
23 |
24 | /**
25 | * Task data structure from the remote database
26 | */
27 | export interface RemoteTask {
28 | id: string;
29 | briefId: string;
30 | documentId: string;
31 | position: number | null;
32 | subtaskPosition: number | null;
33 | status: string;
34 | createdAt: string;
35 | updatedAt: string;
36 | // Document details from join
37 | document?: {
38 | id: string;
39 | document_name: string;
40 | title: string;
41 | description: string;
42 | };
43 | }
44 |
45 | /**
46 | * Service for managing organizations and briefs
47 | */
48 | export class OrganizationService {
49 | private logger = getLogger('OrganizationService');
50 |
51 | constructor(private supabaseClient: SupabaseClient<Database>) {}
52 |
53 | /**
54 | * Get all organizations for the authenticated user
55 | */
56 | async getOrganizations(): Promise<Organization[]> {
57 | try {
58 | // The user is already authenticated via the Authorization header
59 | // Query the user_accounts view/table (filtered by RLS for current user)
60 | const { data, error } = await this.supabaseClient
61 | .from('user_accounts')
62 | .select(`
63 | id,
64 | name,
65 | slug
66 | `);
67 |
68 | if (error) {
69 | throw new TaskMasterError(
70 | `Failed to fetch organizations: ${error.message}`,
71 | ERROR_CODES.API_ERROR,
72 | { operation: 'getOrganizations' },
73 | error
74 | );
75 | }
76 |
77 | if (!data || data.length === 0) {
78 | this.logger.debug('No organizations found for user');
79 | return [];
80 | }
81 |
82 | // Map to our Organization interface
83 | return data.map((org) => ({
84 | id: org.id ?? '',
85 | name: org.name ?? '',
86 | slug: org.slug ?? org.id ?? '' // Use ID as fallback if slug is null
87 | }));
88 | } catch (error) {
89 | if (error instanceof TaskMasterError) {
90 | throw error;
91 | }
92 | throw new TaskMasterError(
93 | 'Failed to fetch organizations',
94 | ERROR_CODES.API_ERROR,
95 | { operation: 'getOrganizations' },
96 | error as Error
97 | );
98 | }
99 | }
100 |
101 | /**
102 | * Get a specific organization by ID
103 | */
104 | async getOrganization(orgId: string): Promise<Organization | null> {
105 | try {
106 | const { data, error } = await this.supabaseClient
107 | .from('accounts')
108 | .select(`
109 | id,
110 | name,
111 | slug
112 | `)
113 | .eq('id', orgId)
114 | .single();
115 |
116 | if (error) {
117 | if (error.code === 'PGRST116') {
118 | // No rows found
119 | return null;
120 | }
121 | throw new TaskMasterError(
122 | `Failed to fetch organization: ${error.message}`,
123 | ERROR_CODES.API_ERROR,
124 | { operation: 'getOrganization', orgId },
125 | error
126 | );
127 | }
128 |
129 | if (!data) {
130 | return null;
131 | }
132 |
133 | const accountData =
134 | data as Database['public']['Tables']['accounts']['Row'];
135 | return {
136 | id: accountData.id,
137 | name: accountData.name,
138 | slug: accountData.slug || accountData.id
139 | };
140 | } catch (error) {
141 | if (error instanceof TaskMasterError) {
142 | throw error;
143 | }
144 | throw new TaskMasterError(
145 | 'Failed to fetch organization',
146 | ERROR_CODES.API_ERROR,
147 | { operation: 'getOrganization', orgId },
148 | error as Error
149 | );
150 | }
151 | }
152 |
153 | /**
154 | * Get all briefs for a specific organization
155 | */
156 | async getBriefs(orgId: string): Promise<Brief[]> {
157 | try {
158 | const { data, error } = await this.supabaseClient
159 | .from('brief')
160 | .select(`
161 | id,
162 | account_id,
163 | document_id,
164 | status,
165 | created_at,
166 | updated_at,
167 | tasks(count),
168 | document:document_id (
169 | id,
170 | document_name,
171 | title
172 | )
173 | `)
174 | .eq('account_id', orgId)
175 | .order('updated_at', { ascending: false });
176 |
177 | if (error) {
178 | throw new TaskMasterError(
179 | `Failed to fetch briefs: ${error.message}`,
180 | ERROR_CODES.API_ERROR,
181 | { operation: 'getBriefs', orgId },
182 | error
183 | );
184 | }
185 |
186 | if (!data || data.length === 0) {
187 | this.logger.debug(`No briefs found for organization ${orgId}`);
188 | return [];
189 | }
190 |
191 | // Map to our Brief interface
192 | return data.map((brief: any) => ({
193 | id: brief.id,
194 | accountId: brief.account_id,
195 | documentId: brief.document_id,
196 | status: brief.status,
197 | createdAt: brief.created_at,
198 | updatedAt: brief.updated_at,
199 | taskCount: Array.isArray(brief.tasks)
200 | ? (brief.tasks[0]?.count ?? 0)
201 | : 0,
202 | document: brief.document
203 | ? {
204 | id: brief.document.id,
205 | document_name: brief.document.document_name,
206 | title: brief.document.title
207 | }
208 | : undefined
209 | }));
210 | } catch (error) {
211 | if (error instanceof TaskMasterError) {
212 | throw error;
213 | }
214 | throw new TaskMasterError(
215 | 'Failed to fetch briefs',
216 | ERROR_CODES.API_ERROR,
217 | { operation: 'getBriefs', orgId },
218 | error as Error
219 | );
220 | }
221 | }
222 |
223 | /**
224 | * Get a specific brief by ID
225 | */
226 | async getBrief(briefId: string): Promise<Brief | null> {
227 | try {
228 | const { data, error } = await this.supabaseClient
229 | .from('brief')
230 | .select(`
231 | id,
232 | account_id,
233 | document_id,
234 | status,
235 | created_at,
236 | updated_at,
237 | document:document_id (
238 | id,
239 | document_name,
240 | title,
241 | description
242 | )
243 | `)
244 | .eq('id', briefId)
245 | .single();
246 |
247 | if (error) {
248 | if (error.code === 'PGRST116') {
249 | // No rows found
250 | return null;
251 | }
252 | throw new TaskMasterError(
253 | `Failed to fetch brief: ${error.message}`,
254 | ERROR_CODES.API_ERROR,
255 | { operation: 'getBrief', briefId },
256 | error
257 | );
258 | }
259 |
260 | if (!data) {
261 | return null;
262 | }
263 |
264 | const briefData = data as any;
265 | return {
266 | id: briefData.id,
267 | accountId: briefData.account_id,
268 | documentId: briefData.document_id,
269 | status: briefData.status,
270 | createdAt: briefData.created_at,
271 | updatedAt: briefData.updated_at,
272 | document: briefData.document
273 | ? {
274 | id: briefData.document.id,
275 | document_name: briefData.document.document_name,
276 | title: briefData.document.title,
277 | description: briefData.document.description
278 | }
279 | : undefined
280 | };
281 | } catch (error) {
282 | if (error instanceof TaskMasterError) {
283 | throw error;
284 | }
285 | throw new TaskMasterError(
286 | 'Failed to fetch brief',
287 | ERROR_CODES.API_ERROR,
288 | { operation: 'getBrief', briefId },
289 | error as Error
290 | );
291 | }
292 | }
293 |
294 | /**
295 | * Validate that a user has access to an organization
296 | */
297 | async validateOrgAccess(orgId: string): Promise<boolean> {
298 | try {
299 | const org = await this.getOrganization(orgId);
300 | return org !== null;
301 | } catch (error) {
302 | this.logger.error(`Failed to validate org access: ${error}`);
303 | return false;
304 | }
305 | }
306 |
307 | /**
308 | * Validate that a user has access to a brief
309 | */
310 | async validateBriefAccess(briefId: string): Promise<boolean> {
311 | try {
312 | const brief = await this.getBrief(briefId);
313 | return brief !== null;
314 | } catch (error) {
315 | this.logger.error(`Failed to validate brief access: ${error}`);
316 | return false;
317 | }
318 | }
319 |
320 | /**
321 | * Get all tasks for a specific brief
322 | */
323 | async getTasks(briefId: string): Promise<RemoteTask[]> {
324 | try {
325 | const { data, error } = await this.supabaseClient
326 | .from('tasks')
327 | .select(`
328 | *,
329 | document:document_id (
330 | id,
331 | document_name,
332 | title,
333 | description
334 | )
335 | `)
336 | .eq('brief_id', briefId)
337 | .order('position', { ascending: true })
338 | .order('subtask_position', { ascending: true })
339 | .order('created_at', { ascending: true });
340 |
341 | if (error) {
342 | throw new TaskMasterError(
343 | `Failed to fetch tasks: ${error.message}`,
344 | ERROR_CODES.API_ERROR,
345 | { operation: 'getTasks', briefId },
346 | error
347 | );
348 | }
349 |
350 | if (!data || data.length === 0) {
351 | this.logger.debug(`No tasks found for brief ${briefId}`);
352 | return [];
353 | }
354 |
355 | // Map to our RemoteTask interface
356 | return data.map((task: any) => ({
357 | id: task.id,
358 | briefId: task.brief_id,
359 | documentId: task.document_id,
360 | position: task.position,
361 | subtaskPosition: task.subtask_position,
362 | status: task.status,
363 | createdAt: task.created_at,
364 | updatedAt: task.updated_at,
365 | document: task.document
366 | ? {
367 | id: task.document.id,
368 | document_name: task.document.document_name,
369 | title: task.document.title,
370 | description: task.document.description
371 | }
372 | : undefined
373 | }));
374 | } catch (error) {
375 | if (error instanceof TaskMasterError) {
376 | throw error;
377 | }
378 | throw new TaskMasterError(
379 | 'Failed to fetch tasks',
380 | ERROR_CODES.API_ERROR,
381 | { operation: 'getTasks', briefId },
382 | error as Error
383 | );
384 | }
385 | }
386 | }
387 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/tasks/repositories/supabase/supabase-repository.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { SupabaseClient } from '@supabase/supabase-js';
2 | import { Task } from '../../../../common/types/index.js';
3 | import { Database, Json } from '../../../../common/types/database.types.js';
4 | import { TaskMapper } from '../../../../common/mappers/TaskMapper.js';
5 | import { AuthManager } from '../../../auth/managers/auth-manager.js';
6 | import { DependencyFetcher } from './dependency-fetcher.js';
7 | import {
8 | TaskWithRelations,
9 | TaskDatabaseUpdate
10 | } from '../../../../common/types/repository-types.js';
11 | import { LoadTasksOptions } from '../../../../common/interfaces/storage.interface.js';
12 | import { Brief } from '../task-repository.interface.js';
13 | import { z } from 'zod';
14 |
15 | // Zod schema for task status validation
16 | const TaskStatusSchema = z.enum([
17 | 'pending',
18 | 'in-progress',
19 | 'done',
20 | 'review',
21 | 'deferred',
22 | 'cancelled',
23 | 'blocked'
24 | ]);
25 |
26 | // Zod schema for task updates
27 | const TaskUpdateSchema = z
28 | .object({
29 | title: z.string().min(1).optional(),
30 | description: z.string().optional(),
31 | status: TaskStatusSchema.optional(),
32 | priority: z.enum(['low', 'medium', 'high', 'critical']).optional(),
33 | details: z.string().optional(),
34 | testStrategy: z.string().optional()
35 | })
36 | .partial();
37 |
38 | export class SupabaseRepository {
39 | private dependencyFetcher: DependencyFetcher;
40 | private authManager: AuthManager;
41 |
42 | constructor(private supabase: SupabaseClient<Database>) {
43 | this.dependencyFetcher = new DependencyFetcher(supabase);
44 | this.authManager = AuthManager.getInstance();
45 | }
46 |
47 | /**
48 | * Gets the current brief ID from auth context
49 | * @throws {Error} If no brief is selected
50 | */
51 | private getBriefIdOrThrow(): string {
52 | const context = this.authManager.getContext();
53 | if (!context?.briefId) {
54 | throw new Error(
55 | 'No brief selected. Please select a brief first using: tm context brief'
56 | );
57 | }
58 | return context.briefId;
59 | }
60 |
61 | async getTasks(
62 | _projectId?: string,
63 | options?: LoadTasksOptions
64 | ): Promise<Task[]> {
65 | const briefId = this.getBriefIdOrThrow();
66 |
67 | // Build query with filters
68 | let query = this.supabase
69 | .from('tasks')
70 | .select(`
71 | *,
72 | document:document_id (
73 | id,
74 | document_name,
75 | title,
76 | description
77 | )
78 | `)
79 | .eq('brief_id', briefId);
80 |
81 | // Apply status filter at database level if specified
82 | if (options?.status) {
83 | const dbStatus = this.mapStatusToDatabase(options.status);
84 | query = query.eq('status', dbStatus);
85 | }
86 |
87 | // Apply subtask exclusion at database level if specified
88 | if (options?.excludeSubtasks) {
89 | // Only fetch parent tasks (where parent_task_id is null)
90 | query = query.is('parent_task_id', null);
91 | }
92 |
93 | // Execute query with ordering
94 | const { data: tasks, error } = await query
95 | .order('position', { ascending: true })
96 | .order('subtask_position', { ascending: true })
97 | .order('created_at', { ascending: true });
98 |
99 | if (error) {
100 | throw new Error(`Failed to fetch tasks: ${error.message}`);
101 | }
102 |
103 | if (!tasks || tasks.length === 0) {
104 | return [];
105 | }
106 |
107 | // Type-safe task ID extraction
108 | const typedTasks = tasks as TaskWithRelations[];
109 | const taskIds = typedTasks.map((t) => t.id);
110 | const dependenciesMap =
111 | await this.dependencyFetcher.fetchDependenciesWithDisplayIds(taskIds);
112 |
113 | // Use mapper to convert to internal format
114 | return TaskMapper.mapDatabaseTasksToTasks(tasks, dependenciesMap);
115 | }
116 |
117 | async getTask(_projectId: string, taskId: string): Promise<Task | null> {
118 | const briefId = this.getBriefIdOrThrow();
119 |
120 | const { data, error } = await this.supabase
121 | .from('tasks')
122 | .select('*')
123 | .eq('brief_id', briefId)
124 | .eq('display_id', taskId.toUpperCase())
125 | .single();
126 |
127 | if (error) {
128 | if (error.code === 'PGRST116') {
129 | return null; // Not found
130 | }
131 | throw new Error(`Failed to fetch task: ${error.message}`);
132 | }
133 |
134 | // Get subtasks if this is a parent task
135 | const { data: subtasksData } = await this.supabase
136 | .from('tasks')
137 | .select('*')
138 | .eq('parent_task_id', data.id)
139 | .order('subtask_position', { ascending: true });
140 |
141 | // Get all task IDs (parent + subtasks) to fetch dependencies
142 | const allTaskIds = [data.id, ...(subtasksData?.map((st) => st.id) || [])];
143 |
144 | // Fetch dependencies using the dedicated fetcher
145 | const dependenciesByTaskId =
146 | await this.dependencyFetcher.fetchDependenciesWithDisplayIds(allTaskIds);
147 |
148 | // Use mapper to convert single task
149 | return TaskMapper.mapDatabaseTaskToTask(
150 | data,
151 | subtasksData || [],
152 | dependenciesByTaskId
153 | );
154 | }
155 |
156 | /**
157 | * Get brief information by ID
158 | * Note: This doesn't use getBriefIdOrThrow() because we may need to fetch
159 | * briefs other than the current context brief
160 | */
161 | async getBrief(briefId: string): Promise<Brief | null> {
162 | const { data, error } = await this.supabase
163 | .from('brief')
164 | .select(`
165 | *,
166 | document:document_id (
167 | id,
168 | title,
169 | description
170 | )
171 | `)
172 | .eq('id', briefId)
173 | .single();
174 |
175 | if (error) {
176 | if (error.code === 'PGRST116') {
177 | return null; // Not found
178 | }
179 | throw new Error(`Failed to fetch brief: ${error.message}`);
180 | }
181 |
182 | if (!data) {
183 | return null;
184 | }
185 |
186 | // Extract document data if available
187 | const document = data.document as any;
188 |
189 | // Map database fields to Brief interface
190 | return {
191 | id: data.id,
192 | accountId: data.account_id,
193 | createdAt: data.created_at,
194 | name: document?.title || undefined,
195 | description: document?.description || undefined,
196 | status: data.status || undefined
197 | };
198 | }
199 |
200 | async updateTask(
201 | projectId: string,
202 | taskId: string,
203 | updates: Partial<Task>
204 | ): Promise<Task> {
205 | const briefId = this.getBriefIdOrThrow();
206 |
207 | // Validate updates using Zod schema
208 | try {
209 | TaskUpdateSchema.parse(updates);
210 | } catch (error) {
211 | if (error instanceof z.ZodError) {
212 | const errorMessages = error.issues
213 | .map((err) => `${err.path.join('.')}: ${err.message}`)
214 | .join(', ');
215 | throw new Error(`Invalid task update data: ${errorMessages}`);
216 | }
217 | throw error;
218 | }
219 |
220 | // Convert Task fields to database fields with proper typing
221 | const dbUpdates: TaskDatabaseUpdate = {};
222 |
223 | if (updates.title !== undefined) dbUpdates.title = updates.title;
224 | if (updates.description !== undefined)
225 | dbUpdates.description = updates.description;
226 | if (updates.status !== undefined)
227 | dbUpdates.status = this.mapStatusToDatabase(updates.status);
228 | if (updates.priority !== undefined)
229 | dbUpdates.priority = this.mapPriorityToDatabase(updates.priority);
230 |
231 | // Handle metadata fields (details, testStrategy, etc.)
232 | // Load existing metadata to preserve fields not being updated
233 | const { data: existingMetadataRow, error: existingMetadataError } =
234 | await this.supabase
235 | .from('tasks')
236 | .select('metadata')
237 | .eq('brief_id', briefId)
238 | .eq('display_id', taskId.toUpperCase())
239 | .single();
240 |
241 | if (existingMetadataError) {
242 | throw new Error(
243 | `Failed to load existing task metadata: ${existingMetadataError.message}`
244 | );
245 | }
246 |
247 | const metadata: Record<string, unknown> = {
248 | ...((existingMetadataRow?.metadata as Record<string, unknown>) ?? {})
249 | };
250 |
251 | if (updates.details !== undefined) metadata.details = updates.details;
252 | if (updates.testStrategy !== undefined)
253 | metadata.testStrategy = updates.testStrategy;
254 |
255 | if (Object.keys(metadata).length > 0) {
256 | dbUpdates.metadata = metadata as Json;
257 | }
258 |
259 | // Update the task
260 | const { error } = await this.supabase
261 | .from('tasks')
262 | .update(dbUpdates)
263 | .eq('brief_id', briefId)
264 | .eq('display_id', taskId.toUpperCase());
265 |
266 | if (error) {
267 | throw new Error(`Failed to update task: ${error.message}`);
268 | }
269 |
270 | // Return the updated task by fetching it
271 | const updatedTask = await this.getTask(projectId, taskId);
272 | if (!updatedTask) {
273 | throw new Error(`Failed to retrieve updated task ${taskId}`);
274 | }
275 |
276 | return updatedTask;
277 | }
278 |
279 | /**
280 | * Maps internal status to database status
281 | */
282 | private mapStatusToDatabase(
283 | status: string
284 | ): Database['public']['Enums']['task_status'] {
285 | switch (status) {
286 | case 'pending':
287 | return 'todo';
288 | case 'in-progress':
289 | case 'in_progress': // Accept both formats
290 | return 'in_progress';
291 | case 'done':
292 | return 'done';
293 | default:
294 | throw new Error(
295 | `Invalid task status: ${status}. Valid statuses are: pending, in-progress, done`
296 | );
297 | }
298 | }
299 |
300 | /**
301 | * Maps internal priority to database priority
302 | * Task Master uses 'critical', database uses 'urgent'
303 | */
304 | private mapPriorityToDatabase(
305 | priority: string
306 | ): Database['public']['Enums']['task_priority'] {
307 | switch (priority) {
308 | case 'critical':
309 | return 'urgent';
310 | case 'low':
311 | case 'medium':
312 | case 'high':
313 | return priority as Database['public']['Enums']['task_priority'];
314 | default:
315 | throw new Error(
316 | `Invalid task priority: ${priority}. Valid priorities are: low, medium, high, critical`
317 | );
318 | }
319 | }
320 | }
321 |
```
--------------------------------------------------------------------------------
/src/profiles/claude.js:
--------------------------------------------------------------------------------
```javascript
1 | // Claude Code 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 } from './base-profile.js';
6 |
7 | // Helper function to recursively copy directory (adopted from Roo profile)
8 | function copyRecursiveSync(src, dest) {
9 | const exists = fs.existsSync(src);
10 | const stats = exists && fs.statSync(src);
11 | const isDirectory = exists && stats.isDirectory();
12 | if (isDirectory) {
13 | if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
14 | fs.readdirSync(src).forEach((childItemName) => {
15 | copyRecursiveSync(
16 | path.join(src, childItemName),
17 | path.join(dest, childItemName)
18 | );
19 | });
20 | } else {
21 | fs.copyFileSync(src, dest);
22 | }
23 | }
24 |
25 | // Helper function to recursively remove directory
26 | function removeDirectoryRecursive(dirPath) {
27 | if (fs.existsSync(dirPath)) {
28 | try {
29 | fs.rmSync(dirPath, { recursive: true, force: true });
30 | return true;
31 | } catch (err) {
32 | log('error', `Failed to remove directory ${dirPath}: ${err.message}`);
33 | return false;
34 | }
35 | }
36 | return true;
37 | }
38 |
39 | // Lifecycle functions for Claude Code profile
40 | function onAddRulesProfile(targetDir, assetsDir) {
41 | // Note: Commands and agents are now distributed via Claude Code plugin
42 | // Legacy .claude directory copying has been deprecated
43 | log(
44 | 'info',
45 | '[Claude] Commands and agents are now available via the Task Master plugin'
46 | );
47 | log('info', '[Claude] Install with: /plugin marketplace add taskmaster');
48 | log('info', '[Claude] Then: /plugin install taskmaster@taskmaster');
49 |
50 | // Handle CLAUDE.md import for non-destructive integration
51 | const sourceFile = path.join(assetsDir, 'AGENTS.md');
52 | const userClaudeFile = path.join(targetDir, 'CLAUDE.md');
53 | const taskMasterClaudeFile = path.join(targetDir, '.taskmaster', 'CLAUDE.md');
54 | const importLine = '@./.taskmaster/CLAUDE.md';
55 | const importSection = `\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main CLAUDE.md file.**\n${importLine}`;
56 |
57 | if (fs.existsSync(sourceFile)) {
58 | try {
59 | // Ensure .taskmaster directory exists
60 | const taskMasterDir = path.join(targetDir, '.taskmaster');
61 | if (!fs.existsSync(taskMasterDir)) {
62 | fs.mkdirSync(taskMasterDir, { recursive: true });
63 | }
64 |
65 | // Copy Task Master instructions to .taskmaster/CLAUDE.md
66 | fs.copyFileSync(sourceFile, taskMasterClaudeFile);
67 | log(
68 | 'debug',
69 | `[Claude] Created Task Master instructions at ${taskMasterClaudeFile}`
70 | );
71 |
72 | // Handle user's CLAUDE.md
73 | if (fs.existsSync(userClaudeFile)) {
74 | // Check if import already exists
75 | const content = fs.readFileSync(userClaudeFile, 'utf8');
76 | if (!content.includes(importLine)) {
77 | // Append import section at the end
78 | const updatedContent = content.trim() + '\n' + importSection + '\n';
79 | fs.writeFileSync(userClaudeFile, updatedContent);
80 | log(
81 | 'info',
82 | `[Claude] Added Task Master import to existing ${userClaudeFile}`
83 | );
84 | } else {
85 | log(
86 | 'info',
87 | `[Claude] Task Master import already present in ${userClaudeFile}`
88 | );
89 | }
90 | } else {
91 | // Create minimal CLAUDE.md with the import section
92 | const minimalContent = `# Claude Code Instructions\n${importSection}\n`;
93 | fs.writeFileSync(userClaudeFile, minimalContent);
94 | log(
95 | 'info',
96 | `[Claude] Created ${userClaudeFile} with Task Master import`
97 | );
98 | }
99 | } catch (err) {
100 | log(
101 | 'error',
102 | `[Claude] Failed to set up Claude instructions: ${err.message}`
103 | );
104 | }
105 | }
106 | }
107 |
108 | function onRemoveRulesProfile(targetDir) {
109 | // Note: .claude directory (commands/agents) are now managed by Claude Code plugin
110 | // We no longer remove them here - users should uninstall the plugin separately
111 | log(
112 | 'info',
113 | '[Claude] To remove Task Master commands/agents, uninstall the plugin with: /plugin uninstall taskmaster'
114 | );
115 |
116 | // Clean up CLAUDE.md import
117 | const userClaudeFile = path.join(targetDir, 'CLAUDE.md');
118 | const taskMasterClaudeFile = path.join(targetDir, '.taskmaster', 'CLAUDE.md');
119 | const importLine = '@./.taskmaster/CLAUDE.md';
120 |
121 | try {
122 | // Remove Task Master CLAUDE.md from .taskmaster
123 | if (fs.existsSync(taskMasterClaudeFile)) {
124 | fs.rmSync(taskMasterClaudeFile, { force: true });
125 | log('debug', `[Claude] Removed ${taskMasterClaudeFile}`);
126 | }
127 |
128 | // Clean up import from user's CLAUDE.md
129 | if (fs.existsSync(userClaudeFile)) {
130 | const content = fs.readFileSync(userClaudeFile, 'utf8');
131 | const lines = content.split('\n');
132 | const filteredLines = [];
133 | let skipNextLines = 0;
134 |
135 | // Remove the Task Master section
136 | for (let i = 0; i < lines.length; i++) {
137 | if (skipNextLines > 0) {
138 | skipNextLines--;
139 | continue;
140 | }
141 |
142 | // Check if this is the start of our Task Master section
143 | if (lines[i].includes('## Task Master AI Instructions')) {
144 | // Skip this line and the next two lines (bold text and import)
145 | skipNextLines = 2;
146 | continue;
147 | }
148 |
149 | // Also remove standalone import lines (for backward compatibility)
150 | if (lines[i].trim() === importLine) {
151 | continue;
152 | }
153 |
154 | filteredLines.push(lines[i]);
155 | }
156 |
157 | // Join back and clean up excessive newlines
158 | let updatedContent = filteredLines
159 | .join('\n')
160 | .replace(/\n{3,}/g, '\n\n')
161 | .trim();
162 |
163 | // Check if file only contained our minimal template
164 | if (
165 | updatedContent === '# Claude Code Instructions' ||
166 | updatedContent === ''
167 | ) {
168 | // File only contained our import, remove it
169 | fs.rmSync(userClaudeFile, { force: true });
170 | log('debug', `[Claude] Removed empty ${userClaudeFile}`);
171 | } else {
172 | // Write back without the import
173 | fs.writeFileSync(userClaudeFile, updatedContent + '\n');
174 | log(
175 | 'debug',
176 | `[Claude] Removed Task Master import from ${userClaudeFile}`
177 | );
178 | }
179 | }
180 | } catch (err) {
181 | log(
182 | 'error',
183 | `[Claude] Failed to remove Claude instructions: ${err.message}`
184 | );
185 | }
186 | }
187 |
188 | /**
189 | * Transform standard MCP config format to Claude format
190 | * @param {Object} mcpConfig - Standard MCP configuration object
191 | * @returns {Object} - Transformed Claude configuration object
192 | */
193 | function transformToClaudeFormat(mcpConfig) {
194 | const claudeConfig = {};
195 |
196 | // Transform mcpServers to servers (keeping the same structure but adding type)
197 | if (mcpConfig.mcpServers) {
198 | claudeConfig.mcpServers = {};
199 |
200 | for (const [serverName, serverConfig] of Object.entries(
201 | mcpConfig.mcpServers
202 | )) {
203 | // Transform server configuration with type as first key
204 | const reorderedServer = {};
205 |
206 | // Add type: "stdio" as the first key
207 | reorderedServer.type = 'stdio';
208 |
209 | // Then add the rest of the properties in order
210 | if (serverConfig.command) reorderedServer.command = serverConfig.command;
211 | if (serverConfig.args) reorderedServer.args = serverConfig.args;
212 | if (serverConfig.env) reorderedServer.env = serverConfig.env;
213 |
214 | // Add any other properties that might exist
215 | Object.keys(serverConfig).forEach((key) => {
216 | if (!['command', 'args', 'env', 'type'].includes(key)) {
217 | reorderedServer[key] = serverConfig[key];
218 | }
219 | });
220 |
221 | claudeConfig.mcpServers[serverName] = reorderedServer;
222 | }
223 | }
224 |
225 | return claudeConfig;
226 | }
227 |
228 | function onPostConvertRulesProfile(targetDir, assetsDir) {
229 | // For Claude, post-convert is the same as add since we don't transform rules
230 | onAddRulesProfile(targetDir, assetsDir);
231 |
232 | // Transform MCP configuration to Claude format
233 | const mcpConfigPath = path.join(targetDir, '.mcp.json');
234 | if (fs.existsSync(mcpConfigPath)) {
235 | try {
236 | const mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
237 | const claudeConfig = transformToClaudeFormat(mcpConfig);
238 |
239 | // Write back the transformed configuration
240 | fs.writeFileSync(
241 | mcpConfigPath,
242 | JSON.stringify(claudeConfig, null, '\t') + '\n'
243 | );
244 | log(
245 | 'debug',
246 | `[Claude] Transformed MCP configuration to Claude format at ${mcpConfigPath}`
247 | );
248 | } catch (err) {
249 | log(
250 | 'error',
251 | `[Claude] Failed to transform MCP configuration: ${err.message}`
252 | );
253 | }
254 | }
255 | }
256 |
257 | // Create and export claude profile using the base factory
258 | export const claudeProfile = createProfile({
259 | name: 'claude',
260 | displayName: 'Claude Code',
261 | url: 'claude.ai',
262 | docsUrl: 'docs.anthropic.com/en/docs/claude-code',
263 | profileDir: '.', // Root directory
264 | rulesDir: '.', // No specific rules directory needed
265 | mcpConfigName: '.mcp.json', // Place MCP config in project root
266 | includeDefaultRules: false,
267 | fileMap: {
268 | 'AGENTS.md': '.taskmaster/CLAUDE.md'
269 | },
270 | onAdd: onAddRulesProfile,
271 | onRemove: onRemoveRulesProfile,
272 | onPostConvert: onPostConvertRulesProfile
273 | });
274 |
275 | // Export lifecycle functions separately to avoid naming conflicts
276 | export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };
277 |
```
--------------------------------------------------------------------------------
/apps/extension/src/webview/hooks/useTaskQueries.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
2 | import { useVSCodeContext } from '../contexts/VSCodeContext';
3 | import type { TaskMasterTask, TaskUpdates } from '../types';
4 |
5 | // Query keys factory
6 | export const taskKeys = {
7 | all: ['tasks'] as const,
8 | lists: () => [...taskKeys.all, 'list'] as const,
9 | list: (filters: { tag?: string; status?: string }) =>
10 | [...taskKeys.lists(), filters] as const,
11 | details: () => [...taskKeys.all, 'detail'] as const,
12 | detail: (id: string) => [...taskKeys.details(), id] as const
13 | };
14 |
15 | // Hook to fetch all tasks
16 | export function useTasks(options?: { tag?: string; status?: string }) {
17 | const { sendMessage } = useVSCodeContext();
18 |
19 | return useQuery({
20 | queryKey: taskKeys.list(options || {}),
21 | queryFn: async () => {
22 | console.log('🔍 Fetching tasks with options:', options);
23 | const response = await sendMessage({
24 | type: 'getTasks',
25 | data: {
26 | tag: options?.tag,
27 | withSubtasks: true
28 | }
29 | });
30 | console.log('📋 Tasks fetched:', response);
31 | return response as TaskMasterTask[];
32 | },
33 | staleTime: 0 // Consider data stale immediately
34 | });
35 | }
36 |
37 | // Hook to fetch a single task with full details
38 | export function useTaskDetails(taskId: string) {
39 | const { sendMessage } = useVSCodeContext();
40 |
41 | return useQuery({
42 | queryKey: taskKeys.detail(taskId),
43 | queryFn: async () => {
44 | const response = await sendMessage({
45 | type: 'mcpRequest',
46 | tool: 'get_task',
47 | params: {
48 | id: taskId
49 | }
50 | });
51 |
52 | // Parse the MCP response
53 | let fullTaskData = null;
54 | if (response?.data?.content?.[0]?.text) {
55 | try {
56 | const parsed = JSON.parse(response.data.content[0].text);
57 | fullTaskData = parsed.data;
58 | } catch (e) {
59 | console.error('Failed to parse MCP response:', e);
60 | }
61 | } else if (response?.data?.data) {
62 | fullTaskData = response.data.data;
63 | }
64 |
65 | return fullTaskData as TaskMasterTask;
66 | },
67 | enabled: !!taskId
68 | });
69 | }
70 |
71 | // Hook to update task status
72 | export function useUpdateTaskStatus() {
73 | const { sendMessage } = useVSCodeContext();
74 | const queryClient = useQueryClient();
75 |
76 | return useMutation({
77 | mutationFn: async ({
78 | taskId,
79 | newStatus
80 | }: {
81 | taskId: string;
82 | newStatus: TaskMasterTask['status'];
83 | }) => {
84 | const response = await sendMessage({
85 | type: 'updateTaskStatus',
86 | data: { taskId, newStatus }
87 | });
88 | return { taskId, newStatus, response };
89 | },
90 | // Optimistic update to prevent snap-back
91 | onMutate: async ({ taskId, newStatus }) => {
92 | // Cancel any outgoing refetches
93 | await queryClient.cancelQueries({ queryKey: taskKeys.all });
94 |
95 | // Snapshot the previous value
96 | const previousTasks = queryClient.getQueriesData({
97 | queryKey: taskKeys.all
98 | });
99 |
100 | // Optimistically update all task queries
101 | queryClient.setQueriesData({ queryKey: taskKeys.all }, (old: any) => {
102 | if (!old) return old;
103 |
104 | // Handle both array and object responses
105 | if (Array.isArray(old)) {
106 | return old.map((task: TaskMasterTask) =>
107 | task.id === taskId ? { ...task, status: newStatus } : task
108 | );
109 | }
110 |
111 | return old;
112 | });
113 |
114 | // Return a context object with the snapshot
115 | return { previousTasks };
116 | },
117 | // If the mutation fails, roll back to the previous value
118 | onError: (err, variables, context) => {
119 | if (context?.previousTasks) {
120 | context.previousTasks.forEach(([queryKey, data]) => {
121 | queryClient.setQueryData(queryKey, data);
122 | });
123 | }
124 | },
125 | // Always refetch after error or success to ensure consistency
126 | onSettled: () => {
127 | queryClient.invalidateQueries({ queryKey: taskKeys.all });
128 | }
129 | });
130 | }
131 |
132 | // Hook to update task content
133 | export function useUpdateTask() {
134 | const { sendMessage } = useVSCodeContext();
135 | const queryClient = useQueryClient();
136 |
137 | return useMutation({
138 | mutationFn: async ({
139 | taskId,
140 | updates,
141 | options = {}
142 | }: {
143 | taskId: string;
144 | updates: TaskUpdates | { description: string };
145 | options?: { append?: boolean; research?: boolean };
146 | }) => {
147 | console.log('🔄 Updating task:', taskId, updates, options);
148 |
149 | const response = await sendMessage({
150 | type: 'updateTask',
151 | data: { taskId, updates, options }
152 | });
153 |
154 | console.log('📥 Update task response:', response);
155 |
156 | // Check for error in response
157 | if (response && typeof response === 'object' && 'error' in response) {
158 | throw new Error(response.error || 'Failed to update task');
159 | }
160 |
161 | return response;
162 | },
163 | onSuccess: async (data, variables) => {
164 | console.log('✅ Task update successful, invalidating all task queries');
165 | console.log('Response data:', data);
166 | console.log('Task ID:', variables.taskId);
167 |
168 | // Invalidate ALL task-related queries (same as handleRefresh)
169 | await queryClient.invalidateQueries({
170 | queryKey: taskKeys.all
171 | });
172 |
173 | console.log(
174 | '🔄 All task queries invalidated for task:',
175 | variables.taskId
176 | );
177 | }
178 | });
179 | }
180 |
181 | // Hook to update subtask
182 | export function useUpdateSubtask() {
183 | const { sendMessage } = useVSCodeContext();
184 | const queryClient = useQueryClient();
185 |
186 | return useMutation({
187 | mutationFn: async ({
188 | taskId,
189 | prompt,
190 | options = {}
191 | }: {
192 | taskId: string;
193 | prompt: string;
194 | options?: { research?: boolean };
195 | }) => {
196 | console.log('🔄 Updating subtask:', taskId, prompt, options);
197 |
198 | const response = await sendMessage({
199 | type: 'updateSubtask',
200 | data: { taskId, prompt, options }
201 | });
202 |
203 | console.log('📥 Update subtask response:', response);
204 |
205 | // Check for error in response
206 | if (response && typeof response === 'object' && 'error' in response) {
207 | throw new Error(response.error || 'Failed to update subtask');
208 | }
209 |
210 | return response;
211 | },
212 | onSuccess: async (data, variables) => {
213 | console.log(
214 | '✅ Subtask update successful, invalidating all task queries'
215 | );
216 | console.log('Subtask ID:', variables.taskId);
217 |
218 | // Invalidate ALL task-related queries (same as handleRefresh)
219 | await queryClient.invalidateQueries({
220 | queryKey: taskKeys.all
221 | });
222 |
223 | console.log(
224 | '🔄 All task queries invalidated for subtask:',
225 | variables.taskId
226 | );
227 | }
228 | });
229 | }
230 |
231 | // Hook to scope up task complexity
232 | export function useScopeUpTask() {
233 | const { sendMessage } = useVSCodeContext();
234 | const queryClient = useQueryClient();
235 |
236 | return useMutation({
237 | mutationFn: async ({
238 | taskId,
239 | strength = 'regular',
240 | prompt,
241 | options = {}
242 | }: {
243 | taskId: string;
244 | strength?: 'light' | 'regular' | 'heavy';
245 | prompt?: string;
246 | options?: { research?: boolean };
247 | }) => {
248 | console.log('🔄 Scoping up task:', taskId, strength, prompt, options);
249 |
250 | const response = await sendMessage({
251 | type: 'mcpRequest',
252 | tool: 'scope_up_task',
253 | params: {
254 | id: String(taskId),
255 | strength,
256 | prompt,
257 | research: options.research || false
258 | }
259 | });
260 |
261 | console.log('📥 Scope up task response:', response);
262 |
263 | // Check for error in response
264 | if (response && typeof response === 'object' && 'error' in response) {
265 | throw new Error(response.error || 'Failed to scope up task');
266 | }
267 |
268 | return response;
269 | },
270 | onSuccess: async (data, variables) => {
271 | console.log('✅ Task scope up successful, invalidating all task queries');
272 | console.log('Task ID:', variables.taskId);
273 |
274 | // Invalidate ALL task-related queries
275 | await queryClient.invalidateQueries({
276 | queryKey: taskKeys.all
277 | });
278 |
279 | console.log(
280 | '🔄 All task queries invalidated for scoped up task:',
281 | variables.taskId
282 | );
283 | }
284 | });
285 | }
286 |
287 | // Hook to scope down task complexity
288 | export function useScopeDownTask() {
289 | const { sendMessage } = useVSCodeContext();
290 | const queryClient = useQueryClient();
291 |
292 | return useMutation({
293 | mutationFn: async ({
294 | taskId,
295 | strength = 'regular',
296 | prompt,
297 | options = {}
298 | }: {
299 | taskId: string;
300 | strength?: 'light' | 'regular' | 'heavy';
301 | prompt?: string;
302 | options?: { research?: boolean };
303 | }) => {
304 | console.log('🔄 Scoping down task:', taskId, strength, prompt, options);
305 |
306 | const response = await sendMessage({
307 | type: 'mcpRequest',
308 | tool: 'scope_down_task',
309 | params: {
310 | id: String(taskId),
311 | strength,
312 | prompt,
313 | research: options.research || false
314 | }
315 | });
316 |
317 | console.log('📥 Scope down task response:', response);
318 |
319 | // Check for error in response
320 | if (response && typeof response === 'object' && 'error' in response) {
321 | throw new Error(response.error || 'Failed to scope down task');
322 | }
323 |
324 | return response;
325 | },
326 | onSuccess: async (data, variables) => {
327 | console.log(
328 | '✅ Task scope down successful, invalidating all task queries'
329 | );
330 | console.log('Task ID:', variables.taskId);
331 |
332 | // Invalidate ALL task-related queries
333 | await queryClient.invalidateQueries({
334 | queryKey: taskKeys.all
335 | });
336 |
337 | console.log(
338 | '🔄 All task queries invalidated for scoped down task:',
339 | variables.taskId
340 | );
341 | }
342 | });
343 | }
344 |
```
--------------------------------------------------------------------------------
/src/profiles/base-profile.js:
--------------------------------------------------------------------------------
```javascript
1 | // Base profile factory for rule-transformer
2 | import path from 'path';
3 |
4 | /**
5 | * Creates a standardized profile configuration for different editors
6 | * @param {Object} editorConfig - Editor-specific configuration
7 | * @param {string} editorConfig.name - Profile name (e.g., 'cursor', 'vscode')
8 | * @param {string} [editorConfig.displayName] - Display name for the editor (defaults to name)
9 | * @param {string} editorConfig.url - Editor website URL
10 | * @param {string} editorConfig.docsUrl - Editor documentation URL
11 | * @param {string} editorConfig.profileDir - Directory for profile configuration
12 | * @param {string} [editorConfig.rulesDir] - Directory for rules files (defaults to profileDir/rules)
13 | * @param {boolean} [editorConfig.mcpConfig=true] - Whether to create MCP configuration
14 | * @param {string} [editorConfig.mcpConfigName='mcp.json'] - Name of MCP config file
15 | * @param {string} [editorConfig.fileExtension='.mdc'] - Source file extension
16 | * @param {string} [editorConfig.targetExtension='.md'] - Target file extension
17 | * @param {Object} [editorConfig.toolMappings={}] - Tool name mappings
18 | * @param {Array} [editorConfig.customReplacements=[]] - Custom text replacements
19 | * @param {Object} [editorConfig.fileMap={}] - Custom file name mappings
20 | * @param {boolean} [editorConfig.supportsRulesSubdirectories=false] - Whether to use taskmaster/ subdirectory for taskmaster-specific rules (only Cursor uses this by default)
21 | * @param {boolean} [editorConfig.includeDefaultRules=true] - Whether to include default rule files
22 | * @param {Function} [editorConfig.onAdd] - Lifecycle hook for profile addition
23 | * @param {Function} [editorConfig.onRemove] - Lifecycle hook for profile removal
24 | * @param {Function} [editorConfig.onPostConvert] - Lifecycle hook for post-conversion
25 | * @returns {Object} - Complete profile configuration
26 | */
27 | export function createProfile(editorConfig) {
28 | const {
29 | name,
30 | displayName = name,
31 | url,
32 | docsUrl,
33 | profileDir = `.${name.toLowerCase()}`,
34 | rulesDir = `${profileDir}/rules`,
35 | mcpConfig = true,
36 | mcpConfigName = mcpConfig ? 'mcp.json' : null,
37 | fileExtension = '.mdc',
38 | targetExtension = '.md',
39 | toolMappings = {},
40 | customReplacements = [],
41 | fileMap = {},
42 | supportsRulesSubdirectories = false,
43 | includeDefaultRules = true,
44 | onAdd,
45 | onRemove,
46 | onPostConvert
47 | } = editorConfig;
48 |
49 | const mcpConfigPath = mcpConfigName
50 | ? path.join(profileDir, mcpConfigName)
51 | : null;
52 |
53 | // Standard file mapping with custom overrides
54 | // Use taskmaster subdirectory only if profile supports it
55 | const taskmasterPrefix = supportsRulesSubdirectories ? 'taskmaster/' : '';
56 | const defaultFileMap = {
57 | 'rules/cursor_rules.mdc': `${name.toLowerCase()}_rules${targetExtension}`,
58 | 'rules/dev_workflow.mdc': `${taskmasterPrefix}dev_workflow${targetExtension}`,
59 | 'rules/self_improve.mdc': `self_improve${targetExtension}`,
60 | 'rules/taskmaster.mdc': `${taskmasterPrefix}taskmaster${targetExtension}`
61 | };
62 |
63 | // Build final fileMap - merge defaults with custom entries when includeDefaultRules is true
64 | const finalFileMap = includeDefaultRules
65 | ? { ...defaultFileMap, ...fileMap }
66 | : fileMap;
67 |
68 | // Base global replacements that work for all editors
69 | const baseGlobalReplacements = [
70 | // Handle URLs in any context
71 | { from: /cursor\.so/gi, to: url },
72 | { from: /cursor\s*\.\s*so/gi, to: url },
73 | { from: /https?:\/\/cursor\.so/gi, to: `https://${url}` },
74 | { from: /https?:\/\/www\.cursor\.so/gi, to: `https://www.${url}` },
75 |
76 | // Handle tool references
77 | { from: /\bedit_file\b/gi, to: toolMappings.edit_file || 'edit_file' },
78 | {
79 | from: /\bsearch tool\b/gi,
80 | to: `${toolMappings.search || 'search'} tool`
81 | },
82 | { from: /\bSearch Tool\b/g, to: `${toolMappings.search || 'Search'} Tool` },
83 |
84 | // Handle basic terms with proper case handling
85 | {
86 | from: /\bcursor\b/gi,
87 | to: (match) =>
88 | match.charAt(0) === 'C' ? displayName : name.toLowerCase()
89 | },
90 | { from: /Cursor/g, to: displayName },
91 | { from: /CURSOR/g, to: displayName.toUpperCase() },
92 |
93 | // Handle file extensions if different
94 | ...(targetExtension !== fileExtension
95 | ? [
96 | {
97 | from: new RegExp(`\\${fileExtension}(?!\\])\\b`, 'g'),
98 | to: targetExtension
99 | }
100 | ]
101 | : []),
102 |
103 | // Handle documentation URLs
104 | { from: /docs\.cursor\.com/gi, to: docsUrl },
105 |
106 | // Custom editor-specific replacements
107 | ...customReplacements
108 | ];
109 |
110 | // Standard tool mappings
111 | const defaultToolMappings = {
112 | search: 'search',
113 | read_file: 'read_file',
114 | edit_file: 'edit_file',
115 | create_file: 'create_file',
116 | run_command: 'run_command',
117 | terminal_command: 'terminal_command',
118 | use_mcp: 'use_mcp',
119 | switch_mode: 'switch_mode',
120 | ...toolMappings
121 | };
122 |
123 | // Create conversion config
124 | const conversionConfig = {
125 | // Profile name replacements
126 | profileTerms: [
127 | { from: /cursor\.so/g, to: url },
128 | { from: /\[cursor\.so\]/g, to: `[${url}]` },
129 | { from: /href="https:\/\/cursor\.so/g, to: `href="https://${url}` },
130 | { from: /\(https:\/\/cursor\.so/g, to: `(https://${url}` },
131 | {
132 | from: /\bcursor\b/gi,
133 | to: (match) => (match === 'Cursor' ? displayName : name.toLowerCase())
134 | },
135 | { from: /Cursor/g, to: displayName }
136 | ],
137 |
138 | // File extension replacements
139 | fileExtensions:
140 | targetExtension !== fileExtension
141 | ? [
142 | {
143 | from: new RegExp(`\\${fileExtension}\\b`, 'g'),
144 | to: targetExtension
145 | }
146 | ]
147 | : [],
148 |
149 | // Documentation URL replacements
150 | docUrls: [
151 | {
152 | from: new RegExp(`https:\\/\\/docs\\.cursor\\.com\\/[^\\s)'\"]+`, 'g'),
153 | to: (match) => match.replace('docs.cursor.com', docsUrl)
154 | },
155 | {
156 | from: new RegExp(`https:\\/\\/${docsUrl}\\/`, 'g'),
157 | to: `https://${docsUrl}/`
158 | }
159 | ],
160 |
161 | // Tool references - direct replacements
162 | toolNames: defaultToolMappings,
163 |
164 | // Tool references in context - more specific replacements
165 | toolContexts: Object.entries(defaultToolMappings).flatMap(
166 | ([original, mapped]) => [
167 | {
168 | from: new RegExp(`\\b${original} tool\\b`, 'g'),
169 | to: `${mapped} tool`
170 | },
171 | { from: new RegExp(`\\bthe ${original}\\b`, 'g'), to: `the ${mapped}` },
172 | { from: new RegExp(`\\bThe ${original}\\b`, 'g'), to: `The ${mapped}` },
173 | {
174 | from: new RegExp(`\\bCursor ${original}\\b`, 'g'),
175 | to: `${displayName} ${mapped}`
176 | }
177 | ]
178 | ),
179 |
180 | // Tool group and category names
181 | toolGroups: [
182 | { from: /\bSearch tools\b/g, to: 'Read Group tools' },
183 | { from: /\bEdit tools\b/g, to: 'Edit Group tools' },
184 | { from: /\bRun tools\b/g, to: 'Command Group tools' },
185 | { from: /\bMCP servers\b/g, to: 'MCP Group tools' },
186 | { from: /\bSearch Group\b/g, to: 'Read Group' },
187 | { from: /\bEdit Group\b/g, to: 'Edit Group' },
188 | { from: /\bRun Group\b/g, to: 'Command Group' }
189 | ],
190 |
191 | // File references in markdown links
192 | fileReferences: {
193 | pathPattern: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
194 | replacement: (match, text, filePath) => {
195 | const baseName = path.basename(filePath, '.mdc');
196 | const newFileName =
197 | finalFileMap[`rules/${baseName}.mdc`] ||
198 | `${baseName}${targetExtension}`;
199 | // Update the link text to match the new filename (strip directory path for display)
200 | const newLinkText = path.basename(newFileName);
201 | // For Cursor, keep the mdc: protocol; for others, use standard relative paths
202 | if (name.toLowerCase() === 'cursor') {
203 | return `[${newLinkText}](mdc:${rulesDir}/${newFileName})`;
204 | } else {
205 | return `[${newLinkText}](${rulesDir}/${newFileName})`;
206 | }
207 | }
208 | }
209 | };
210 |
211 | function getTargetRuleFilename(sourceFilename) {
212 | if (finalFileMap[sourceFilename]) {
213 | return finalFileMap[sourceFilename];
214 | }
215 | return targetExtension !== fileExtension
216 | ? sourceFilename.replace(
217 | new RegExp(`\\${fileExtension}$`),
218 | targetExtension
219 | )
220 | : sourceFilename;
221 | }
222 |
223 | return {
224 | profileName: name, // Use name for programmatic access (tests expect this)
225 | displayName: displayName, // Keep displayName for UI purposes
226 | profileDir,
227 | rulesDir,
228 | mcpConfig,
229 | mcpConfigName,
230 | mcpConfigPath,
231 | supportsRulesSubdirectories,
232 | includeDefaultRules,
233 | fileMap: finalFileMap,
234 | globalReplacements: baseGlobalReplacements,
235 | conversionConfig,
236 | getTargetRuleFilename,
237 | targetExtension,
238 | // Optional lifecycle hooks
239 | ...(onAdd && { onAddRulesProfile: onAdd }),
240 | ...(onRemove && { onRemoveRulesProfile: onRemove }),
241 | ...(onPostConvert && { onPostConvertRulesProfile: onPostConvert })
242 | };
243 | }
244 |
245 | // Common tool mappings for editors that share similar tool sets
246 | export const COMMON_TOOL_MAPPINGS = {
247 | // Most editors (Cursor, Cline, Windsurf) keep original tool names
248 | STANDARD: {},
249 |
250 | // Roo Code uses different tool names
251 | ROO_STYLE: {
252 | edit_file: 'apply_diff',
253 | search: 'search_files',
254 | create_file: 'write_to_file',
255 | run_command: 'execute_command',
256 | terminal_command: 'execute_command',
257 | use_mcp: 'use_mcp_tool'
258 | }
259 | };
260 |
```
--------------------------------------------------------------------------------
/src/profiles/amp.js:
--------------------------------------------------------------------------------
```javascript
1 | // Amp 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 } from './base-profile.js';
6 |
7 | /**
8 | * Transform standard MCP config format to Amp format
9 | * @param {Object} mcpConfig - Standard MCP configuration object
10 | * @returns {Object} - Transformed Amp configuration object
11 | */
12 | function transformToAmpFormat(mcpConfig) {
13 | const ampConfig = {};
14 |
15 | // Transform mcpServers to amp.mcpServers
16 | if (mcpConfig.mcpServers) {
17 | ampConfig['amp.mcpServers'] = mcpConfig.mcpServers;
18 | }
19 |
20 | // Preserve any other existing settings
21 | for (const [key, value] of Object.entries(mcpConfig)) {
22 | if (key !== 'mcpServers') {
23 | ampConfig[key] = value;
24 | }
25 | }
26 |
27 | return ampConfig;
28 | }
29 |
30 | // Lifecycle functions for Amp profile
31 | function onAddRulesProfile(targetDir, assetsDir) {
32 | // Handle AGENT.md import for non-destructive integration (Amp uses AGENT.md, copies from AGENTS.md)
33 | const sourceFile = path.join(assetsDir, 'AGENTS.md');
34 | const userAgentFile = path.join(targetDir, 'AGENT.md');
35 | const taskMasterAgentFile = path.join(targetDir, '.taskmaster', 'AGENT.md');
36 | const importLine = '@./.taskmaster/AGENT.md';
37 | const importSection = `\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main AGENT.md file.**\n${importLine}`;
38 |
39 | if (fs.existsSync(sourceFile)) {
40 | try {
41 | // Ensure .taskmaster directory exists
42 | const taskMasterDir = path.join(targetDir, '.taskmaster');
43 | if (!fs.existsSync(taskMasterDir)) {
44 | fs.mkdirSync(taskMasterDir, { recursive: true });
45 | }
46 |
47 | // Copy Task Master instructions to .taskmaster/AGENT.md
48 | fs.copyFileSync(sourceFile, taskMasterAgentFile);
49 | log(
50 | 'debug',
51 | `[Amp] Created Task Master instructions at ${taskMasterAgentFile}`
52 | );
53 |
54 | // Handle user's AGENT.md
55 | if (fs.existsSync(userAgentFile)) {
56 | // Check if import already exists
57 | const content = fs.readFileSync(userAgentFile, 'utf8');
58 | if (!content.includes(importLine)) {
59 | // Append import section at the end
60 | const updatedContent = content.trim() + '\n' + importSection + '\n';
61 | fs.writeFileSync(userAgentFile, updatedContent);
62 | log(
63 | 'info',
64 | `[Amp] Added Task Master import to existing ${userAgentFile}`
65 | );
66 | } else {
67 | log(
68 | 'info',
69 | `[Amp] Task Master import already present in ${userAgentFile}`
70 | );
71 | }
72 | } else {
73 | // Create minimal AGENT.md with the import section
74 | const minimalContent = `# Amp Instructions\n${importSection}\n`;
75 | fs.writeFileSync(userAgentFile, minimalContent);
76 | log('info', `[Amp] Created ${userAgentFile} with Task Master import`);
77 | }
78 | } catch (err) {
79 | log('error', `[Amp] Failed to set up Amp instructions: ${err.message}`);
80 | }
81 | }
82 |
83 | // MCP transformation will be handled in onPostConvertRulesProfile
84 | }
85 |
86 | function onRemoveRulesProfile(targetDir) {
87 | // Clean up AGENT.md import (Amp uses AGENT.md, not AGENTS.md)
88 | const userAgentFile = path.join(targetDir, 'AGENT.md');
89 | const taskMasterAgentFile = path.join(targetDir, '.taskmaster', 'AGENT.md');
90 | const importLine = '@./.taskmaster/AGENT.md';
91 |
92 | try {
93 | // Remove Task Master AGENT.md from .taskmaster
94 | if (fs.existsSync(taskMasterAgentFile)) {
95 | fs.rmSync(taskMasterAgentFile, { force: true });
96 | log('debug', `[Amp] Removed ${taskMasterAgentFile}`);
97 | }
98 |
99 | // Clean up import from user's AGENT.md
100 | if (fs.existsSync(userAgentFile)) {
101 | const content = fs.readFileSync(userAgentFile, 'utf8');
102 | const lines = content.split('\n');
103 | const filteredLines = [];
104 | let skipNextLines = 0;
105 |
106 | // Remove the Task Master section
107 | for (let i = 0; i < lines.length; i++) {
108 | if (skipNextLines > 0) {
109 | skipNextLines--;
110 | continue;
111 | }
112 |
113 | // Check if this is the start of our Task Master section
114 | if (lines[i].includes('## Task Master AI Instructions')) {
115 | // Skip this line and the next two lines (bold text and import)
116 | skipNextLines = 2;
117 | continue;
118 | }
119 |
120 | // Also remove standalone import lines (for backward compatibility)
121 | if (lines[i].trim() === importLine) {
122 | continue;
123 | }
124 |
125 | filteredLines.push(lines[i]);
126 | }
127 |
128 | // Join back and clean up excessive newlines
129 | let updatedContent = filteredLines
130 | .join('\n')
131 | .replace(/\n{3,}/g, '\n\n')
132 | .trim();
133 |
134 | // Check if file only contained our minimal template
135 | if (updatedContent === '# Amp Instructions' || updatedContent === '') {
136 | // File only contained our import, remove it
137 | fs.rmSync(userAgentFile, { force: true });
138 | log('debug', `[Amp] Removed empty ${userAgentFile}`);
139 | } else {
140 | // Write back without the import
141 | fs.writeFileSync(userAgentFile, updatedContent + '\n');
142 | log('debug', `[Amp] Removed Task Master import from ${userAgentFile}`);
143 | }
144 | }
145 | } catch (err) {
146 | log('error', `[Amp] Failed to remove Amp instructions: ${err.message}`);
147 | }
148 |
149 | // MCP Removal: Remove amp.mcpServers section
150 | const mcpConfigPath = path.join(targetDir, '.vscode', 'settings.json');
151 |
152 | if (!fs.existsSync(mcpConfigPath)) {
153 | log('debug', '[Amp] No .vscode/settings.json found to clean up');
154 | return;
155 | }
156 |
157 | try {
158 | // Read the current config
159 | const configContent = fs.readFileSync(mcpConfigPath, 'utf8');
160 | const config = JSON.parse(configContent);
161 |
162 | // Check if it has the amp.mcpServers section and task-master-ai server
163 | if (
164 | config['amp.mcpServers'] &&
165 | config['amp.mcpServers']['task-master-ai']
166 | ) {
167 | // Remove task-master-ai server
168 | delete config['amp.mcpServers']['task-master-ai'];
169 |
170 | // Check if there are other MCP servers in amp.mcpServers
171 | const remainingServers = Object.keys(config['amp.mcpServers']);
172 |
173 | if (remainingServers.length === 0) {
174 | // No other servers, remove entire amp.mcpServers section
175 | delete config['amp.mcpServers'];
176 | log('debug', '[Amp] Removed empty amp.mcpServers section');
177 | }
178 |
179 | // Check if config is now empty
180 | const remainingKeys = Object.keys(config);
181 |
182 | if (remainingKeys.length === 0) {
183 | // Config is empty, remove entire file
184 | fs.rmSync(mcpConfigPath, { force: true });
185 | log('info', '[Amp] Removed empty settings.json file');
186 |
187 | // Check if .vscode directory is empty
188 | const vscodeDirPath = path.join(targetDir, '.vscode');
189 | if (fs.existsSync(vscodeDirPath)) {
190 | const remainingContents = fs.readdirSync(vscodeDirPath);
191 | if (remainingContents.length === 0) {
192 | fs.rmSync(vscodeDirPath, { recursive: true, force: true });
193 | log('debug', '[Amp] Removed empty .vscode directory');
194 | }
195 | }
196 | } else {
197 | // Write back the modified config
198 | fs.writeFileSync(
199 | mcpConfigPath,
200 | JSON.stringify(config, null, '\t') + '\n'
201 | );
202 | log(
203 | 'info',
204 | '[Amp] Removed TaskMaster from settings.json, preserved other configurations'
205 | );
206 | }
207 | } else {
208 | log('debug', '[Amp] TaskMaster not found in amp.mcpServers');
209 | }
210 | } catch (error) {
211 | log('error', `[Amp] Failed to clean up settings.json: ${error.message}`);
212 | }
213 | }
214 |
215 | function onPostConvertRulesProfile(targetDir, assetsDir) {
216 | // Handle AGENT.md setup (same as onAddRulesProfile)
217 | onAddRulesProfile(targetDir, assetsDir);
218 |
219 | // Transform MCP config to Amp format
220 | const mcpConfigPath = path.join(targetDir, '.vscode', 'settings.json');
221 |
222 | if (!fs.existsSync(mcpConfigPath)) {
223 | log('debug', '[Amp] No .vscode/settings.json found to transform');
224 | return;
225 | }
226 |
227 | try {
228 | // Read the generated standard MCP config
229 | const mcpConfigContent = fs.readFileSync(mcpConfigPath, 'utf8');
230 | const mcpConfig = JSON.parse(mcpConfigContent);
231 |
232 | // Check if it's already in Amp format (has amp.mcpServers)
233 | if (mcpConfig['amp.mcpServers']) {
234 | log(
235 | 'info',
236 | '[Amp] settings.json already in Amp format, skipping transformation'
237 | );
238 | return;
239 | }
240 |
241 | // Transform to Amp format
242 | const ampConfig = transformToAmpFormat(mcpConfig);
243 |
244 | // Write back the transformed config with proper formatting
245 | fs.writeFileSync(
246 | mcpConfigPath,
247 | JSON.stringify(ampConfig, null, '\t') + '\n'
248 | );
249 |
250 | log('info', '[Amp] Transformed settings.json to Amp format');
251 | log('debug', '[Amp] Renamed mcpServers to amp.mcpServers');
252 | } catch (error) {
253 | log('error', `[Amp] Failed to transform settings.json: ${error.message}`);
254 | }
255 | }
256 |
257 | // Create and export amp profile using the base factory
258 | export const ampProfile = createProfile({
259 | name: 'amp',
260 | displayName: 'Amp',
261 | url: 'ampcode.com',
262 | docsUrl: 'ampcode.com/manual',
263 | profileDir: '.vscode',
264 | rulesDir: '.',
265 | mcpConfig: true,
266 | mcpConfigName: 'settings.json',
267 | includeDefaultRules: false,
268 | fileMap: {
269 | 'AGENTS.md': '.taskmaster/AGENT.md'
270 | },
271 | onAdd: onAddRulesProfile,
272 | onRemove: onRemoveRulesProfile,
273 | onPostConvert: onPostConvertRulesProfile
274 | });
275 |
276 | // Export lifecycle functions separately to avoid naming conflicts
277 | export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };
278 |
```
--------------------------------------------------------------------------------
/apps/docs/capabilities/rpg-method.mdx:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: RPG Method for PRD Creation
3 | sidebarTitle: "RPG Method"
4 | ---
5 |
6 | # Repository Planning Graph (RPG) Method
7 |
8 | The RPG (Repository Planning Graph) method is an advanced approach to creating Product Requirements Documents that generate highly-structured, dependency-aware task graphs. It's based on Microsoft Research's methodology for scalable codebase generation.
9 |
10 | ## When to Use RPG
11 |
12 | Use the RPG template (`example_prd_rpg.md`) for:
13 |
14 | - **Complex multi-module systems** with intricate dependencies
15 | - **Large-scale codebases** being built from scratch
16 | - **Projects requiring explicit architecture** and clear module boundaries
17 | - **Teams needing dependency visibility** for parallel development
18 |
19 | For simpler features or smaller projects, the standard `example_prd.md` template may be more appropriate.
20 |
21 | ---
22 |
23 | ## Core Principles
24 |
25 | ### 1. Dual-Semantics
26 |
27 | Separate **functional** thinking (WHAT) from **structural** thinking (HOW):
28 |
29 | ```
30 | Functional: "Data Validation capability with schema checking and rule enforcement"
31 | ↓
32 | Structural: "src/validation/ with schema-validator.js and rule-validator.js"
33 | ```
34 |
35 | This separation prevents mixing concerns and creates clearer module boundaries.
36 |
37 | ### 2. Explicit Dependencies
38 |
39 | Never assume dependencies - always state them explicitly:
40 |
41 | ```
42 | Good:
43 | Module: data-ingestion
44 | Depends on: [schema-validator, config-manager]
45 |
46 | Bad:
47 | Module: data-ingestion
48 | (Assumes schema-validator exists somewhere)
49 | ```
50 |
51 | Explicit dependencies enable:
52 | - Topological ordering of implementation
53 | - Parallel development of independent modules
54 | - Clear build/test order
55 | - Early detection of circular dependencies
56 |
57 | ### 3. Topological Order
58 |
59 | Build foundation layers before higher layers:
60 |
61 | ```
62 | Phase 0 (Foundation): error-handling, base-types, config
63 | ↓
64 | Phase 1 (Data): validation, ingestion (depend on Phase 0)
65 | ↓
66 | Phase 2 (Core): algorithms, pipelines (depend on Phase 1)
67 | ↓
68 | Phase 3 (API): routes, handlers (depend on Phase 2)
69 | ```
70 |
71 | Task Master automatically orders tasks based on this dependency chain.
72 |
73 | ### 4. Progressive Refinement
74 |
75 | Start broad, refine iteratively:
76 |
77 | 1. High-level capabilities → Main tasks
78 | 2. Features per capability → Subtasks
79 | 3. Implementation details → Expanded subtasks
80 |
81 | ---
82 |
83 | ## Template Structure
84 |
85 | The RPG template guides you through 7 key sections:
86 |
87 | ### 1. Overview
88 | - Problem statement
89 | - Target users
90 | - Success metrics
91 |
92 | ### 2. Functional Decomposition (WHAT)
93 | - High-level capability domains
94 | - Features per capability
95 | - Inputs/outputs/behavior for each feature
96 |
97 | **Example:**
98 | ```
99 | Capability: Data Management
100 | Feature: Schema validation
101 | Description: Validate JSON against defined schemas
102 | Inputs: JSON object, schema definition
103 | Outputs: Validation result + error details
104 | Behavior: Iterate fields, check types, enforce constraints
105 | ```
106 |
107 | ### 3. Structural Decomposition (HOW)
108 | - Repository folder structure
109 | - Module-to-capability mapping
110 | - File organization
111 | - Public interfaces/exports
112 |
113 | **Example:**
114 | ```
115 | Capability: Data Management
116 | → Maps to: src/data/
117 | ├── schema-validator.js (Schema validation feature)
118 | ├── rule-validator.js (Rule validation feature)
119 | └── index.js (Exports)
120 | ```
121 |
122 | ### 4. Dependency Graph (CRITICAL)
123 | - Foundation layer (no dependencies)
124 | - Each subsequent layer's dependencies
125 | - Explicit "depends on" declarations
126 |
127 | **Example:**
128 | ```
129 | Foundation Layer (Phase 0):
130 | - error-handling: No dependencies
131 | - base-types: No dependencies
132 |
133 | Data Layer (Phase 1):
134 | - schema-validator: Depends on [base-types, error-handling]
135 | - data-ingestion: Depends on [schema-validator]
136 | ```
137 |
138 | ### 5. Implementation Roadmap
139 | - Phases with entry/exit criteria
140 | - Tasks grouped by phase
141 | - Clear deliverables per phase
142 |
143 | ### 6. Test Strategy
144 | - Test pyramid ratios
145 | - Coverage requirements
146 | - Critical test scenarios per module
147 | - Guidelines for test generation
148 |
149 | ### 7. Architecture & Risks
150 | - Technical architecture
151 | - Data models
152 | - Technology decisions
153 | - Risk mitigation strategies
154 |
155 | ---
156 |
157 | ## Using RPG with Task Master
158 |
159 | ### Step 1: Create PRD with RPG Template
160 |
161 | Use a code-context-aware tool to fill out the template:
162 |
163 | ```bash
164 | # In Claude Code, Cursor, or similar
165 | "Create a PRD using @.taskmaster/templates/example_prd_rpg.txt for [your project]"
166 | ```
167 |
168 | **Why code context matters:** The AI needs to understand your existing codebase to make informed decisions about:
169 | - Module boundaries
170 | - Dependency relationships
171 | - Integration points
172 | - Naming conventions
173 |
174 | **Recommended tools:**
175 | - Claude Code (claude-code CLI)
176 | - Cursor/Windsurf
177 | - Gemini CLI (large contexts)
178 | - Codex/Grok CLI
179 |
180 | ### Step 2: Parse PRD into Tasks
181 |
182 | ```bash
183 | task-master parse-prd .taskmaster/docs/your-prd.md --research
184 | ```
185 |
186 | Task Master will:
187 | 1. Extract capabilities → Main tasks
188 | 2. Extract features → Subtasks
189 | 3. Parse dependencies → Task dependencies
190 | 4. Order by phases → Task priorities
191 |
192 | **Result:** A dependency-aware task graph ready for topological execution.
193 |
194 | ### Step 3: Analyze Complexity
195 |
196 | ```bash
197 | task-master analyze-complexity --research
198 | ```
199 |
200 | Review the complexity report to identify tasks that need expansion.
201 |
202 | ### Step 4: Expand Tasks
203 |
204 | ```bash
205 | task-master expand --all --research
206 | ```
207 |
208 | Break down complex tasks into manageable subtasks while preserving dependency chains.
209 |
210 | ---
211 |
212 | ## RPG Benefits
213 |
214 | ### For Solo Developers
215 | - Clear roadmap for implementing complex features
216 | - Prevents architectural mistakes early
217 | - Explicit dependency tracking avoids integration issues
218 | - Enables resuming work after interruptions
219 |
220 | ### For Teams
221 | - Parallel development of independent modules
222 | - Clear contracts between modules (explicit dependencies)
223 | - Reduced merge conflicts (proper module boundaries)
224 | - Onboarding aid (architectural overview in PRD)
225 |
226 | ### For AI Agents
227 | - Structured context for code generation
228 | - Clear scope boundaries per task
229 | - Dependency awareness prevents incomplete implementations
230 | - Test strategy guidance for TDD workflows
231 |
232 | ---
233 |
234 | ## RPG vs Standard Template
235 |
236 | | Aspect | Standard Template | RPG Template |
237 | |--------|------------------|--------------|
238 | | **Best for** | Simple features | Complex systems |
239 | | **Dependency handling** | Implicit | Explicit graph |
240 | | **Structure guidance** | Minimal | Step-by-step |
241 | | **Examples** | Few | Inline good/bad examples |
242 | | **Module boundaries** | Vague | Precise mapping |
243 | | **Task ordering** | Manual | Automatic (topological) |
244 | | **Learning curve** | Low | Medium |
245 | | **Resulting task quality** | Good | Excellent |
246 |
247 | ---
248 |
249 | ## Tips for Best Results
250 |
251 | ### 1. Spend Time on Dependencies
252 | The dependency graph section is the most valuable. List all dependencies explicitly, even if they seem obvious.
253 |
254 | ### 2. Keep Features Atomic
255 | Each feature should be independently testable. If a feature description is vague ("handle data"), break it into specific features.
256 |
257 | ### 3. Progressive Refinement
258 | Don't try to get everything perfect on the first pass:
259 | 1. Fill out high-level sections
260 | 2. Review and refine
261 | 3. Add detail where needed
262 | 4. Let `task-master expand` break down complex tasks further
263 |
264 | ### 4. Use Research Mode
265 | ```bash
266 | task-master parse-prd --research
267 | ```
268 | The `--research` flag leverages AI to enhance task generation with domain knowledge.
269 |
270 | ### 5. Validate Early
271 | ```bash
272 | task-master validate-dependencies
273 | ```
274 | Check for circular dependencies or orphaned modules before starting implementation.
275 |
276 | ---
277 |
278 | ## Common Pitfalls
279 |
280 | ### ❌ Mixing Functional and Structural
281 | ```
282 | Bad: "Capability: validation.js"
283 | Good: "Capability: Data Validation" → maps to "src/validation/"
284 | ```
285 |
286 | ### ❌ Vague Module Boundaries
287 | ```
288 | Bad: "Module: utils"
289 | Good: "Module: string-utilities" with clear exports
290 | ```
291 |
292 | ### ❌ Implicit Dependencies
293 | ```
294 | Bad: "Module: API handlers (needs validation)"
295 | Good: "Module: API handlers, Depends on: [validation, error-handling]"
296 | ```
297 |
298 | ### ❌ Skipping Test Strategy
299 | Without test strategy, the AI won't know what to test during implementation.
300 |
301 | ---
302 |
303 | ## Example Workflow
304 |
305 | 1. **Discuss idea with AI**: Explain your project concept
306 | 2. **Reference RPG template**: Show AI the `example_prd_rpg.md`
307 | 3. **Co-create PRD**: Work through each section with AI guidance
308 | 4. **Save to docs**: Place in `.taskmaster/docs/your-project.md` (use `.md` for better editor support)
309 | 5. **Parse PRD**: `task-master parse-prd .taskmaster/docs/your-project.md --research`
310 | 6. **Analyze**: `task-master analyze-complexity --research`
311 | 7. **Expand**: `task-master expand --all --research`
312 | 8. **Start work**: `task-master next`
313 |
314 | ---
315 |
316 | ## Further Reading
317 |
318 | - [PRD Creation and Parsing Guide](/getting-started/quick-start/prd-quick)
319 | - [Task Structure Documentation](/capabilities/task-structure)
320 | - [Microsoft Research RPG Paper](https://arxiv.org/abs/2410.21376) (Original methodology)
321 |
322 | ---
323 |
324 | <Tip>
325 | The RPG template includes inline `<instruction>` and `<example>` blocks that teach the method as you use it. Read these sections carefully - they provide valuable guidance at each decision point.
326 | </Tip>
327 |
```
--------------------------------------------------------------------------------
/apps/extension/src/utils/connectionManager.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as vscode from 'vscode';
2 | import { logger } from './logger';
3 | import {
4 | MCPClientManager,
5 | type MCPConfig,
6 | type MCPServerStatus
7 | } from './mcpClient';
8 |
9 | export interface ConnectionEvent {
10 | type: 'connected' | 'disconnected' | 'error' | 'reconnecting';
11 | timestamp: Date;
12 | data?: any;
13 | }
14 |
15 | export interface ConnectionHealth {
16 | isHealthy: boolean;
17 | lastSuccessfulCall?: Date;
18 | consecutiveFailures: number;
19 | averageResponseTime: number;
20 | uptime: number;
21 | }
22 |
23 | export class ConnectionManager {
24 | private mcpClient: MCPClientManager | null = null;
25 | private config: MCPConfig;
26 | private connectionEvents: ConnectionEvent[] = [];
27 | private health: ConnectionHealth = {
28 | isHealthy: false,
29 | consecutiveFailures: 0,
30 | averageResponseTime: 0,
31 | uptime: 0
32 | };
33 | private startTime: Date | null = null;
34 | private healthCheckInterval: NodeJS.Timeout | null = null;
35 | private reconnectAttempts = 0;
36 | private maxReconnectAttempts = 5;
37 | private reconnectBackoffMs = 1000; // Start with 1 second
38 | private maxBackoffMs = 30000; // Max 30 seconds
39 | private isReconnecting = false;
40 |
41 | // Event handlers
42 | private onConnectionChange?: (
43 | status: MCPServerStatus,
44 | health: ConnectionHealth
45 | ) => void;
46 | private onConnectionEvent?: (event: ConnectionEvent) => void;
47 |
48 | constructor(config: MCPConfig) {
49 | this.config = config;
50 | this.mcpClient = new MCPClientManager(config);
51 | }
52 |
53 | /**
54 | * Set event handlers
55 | */
56 | setEventHandlers(handlers: {
57 | onConnectionChange?: (
58 | status: MCPServerStatus,
59 | health: ConnectionHealth
60 | ) => void;
61 | onConnectionEvent?: (event: ConnectionEvent) => void;
62 | }) {
63 | this.onConnectionChange = handlers.onConnectionChange;
64 | this.onConnectionEvent = handlers.onConnectionEvent;
65 | }
66 |
67 | /**
68 | * Connect with automatic retry and health monitoring
69 | */
70 | async connect(): Promise<void> {
71 | try {
72 | if (!this.mcpClient) {
73 | throw new Error('MCP client not initialized');
74 | }
75 |
76 | this.logEvent({ type: 'reconnecting', timestamp: new Date() });
77 |
78 | await this.mcpClient.connect();
79 |
80 | this.reconnectAttempts = 0;
81 | this.reconnectBackoffMs = 1000;
82 | this.isReconnecting = false;
83 | this.startTime = new Date();
84 |
85 | this.updateHealth();
86 | this.startHealthMonitoring();
87 |
88 | this.logEvent({ type: 'connected', timestamp: new Date() });
89 |
90 | logger.log('Connection manager: Successfully connected');
91 | } catch (error) {
92 | this.logEvent({
93 | type: 'error',
94 | timestamp: new Date(),
95 | data: {
96 | error: error instanceof Error ? error.message : 'Unknown error'
97 | }
98 | });
99 |
100 | await this.handleConnectionFailure(error);
101 | throw error;
102 | }
103 | }
104 |
105 | /**
106 | * Disconnect and stop health monitoring
107 | */
108 | async disconnect(): Promise<void> {
109 | this.stopHealthMonitoring();
110 | this.isReconnecting = false;
111 |
112 | if (this.mcpClient) {
113 | await this.mcpClient.disconnect();
114 | }
115 |
116 | this.health.isHealthy = false;
117 | this.startTime = null;
118 |
119 | this.logEvent({ type: 'disconnected', timestamp: new Date() });
120 |
121 | this.notifyConnectionChange();
122 | }
123 |
124 | /**
125 | * Get current connection status
126 | */
127 | getStatus(): MCPServerStatus {
128 | return this.mcpClient?.getStatus() || { isRunning: false };
129 | }
130 |
131 | /**
132 | * Get connection health metrics
133 | */
134 | getHealth(): ConnectionHealth {
135 | this.updateHealth();
136 | return { ...this.health };
137 | }
138 |
139 | /**
140 | * Get recent connection events
141 | */
142 | getEvents(limit = 10): ConnectionEvent[] {
143 | return this.connectionEvents.slice(-limit);
144 | }
145 |
146 | /**
147 | * Test connection with performance monitoring
148 | */
149 | async testConnection(): Promise<{
150 | success: boolean;
151 | responseTime: number;
152 | error?: string;
153 | }> {
154 | if (!this.mcpClient) {
155 | return {
156 | success: false,
157 | responseTime: 0,
158 | error: 'Client not initialized'
159 | };
160 | }
161 |
162 | const startTime = Date.now();
163 |
164 | try {
165 | const success = await this.mcpClient.testConnection();
166 | const responseTime = Date.now() - startTime;
167 |
168 | if (success) {
169 | this.health.lastSuccessfulCall = new Date();
170 | this.health.consecutiveFailures = 0;
171 | this.updateAverageResponseTime(responseTime);
172 | } else {
173 | this.health.consecutiveFailures++;
174 | }
175 |
176 | this.updateHealth();
177 | this.notifyConnectionChange();
178 |
179 | return { success, responseTime };
180 | } catch (error) {
181 | const responseTime = Date.now() - startTime;
182 | this.health.consecutiveFailures++;
183 | this.updateHealth();
184 | this.notifyConnectionChange();
185 |
186 | return {
187 | success: false,
188 | responseTime,
189 | error: error instanceof Error ? error.message : 'Unknown error'
190 | };
191 | }
192 | }
193 |
194 | /**
195 | * Call MCP tool with automatic retry and health monitoring
196 | */
197 | async callTool(
198 | toolName: string,
199 | arguments_: Record<string, unknown>
200 | ): Promise<any> {
201 | if (!this.mcpClient) {
202 | throw new Error('MCP client not initialized');
203 | }
204 |
205 | const startTime = Date.now();
206 |
207 | try {
208 | const result = await this.mcpClient.callTool(toolName, arguments_);
209 | const responseTime = Date.now() - startTime;
210 |
211 | this.health.lastSuccessfulCall = new Date();
212 | this.health.consecutiveFailures = 0;
213 | this.updateAverageResponseTime(responseTime);
214 | this.updateHealth();
215 | this.notifyConnectionChange();
216 |
217 | return result;
218 | } catch (error) {
219 | this.health.consecutiveFailures++;
220 | this.updateHealth();
221 |
222 | // Attempt reconnection if connection seems lost
223 | if (this.health.consecutiveFailures >= 3 && !this.isReconnecting) {
224 | logger.log(
225 | 'Multiple consecutive failures detected, attempting reconnection...'
226 | );
227 | this.reconnectWithBackoff().catch((err) => {
228 | logger.error('Reconnection failed:', err);
229 | });
230 | }
231 |
232 | this.notifyConnectionChange();
233 | throw error;
234 | }
235 | }
236 |
237 | /**
238 | * Update configuration and reconnect
239 | */
240 | async updateConfig(newConfig: MCPConfig): Promise<void> {
241 | this.config = newConfig;
242 |
243 | await this.disconnect();
244 | this.mcpClient = new MCPClientManager(newConfig);
245 |
246 | // Attempt to reconnect with new config
247 | try {
248 | await this.connect();
249 | } catch (error) {
250 | logger.error('Failed to connect with new configuration:', error);
251 | }
252 | }
253 |
254 | /**
255 | * Start health monitoring
256 | */
257 | private startHealthMonitoring(): void {
258 | this.stopHealthMonitoring();
259 |
260 | this.healthCheckInterval = setInterval(async () => {
261 | try {
262 | await this.testConnection();
263 | } catch (error) {
264 | logger.error('Health check failed:', error);
265 | }
266 | }, 15000); // Check every 15 seconds
267 | }
268 |
269 | /**
270 | * Stop health monitoring
271 | */
272 | private stopHealthMonitoring(): void {
273 | if (this.healthCheckInterval) {
274 | clearInterval(this.healthCheckInterval);
275 | this.healthCheckInterval = null;
276 | }
277 | }
278 |
279 | /**
280 | * Handle connection failure with exponential backoff
281 | */
282 | private async handleConnectionFailure(error: any): Promise<void> {
283 | this.health.consecutiveFailures++;
284 | this.updateHealth();
285 | this.notifyConnectionChange();
286 |
287 | if (
288 | this.reconnectAttempts < this.maxReconnectAttempts &&
289 | !this.isReconnecting
290 | ) {
291 | await this.reconnectWithBackoff();
292 | }
293 | }
294 |
295 | /**
296 | * Reconnect with exponential backoff
297 | */
298 | private async reconnectWithBackoff(): Promise<void> {
299 | if (this.isReconnecting) {
300 | return;
301 | }
302 |
303 | this.isReconnecting = true;
304 | this.reconnectAttempts++;
305 |
306 | const backoffMs = Math.min(
307 | this.reconnectBackoffMs * 2 ** (this.reconnectAttempts - 1),
308 | this.maxBackoffMs
309 | );
310 |
311 | logger.log(
312 | `Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${backoffMs}ms...`
313 | );
314 |
315 | await new Promise((resolve) => setTimeout(resolve, backoffMs));
316 |
317 | try {
318 | await this.connect();
319 | } catch (error) {
320 | logger.error(
321 | `Reconnection attempt ${this.reconnectAttempts} failed:`,
322 | error
323 | );
324 |
325 | if (this.reconnectAttempts >= this.maxReconnectAttempts) {
326 | this.isReconnecting = false;
327 | vscode.window.showErrorMessage(
328 | `Failed to reconnect to Task Master after ${this.maxReconnectAttempts} attempts. Please check your configuration and try manually reconnecting.`
329 | );
330 | } else {
331 | // Try again
332 | await this.reconnectWithBackoff();
333 | }
334 | }
335 | }
336 |
337 | /**
338 | * Update health metrics
339 | */
340 | private updateHealth(): void {
341 | const status = this.getStatus();
342 | this.health.isHealthy =
343 | status.isRunning && this.health.consecutiveFailures < 3;
344 |
345 | if (this.startTime) {
346 | this.health.uptime = Date.now() - this.startTime.getTime();
347 | }
348 | }
349 |
350 | /**
351 | * Update average response time
352 | */
353 | private updateAverageResponseTime(responseTime: number): void {
354 | // Simple moving average calculation
355 | if (this.health.averageResponseTime === 0) {
356 | this.health.averageResponseTime = responseTime;
357 | } else {
358 | this.health.averageResponseTime =
359 | this.health.averageResponseTime * 0.8 + responseTime * 0.2;
360 | }
361 | }
362 |
363 | /**
364 | * Log connection event
365 | */
366 | private logEvent(event: ConnectionEvent): void {
367 | this.connectionEvents.push(event);
368 |
369 | // Keep only last 100 events
370 | if (this.connectionEvents.length > 100) {
371 | this.connectionEvents = this.connectionEvents.slice(-100);
372 | }
373 |
374 | if (this.onConnectionEvent) {
375 | this.onConnectionEvent(event);
376 | }
377 | }
378 |
379 | /**
380 | * Notify connection change
381 | */
382 | private notifyConnectionChange(): void {
383 | if (this.onConnectionChange) {
384 | this.onConnectionChange(this.getStatus(), this.getHealth());
385 | }
386 | }
387 | }
388 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/utils/path-utils.js:
--------------------------------------------------------------------------------
```javascript
1 | import path from 'path';
2 | import {
3 | findTasksPath as coreFindTasksPath,
4 | findPRDPath as coreFindPrdPath,
5 | findComplexityReportPath as coreFindComplexityReportPath,
6 | resolveComplexityReportOutputPath as coreResolveComplexityReportOutputPath,
7 | findProjectRoot as coreFindProjectRoot,
8 | normalizeProjectRoot
9 | } from '../../../../src/utils/path-utils.js';
10 | import { PROJECT_MARKERS } from '../../../../src/constants/paths.js';
11 |
12 | /**
13 | * MCP-specific path utilities that extend core path utilities with session support
14 | * This module handles session-specific path resolution for the MCP server
15 | */
16 |
17 | /**
18 | * Silent logger for MCP context to prevent console output
19 | */
20 | const silentLogger = {
21 | info: () => {},
22 | warn: () => {},
23 | error: () => {},
24 | debug: () => {},
25 | success: () => {}
26 | };
27 |
28 | /**
29 | * Cache for last found project root to improve performance
30 | */
31 | export const lastFoundProjectRoot = null;
32 |
33 | /**
34 | * Find PRD file with MCP support
35 | * @param {string} [explicitPath] - Explicit path to PRD file (highest priority)
36 | * @param {Object} [args] - Arguments object for context
37 | * @param {Object} [log] - Logger object to prevent console logging
38 | * @returns {string|null} - Resolved path to PRD file or null if not found
39 | */
40 | export function findPrdPath(explicitPath, args = null, log = silentLogger) {
41 | return coreFindPrdPath(explicitPath, args, log);
42 | }
43 |
44 | /**
45 | * Resolve tasks.json path from arguments
46 | * Prioritizes explicit path parameter, then uses fallback logic
47 | * @param {Object} args - Arguments object containing projectRoot and optional file path
48 | * @param {Object} [log] - Logger object to prevent console logging
49 | * @returns {string|null} - Resolved path to tasks.json or null if not found
50 | */
51 | export function resolveTasksPath(args, log = silentLogger) {
52 | // Get explicit path from args.file if provided
53 | const explicitPath = args?.file;
54 | const rawProjectRoot = args?.projectRoot;
55 |
56 | // If explicit path is provided and absolute, use it directly
57 | if (explicitPath && path.isAbsolute(explicitPath)) {
58 | return explicitPath;
59 | }
60 |
61 | // Normalize project root if provided
62 | const projectRoot = rawProjectRoot
63 | ? normalizeProjectRoot(rawProjectRoot)
64 | : null;
65 |
66 | // If explicit path is relative, resolve it relative to normalized projectRoot
67 | if (explicitPath && projectRoot) {
68 | return path.resolve(projectRoot, explicitPath);
69 | }
70 |
71 | // Use core findTasksPath with explicit path and normalized projectRoot context
72 | if (projectRoot) {
73 | const foundPath = coreFindTasksPath(explicitPath, { projectRoot }, log);
74 | // If core function returns null and no explicit path was provided,
75 | // construct the expected default path as documented
76 | if (foundPath === null && !explicitPath) {
77 | const defaultPath = path.join(
78 | projectRoot,
79 | '.taskmaster',
80 | 'tasks',
81 | 'tasks.json'
82 | );
83 | log?.info?.(
84 | `Core findTasksPath returned null, using default path: ${defaultPath}`
85 | );
86 | return defaultPath;
87 | }
88 | return foundPath;
89 | }
90 |
91 | // Fallback to core function without projectRoot context
92 | const foundPath = coreFindTasksPath(explicitPath, null, log);
93 | // Note: When no projectRoot is available, we can't construct a default path
94 | // so we return null and let the calling code handle the error
95 | return foundPath;
96 | }
97 |
98 | /**
99 | * Resolve PRD path from arguments
100 | * @param {Object} args - Arguments object containing projectRoot and optional input path
101 | * @param {Object} [log] - Logger object to prevent console logging
102 | * @returns {string|null} - Resolved path to PRD file or null if not found
103 | */
104 | export function resolvePrdPath(args, log = silentLogger) {
105 | // Get explicit path from args.input if provided
106 | const explicitPath = args?.input;
107 | const rawProjectRoot = args?.projectRoot;
108 |
109 | // If explicit path is provided and absolute, use it directly
110 | if (explicitPath && path.isAbsolute(explicitPath)) {
111 | return explicitPath;
112 | }
113 |
114 | // Normalize project root if provided
115 | const projectRoot = rawProjectRoot
116 | ? normalizeProjectRoot(rawProjectRoot)
117 | : null;
118 |
119 | // If explicit path is relative, resolve it relative to normalized projectRoot
120 | if (explicitPath && projectRoot) {
121 | return path.resolve(projectRoot, explicitPath);
122 | }
123 |
124 | // Use core findPRDPath with explicit path and normalized projectRoot context
125 | if (projectRoot) {
126 | return coreFindPrdPath(explicitPath, { projectRoot }, log);
127 | }
128 |
129 | // Fallback to core function without projectRoot context
130 | return coreFindPrdPath(explicitPath, null, log);
131 | }
132 |
133 | /**
134 | * Resolve complexity report path from arguments
135 | * @param {Object} args - Arguments object containing projectRoot and optional complexityReport path
136 | * @param {Object} [log] - Logger object to prevent console logging
137 | * @returns {string|null} - Resolved path to complexity report or null if not found
138 | */
139 | export function resolveComplexityReportPath(args, log = silentLogger) {
140 | // Get explicit path from args.complexityReport if provided
141 | const explicitPath = args?.complexityReport;
142 | const rawProjectRoot = args?.projectRoot;
143 | const tag = args?.tag;
144 |
145 | // If explicit path is provided and absolute, use it directly
146 | if (explicitPath && path.isAbsolute(explicitPath)) {
147 | return explicitPath;
148 | }
149 |
150 | // Normalize project root if provided
151 | const projectRoot = rawProjectRoot
152 | ? normalizeProjectRoot(rawProjectRoot)
153 | : null;
154 |
155 | // If explicit path is relative, resolve it relative to normalized projectRoot
156 | if (explicitPath && projectRoot) {
157 | return path.resolve(projectRoot, explicitPath);
158 | }
159 |
160 | // Use core findComplexityReportPath with explicit path and normalized projectRoot context
161 | if (projectRoot) {
162 | return coreFindComplexityReportPath(
163 | explicitPath,
164 | { projectRoot, tag },
165 | log
166 | );
167 | }
168 |
169 | // Fallback to core function without projectRoot context
170 | return coreFindComplexityReportPath(explicitPath, null, log);
171 | }
172 |
173 | /**
174 | * Resolve any project-relative path from arguments
175 | * @param {string} relativePath - Relative path to resolve
176 | * @param {Object} args - Arguments object containing projectRoot
177 | * @returns {string} - Resolved absolute path
178 | */
179 | export function resolveProjectPath(relativePath, args) {
180 | // Ensure we have a projectRoot from args
181 | if (!args?.projectRoot) {
182 | throw new Error('projectRoot is required in args to resolve project paths');
183 | }
184 |
185 | // Normalize the project root to prevent double .taskmaster paths
186 | const projectRoot = normalizeProjectRoot(args.projectRoot);
187 |
188 | // If already absolute, return as-is
189 | if (path.isAbsolute(relativePath)) {
190 | return relativePath;
191 | }
192 |
193 | // Resolve relative to normalized projectRoot
194 | return path.resolve(projectRoot, relativePath);
195 | }
196 |
197 | /**
198 | * Find project root using core utility
199 | * @param {string} [startDir] - Directory to start searching from
200 | * @returns {string|null} - Project root path or null if not found
201 | */
202 | export function findProjectRoot(startDir) {
203 | return coreFindProjectRoot(startDir);
204 | }
205 |
206 | // MAIN EXPORTS FOR MCP TOOLS - these are the functions MCP tools should use
207 |
208 | /**
209 | * Find tasks.json path from arguments - primary MCP function
210 | * @param {Object} args - Arguments object containing projectRoot and optional file path
211 | * @param {Object} [log] - Log function to prevent console logging
212 | * @returns {string|null} - Resolved path to tasks.json or null if not found
213 | */
214 | export function findTasksPath(args, log = silentLogger) {
215 | return resolveTasksPath(args, log);
216 | }
217 |
218 | /**
219 | * Find complexity report path from arguments - primary MCP function
220 | * @param {Object} args - Arguments object containing projectRoot and optional complexityReport path
221 | * @param {Object} [log] - Log function to prevent console logging
222 | * @returns {string|null} - Resolved path to complexity report or null if not found
223 | */
224 | export function findComplexityReportPath(args, log = silentLogger) {
225 | return resolveComplexityReportPath(args, log);
226 | }
227 |
228 | /**
229 | * Resolve complexity report output path (create if needed) - primary MCP function
230 | * @param {string|null} [explicitPath] - Explicit path to complexity report
231 | * @param {Object} args - Arguments object containing projectRoot and tag
232 | * @param {Object} [log] - Log function to prevent console logging
233 | * @returns {string} - Resolved output path for complexity report
234 | */
235 | export function resolveComplexityReportOutputPath(
236 | explicitPath,
237 | args,
238 | log = silentLogger
239 | ) {
240 | return coreResolveComplexityReportOutputPath(explicitPath, args, log);
241 | }
242 |
243 | /**
244 | * Find PRD path - primary MCP function
245 | * @param {string} [explicitPath] - Explicit path to PRD file
246 | * @param {Object} [args] - Arguments object for context (not used in current implementation)
247 | * @param {Object} [log] - Logger object to prevent console logging
248 | * @returns {string|null} - Resolved path to PRD file or null if not found
249 | */
250 | export function findPRDPath(explicitPath, args = null, log = silentLogger) {
251 | return findPrdPath(explicitPath, args, log);
252 | }
253 |
254 | // Legacy aliases for backward compatibility - DEPRECATED
255 | export const findTasksJsonPath = findTasksPath;
256 | export const findComplexityReportJsonPath = findComplexityReportPath;
257 |
258 | // Re-export PROJECT_MARKERS for MCP tools that import it from this module
259 | export { PROJECT_MARKERS };
260 |
```