This is page 13 of 69. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── commands
│ │ └── dedupe.md
│ └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│ └── marketplace.json
├── .coderabbit.yaml
├── .cursor
│ ├── mcp.json
│ └── rules
│ ├── ai_providers.mdc
│ ├── ai_services.mdc
│ ├── architecture.mdc
│ ├── changeset.mdc
│ ├── commands.mdc
│ ├── context_gathering.mdc
│ ├── cursor_rules.mdc
│ ├── dependencies.mdc
│ ├── dev_workflow.mdc
│ ├── git_workflow.mdc
│ ├── glossary.mdc
│ ├── mcp.mdc
│ ├── new_features.mdc
│ ├── self_improve.mdc
│ ├── tags.mdc
│ ├── taskmaster.mdc
│ ├── tasks.mdc
│ ├── telemetry.mdc
│ ├── test_workflow.mdc
│ ├── tests.mdc
│ ├── ui.mdc
│ └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── enhancements---feature-requests.md
│ │ └── feedback.md
│ ├── PULL_REQUEST_TEMPLATE
│ │ ├── bugfix.md
│ │ ├── config.yml
│ │ ├── feature.md
│ │ └── integration.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── scripts
│ │ ├── auto-close-duplicates.mjs
│ │ ├── backfill-duplicate-comments.mjs
│ │ ├── check-pre-release-mode.mjs
│ │ ├── parse-metrics.mjs
│ │ ├── release.mjs
│ │ ├── tag-extension.mjs
│ │ ├── utils.mjs
│ │ └── validate-changesets.mjs
│ └── workflows
│ ├── auto-close-duplicates.yml
│ ├── backfill-duplicate-comments.yml
│ ├── ci.yml
│ ├── claude-dedupe-issues.yml
│ ├── claude-docs-trigger.yml
│ ├── claude-docs-updater.yml
│ ├── claude-issue-triage.yml
│ ├── claude.yml
│ ├── extension-ci.yml
│ ├── extension-release.yml
│ ├── log-issue-events.yml
│ ├── pre-release.yml
│ ├── release-check.yml
│ ├── release.yml
│ ├── update-models-md.yml
│ └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│ ├── hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── settings
│ │ └── mcp.json
│ └── steering
│ ├── dev_workflow.md
│ ├── kiro_rules.md
│ ├── self_improve.md
│ ├── taskmaster_hooks_workflow.md
│ └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│ ├── CLAUDE.md
│ ├── config.json
│ ├── docs
│ │ ├── autonomous-tdd-git-workflow.md
│ │ ├── MIGRATION-ROADMAP.md
│ │ ├── prd-tm-start.txt
│ │ ├── prd.txt
│ │ ├── README.md
│ │ ├── research
│ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│ │ │ ├── 2025-06-14_test-save-functionality.md
│ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│ │ ├── task-template-importing-prd.txt
│ │ ├── tdd-workflow-phase-0-spike.md
│ │ ├── tdd-workflow-phase-1-core-rails.md
│ │ ├── tdd-workflow-phase-1-orchestrator.md
│ │ ├── tdd-workflow-phase-2-pr-resumability.md
│ │ ├── tdd-workflow-phase-3-extensibility-guardrails.md
│ │ ├── test-prd.txt
│ │ └── tm-core-phase-1.txt
│ ├── reports
│ │ ├── task-complexity-report_autonomous-tdd-git-workflow.json
│ │ ├── task-complexity-report_cc-kiro-hooks.json
│ │ ├── task-complexity-report_tdd-phase-1-core-rails.json
│ │ ├── task-complexity-report_tdd-workflow-phase-0.json
│ │ ├── task-complexity-report_test-prd-tag.json
│ │ ├── task-complexity-report_tm-core-phase-1.json
│ │ ├── task-complexity-report.json
│ │ └── tm-core-complexity.json
│ ├── state.json
│ ├── tasks
│ │ ├── task_001_tm-start.txt
│ │ ├── task_002_tm-start.txt
│ │ ├── task_003_tm-start.txt
│ │ ├── task_004_tm-start.txt
│ │ ├── task_007_tm-start.txt
│ │ └── tasks.json
│ └── templates
│ ├── example_prd_rpg.md
│ └── example_prd.md
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── apps
│ ├── cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── command-registry.ts
│ │ │ ├── commands
│ │ │ │ ├── auth.command.ts
│ │ │ │ ├── autopilot
│ │ │ │ │ ├── abort.command.ts
│ │ │ │ │ ├── commit.command.ts
│ │ │ │ │ ├── complete.command.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next.command.ts
│ │ │ │ │ ├── resume.command.ts
│ │ │ │ │ ├── shared.ts
│ │ │ │ │ ├── start.command.ts
│ │ │ │ │ └── status.command.ts
│ │ │ │ ├── briefs.command.ts
│ │ │ │ ├── context.command.ts
│ │ │ │ ├── export.command.ts
│ │ │ │ ├── list.command.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── custom-providers.ts
│ │ │ │ │ ├── fetchers.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompts.ts
│ │ │ │ │ ├── setup.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── next.command.ts
│ │ │ │ ├── set-status.command.ts
│ │ │ │ ├── show.command.ts
│ │ │ │ ├── start.command.ts
│ │ │ │ └── tags.command.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── model-management.ts
│ │ │ ├── types
│ │ │ │ └── tag-management.d.ts
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── cardBox.component.ts
│ │ │ │ │ ├── dashboard.component.ts
│ │ │ │ │ ├── header.component.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next-task.component.ts
│ │ │ │ │ ├── suggested-steps.component.ts
│ │ │ │ │ └── task-detail.component.ts
│ │ │ │ ├── display
│ │ │ │ │ ├── messages.ts
│ │ │ │ │ └── tables.ts
│ │ │ │ ├── formatters
│ │ │ │ │ ├── complexity-formatters.ts
│ │ │ │ │ ├── dependency-formatters.ts
│ │ │ │ │ ├── priority-formatters.ts
│ │ │ │ │ ├── status-formatters.spec.ts
│ │ │ │ │ └── status-formatters.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── layout
│ │ │ │ ├── helpers.spec.ts
│ │ │ │ └── helpers.ts
│ │ │ └── utils
│ │ │ ├── auth-helpers.ts
│ │ │ ├── auto-update.ts
│ │ │ ├── brief-selection.ts
│ │ │ ├── display-helpers.ts
│ │ │ ├── error-handler.ts
│ │ │ ├── index.ts
│ │ │ ├── project-root.ts
│ │ │ ├── task-status.ts
│ │ │ ├── ui.spec.ts
│ │ │ └── ui.ts
│ │ ├── tests
│ │ │ ├── integration
│ │ │ │ └── commands
│ │ │ │ └── autopilot
│ │ │ │ └── workflow.test.ts
│ │ │ └── unit
│ │ │ ├── commands
│ │ │ │ ├── autopilot
│ │ │ │ │ └── shared.test.ts
│ │ │ │ ├── list.command.spec.ts
│ │ │ │ └── show.command.spec.ts
│ │ │ └── ui
│ │ │ └── dashboard.component.spec.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── docs
│ │ ├── archive
│ │ │ ├── ai-client-utils-example.mdx
│ │ │ ├── ai-development-workflow.mdx
│ │ │ ├── command-reference.mdx
│ │ │ ├── configuration.mdx
│ │ │ ├── cursor-setup.mdx
│ │ │ ├── examples.mdx
│ │ │ └── Installation.mdx
│ │ ├── best-practices
│ │ │ ├── advanced-tasks.mdx
│ │ │ ├── configuration-advanced.mdx
│ │ │ └── index.mdx
│ │ ├── capabilities
│ │ │ ├── cli-root-commands.mdx
│ │ │ ├── index.mdx
│ │ │ ├── mcp.mdx
│ │ │ ├── rpg-method.mdx
│ │ │ └── task-structure.mdx
│ │ ├── CHANGELOG.md
│ │ ├── command-reference.mdx
│ │ ├── configuration.mdx
│ │ ├── docs.json
│ │ ├── favicon.svg
│ │ ├── getting-started
│ │ │ ├── api-keys.mdx
│ │ │ ├── contribute.mdx
│ │ │ ├── faq.mdx
│ │ │ └── quick-start
│ │ │ ├── configuration-quick.mdx
│ │ │ ├── execute-quick.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── moving-forward.mdx
│ │ │ ├── prd-quick.mdx
│ │ │ ├── quick-start.mdx
│ │ │ ├── requirements.mdx
│ │ │ ├── rules-quick.mdx
│ │ │ └── tasks-quick.mdx
│ │ ├── introduction.mdx
│ │ ├── licensing.md
│ │ ├── logo
│ │ │ ├── dark.svg
│ │ │ ├── light.svg
│ │ │ └── task-master-logo.png
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── style.css
│ │ ├── tdd-workflow
│ │ │ ├── ai-agent-integration.mdx
│ │ │ └── quickstart.mdx
│ │ ├── vercel.json
│ │ └── whats-new.mdx
│ ├── extension
│ │ ├── .vscodeignore
│ │ ├── assets
│ │ │ ├── banner.png
│ │ │ ├── icon-dark.svg
│ │ │ ├── icon-light.svg
│ │ │ ├── icon.png
│ │ │ ├── screenshots
│ │ │ │ ├── kanban-board.png
│ │ │ │ └── task-details.png
│ │ │ └── sidebar-icon.svg
│ │ ├── CHANGELOG.md
│ │ ├── components.json
│ │ ├── docs
│ │ │ ├── extension-CI-setup.md
│ │ │ └── extension-development-guide.md
│ │ ├── esbuild.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── package.mjs
│ │ ├── package.publish.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── components
│ │ │ │ ├── ConfigView.tsx
│ │ │ │ ├── constants.ts
│ │ │ │ ├── TaskDetails
│ │ │ │ │ ├── AIActionsSection.tsx
│ │ │ │ │ ├── DetailsSection.tsx
│ │ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ │ ├── SubtasksSection.tsx
│ │ │ │ │ ├── TaskMetadataSidebar.tsx
│ │ │ │ │ └── useTaskDetails.ts
│ │ │ │ ├── TaskDetailsView.tsx
│ │ │ │ ├── TaskMasterLogo.tsx
│ │ │ │ └── ui
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── breadcrumb.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── collapsible.tsx
│ │ │ │ ├── CollapsibleSection.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── scroll-area.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── shadcn-io
│ │ │ │ │ └── kanban
│ │ │ │ │ └── index.tsx
│ │ │ │ └── textarea.tsx
│ │ │ ├── extension.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── utils.ts
│ │ │ ├── services
│ │ │ │ ├── config-service.ts
│ │ │ │ ├── error-handler.ts
│ │ │ │ ├── notification-preferences.ts
│ │ │ │ ├── polling-service.ts
│ │ │ │ ├── polling-strategies.ts
│ │ │ │ ├── sidebar-webview-manager.ts
│ │ │ │ ├── task-repository.ts
│ │ │ │ ├── terminal-manager.ts
│ │ │ │ └── webview-manager.ts
│ │ │ ├── test
│ │ │ │ └── extension.test.ts
│ │ │ ├── utils
│ │ │ │ ├── configManager.ts
│ │ │ │ ├── connectionManager.ts
│ │ │ │ ├── errorHandler.ts
│ │ │ │ ├── event-emitter.ts
│ │ │ │ ├── logger.ts
│ │ │ │ ├── mcpClient.ts
│ │ │ │ ├── notificationPreferences.ts
│ │ │ │ └── task-master-api
│ │ │ │ ├── cache
│ │ │ │ │ └── cache-manager.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mcp-client.ts
│ │ │ │ ├── transformers
│ │ │ │ │ └── task-transformer.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ └── webview
│ │ │ ├── App.tsx
│ │ │ ├── components
│ │ │ │ ├── AppContent.tsx
│ │ │ │ ├── EmptyState.tsx
│ │ │ │ ├── ErrorBoundary.tsx
│ │ │ │ ├── PollingStatus.tsx
│ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ ├── SidebarView.tsx
│ │ │ │ ├── TagDropdown.tsx
│ │ │ │ ├── TaskCard.tsx
│ │ │ │ ├── TaskEditModal.tsx
│ │ │ │ ├── TaskMasterKanban.tsx
│ │ │ │ ├── ToastContainer.tsx
│ │ │ │ └── ToastNotification.tsx
│ │ │ ├── constants
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ └── VSCodeContext.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useTaskQueries.ts
│ │ │ │ ├── useVSCodeMessages.ts
│ │ │ │ └── useWebviewHeight.ts
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── providers
│ │ │ │ └── QueryProvider.tsx
│ │ │ ├── reducers
│ │ │ │ └── appReducer.ts
│ │ │ ├── sidebar.tsx
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ └── utils
│ │ │ ├── logger.ts
│ │ │ └── toast.ts
│ │ └── tsconfig.json
│ └── mcp
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── tools
│ │ ├── autopilot
│ │ │ ├── abort.tool.ts
│ │ │ ├── commit.tool.ts
│ │ │ ├── complete.tool.ts
│ │ │ ├── finalize.tool.ts
│ │ │ ├── index.ts
│ │ │ ├── next.tool.ts
│ │ │ ├── resume.tool.ts
│ │ │ ├── start.tool.ts
│ │ │ └── status.tool.ts
│ │ ├── README-ZOD-V3.md
│ │ └── tasks
│ │ ├── get-task.tool.ts
│ │ ├── get-tasks.tool.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── assets
│ ├── .windsurfrules
│ ├── AGENTS.md
│ ├── claude
│ │ └── TM_COMMANDS_GUIDE.md
│ ├── config.json
│ ├── env.example
│ ├── example_prd_rpg.txt
│ ├── example_prd.txt
│ ├── GEMINI.md
│ ├── gitignore
│ ├── kiro-hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── roocode
│ │ ├── .roo
│ │ │ ├── rules-architect
│ │ │ │ └── architect-rules
│ │ │ ├── rules-ask
│ │ │ │ └── ask-rules
│ │ │ ├── rules-code
│ │ │ │ └── code-rules
│ │ │ ├── rules-debug
│ │ │ │ └── debug-rules
│ │ │ ├── rules-orchestrator
│ │ │ │ └── orchestrator-rules
│ │ │ └── rules-test
│ │ │ └── test-rules
│ │ └── .roomodes
│ ├── rules
│ │ ├── cursor_rules.mdc
│ │ ├── dev_workflow.mdc
│ │ ├── self_improve.mdc
│ │ ├── taskmaster_hooks_workflow.mdc
│ │ └── taskmaster.mdc
│ └── scripts_README.md
├── bin
│ └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE_CODE_PLUGIN.md
├── CLAUDE.md
├── context
│ ├── chats
│ │ ├── add-task-dependencies-1.md
│ │ └── max-min-tokens.txt.md
│ ├── fastmcp-core.txt
│ ├── fastmcp-docs.txt
│ ├── MCP_INTEGRATION.md
│ ├── mcp-js-sdk-docs.txt
│ ├── mcp-protocol-repo.txt
│ ├── mcp-protocol-schema-03262025.json
│ └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│ ├── claude-code-integration.md
│ ├── CLI-COMMANDER-PATTERN.md
│ ├── command-reference.md
│ ├── configuration.md
│ ├── contributor-docs
│ │ ├── testing-roo-integration.md
│ │ └── worktree-setup.md
│ ├── cross-tag-task-movement.md
│ ├── examples
│ │ ├── claude-code-usage.md
│ │ └── codex-cli-usage.md
│ ├── examples.md
│ ├── licensing.md
│ ├── mcp-provider-guide.md
│ ├── mcp-provider.md
│ ├── migration-guide.md
│ ├── models.md
│ ├── providers
│ │ ├── codex-cli.md
│ │ └── gemini-cli.md
│ ├── README.md
│ ├── scripts
│ │ └── models-json-to-markdown.js
│ ├── task-structure.md
│ └── tutorial.md
├── images
│ ├── hamster-hiring.png
│ └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│ ├── server.js
│ └── src
│ ├── core
│ │ ├── __tests__
│ │ │ └── context-manager.test.js
│ │ ├── context-manager.js
│ │ ├── direct-functions
│ │ │ ├── add-dependency.js
│ │ │ ├── add-subtask.js
│ │ │ ├── add-tag.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── cache-stats.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── complexity-report.js
│ │ │ ├── copy-tag.js
│ │ │ ├── create-tag-from-branch.js
│ │ │ ├── delete-tag.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── fix-dependencies.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── initialize-project.js
│ │ │ ├── list-tags.js
│ │ │ ├── models.js
│ │ │ ├── move-task-cross-tag.js
│ │ │ ├── move-task.js
│ │ │ ├── next-task.js
│ │ │ ├── parse-prd.js
│ │ │ ├── remove-dependency.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── rename-tag.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── rules.js
│ │ │ ├── scope-down.js
│ │ │ ├── scope-up.js
│ │ │ ├── set-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ ├── update-tasks.js
│ │ │ ├── use-tag.js
│ │ │ └── validate-dependencies.js
│ │ ├── task-master-core.js
│ │ └── utils
│ │ ├── env-utils.js
│ │ └── path-utils.js
│ ├── custom-sdk
│ │ ├── errors.js
│ │ ├── index.js
│ │ ├── json-extractor.js
│ │ ├── language-model.js
│ │ ├── message-converter.js
│ │ └── schema-converter.js
│ ├── index.js
│ ├── logger.js
│ ├── providers
│ │ └── mcp-provider.js
│ └── tools
│ ├── add-dependency.js
│ ├── add-subtask.js
│ ├── add-tag.js
│ ├── add-task.js
│ ├── analyze.js
│ ├── clear-subtasks.js
│ ├── complexity-report.js
│ ├── copy-tag.js
│ ├── delete-tag.js
│ ├── expand-all.js
│ ├── expand-task.js
│ ├── fix-dependencies.js
│ ├── generate.js
│ ├── get-operation-status.js
│ ├── index.js
│ ├── initialize-project.js
│ ├── list-tags.js
│ ├── models.js
│ ├── move-task.js
│ ├── next-task.js
│ ├── parse-prd.js
│ ├── README-ZOD-V3.md
│ ├── remove-dependency.js
│ ├── remove-subtask.js
│ ├── remove-task.js
│ ├── rename-tag.js
│ ├── research.js
│ ├── response-language.js
│ ├── rules.js
│ ├── scope-down.js
│ ├── scope-up.js
│ ├── set-task-status.js
│ ├── tool-registry.js
│ ├── update-subtask.js
│ ├── update-task.js
│ ├── update.js
│ ├── use-tag.js
│ ├── utils.js
│ └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│ ├── ai-sdk-provider-grok-cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── errors.test.ts
│ │ │ ├── errors.ts
│ │ │ ├── grok-cli-language-model.ts
│ │ │ ├── grok-cli-provider.test.ts
│ │ │ ├── grok-cli-provider.ts
│ │ │ ├── index.ts
│ │ │ ├── json-extractor.test.ts
│ │ │ ├── json-extractor.ts
│ │ │ ├── message-converter.test.ts
│ │ │ ├── message-converter.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── build-config
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ └── tsdown.base.ts
│ │ └── tsconfig.json
│ ├── claude-code-plugin
│ │ ├── .claude-plugin
│ │ │ └── plugin.json
│ │ ├── .gitignore
│ │ ├── agents
│ │ │ ├── task-checker.md
│ │ │ ├── task-executor.md
│ │ │ └── task-orchestrator.md
│ │ ├── CHANGELOG.md
│ │ ├── commands
│ │ │ ├── add-dependency.md
│ │ │ ├── add-subtask.md
│ │ │ ├── add-task.md
│ │ │ ├── analyze-complexity.md
│ │ │ ├── analyze-project.md
│ │ │ ├── auto-implement-tasks.md
│ │ │ ├── command-pipeline.md
│ │ │ ├── complexity-report.md
│ │ │ ├── convert-task-to-subtask.md
│ │ │ ├── expand-all-tasks.md
│ │ │ ├── expand-task.md
│ │ │ ├── fix-dependencies.md
│ │ │ ├── generate-tasks.md
│ │ │ ├── help.md
│ │ │ ├── init-project-quick.md
│ │ │ ├── init-project.md
│ │ │ ├── install-taskmaster.md
│ │ │ ├── learn.md
│ │ │ ├── list-tasks-by-status.md
│ │ │ ├── list-tasks-with-subtasks.md
│ │ │ ├── list-tasks.md
│ │ │ ├── next-task.md
│ │ │ ├── parse-prd-with-research.md
│ │ │ ├── parse-prd.md
│ │ │ ├── project-status.md
│ │ │ ├── quick-install-taskmaster.md
│ │ │ ├── remove-all-subtasks.md
│ │ │ ├── remove-dependency.md
│ │ │ ├── remove-subtask.md
│ │ │ ├── remove-subtasks.md
│ │ │ ├── remove-task.md
│ │ │ ├── setup-models.md
│ │ │ ├── show-task.md
│ │ │ ├── smart-workflow.md
│ │ │ ├── sync-readme.md
│ │ │ ├── tm-main.md
│ │ │ ├── to-cancelled.md
│ │ │ ├── to-deferred.md
│ │ │ ├── to-done.md
│ │ │ ├── to-in-progress.md
│ │ │ ├── to-pending.md
│ │ │ ├── to-review.md
│ │ │ ├── update-single-task.md
│ │ │ ├── update-task.md
│ │ │ ├── update-tasks-from-id.md
│ │ │ ├── validate-dependencies.md
│ │ │ └── view-models.md
│ │ ├── mcp.json
│ │ └── package.json
│ ├── tm-bridge
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── add-tag-bridge.ts
│ │ │ ├── bridge-types.ts
│ │ │ ├── bridge-utils.ts
│ │ │ ├── expand-bridge.ts
│ │ │ ├── index.ts
│ │ │ ├── tags-bridge.ts
│ │ │ ├── update-bridge.ts
│ │ │ └── use-tag-bridge.ts
│ │ └── tsconfig.json
│ └── tm-core
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docs
│ │ └── listTasks-architecture.md
│ ├── package.json
│ ├── POC-STATUS.md
│ ├── README.md
│ ├── src
│ │ ├── common
│ │ │ ├── constants
│ │ │ │ ├── index.ts
│ │ │ │ ├── paths.ts
│ │ │ │ └── providers.ts
│ │ │ ├── errors
│ │ │ │ ├── index.ts
│ │ │ │ └── task-master-error.ts
│ │ │ ├── interfaces
│ │ │ │ ├── configuration.interface.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── storage.interface.ts
│ │ │ ├── logger
│ │ │ │ ├── factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── logger.spec.ts
│ │ │ │ └── logger.ts
│ │ │ ├── mappers
│ │ │ │ ├── TaskMapper.test.ts
│ │ │ │ └── TaskMapper.ts
│ │ │ ├── types
│ │ │ │ ├── database.types.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── legacy.ts
│ │ │ │ └── repository-types.ts
│ │ │ └── utils
│ │ │ ├── git-utils.ts
│ │ │ ├── id-generator.ts
│ │ │ ├── index.ts
│ │ │ ├── path-helpers.ts
│ │ │ ├── path-normalizer.spec.ts
│ │ │ ├── path-normalizer.ts
│ │ │ ├── project-root-finder.spec.ts
│ │ │ ├── project-root-finder.ts
│ │ │ ├── run-id-generator.spec.ts
│ │ │ └── run-id-generator.ts
│ │ ├── index.ts
│ │ ├── modules
│ │ │ ├── ai
│ │ │ │ ├── index.ts
│ │ │ │ ├── interfaces
│ │ │ │ │ └── ai-provider.interface.ts
│ │ │ │ └── providers
│ │ │ │ ├── base-provider.ts
│ │ │ │ └── index.ts
│ │ │ ├── auth
│ │ │ │ ├── auth-domain.spec.ts
│ │ │ │ ├── auth-domain.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── auth-manager.spec.ts
│ │ │ │ │ └── auth-manager.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── context-store.ts
│ │ │ │ │ ├── oauth-service.ts
│ │ │ │ │ ├── organization.service.ts
│ │ │ │ │ ├── supabase-session-storage.spec.ts
│ │ │ │ │ └── supabase-session-storage.ts
│ │ │ │ └── types.ts
│ │ │ ├── briefs
│ │ │ │ ├── briefs-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── brief-service.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils
│ │ │ │ └── url-parser.ts
│ │ │ ├── commands
│ │ │ │ └── index.ts
│ │ │ ├── config
│ │ │ │ ├── config-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── config-manager.spec.ts
│ │ │ │ │ └── config-manager.ts
│ │ │ │ └── services
│ │ │ │ ├── config-loader.service.spec.ts
│ │ │ │ ├── config-loader.service.ts
│ │ │ │ ├── config-merger.service.spec.ts
│ │ │ │ ├── config-merger.service.ts
│ │ │ │ ├── config-persistence.service.spec.ts
│ │ │ │ ├── config-persistence.service.ts
│ │ │ │ ├── environment-config-provider.service.spec.ts
│ │ │ │ ├── environment-config-provider.service.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── runtime-state-manager.service.spec.ts
│ │ │ │ └── runtime-state-manager.service.ts
│ │ │ ├── dependencies
│ │ │ │ └── index.ts
│ │ │ ├── execution
│ │ │ │ ├── executors
│ │ │ │ │ ├── base-executor.ts
│ │ │ │ │ ├── claude-executor.ts
│ │ │ │ │ └── executor-factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── executor-service.ts
│ │ │ │ └── types.ts
│ │ │ ├── git
│ │ │ │ ├── adapters
│ │ │ │ │ ├── git-adapter.test.ts
│ │ │ │ │ └── git-adapter.ts
│ │ │ │ ├── git-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── services
│ │ │ │ ├── branch-name-generator.spec.ts
│ │ │ │ ├── branch-name-generator.ts
│ │ │ │ ├── commit-message-generator.test.ts
│ │ │ │ ├── commit-message-generator.ts
│ │ │ │ ├── scope-detector.test.ts
│ │ │ │ ├── scope-detector.ts
│ │ │ │ ├── template-engine.test.ts
│ │ │ │ └── template-engine.ts
│ │ │ ├── integration
│ │ │ │ ├── clients
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── supabase-client.ts
│ │ │ │ ├── integration-domain.ts
│ │ │ │ └── services
│ │ │ │ ├── export.service.ts
│ │ │ │ ├── task-expansion.service.ts
│ │ │ │ └── task-retrieval.service.ts
│ │ │ ├── reports
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ └── complexity-report-manager.ts
│ │ │ │ └── types.ts
│ │ │ ├── storage
│ │ │ │ ├── adapters
│ │ │ │ │ ├── activity-logger.ts
│ │ │ │ │ ├── api-storage.ts
│ │ │ │ │ └── file-storage
│ │ │ │ │ ├── file-operations.ts
│ │ │ │ │ ├── file-storage.ts
│ │ │ │ │ ├── format-handler.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── path-resolver.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── storage-factory.ts
│ │ │ │ └── utils
│ │ │ │ └── api-client.ts
│ │ │ ├── tasks
│ │ │ │ ├── entities
│ │ │ │ │ └── task.entity.ts
│ │ │ │ ├── parser
│ │ │ │ │ └── index.ts
│ │ │ │ ├── repositories
│ │ │ │ │ ├── supabase
│ │ │ │ │ │ ├── dependency-fetcher.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── supabase-repository.ts
│ │ │ │ │ └── task-repository.interface.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── preflight-checker.service.ts
│ │ │ │ │ ├── tag.service.ts
│ │ │ │ │ ├── task-execution-service.ts
│ │ │ │ │ ├── task-loader.service.ts
│ │ │ │ │ └── task-service.ts
│ │ │ │ └── tasks-domain.ts
│ │ │ ├── ui
│ │ │ │ └── index.ts
│ │ │ └── workflow
│ │ │ ├── managers
│ │ │ │ ├── workflow-state-manager.spec.ts
│ │ │ │ └── workflow-state-manager.ts
│ │ │ ├── orchestrators
│ │ │ │ ├── workflow-orchestrator.test.ts
│ │ │ │ └── workflow-orchestrator.ts
│ │ │ ├── services
│ │ │ │ ├── test-result-validator.test.ts
│ │ │ │ ├── test-result-validator.ts
│ │ │ │ ├── test-result-validator.types.ts
│ │ │ │ ├── workflow-activity-logger.ts
│ │ │ │ └── workflow.service.ts
│ │ │ ├── types.ts
│ │ │ └── workflow-domain.ts
│ │ ├── subpath-exports.test.ts
│ │ ├── tm-core.ts
│ │ └── utils
│ │ └── time.utils.ts
│ ├── tests
│ │ ├── auth
│ │ │ └── auth-refresh.test.ts
│ │ ├── integration
│ │ │ ├── auth-token-refresh.test.ts
│ │ │ ├── list-tasks.test.ts
│ │ │ └── storage
│ │ │ └── activity-logger.test.ts
│ │ ├── mocks
│ │ │ └── mock-provider.ts
│ │ ├── setup.ts
│ │ └── unit
│ │ ├── base-provider.test.ts
│ │ ├── executor.test.ts
│ │ └── smoke.test.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│ ├── create-worktree.sh
│ ├── dev.js
│ ├── init.js
│ ├── list-worktrees.sh
│ ├── modules
│ │ ├── ai-services-unified.js
│ │ ├── bridge-utils.js
│ │ ├── commands.js
│ │ ├── config-manager.js
│ │ ├── dependency-manager.js
│ │ ├── index.js
│ │ ├── prompt-manager.js
│ │ ├── supported-models.json
│ │ ├── sync-readme.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── find-next-task.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── is-task-dependent.js
│ │ │ ├── list-tasks.js
│ │ │ ├── migrate.js
│ │ │ ├── models.js
│ │ │ ├── move-task.js
│ │ │ ├── parse-prd
│ │ │ │ ├── index.js
│ │ │ │ ├── parse-prd-config.js
│ │ │ │ ├── parse-prd-helpers.js
│ │ │ │ ├── parse-prd-non-streaming.js
│ │ │ │ ├── parse-prd-streaming.js
│ │ │ │ └── parse-prd.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── scope-adjustment.js
│ │ │ ├── set-task-status.js
│ │ │ ├── tag-management.js
│ │ │ ├── task-exists.js
│ │ │ ├── update-single-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ └── update-tasks.js
│ │ ├── task-manager.js
│ │ ├── ui.js
│ │ ├── update-config-tokens.js
│ │ ├── utils
│ │ │ ├── contextGatherer.js
│ │ │ ├── fuzzyTaskSearch.js
│ │ │ └── git-utils.js
│ │ └── utils.js
│ ├── task-complexity-report.json
│ ├── test-claude-errors.js
│ └── test-claude.js
├── sonar-project.properties
├── src
│ ├── ai-providers
│ │ ├── anthropic.js
│ │ ├── azure.js
│ │ ├── base-provider.js
│ │ ├── bedrock.js
│ │ ├── claude-code.js
│ │ ├── codex-cli.js
│ │ ├── gemini-cli.js
│ │ ├── google-vertex.js
│ │ ├── google.js
│ │ ├── grok-cli.js
│ │ ├── groq.js
│ │ ├── index.js
│ │ ├── lmstudio.js
│ │ ├── ollama.js
│ │ ├── openai-compatible.js
│ │ ├── openai.js
│ │ ├── openrouter.js
│ │ ├── perplexity.js
│ │ ├── xai.js
│ │ ├── zai-coding.js
│ │ └── zai.js
│ ├── constants
│ │ ├── commands.js
│ │ ├── paths.js
│ │ ├── profiles.js
│ │ ├── rules-actions.js
│ │ ├── task-priority.js
│ │ └── task-status.js
│ ├── profiles
│ │ ├── amp.js
│ │ ├── base-profile.js
│ │ ├── claude.js
│ │ ├── cline.js
│ │ ├── codex.js
│ │ ├── cursor.js
│ │ ├── gemini.js
│ │ ├── index.js
│ │ ├── kilo.js
│ │ ├── kiro.js
│ │ ├── opencode.js
│ │ ├── roo.js
│ │ ├── trae.js
│ │ ├── vscode.js
│ │ ├── windsurf.js
│ │ └── zed.js
│ ├── progress
│ │ ├── base-progress-tracker.js
│ │ ├── cli-progress-factory.js
│ │ ├── parse-prd-tracker.js
│ │ ├── progress-tracker-builder.js
│ │ └── tracker-ui.js
│ ├── prompts
│ │ ├── add-task.json
│ │ ├── analyze-complexity.json
│ │ ├── expand-task.json
│ │ ├── parse-prd.json
│ │ ├── README.md
│ │ ├── research.json
│ │ ├── schemas
│ │ │ ├── parameter.schema.json
│ │ │ ├── prompt-template.schema.json
│ │ │ ├── README.md
│ │ │ └── variant.schema.json
│ │ ├── update-subtask.json
│ │ ├── update-task.json
│ │ └── update-tasks.json
│ ├── provider-registry
│ │ └── index.js
│ ├── schemas
│ │ ├── add-task.js
│ │ ├── analyze-complexity.js
│ │ ├── base-schemas.js
│ │ ├── expand-task.js
│ │ ├── parse-prd.js
│ │ ├── registry.js
│ │ ├── update-subtask.js
│ │ ├── update-task.js
│ │ └── update-tasks.js
│ ├── task-master.js
│ ├── ui
│ │ ├── confirm.js
│ │ ├── indicators.js
│ │ └── parse-prd.js
│ └── utils
│ ├── asset-resolver.js
│ ├── create-mcp-config.js
│ ├── format.js
│ ├── getVersion.js
│ ├── logger-utils.js
│ ├── manage-gitignore.js
│ ├── path-utils.js
│ ├── profiles.js
│ ├── rule-transformer.js
│ ├── stream-parser.js
│ └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│ ├── e2e
│ │ ├── e2e_helpers.sh
│ │ ├── parse_llm_output.cjs
│ │ ├── run_e2e.sh
│ │ ├── run_fallback_verification.sh
│ │ └── test_llm_analysis.sh
│ ├── fixtures
│ │ ├── .taskmasterconfig
│ │ ├── sample-claude-response.js
│ │ ├── sample-prd.txt
│ │ └── sample-tasks.js
│ ├── helpers
│ │ └── tool-counts.js
│ ├── integration
│ │ ├── claude-code-error-handling.test.js
│ │ ├── claude-code-optional.test.js
│ │ ├── cli
│ │ │ ├── commands.test.js
│ │ │ ├── complex-cross-tag-scenarios.test.js
│ │ │ └── move-cross-tag.test.js
│ │ ├── manage-gitignore.test.js
│ │ ├── mcp-server
│ │ │ └── direct-functions.test.js
│ │ ├── move-task-cross-tag.integration.test.js
│ │ ├── move-task-simple.integration.test.js
│ │ ├── profiles
│ │ │ ├── amp-init-functionality.test.js
│ │ │ ├── claude-init-functionality.test.js
│ │ │ ├── cline-init-functionality.test.js
│ │ │ ├── codex-init-functionality.test.js
│ │ │ ├── cursor-init-functionality.test.js
│ │ │ ├── gemini-init-functionality.test.js
│ │ │ ├── opencode-init-functionality.test.js
│ │ │ ├── roo-files-inclusion.test.js
│ │ │ ├── roo-init-functionality.test.js
│ │ │ ├── rules-files-inclusion.test.js
│ │ │ ├── trae-init-functionality.test.js
│ │ │ ├── vscode-init-functionality.test.js
│ │ │ └── windsurf-init-functionality.test.js
│ │ └── providers
│ │ └── temperature-support.test.js
│ ├── manual
│ │ ├── progress
│ │ │ ├── parse-prd-analysis.js
│ │ │ ├── test-parse-prd.js
│ │ │ └── TESTING_GUIDE.md
│ │ └── prompts
│ │ ├── prompt-test.js
│ │ └── README.md
│ ├── README.md
│ ├── setup.js
│ └── unit
│ ├── ai-providers
│ │ ├── base-provider.test.js
│ │ ├── claude-code.test.js
│ │ ├── codex-cli.test.js
│ │ ├── gemini-cli.test.js
│ │ ├── lmstudio.test.js
│ │ ├── mcp-components.test.js
│ │ ├── openai-compatible.test.js
│ │ ├── openai.test.js
│ │ ├── provider-registry.test.js
│ │ ├── zai-coding.test.js
│ │ ├── zai-provider.test.js
│ │ ├── zai-schema-introspection.test.js
│ │ └── zai.test.js
│ ├── ai-services-unified.test.js
│ ├── commands.test.js
│ ├── config-manager.test.js
│ ├── config-manager.test.mjs
│ ├── dependency-manager.test.js
│ ├── init.test.js
│ ├── initialize-project.test.js
│ ├── kebab-case-validation.test.js
│ ├── manage-gitignore.test.js
│ ├── mcp
│ │ └── tools
│ │ ├── __mocks__
│ │ │ └── move-task.js
│ │ ├── add-task.test.js
│ │ ├── analyze-complexity.test.js
│ │ ├── expand-all.test.js
│ │ ├── get-tasks.test.js
│ │ ├── initialize-project.test.js
│ │ ├── move-task-cross-tag-options.test.js
│ │ ├── move-task-cross-tag.test.js
│ │ ├── remove-task.test.js
│ │ └── tool-registration.test.js
│ ├── mcp-providers
│ │ ├── mcp-components.test.js
│ │ └── mcp-provider.test.js
│ ├── parse-prd.test.js
│ ├── profiles
│ │ ├── amp-integration.test.js
│ │ ├── claude-integration.test.js
│ │ ├── cline-integration.test.js
│ │ ├── codex-integration.test.js
│ │ ├── cursor-integration.test.js
│ │ ├── gemini-integration.test.js
│ │ ├── kilo-integration.test.js
│ │ ├── kiro-integration.test.js
│ │ ├── mcp-config-validation.test.js
│ │ ├── opencode-integration.test.js
│ │ ├── profile-safety-check.test.js
│ │ ├── roo-integration.test.js
│ │ ├── rule-transformer-cline.test.js
│ │ ├── rule-transformer-cursor.test.js
│ │ ├── rule-transformer-gemini.test.js
│ │ ├── rule-transformer-kilo.test.js
│ │ ├── rule-transformer-kiro.test.js
│ │ ├── rule-transformer-opencode.test.js
│ │ ├── rule-transformer-roo.test.js
│ │ ├── rule-transformer-trae.test.js
│ │ ├── rule-transformer-vscode.test.js
│ │ ├── rule-transformer-windsurf.test.js
│ │ ├── rule-transformer-zed.test.js
│ │ ├── rule-transformer.test.js
│ │ ├── selective-profile-removal.test.js
│ │ ├── subdirectory-support.test.js
│ │ ├── trae-integration.test.js
│ │ ├── vscode-integration.test.js
│ │ ├── windsurf-integration.test.js
│ │ └── zed-integration.test.js
│ ├── progress
│ │ └── base-progress-tracker.test.js
│ ├── prompt-manager.test.js
│ ├── prompts
│ │ ├── expand-task-prompt.test.js
│ │ └── prompt-migration.test.js
│ ├── scripts
│ │ └── modules
│ │ ├── commands
│ │ │ ├── move-cross-tag.test.js
│ │ │ └── README.md
│ │ ├── dependency-manager
│ │ │ ├── circular-dependencies.test.js
│ │ │ ├── cross-tag-dependencies.test.js
│ │ │ └── fix-dependencies-command.test.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.test.js
│ │ │ ├── add-task.test.js
│ │ │ ├── analyze-task-complexity.test.js
│ │ │ ├── clear-subtasks.test.js
│ │ │ ├── complexity-report-tag-isolation.test.js
│ │ │ ├── expand-all-tasks.test.js
│ │ │ ├── expand-task.test.js
│ │ │ ├── find-next-task.test.js
│ │ │ ├── generate-task-files.test.js
│ │ │ ├── list-tasks.test.js
│ │ │ ├── models-baseurl.test.js
│ │ │ ├── move-task-cross-tag.test.js
│ │ │ ├── move-task.test.js
│ │ │ ├── parse-prd-schema.test.js
│ │ │ ├── parse-prd.test.js
│ │ │ ├── remove-subtask.test.js
│ │ │ ├── remove-task.test.js
│ │ │ ├── research.test.js
│ │ │ ├── scope-adjustment.test.js
│ │ │ ├── set-task-status.test.js
│ │ │ ├── setup.js
│ │ │ ├── update-single-task-status.test.js
│ │ │ ├── update-subtask-by-id.test.js
│ │ │ ├── update-task-by-id.test.js
│ │ │ └── update-tasks.test.js
│ │ ├── ui
│ │ │ └── cross-tag-error-display.test.js
│ │ └── utils-tag-aware-paths.test.js
│ ├── task-finder.test.js
│ ├── task-manager
│ │ ├── clear-subtasks.test.js
│ │ ├── move-task.test.js
│ │ ├── tag-boundary.test.js
│ │ └── tag-management.test.js
│ ├── task-master.test.js
│ ├── ui
│ │ └── indicators.test.js
│ ├── ui.test.js
│ ├── utils-strip-ansi.test.js
│ └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
├── turbo.json
└── update-task-migration-plan.md
```
# Files
--------------------------------------------------------------------------------
/packages/tm-core/src/common/utils/id-generator.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview ID generation utilities for Task Master
3 | * Provides functions to generate unique identifiers for tasks and subtasks
4 | */
5 |
6 | import { randomBytes } from 'node:crypto';
7 |
8 | /**
9 | * Generates a unique task ID using the format: TASK-{timestamp}-{random}
10 | *
11 | * @returns A unique task ID string
12 | * @example
13 | * ```typescript
14 | * const taskId = generateTaskId();
15 | * // Returns something like: "TASK-1704067200000-A7B3"
16 | * ```
17 | */
18 | export function generateTaskId(): string {
19 | const timestamp = Date.now();
20 | const random = generateRandomString(4);
21 | return `TASK-${timestamp}-${random}`;
22 | }
23 |
24 | /**
25 | * Generates a subtask ID using the format: {parentId}.{sequential}
26 | *
27 | * @param parentId - The ID of the parent task
28 | * @param existingSubtasks - Array of existing subtask IDs to determine the next sequential number
29 | * @returns A unique subtask ID string
30 | * @example
31 | * ```typescript
32 | * const subtaskId = generateSubtaskId("TASK-123-A7B3", ["TASK-123-A7B3.1"]);
33 | * // Returns: "TASK-123-A7B3.2"
34 | * ```
35 | */
36 | export function generateSubtaskId(
37 | parentId: string,
38 | existingSubtasks: string[] = []
39 | ): string {
40 | // Find existing subtasks for this parent
41 | const parentSubtasks = existingSubtasks.filter((id) =>
42 | id.startsWith(`${parentId}.`)
43 | );
44 |
45 | // Extract sequential numbers and find the highest
46 | const sequentialNumbers = parentSubtasks
47 | .map((id) => {
48 | const parts = id.split('.');
49 | const lastPart = parts[parts.length - 1];
50 | return Number.parseInt(lastPart, 10);
51 | })
52 | .filter((num) => !Number.isNaN(num))
53 | .sort((a, b) => a - b);
54 |
55 | // Determine the next sequential number
56 | const nextSequential =
57 | sequentialNumbers.length > 0 ? Math.max(...sequentialNumbers) + 1 : 1;
58 |
59 | return `${parentId}.${nextSequential}`;
60 | }
61 |
62 | /**
63 | * Generates a random alphanumeric string of specified length
64 | * Uses crypto.randomBytes for cryptographically secure randomness
65 | *
66 | * @param length - The desired length of the random string
67 | * @returns A random alphanumeric string
68 | * @internal
69 | */
70 | function generateRandomString(length: number): string {
71 | const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
72 | const bytes = randomBytes(length);
73 | let result = '';
74 |
75 | for (let i = 0; i < length; i++) {
76 | result += chars[bytes[i] % chars.length];
77 | }
78 |
79 | return result;
80 | }
81 |
82 | /**
83 | * Validates a task ID format
84 | *
85 | * @param id - The ID to validate
86 | * @returns True if the ID matches the expected task ID format
87 | * @example
88 | * ```typescript
89 | * isValidTaskId("TASK-1704067200000-A7B3"); // true
90 | * isValidTaskId("invalid-id"); // false
91 | * ```
92 | */
93 | export function isValidTaskId(id: string): boolean {
94 | const taskIdRegex = /^TASK-\d{13}-[A-Z0-9]{4}$/;
95 | return taskIdRegex.test(id);
96 | }
97 |
98 | /**
99 | * Validates a subtask ID format
100 | *
101 | * @param id - The ID to validate
102 | * @returns True if the ID matches the expected subtask ID format
103 | * @example
104 | * ```typescript
105 | * isValidSubtaskId("TASK-1704067200000-A7B3.1"); // true
106 | * isValidSubtaskId("TASK-1704067200000-A7B3.1.2"); // true (nested subtask)
107 | * isValidSubtaskId("invalid.id"); // false
108 | * ```
109 | */
110 | export function isValidSubtaskId(id: string): boolean {
111 | const parts = id.split('.');
112 | if (parts.length < 2) return false;
113 |
114 | // First part should be a valid task ID
115 | const taskIdPart = parts[0];
116 | if (!isValidTaskId(taskIdPart)) return false;
117 |
118 | // Remaining parts should be positive integers
119 | const sequentialParts = parts.slice(1);
120 | return sequentialParts.every((part) => {
121 | const num = Number.parseInt(part, 10);
122 | return !Number.isNaN(num) && num > 0 && part === num.toString();
123 | });
124 | }
125 |
126 | /**
127 | * Extracts the parent task ID from a subtask ID
128 | *
129 | * @param subtaskId - The subtask ID
130 | * @returns The parent task ID, or null if the input is not a valid subtask ID
131 | * @example
132 | * ```typescript
133 | * getParentTaskId("TASK-1704067200000-A7B3.1.2"); // "TASK-1704067200000-A7B3"
134 | * getParentTaskId("TASK-1704067200000-A7B3"); // null (not a subtask)
135 | * ```
136 | */
137 | export function getParentTaskId(subtaskId: string): string | null {
138 | if (!isValidSubtaskId(subtaskId)) return null;
139 |
140 | const parts = subtaskId.split('.');
141 | return parts[0];
142 | }
143 |
```
--------------------------------------------------------------------------------
/apps/extension/src/services/task-repository.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Task Repository - Simplified version
3 | * Handles data access with caching
4 | */
5 |
6 | import { EventEmitter } from '../utils/event-emitter';
7 | import type { ExtensionLogger } from '../utils/logger';
8 | import type { TaskMasterApi, TaskMasterTask } from '../utils/task-master-api';
9 |
10 | // Use the TaskMasterTask type directly to ensure compatibility
11 | export type Task = TaskMasterTask;
12 |
13 | export class TaskRepository extends EventEmitter {
14 | private cache: Task[] | null = null;
15 | private cacheTimestamp = 0;
16 | private readonly CACHE_DURATION = 30000; // 30 seconds
17 |
18 | constructor(
19 | private api: TaskMasterApi,
20 | private logger: ExtensionLogger
21 | ) {
22 | super();
23 | }
24 |
25 | async getAll(options?: {
26 | tag?: string;
27 | withSubtasks?: boolean;
28 | }): Promise<Task[]> {
29 | // If a tag is specified, always fetch fresh data
30 | const shouldUseCache =
31 | !options?.tag &&
32 | this.cache &&
33 | Date.now() - this.cacheTimestamp < this.CACHE_DURATION;
34 |
35 | if (shouldUseCache) {
36 | return this.cache || [];
37 | }
38 |
39 | try {
40 | const result = await this.api.getTasks({
41 | withSubtasks: options?.withSubtasks ?? true,
42 | tag: options?.tag
43 | });
44 |
45 | if (result.success && result.data) {
46 | this.cache = result.data;
47 | this.cacheTimestamp = Date.now();
48 | this.emit('tasks:updated', result.data);
49 | return result.data;
50 | }
51 |
52 | throw new Error(result.error || 'Failed to fetch tasks');
53 | } catch (error) {
54 | this.logger.error('Failed to get tasks', error);
55 | throw error;
56 | }
57 | }
58 |
59 | async getById(taskId: string): Promise<Task | null> {
60 | // First check cache
61 | if (this.cache) {
62 | // Handle both main tasks and subtasks
63 | for (const task of this.cache) {
64 | if (task.id === taskId) {
65 | return task;
66 | }
67 | // Check subtasks
68 | if (task.subtasks) {
69 | for (const subtask of task.subtasks) {
70 | if (
71 | subtask.id.toString() === taskId ||
72 | `${task.id}.${subtask.id}` === taskId
73 | ) {
74 | return {
75 | ...subtask,
76 | id: subtask.id.toString(),
77 | description: subtask.description || '',
78 | status: (subtask.status ||
79 | 'pending') as TaskMasterTask['status'],
80 | priority: 'medium' as const,
81 | dependencies:
82 | subtask.dependencies?.map((d) => d.toString()) || []
83 | };
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | // If not in cache, fetch all and search
91 | const tasks = await this.getAll();
92 | for (const task of tasks) {
93 | if (task.id === taskId) {
94 | return task;
95 | }
96 | // Check subtasks
97 | if (task.subtasks) {
98 | for (const subtask of task.subtasks) {
99 | if (
100 | subtask.id.toString() === taskId ||
101 | `${task.id}.${subtask.id}` === taskId
102 | ) {
103 | return {
104 | ...subtask,
105 | id: subtask.id.toString(),
106 | description: subtask.description || '',
107 | status: (subtask.status || 'pending') as TaskMasterTask['status'],
108 | priority: 'medium' as const,
109 | dependencies: subtask.dependencies?.map((d) => d.toString()) || []
110 | };
111 | }
112 | }
113 | }
114 | }
115 |
116 | return null;
117 | }
118 |
119 | async updateStatus(taskId: string, status: Task['status']): Promise<void> {
120 | try {
121 | const result = await this.api.updateTaskStatus(taskId, status);
122 |
123 | if (!result.success) {
124 | throw new Error(result.error || 'Failed to update status');
125 | }
126 |
127 | // Invalidate cache
128 | this.cache = null;
129 |
130 | // Fetch updated tasks
131 | await this.getAll();
132 | } catch (error) {
133 | this.logger.error('Failed to update task status', error);
134 | throw error;
135 | }
136 | }
137 |
138 | async updateContent(taskId: string, updates: any): Promise<void> {
139 | try {
140 | const result = await this.api.updateTask(taskId, updates, {
141 | append: false,
142 | research: false
143 | });
144 |
145 | if (!result.success) {
146 | throw new Error(result.error || 'Failed to update task');
147 | }
148 |
149 | // Invalidate cache
150 | this.cache = null;
151 |
152 | // Fetch updated tasks
153 | await this.getAll();
154 | } catch (error) {
155 | this.logger.error('Failed to update task content', error);
156 | throw error;
157 | }
158 | }
159 |
160 | async refresh(): Promise<void> {
161 | this.cache = null;
162 | await this.getAll();
163 | }
164 |
165 | isConnected(): boolean {
166 | return this.api.getConnectionStatus().isConnected;
167 | }
168 | }
169 |
```
--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/errors.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Error handling utilities for Grok CLI provider
3 | */
4 |
5 | import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';
6 | import type { GrokCliErrorMetadata } from './types.js';
7 |
8 | /**
9 | * Parameters for creating API call errors
10 | */
11 | interface CreateAPICallErrorParams {
12 | /** Error message */
13 | message: string;
14 | /** Error code */
15 | code?: string;
16 | /** Process exit code */
17 | exitCode?: number;
18 | /** Standard error output */
19 | stderr?: string;
20 | /** Standard output */
21 | stdout?: string;
22 | /** Excerpt of the prompt */
23 | promptExcerpt?: string;
24 | /** Whether the error is retryable */
25 | isRetryable?: boolean;
26 | }
27 |
28 | /**
29 | * Parameters for creating authentication errors
30 | */
31 | interface CreateAuthenticationErrorParams {
32 | /** Error message */
33 | message?: string;
34 | }
35 |
36 | /**
37 | * Parameters for creating timeout errors
38 | */
39 | interface CreateTimeoutErrorParams {
40 | /** Error message */
41 | message: string;
42 | /** Excerpt of the prompt */
43 | promptExcerpt?: string;
44 | /** Timeout in milliseconds */
45 | timeoutMs: number;
46 | }
47 |
48 | /**
49 | * Parameters for creating installation errors
50 | */
51 | interface CreateInstallationErrorParams {
52 | /** Error message */
53 | message?: string;
54 | }
55 |
56 | /**
57 | * Create an API call error with Grok CLI specific metadata
58 | */
59 | export function createAPICallError({
60 | message,
61 | code,
62 | exitCode,
63 | stderr,
64 | stdout,
65 | promptExcerpt,
66 | isRetryable = false
67 | }: CreateAPICallErrorParams): APICallError {
68 | const metadata: GrokCliErrorMetadata = {
69 | code,
70 | exitCode,
71 | stderr,
72 | stdout,
73 | promptExcerpt
74 | };
75 |
76 | return new APICallError({
77 | message,
78 | isRetryable,
79 | url: 'grok-cli://command',
80 | requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
81 | data: metadata
82 | });
83 | }
84 |
85 | /**
86 | * Create an authentication error
87 | */
88 | export function createAuthenticationError({
89 | message
90 | }: CreateAuthenticationErrorParams): LoadAPIKeyError {
91 | return new LoadAPIKeyError({
92 | message:
93 | message ||
94 | 'Authentication failed. Please ensure Grok CLI is properly configured with API key.'
95 | });
96 | }
97 |
98 | /**
99 | * Create a timeout error
100 | */
101 | export function createTimeoutError({
102 | message,
103 | promptExcerpt,
104 | timeoutMs
105 | }: CreateTimeoutErrorParams): APICallError {
106 | const metadata: GrokCliErrorMetadata & { timeoutMs: number } = {
107 | code: 'TIMEOUT',
108 | promptExcerpt,
109 | timeoutMs
110 | };
111 |
112 | return new APICallError({
113 | message,
114 | isRetryable: true,
115 | url: 'grok-cli://command',
116 | requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
117 | data: metadata
118 | });
119 | }
120 |
121 | /**
122 | * Create a CLI installation error
123 | */
124 | export function createInstallationError({
125 | message
126 | }: CreateInstallationErrorParams): APICallError {
127 | return new APICallError({
128 | message:
129 | message ||
130 | 'Grok CLI is not installed or not found in PATH. Please install with: npm install -g @vibe-kit/grok-cli',
131 | isRetryable: false,
132 | url: 'grok-cli://installation',
133 | requestBodyValues: undefined
134 | });
135 | }
136 |
137 | /**
138 | * Check if an error is an authentication error
139 | */
140 | export function isAuthenticationError(
141 | error: unknown
142 | ): error is LoadAPIKeyError {
143 | if (error instanceof LoadAPIKeyError) return true;
144 | if (error instanceof APICallError) {
145 | const metadata = error.data as GrokCliErrorMetadata | undefined;
146 | if (!metadata) return false;
147 | return (
148 | metadata.exitCode === 401 ||
149 | metadata.code === 'AUTHENTICATION_ERROR' ||
150 | metadata.code === 'UNAUTHORIZED'
151 | );
152 | }
153 | return false;
154 | }
155 |
156 | /**
157 | * Check if an error is a timeout error
158 | */
159 | export function isTimeoutError(error: unknown): error is APICallError {
160 | if (
161 | error instanceof APICallError &&
162 | (error.data as GrokCliErrorMetadata)?.code === 'TIMEOUT'
163 | )
164 | return true;
165 | return false;
166 | }
167 |
168 | /**
169 | * Check if an error is an installation error
170 | */
171 | export function isInstallationError(error: unknown): error is APICallError {
172 | if (error instanceof APICallError && error.url === 'grok-cli://installation')
173 | return true;
174 | return false;
175 | }
176 |
177 | /**
178 | * Get error metadata from an error
179 | */
180 | export function getErrorMetadata(
181 | error: unknown
182 | ): GrokCliErrorMetadata | undefined {
183 | if (error instanceof APICallError && error.data) {
184 | return error.data as GrokCliErrorMetadata;
185 | }
186 | return undefined;
187 | }
188 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/workflow/services/workflow-activity-logger.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview WorkflowActivityLogger - Logs all workflow events to activity.jsonl
3 | *
4 | * Subscribes to all WorkflowOrchestrator events and persists them to a JSONL file
5 | * for debugging, auditing, and workflow analysis.
6 | */
7 |
8 | import { getLogger } from '../../../common/logger/index.js';
9 | import {
10 | type ActivityEvent,
11 | logActivity
12 | } from '../../storage/adapters/activity-logger.js';
13 | import type { WorkflowOrchestrator } from '../orchestrators/workflow-orchestrator.js';
14 | import type { WorkflowEventData, WorkflowEventType } from '../types.js';
15 |
16 | /**
17 | * All workflow event types that should be logged
18 | */
19 | const WORKFLOW_EVENT_TYPES: WorkflowEventType[] = [
20 | 'workflow:started',
21 | 'workflow:completed',
22 | 'workflow:error',
23 | 'workflow:resumed',
24 | 'phase:entered',
25 | 'phase:exited',
26 | 'tdd:feature-already-implemented',
27 | 'tdd:red:started',
28 | 'tdd:red:completed',
29 | 'tdd:green:started',
30 | 'tdd:green:completed',
31 | 'tdd:commit:started',
32 | 'tdd:commit:completed',
33 | 'subtask:started',
34 | 'subtask:completed',
35 | 'subtask:failed',
36 | 'test:run',
37 | 'test:passed',
38 | 'test:failed',
39 | 'git:branch:created',
40 | 'git:commit:created',
41 | 'error:occurred',
42 | 'state:persisted',
43 | 'progress:updated',
44 | 'adapter:configured'
45 | ];
46 |
47 | /**
48 | * Logs all workflow events to an activity.jsonl file
49 | */
50 | export class WorkflowActivityLogger {
51 | private readonly activityLogPath: string;
52 | private readonly orchestrator: WorkflowOrchestrator;
53 | private readonly logger = getLogger('WorkflowActivityLogger');
54 | private readonly listenerMap: Map<
55 | WorkflowEventType,
56 | (event: WorkflowEventData) => void
57 | > = new Map();
58 | private isActive = false;
59 |
60 | constructor(orchestrator: WorkflowOrchestrator, activityLogPath: string) {
61 | this.orchestrator = orchestrator;
62 | this.activityLogPath = activityLogPath;
63 | }
64 |
65 | /**
66 | * Start logging workflow events
67 | */
68 | start(): void {
69 | if (this.isActive) {
70 | this.logger.warn('Activity logger is already active');
71 | return;
72 | }
73 |
74 | // Subscribe to all workflow events, storing listener references for cleanup
75 | WORKFLOW_EVENT_TYPES.forEach((eventType) => {
76 | const listener = (event: WorkflowEventData) => this.logEvent(event);
77 | this.listenerMap.set(eventType, listener);
78 | this.orchestrator.on(eventType, listener);
79 | });
80 |
81 | this.isActive = true;
82 | this.logger.debug(
83 | `Activity logger started, logging to: ${this.activityLogPath}`
84 | );
85 | }
86 |
87 | /**
88 | * Stop logging workflow events and remove all listeners
89 | */
90 | stop(): void {
91 | if (!this.isActive) {
92 | return;
93 | }
94 |
95 | // Remove all registered listeners
96 | this.listenerMap.forEach((listener, eventType) => {
97 | this.orchestrator.off(eventType, listener);
98 | });
99 |
100 | // Clear the listener map
101 | this.listenerMap.clear();
102 |
103 | this.isActive = false;
104 | this.logger.debug('Activity logger stopped and listeners removed');
105 | }
106 |
107 | /**
108 | * Log a workflow event to the activity log
109 | */
110 | private async logEvent(event: WorkflowEventData): Promise<void> {
111 | if (!this.isActive) {
112 | return;
113 | }
114 |
115 | try {
116 | // Convert timestamp to ISO string, handling both Date objects and string/number timestamps
117 | const ts =
118 | (event.timestamp as any) instanceof Date
119 | ? (event.timestamp as Date).toISOString()
120 | : new Date(event.timestamp as any).toISOString();
121 |
122 | // Convert WorkflowEventData to ActivityEvent format
123 | const activityEvent: Omit<ActivityEvent, 'timestamp'> = {
124 | type: event.type,
125 | phase: event.phase,
126 | tddPhase: event.tddPhase,
127 | subtaskId: event.subtaskId,
128 | // Event timestamp kept as ISO for readability; storage layer adds its own "timestamp"
129 | eventTimestamp: ts,
130 | ...(event.data || {})
131 | };
132 |
133 | await logActivity(this.activityLogPath, activityEvent);
134 | } catch (error: any) {
135 | // Log errors but don't throw - we don't want activity logging to break the workflow
136 | this.logger.error(
137 | `Failed to log activity event ${event.type}: ${error.message}`
138 | );
139 | }
140 | }
141 |
142 | /**
143 | * Get the path to the activity log file
144 | */
145 | getActivityLogPath(): string {
146 | return this.activityLogPath;
147 | }
148 |
149 | /**
150 | * Check if the logger is currently active
151 | */
152 | isLogging(): boolean {
153 | return this.isActive;
154 | }
155 | }
156 |
```
--------------------------------------------------------------------------------
/apps/docs/getting-started/quick-start/prd-quick.mdx:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: PRD Creation and Parsing
3 | sidebarTitle: "PRD Creation and Parsing"
4 | ---
5 |
6 | # Writing a PRD
7 |
8 | A PRD (Product Requirements Document) is the starting point of every task flow in Task Master. It defines what you're building and why. A clear PRD dramatically improves the quality of your tasks, your model outputs, and your final product — so it’s worth taking the time to get it right.
9 |
10 | <Tip>
11 | You don’t need to define your whole app up front. You can write a focused PRD just for the next feature or module you’re working on.
12 | </Tip>
13 |
14 | <Tip>
15 | You can start with an empty project or you can start with a feature PRD on an existing project.
16 | </Tip>
17 |
18 | <Tip>
19 | You can add and parse multiple PRDs per project using the --append flag
20 | </Tip>
21 |
22 | ## What Makes a Good PRD?
23 |
24 | - Clear objective — what’s the outcome or feature?
25 | - Context — what’s already in place or assumed?
26 | - Constraints — what limits or requirements need to be respected?
27 | - Reasoning — why are you building it this way?
28 |
29 | The more context you give the model, the better the breakdown and results.
30 |
31 | ---
32 |
33 | ## Writing a PRD for Task Master
34 |
35 | <Note>
36 | Two example PRD templates are available in `.taskmaster/templates/`:
37 | - `example_prd.md` - Simple template for straightforward projects (`.md` recommended for better editor support)
38 | - `example_prd_rpg.md` - Advanced RPG (Repository Planning Graph) template for complex projects with dependencies
39 |
40 | **Why `.md`?** While both `.txt` and `.md` work, Markdown files provide syntax highlighting, proper rendering in VS Code/GitHub, and better collaboration through formatted documentation.
41 | </Note>
42 |
43 |
44 | You can co-write your PRD with an LLM model using the following workflow:
45 |
46 | 1. **Chat about requirements** — explain what you want to build.
47 | 2. **Show an example PRD** — share the example PRD so the model understands the expected format. The example uses formatting that work well with Task Master's code. Following the example will yield better results.
48 | 3. **Iterate and refine** — work with the model to shape the draft into a clear and well-structured PRD.
49 |
50 | This approach works great in Cursor, or anywhere you use a chat-based LLM.
51 |
52 | ### Choosing Between Templates
53 |
54 | **Use `example_prd.md` when:**
55 | - Building straightforward features
56 | - Working on smaller projects
57 | - Dependencies are simple and obvious
58 |
59 | **Use `example_prd_rpg.md` when:**
60 | - Building complex systems with multiple modules
61 | - Need explicit dependency management
62 | - Want structured guidance on architecture decisions
63 | - Planning a large codebase from scratch
64 |
65 | The RPG template teaches you to think about:
66 | 1. **Functional decomposition** (WHAT the system does)
67 | 2. **Structural decomposition** (HOW it's organized in code)
68 | 3. **Explicit dependencies** (WHAT depends on WHAT)
69 | 4. **Topological ordering** (build foundation first, then layers)
70 |
71 | <Tip>
72 | For complex projects, using the RPG template with a code-context-aware ai agent produces the best results because the AI can understand your existing codebase structure. [Learn more about the RPG method →](/capabilities/rpg-method)
73 | </Tip>
74 |
75 | ---
76 |
77 | ## Where to Save Your PRD
78 |
79 | Place your PRD file in the `.taskmaster/docs` folder in your project.
80 |
81 | - You can have **multiple PRDs** per project.
82 | - Name your PRDs clearly so they're easy to reference later.
83 | - Examples: `dashboard_redesign.md`, `user_onboarding.md`
84 | - Tip: Use `.md` extension for better editor support and syntax highlighting
85 |
86 | ---
87 |
88 | # Parse your PRD into Tasks
89 |
90 | This is where the Task Master magic begins.
91 |
92 | In Cursor's AI chat, instruct the agent to generate tasks from your PRD:
93 |
94 | ```
95 | Please use the task-master parse-prd command to generate tasks from my PRD. The PRD is located at .taskmaster/docs/<prd-name>.md.
96 | ```
97 |
98 | The agent will execute the following command which you can alternatively paste into the CLI:
99 |
100 | ```bash
101 | task-master parse-prd .taskmaster/docs/<prd-name>.md
102 | ```
103 |
104 | This will:
105 |
106 | - Parse your PRD document
107 | - Generate a structured `tasks.json` file with tasks, dependencies, priorities, and test strategies
108 |
109 | Now that you have written and parsed a PRD, you are ready to start setting up your tasks.
110 |
111 |
112 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/common/mappers/TaskMapper.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, expect, it, vi } from 'vitest';
2 | import type { Tables } from '../types/database.types.js';
3 | import { TaskMapper } from './TaskMapper.js';
4 |
5 | type TaskRow = Tables<'tasks'>;
6 |
7 | describe('TaskMapper', () => {
8 | describe('extractMetadataField', () => {
9 | it('should extract string field from metadata', () => {
10 | const taskRow: TaskRow = {
11 | id: '123',
12 | display_id: '1',
13 | title: 'Test Task',
14 | description: 'Test description',
15 | status: 'todo',
16 | priority: 'medium',
17 | parent_task_id: null,
18 | subtask_position: 0,
19 | created_at: new Date().toISOString(),
20 | updated_at: new Date().toISOString(),
21 | metadata: {
22 | details: 'Some details',
23 | testStrategy: 'Test with unit tests'
24 | },
25 | complexity: null,
26 | assignee_id: null,
27 | estimated_hours: null,
28 | actual_hours: null,
29 | due_date: null,
30 | completed_at: null
31 | };
32 |
33 | const task = TaskMapper.mapDatabaseTaskToTask(taskRow, [], new Map());
34 |
35 | expect(task.details).toBe('Some details');
36 | expect(task.testStrategy).toBe('Test with unit tests');
37 | });
38 |
39 | it('should use default value when metadata field is missing', () => {
40 | const taskRow: TaskRow = {
41 | id: '123',
42 | display_id: '1',
43 | title: 'Test Task',
44 | description: 'Test description',
45 | status: 'todo',
46 | priority: 'medium',
47 | parent_task_id: null,
48 | subtask_position: 0,
49 | created_at: new Date().toISOString(),
50 | updated_at: new Date().toISOString(),
51 | metadata: {},
52 | complexity: null,
53 | assignee_id: null,
54 | estimated_hours: null,
55 | actual_hours: null,
56 | due_date: null,
57 | completed_at: null
58 | };
59 |
60 | const task = TaskMapper.mapDatabaseTaskToTask(taskRow, [], new Map());
61 |
62 | expect(task.details).toBe('');
63 | expect(task.testStrategy).toBe('');
64 | });
65 |
66 | it('should use default value when metadata is null', () => {
67 | const taskRow: TaskRow = {
68 | id: '123',
69 | display_id: '1',
70 | title: 'Test Task',
71 | description: 'Test description',
72 | status: 'todo',
73 | priority: 'medium',
74 | parent_task_id: null,
75 | subtask_position: 0,
76 | created_at: new Date().toISOString(),
77 | updated_at: new Date().toISOString(),
78 | metadata: null,
79 | complexity: null,
80 | assignee_id: null,
81 | estimated_hours: null,
82 | actual_hours: null,
83 | due_date: null,
84 | completed_at: null
85 | };
86 |
87 | const task = TaskMapper.mapDatabaseTaskToTask(taskRow, [], new Map());
88 |
89 | expect(task.details).toBe('');
90 | expect(task.testStrategy).toBe('');
91 | });
92 |
93 | it('should use default value and warn when metadata field has wrong type', () => {
94 | const consoleWarnSpy = vi
95 | .spyOn(console, 'warn')
96 | .mockImplementation(() => {});
97 |
98 | const taskRow: TaskRow = {
99 | id: '123',
100 | display_id: '1',
101 | title: 'Test Task',
102 | description: 'Test description',
103 | status: 'todo',
104 | priority: 'medium',
105 | parent_task_id: null,
106 | subtask_position: 0,
107 | created_at: new Date().toISOString(),
108 | updated_at: new Date().toISOString(),
109 | metadata: {
110 | details: 12345, // Wrong type: number instead of string
111 | testStrategy: ['test1', 'test2'] // Wrong type: array instead of string
112 | },
113 | complexity: null,
114 | assignee_id: null,
115 | estimated_hours: null,
116 | actual_hours: null,
117 | due_date: null,
118 | completed_at: null
119 | };
120 |
121 | const task = TaskMapper.mapDatabaseTaskToTask(taskRow, [], new Map());
122 |
123 | // Should use empty string defaults when type doesn't match
124 | expect(task.details).toBe('');
125 | expect(task.testStrategy).toBe('');
126 |
127 | // Should have logged warnings
128 | expect(consoleWarnSpy).toHaveBeenCalledWith(
129 | expect.stringContaining('Type mismatch in metadata field "details"')
130 | );
131 | expect(consoleWarnSpy).toHaveBeenCalledWith(
132 | expect.stringContaining(
133 | 'Type mismatch in metadata field "testStrategy"'
134 | )
135 | );
136 |
137 | consoleWarnSpy.mockRestore();
138 | });
139 | });
140 |
141 | describe('mapStatus', () => {
142 | it('should map database status to internal status', () => {
143 | expect(TaskMapper.mapStatus('todo')).toBe('pending');
144 | expect(TaskMapper.mapStatus('in_progress')).toBe('in-progress');
145 | expect(TaskMapper.mapStatus('done')).toBe('done');
146 | });
147 | });
148 | });
149 |
```
--------------------------------------------------------------------------------
/.claude/TM_COMMANDS_GUIDE.md:
--------------------------------------------------------------------------------
```markdown
1 | # Task Master Commands for Claude Code
2 |
3 | Complete guide to using Task Master through Claude Code's slash commands.
4 |
5 | ## Overview
6 |
7 | All Task Master functionality is available through the `/project:tm/` namespace with natural language support and intelligent features.
8 |
9 | ## Quick Start
10 |
11 | ```bash
12 | # Install Task Master
13 | /project:tm/setup/quick-install
14 |
15 | # Initialize project
16 | /project:tm/init/quick
17 |
18 | # Parse requirements
19 | /project:tm/parse-prd requirements.md
20 |
21 | # Start working
22 | /project:tm/next
23 | ```
24 |
25 | ## Command Structure
26 |
27 | Commands are organized hierarchically to match Task Master's CLI:
28 | - Main commands at `/project:tm/[command]`
29 | - Subcommands for specific operations `/project:tm/[command]/[subcommand]`
30 | - Natural language arguments accepted throughout
31 |
32 | ## Complete Command Reference
33 |
34 | ### Setup & Configuration
35 | - `/project:tm/setup/install` - Full installation guide
36 | - `/project:tm/setup/quick-install` - One-line install
37 | - `/project:tm/init` - Initialize project
38 | - `/project:tm/init/quick` - Quick init with -y
39 | - `/project:tm/models` - View AI config
40 | - `/project:tm/models/setup` - Configure AI
41 |
42 | ### Task Generation
43 | - `/project:tm/parse-prd` - Generate from PRD
44 | - `/project:tm/parse-prd/with-research` - Enhanced parsing
45 | - `/project:tm/generate` - Create task files
46 |
47 | ### Task Management
48 | - `/project:tm/list` - List with natural language filters
49 | - `/project:tm/list/with-subtasks` - Hierarchical view
50 | - `/project:tm/list/by-status <status>` - Filter by status
51 | - `/project:tm/show <id>` - Task details
52 | - `/project:tm/add-task` - Create task
53 | - `/project:tm/update` - Update tasks
54 | - `/project:tm/remove-task` - Delete task
55 |
56 | ### Status Management
57 | - `/project:tm/set-status/to-pending <id>`
58 | - `/project:tm/set-status/to-in-progress <id>`
59 | - `/project:tm/set-status/to-done <id>`
60 | - `/project:tm/set-status/to-review <id>`
61 | - `/project:tm/set-status/to-deferred <id>`
62 | - `/project:tm/set-status/to-cancelled <id>`
63 |
64 | ### Task Analysis
65 | - `/project:tm/analyze-complexity` - AI analysis
66 | - `/project:tm/complexity-report` - View report
67 | - `/project:tm/expand <id>` - Break down task
68 | - `/project:tm/expand/all` - Expand all complex
69 |
70 | ### Dependencies
71 | - `/project:tm/add-dependency` - Add dependency
72 | - `/project:tm/remove-dependency` - Remove dependency
73 | - `/project:tm/validate-dependencies` - Check issues
74 | - `/project:tm/fix-dependencies` - Auto-fix
75 |
76 | ### Workflows
77 | - `/project:tm/workflows/smart-flow` - Adaptive workflows
78 | - `/project:tm/workflows/pipeline` - Chain commands
79 | - `/project:tm/workflows/auto-implement` - AI implementation
80 |
81 | ### Utilities
82 | - `/project:tm/status` - Project dashboard
83 | - `/project:tm/next` - Next task recommendation
84 | - `/project:tm/utils/analyze` - Project analysis
85 | - `/project:tm/learn` - Interactive help
86 |
87 | ## Key Features
88 |
89 | ### Natural Language Support
90 | All commands understand natural language:
91 | ```
92 | /project:tm/list pending high priority
93 | /project:tm/update mark 23 as done
94 | /project:tm/add-task implement OAuth login
95 | ```
96 |
97 | ### Smart Context
98 | Commands analyze project state and provide intelligent suggestions based on:
99 | - Current task status
100 | - Dependencies
101 | - Team patterns
102 | - Project phase
103 |
104 | ### Visual Enhancements
105 | - Progress bars and indicators
106 | - Status badges
107 | - Organized displays
108 | - Clear hierarchies
109 |
110 | ## Common Workflows
111 |
112 | ### Daily Development
113 | ```
114 | /project:tm/workflows/smart-flow morning
115 | /project:tm/next
116 | /project:tm/set-status/to-in-progress <id>
117 | /project:tm/set-status/to-done <id>
118 | ```
119 |
120 | ### Task Breakdown
121 | ```
122 | /project:tm/show <id>
123 | /project:tm/expand <id>
124 | /project:tm/list/with-subtasks
125 | ```
126 |
127 | ### Sprint Planning
128 | ```
129 | /project:tm/analyze-complexity
130 | /project:tm/workflows/pipeline init → expand/all → status
131 | ```
132 |
133 | ## Migration from Old Commands
134 |
135 | | Old | New |
136 | |-----|-----|
137 | | `/project:task-master:list` | `/project:tm/list` |
138 | | `/project:task-master:complete` | `/project:tm/set-status/to-done` |
139 | | `/project:workflows:auto-implement` | `/project:tm/workflows/auto-implement` |
140 |
141 | ## Tips
142 |
143 | 1. Use `/project:tm/` + Tab for command discovery
144 | 2. Natural language is supported everywhere
145 | 3. Commands provide smart defaults
146 | 4. Chain commands for automation
147 | 5. Check `/project:tm/learn` for interactive help
```
--------------------------------------------------------------------------------
/assets/claude/TM_COMMANDS_GUIDE.md:
--------------------------------------------------------------------------------
```markdown
1 | # Task Master Commands for Claude Code
2 |
3 | Complete guide to using Task Master through Claude Code's slash commands.
4 |
5 | ## Overview
6 |
7 | All Task Master functionality is available through the `/project:tm/` namespace with natural language support and intelligent features.
8 |
9 | ## Quick Start
10 |
11 | ```bash
12 | # Install Task Master
13 | /project:tm/setup/quick-install
14 |
15 | # Initialize project
16 | /project:tm/init/quick
17 |
18 | # Parse requirements
19 | /project:tm/parse-prd requirements.md
20 |
21 | # Start working
22 | /project:tm/next
23 | ```
24 |
25 | ## Command Structure
26 |
27 | Commands are organized hierarchically to match Task Master's CLI:
28 | - Main commands at `/project:tm/[command]`
29 | - Subcommands for specific operations `/project:tm/[command]/[subcommand]`
30 | - Natural language arguments accepted throughout
31 |
32 | ## Complete Command Reference
33 |
34 | ### Setup & Configuration
35 | - `/project:tm/setup/install` - Full installation guide
36 | - `/project:tm/setup/quick-install` - One-line install
37 | - `/project:tm/init` - Initialize project
38 | - `/project:tm/init/quick` - Quick init with -y
39 | - `/project:tm/models` - View AI config
40 | - `/project:tm/models/setup` - Configure AI
41 |
42 | ### Task Generation
43 | - `/project:tm/parse-prd` - Generate from PRD
44 | - `/project:tm/parse-prd/with-research` - Enhanced parsing
45 | - `/project:tm/generate` - Create task files
46 |
47 | ### Task Management
48 | - `/project:tm/list` - List with natural language filters
49 | - `/project:tm/list/with-subtasks` - Hierarchical view
50 | - `/project:tm/list/by-status <status>` - Filter by status
51 | - `/project:tm/show <id>` - Task details
52 | - `/project:tm/add-task` - Create task
53 | - `/project:tm/update` - Update tasks
54 | - `/project:tm/remove-task` - Delete task
55 |
56 | ### Status Management
57 | - `/project:tm/set-status/to-pending <id>`
58 | - `/project:tm/set-status/to-in-progress <id>`
59 | - `/project:tm/set-status/to-done <id>`
60 | - `/project:tm/set-status/to-review <id>`
61 | - `/project:tm/set-status/to-deferred <id>`
62 | - `/project:tm/set-status/to-cancelled <id>`
63 |
64 | ### Task Analysis
65 | - `/project:tm/analyze-complexity` - AI analysis
66 | - `/project:tm/complexity-report` - View report
67 | - `/project:tm/expand <id>` - Break down task
68 | - `/project:tm/expand/all` - Expand all complex
69 |
70 | ### Dependencies
71 | - `/project:tm/add-dependency` - Add dependency
72 | - `/project:tm/remove-dependency` - Remove dependency
73 | - `/project:tm/validate-dependencies` - Check issues
74 | - `/project:tm/fix-dependencies` - Auto-fix
75 |
76 | ### Workflows
77 | - `/project:tm/workflows/smart-flow` - Adaptive workflows
78 | - `/project:tm/workflows/pipeline` - Chain commands
79 | - `/project:tm/workflows/auto-implement` - AI implementation
80 |
81 | ### Utilities
82 | - `/project:tm/status` - Project dashboard
83 | - `/project:tm/next` - Next task recommendation
84 | - `/project:tm/utils/analyze` - Project analysis
85 | - `/project:tm/learn` - Interactive help
86 |
87 | ## Key Features
88 |
89 | ### Natural Language Support
90 | All commands understand natural language:
91 | ```
92 | /project:tm/list pending high priority
93 | /project:tm/update mark 23 as done
94 | /project:tm/add-task implement OAuth login
95 | ```
96 |
97 | ### Smart Context
98 | Commands analyze project state and provide intelligent suggestions based on:
99 | - Current task status
100 | - Dependencies
101 | - Team patterns
102 | - Project phase
103 |
104 | ### Visual Enhancements
105 | - Progress bars and indicators
106 | - Status badges
107 | - Organized displays
108 | - Clear hierarchies
109 |
110 | ## Common Workflows
111 |
112 | ### Daily Development
113 | ```
114 | /project:tm/workflows/smart-flow morning
115 | /project:tm/next
116 | /project:tm/set-status/to-in-progress <id>
117 | /project:tm/set-status/to-done <id>
118 | ```
119 |
120 | ### Task Breakdown
121 | ```
122 | /project:tm/show <id>
123 | /project:tm/expand <id>
124 | /project:tm/list/with-subtasks
125 | ```
126 |
127 | ### Sprint Planning
128 | ```
129 | /project:tm/analyze-complexity
130 | /project:tm/workflows/pipeline init → expand/all → status
131 | ```
132 |
133 | ## Migration from Old Commands
134 |
135 | | Old | New |
136 | |-----|-----|
137 | | `/project:task-master:list` | `/project:tm/list` |
138 | | `/project:task-master:complete` | `/project:tm/set-status/to-done` |
139 | | `/project:workflows:auto-implement` | `/project:tm/workflows/auto-implement` |
140 |
141 | ## Tips
142 |
143 | 1. Use `/project:tm/` + Tab for command discovery
144 | 2. Natural language is supported everywhere
145 | 3. Commands provide smart defaults
146 | 4. Chain commands for automation
147 | 5. Check `/project:tm/learn` for interactive help
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/clear-subtasks.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Direct function wrapper for clearSubtasks
3 | */
4 |
5 | import { clearSubtasks } from '../../../../scripts/modules/task-manager.js';
6 | import {
7 | enableSilentMode,
8 | disableSilentMode,
9 | readJSON
10 | } from '../../../../scripts/modules/utils.js';
11 | import fs from 'fs';
12 | import path from 'path';
13 |
14 | /**
15 | * Clear subtasks from specified tasks
16 | * @param {Object} args - Function arguments
17 | * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
18 | * @param {string} [args.id] - Task IDs (comma-separated) to clear subtasks from
19 | * @param {boolean} [args.all] - Clear subtasks from all tasks
20 | * @param {string} [args.tag] - Tag context to operate on (defaults to current active tag)
21 | * @param {string} [args.projectRoot] - Project root path (for MCP/env fallback)
22 | * @param {Object} log - Logger object
23 | * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
24 | */
25 | export async function clearSubtasksDirect(args, log) {
26 | // Destructure expected args
27 | const { tasksJsonPath, id, all, tag, projectRoot } = args;
28 | try {
29 | log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
30 |
31 | // Check if tasksJsonPath was provided
32 | if (!tasksJsonPath) {
33 | log.error('clearSubtasksDirect called without tasksJsonPath');
34 | return {
35 | success: false,
36 | error: {
37 | code: 'MISSING_ARGUMENT',
38 | message: 'tasksJsonPath is required'
39 | }
40 | };
41 | }
42 |
43 | // Either id or all must be provided
44 | if (!id && !all) {
45 | return {
46 | success: false,
47 | error: {
48 | code: 'INPUT_VALIDATION_ERROR',
49 | message:
50 | 'Either task IDs with id parameter or all parameter must be provided'
51 | }
52 | };
53 | }
54 |
55 | // Use provided path
56 | const tasksPath = tasksJsonPath;
57 |
58 | // Check if tasks.json exists
59 | if (!fs.existsSync(tasksPath)) {
60 | return {
61 | success: false,
62 | error: {
63 | code: 'FILE_NOT_FOUND_ERROR',
64 | message: `Tasks file not found at ${tasksPath}`
65 | }
66 | };
67 | }
68 |
69 | let taskIds;
70 |
71 | // Use readJSON which handles silent migration and tag resolution
72 | const data = readJSON(tasksPath, projectRoot, tag);
73 |
74 | if (!data || !data.tasks) {
75 | return {
76 | success: false,
77 | error: {
78 | code: 'INPUT_VALIDATION_ERROR',
79 | message: `No tasks found in tasks file: ${tasksPath}`
80 | }
81 | };
82 | }
83 |
84 | const currentTag = data.tag || tag;
85 | const tasks = data.tasks;
86 |
87 | // If all is specified, get all task IDs
88 | if (all) {
89 | log.info(`Clearing subtasks from all tasks in tag '${currentTag}'`);
90 | if (tasks.length === 0) {
91 | return {
92 | success: false,
93 | error: {
94 | code: 'INPUT_VALIDATION_ERROR',
95 | message: `No tasks found in tag context '${currentTag}'`
96 | }
97 | };
98 | }
99 | taskIds = tasks.map((t) => t.id).join(',');
100 | } else {
101 | // Use the provided task IDs
102 | taskIds = id;
103 | }
104 |
105 | log.info(`Clearing subtasks from tasks: ${taskIds} in tag '${currentTag}'`);
106 |
107 | // Enable silent mode to prevent console logs from interfering with JSON response
108 | enableSilentMode();
109 |
110 | // Call the core function
111 | clearSubtasks(tasksPath, taskIds, { projectRoot, tag: currentTag });
112 |
113 | // Restore normal logging
114 | disableSilentMode();
115 |
116 | // Read the updated data to provide a summary
117 | const updatedData = readJSON(tasksPath, projectRoot, currentTag);
118 | const taskIdArray = taskIds.split(',').map((id) => parseInt(id.trim(), 10));
119 |
120 | // Build a summary of what was done
121 | const clearedTasksCount = taskIdArray.length;
122 | const updatedTasks = updatedData.tasks || [];
123 |
124 | const taskSummary = taskIdArray.map((id) => {
125 | const task = updatedTasks.find((t) => t.id === id);
126 | return task ? { id, title: task.title } : { id, title: 'Task not found' };
127 | });
128 |
129 | return {
130 | success: true,
131 | data: {
132 | message: `Successfully cleared subtasks from ${clearedTasksCount} task(s) in tag '${currentTag}'`,
133 | tasksCleared: taskSummary,
134 | tag: currentTag
135 | }
136 | };
137 | } catch (error) {
138 | // Make sure to restore normal logging even if there's an error
139 | disableSilentMode();
140 |
141 | log.error(`Error in clearSubtasksDirect: ${error.message}`);
142 | return {
143 | success: false,
144 | error: {
145 | code: 'CORE_FUNCTION_ERROR',
146 | message: error.message
147 | }
148 | };
149 | }
150 | }
151 |
```
--------------------------------------------------------------------------------
/src/ai-providers/openai-compatible.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * openai-compatible.js
3 | * Generic base class for OpenAI-compatible API providers.
4 | * This allows any provider with an OpenAI-compatible API to be easily integrated.
5 | */
6 |
7 | import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
8 | import { BaseAIProvider } from './base-provider.js';
9 |
10 | /**
11 | * Base class for OpenAI-compatible providers (LM Studio, Z.ai, etc.)
12 | * Provides a flexible foundation for any service with OpenAI-compatible endpoints.
13 | */
14 | export class OpenAICompatibleProvider extends BaseAIProvider {
15 | /**
16 | * @param {object} config - Provider configuration
17 | * @param {string} config.name - Provider display name
18 | * @param {string} config.apiKeyEnvVar - Environment variable name for API key
19 | * @param {boolean} [config.requiresApiKey=true] - Whether API key is required
20 | * @param {string} [config.defaultBaseURL] - Default base URL for the API
21 | * @param {Function} [config.getBaseURL] - Function to determine base URL from params
22 | * @param {boolean} [config.supportsStructuredOutputs] - Whether provider supports structured outputs
23 | */
24 | constructor(config) {
25 | super();
26 |
27 | if (!config.name) {
28 | throw new Error('Provider name is required');
29 | }
30 | if (!config.apiKeyEnvVar) {
31 | throw new Error('API key environment variable name is required');
32 | }
33 |
34 | this.name = config.name;
35 | this.apiKeyEnvVar = config.apiKeyEnvVar;
36 | this.requiresApiKey = config.requiresApiKey !== false; // Default to true
37 | this.defaultBaseURL = config.defaultBaseURL;
38 | this.getBaseURLFromParams = config.getBaseURL;
39 | this.supportsStructuredOutputs = config.supportsStructuredOutputs;
40 | }
41 |
42 | /**
43 | * Returns the environment variable name required for this provider's API key.
44 | * @returns {string} The environment variable name for the API key
45 | */
46 | getRequiredApiKeyName() {
47 | return this.apiKeyEnvVar;
48 | }
49 |
50 | /**
51 | * Returns whether this provider requires an API key.
52 | * @returns {boolean} True if API key is required
53 | */
54 | isRequiredApiKey() {
55 | return this.requiresApiKey;
56 | }
57 |
58 | /**
59 | * Override auth validation based on requiresApiKey setting
60 | * @param {object} params - Parameters to validate
61 | */
62 | validateAuth(params) {
63 | if (this.requiresApiKey && !params.apiKey) {
64 | throw new Error(`${this.name} API key is required`);
65 | }
66 | }
67 |
68 | /**
69 | * Determines the base URL to use for the API.
70 | * @param {object} params - Client parameters
71 | * @returns {string|undefined} The base URL to use
72 | */
73 | getBaseURL(params) {
74 | // If custom baseURL provided in params, use it
75 | if (params.baseURL) {
76 | return params.baseURL;
77 | }
78 |
79 | // If provider has a custom getBaseURL function, use it
80 | if (this.getBaseURLFromParams) {
81 | return this.getBaseURLFromParams(params);
82 | }
83 |
84 | // Otherwise use default baseURL if available
85 | return this.defaultBaseURL;
86 | }
87 |
88 | /**
89 | * Creates and returns an OpenAI-compatible client instance.
90 | * @param {object} params - Parameters for client initialization
91 | * @param {string} [params.apiKey] - API key (required if requiresApiKey is true)
92 | * @param {string} [params.baseURL] - Optional custom API endpoint
93 | * @returns {Function} OpenAI-compatible client function
94 | * @throws {Error} If required parameters are missing or initialization fails
95 | */
96 | getClient(params) {
97 | try {
98 | const { apiKey } = params;
99 | const fetchImpl = this.createProxyFetch();
100 |
101 | const baseURL = this.getBaseURL(params);
102 |
103 | const clientConfig = {
104 | // Provider name for SDK (required, used for logging/debugging)
105 | name: this.name.toLowerCase().replace(/[^a-z0-9]/g, '-')
106 | };
107 |
108 | // Only include apiKey if provider requires it
109 | if (this.requiresApiKey && apiKey) {
110 | clientConfig.apiKey = apiKey;
111 | }
112 |
113 | // Include baseURL if available
114 | if (baseURL) {
115 | clientConfig.baseURL = baseURL;
116 | }
117 |
118 | // Configure structured outputs support if specified
119 | if (this.supportsStructuredOutputs !== undefined) {
120 | clientConfig.supportsStructuredOutputs = this.supportsStructuredOutputs;
121 | }
122 |
123 | // Add proxy support if available
124 | if (fetchImpl) {
125 | clientConfig.fetch = fetchImpl;
126 | }
127 |
128 | return createOpenAICompatible(clientConfig);
129 | } catch (error) {
130 | this.handleError('client initialization', error);
131 | }
132 | }
133 | }
134 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/auth/services/supabase-session-storage.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Barebones storage adapter for Supabase Auth sessions
3 | *
4 | * This is a simple key-value store that lets Supabase manage sessions
5 | * without interference. No parsing, no merging, no guessing - just storage.
6 | *
7 | * Supabase handles:
8 | * - Session refresh and token rotation
9 | * - Expiry checking
10 | * - Token validation
11 | *
12 | * We handle:
13 | * - Simple get/set/remove/clear operations
14 | * - Persistence to ~/.taskmaster/session.json with atomic writes via steno
15 | */
16 |
17 | import fs from 'fs/promises';
18 | import fsSync from 'fs';
19 | import path from 'path';
20 | import { Writer } from 'steno';
21 | import type { SupportedStorage } from '@supabase/supabase-js';
22 | import { getLogger } from '../../../common/logger/index.js';
23 |
24 | const DEFAULT_SESSION_FILE = path.join(
25 | process.env.HOME || process.env.USERPROFILE || '~',
26 | '.taskmaster',
27 | 'session.json'
28 | );
29 |
30 | export class SupabaseSessionStorage implements SupportedStorage {
31 | private storage: Map<string, string> = new Map();
32 | private persistPath: string;
33 | private logger = getLogger('SupabaseSessionStorage');
34 | private writer: Writer;
35 | private initPromise: Promise<void>;
36 |
37 | constructor(persistPath: string = DEFAULT_SESSION_FILE) {
38 | this.persistPath = persistPath;
39 | this.writer = new Writer(persistPath);
40 | this.initPromise = this.load();
41 | }
42 |
43 | /**
44 | * Load session data from disk on initialization
45 | */
46 | private async load(): Promise<void> {
47 | try {
48 | // Ensure directory exists
49 | const dir = path.dirname(this.persistPath);
50 | await fs.mkdir(dir, { recursive: true, mode: 0o700 });
51 |
52 | // Try to read existing session
53 | if (fsSync.existsSync(this.persistPath)) {
54 | const data = JSON.parse(await fs.readFile(this.persistPath, 'utf8'));
55 | Object.entries(data).forEach(([k, v]) =>
56 | this.storage.set(k, v as string)
57 | );
58 | this.logger.debug('Loaded session from disk', {
59 | keys: Array.from(this.storage.keys())
60 | });
61 | }
62 | } catch (error) {
63 | this.logger.error('Failed to load session:', error);
64 | // Don't throw - allow starting with fresh session
65 | }
66 | }
67 |
68 | /**
69 | * Persist session data to disk immediately
70 | * Uses steno for atomic writes with fsync guarantees
71 | * This prevents race conditions in rapid CLI command sequences
72 | */
73 | private async persist(): Promise<void> {
74 | try {
75 | const data = Object.fromEntries(this.storage);
76 | const jsonContent = JSON.stringify(data, null, 2);
77 |
78 | // steno handles atomic writes with temp file + rename
79 | // and ensures data is flushed to disk
80 | await this.writer.write(jsonContent + '\n');
81 |
82 | this.logger.debug('Persisted session to disk (steno)');
83 | } catch (error) {
84 | this.logger.error('Failed to persist session:', error);
85 | // Don't throw - session is still in memory
86 | }
87 | }
88 |
89 | /**
90 | * Get item from storage
91 | * Supabase will call this to retrieve session data
92 | * Returns a promise to ensure initialization completes first
93 | */
94 | async getItem(key: string): Promise<string | null> {
95 | // Wait for initialization to complete
96 | await this.initPromise;
97 |
98 | const value = this.storage.get(key) ?? null;
99 | this.logger.debug('getItem called', { key, hasValue: !!value });
100 | return value;
101 | }
102 |
103 | /**
104 | * Set item in storage
105 | * Supabase will call this to store/update session data
106 | * CRITICAL: This is called during token refresh - must persist immediately
107 | */
108 | async setItem(key: string, value: string): Promise<void> {
109 | // Wait for initialization to complete
110 | await this.initPromise;
111 |
112 | this.logger.debug('setItem called', { key });
113 | this.storage.set(key, value);
114 |
115 | // Immediately persist on every write
116 | // steno ensures atomic writes with fsync
117 | await this.persist();
118 | }
119 |
120 | /**
121 | * Remove item from storage
122 | * Supabase will call this during sign out
123 | */
124 | async removeItem(key: string): Promise<void> {
125 | // Wait for initialization to complete
126 | await this.initPromise;
127 |
128 | this.logger.debug('removeItem called', { key });
129 | this.storage.delete(key);
130 | await this.persist();
131 | }
132 |
133 | /**
134 | * Clear all session data
135 | * Useful for complete logout scenarios
136 | */
137 | async clear(): Promise<void> {
138 | // Wait for initialization to complete
139 | await this.initPromise;
140 |
141 | this.logger.debug('clear called');
142 | this.storage.clear();
143 | await this.persist();
144 | }
145 | }
146 |
```
--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/message-converter.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for message conversion utilities
3 | */
4 |
5 | import { describe, expect, it } from 'vitest';
6 | import {
7 | convertFromGrokCliResponse,
8 | convertToGrokCliMessages,
9 | createPromptFromMessages,
10 | escapeShellArg
11 | } from './message-converter.js';
12 |
13 | describe('convertToGrokCliMessages', () => {
14 | it('should convert string content messages', () => {
15 | const messages = [
16 | { role: 'user', content: 'Hello, world!' },
17 | { role: 'assistant', content: 'Hi there!' }
18 | ];
19 |
20 | const result = convertToGrokCliMessages(messages);
21 |
22 | expect(result).toEqual([
23 | { role: 'user', content: 'Hello, world!' },
24 | { role: 'assistant', content: 'Hi there!' }
25 | ]);
26 | });
27 |
28 | it('should convert array content messages', () => {
29 | const messages = [
30 | {
31 | role: 'user',
32 | content: [
33 | { type: 'text', text: 'Hello' },
34 | { type: 'text', text: 'World' }
35 | ]
36 | }
37 | ];
38 |
39 | const result = convertToGrokCliMessages(messages);
40 |
41 | expect(result).toEqual([{ role: 'user', content: 'Hello\nWorld' }]);
42 | });
43 |
44 | it('should convert object content messages', () => {
45 | const messages = [
46 | {
47 | role: 'user',
48 | content: { text: 'Hello from object' }
49 | }
50 | ];
51 |
52 | const result = convertToGrokCliMessages(messages);
53 |
54 | expect(result).toEqual([{ role: 'user', content: 'Hello from object' }]);
55 | });
56 | });
57 |
58 | describe('convertFromGrokCliResponse', () => {
59 | it('should parse JSONL response format', () => {
60 | const responseText = `{"role": "assistant", "content": "Hello there!", "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}}`;
61 |
62 | const result = convertFromGrokCliResponse(responseText);
63 |
64 | expect(result).toEqual({
65 | text: 'Hello there!',
66 | usage: {
67 | promptTokens: 10,
68 | completionTokens: 5,
69 | totalTokens: 15
70 | }
71 | });
72 | });
73 |
74 | it('should handle multiple lines in JSONL format', () => {
75 | const responseText = `{"role": "user", "content": "Hello"}
76 | {"role": "assistant", "content": "Hi there!", "usage": {"prompt_tokens": 5, "completion_tokens": 3}}`;
77 |
78 | const result = convertFromGrokCliResponse(responseText);
79 |
80 | expect(result).toEqual({
81 | text: 'Hi there!',
82 | usage: {
83 | promptTokens: 5,
84 | completionTokens: 3,
85 | totalTokens: 0
86 | }
87 | });
88 | });
89 |
90 | it('should fallback to raw text when parsing fails', () => {
91 | const responseText = 'Invalid JSON response';
92 |
93 | const result = convertFromGrokCliResponse(responseText);
94 |
95 | expect(result).toEqual({
96 | text: 'Invalid JSON response',
97 | usage: undefined
98 | });
99 | });
100 | });
101 |
102 | describe('createPromptFromMessages', () => {
103 | it('should create formatted prompt from messages', () => {
104 | const messages = [
105 | { role: 'system', content: 'You are a helpful assistant.' },
106 | { role: 'user', content: 'What is 2+2?' },
107 | { role: 'assistant', content: '2+2 equals 4.' }
108 | ];
109 |
110 | const result = createPromptFromMessages(messages);
111 |
112 | expect(result).toBe(
113 | 'System: You are a helpful assistant.\n\nUser: What is 2+2?\n\nAssistant: 2+2 equals 4.'
114 | );
115 | });
116 |
117 | it('should handle custom role names', () => {
118 | const messages = [{ role: 'custom', content: 'Custom message' }];
119 |
120 | const result = createPromptFromMessages(messages);
121 |
122 | expect(result).toBe('custom: Custom message');
123 | });
124 |
125 | it('should trim whitespace from message content', () => {
126 | const messages = [
127 | { role: 'user', content: ' Hello with spaces ' },
128 | { role: 'assistant', content: '\n\nResponse with newlines\n\n' }
129 | ];
130 |
131 | const result = createPromptFromMessages(messages);
132 |
133 | expect(result).toBe(
134 | 'User: Hello with spaces\n\nAssistant: Response with newlines'
135 | );
136 | });
137 | });
138 |
139 | describe('escapeShellArg', () => {
140 | it('should escape single quotes', () => {
141 | const arg = "It's a test";
142 | const result = escapeShellArg(arg);
143 | expect(result).toBe("'It'\\''s a test'");
144 | });
145 |
146 | it('should handle strings without special characters', () => {
147 | const arg = 'simple string';
148 | const result = escapeShellArg(arg);
149 | expect(result).toBe("'simple string'");
150 | });
151 |
152 | it('should convert non-string values to strings', () => {
153 | const arg = 123;
154 | const result = escapeShellArg(arg);
155 | expect(result).toBe("'123'");
156 | });
157 |
158 | it('should handle empty strings', () => {
159 | const arg = '';
160 | const result = escapeShellArg(arg);
161 | expect(result).toBe("''");
162 | });
163 | });
164 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/services/environment-config-provider.service.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview Environment Configuration Provider
3 | * Extracts configuration from environment variables
4 | */
5 |
6 | import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
7 | import { getLogger } from '../../../common/logger/index.js';
8 |
9 | /**
10 | * Environment variable mapping definition
11 | */
12 | interface EnvMapping {
13 | /** Environment variable name */
14 | env: string;
15 | /** Path in configuration object */
16 | path: readonly string[];
17 | /** Optional validator function */
18 | validate?: (value: string) => boolean;
19 | /** Whether this is runtime state (not configuration) */
20 | isRuntimeState?: boolean;
21 | }
22 |
23 | /**
24 | * EnvironmentConfigProvider extracts configuration from environment variables
25 | * Single responsibility: Environment variable configuration extraction
26 | */
27 | export class EnvironmentConfigProvider {
28 | private readonly logger = getLogger('EnvironmentConfigProvider');
29 |
30 | /**
31 | * Default environment variable mappings
32 | */
33 | private static readonly DEFAULT_MAPPINGS: EnvMapping[] = [
34 | {
35 | env: 'TASKMASTER_STORAGE_TYPE',
36 | path: ['storage', 'type'],
37 | validate: (v: string) => ['file', 'api'].includes(v)
38 | },
39 | { env: 'TASKMASTER_API_ENDPOINT', path: ['storage', 'apiEndpoint'] },
40 | { env: 'TASKMASTER_API_TOKEN', path: ['storage', 'apiAccessToken'] },
41 | { env: 'TASKMASTER_MODEL_MAIN', path: ['models', 'main'] },
42 | { env: 'TASKMASTER_MODEL_RESEARCH', path: ['models', 'research'] },
43 | { env: 'TASKMASTER_MODEL_FALLBACK', path: ['models', 'fallback'] },
44 | {
45 | env: 'TASKMASTER_RESPONSE_LANGUAGE',
46 | path: ['custom', 'responseLanguage']
47 | }
48 | ];
49 |
50 | /**
51 | * Runtime state mappings (separate from configuration)
52 | */
53 | private static readonly RUNTIME_STATE_MAPPINGS: EnvMapping[] = [
54 | { env: 'TASKMASTER_TAG', path: ['activeTag'], isRuntimeState: true }
55 | ];
56 |
57 | private mappings: EnvMapping[];
58 |
59 | constructor(customMappings?: EnvMapping[]) {
60 | this.mappings = customMappings || [
61 | ...EnvironmentConfigProvider.DEFAULT_MAPPINGS,
62 | ...EnvironmentConfigProvider.RUNTIME_STATE_MAPPINGS
63 | ];
64 | }
65 |
66 | /**
67 | * Load configuration from environment variables
68 | */
69 | loadConfig(): PartialConfiguration {
70 | const config: PartialConfiguration = {};
71 |
72 | for (const mapping of this.mappings) {
73 | // Skip runtime state variables
74 | if (mapping.isRuntimeState) continue;
75 |
76 | const value = process.env[mapping.env];
77 | if (!value) continue;
78 |
79 | // Validate value if validator is provided
80 | if (mapping.validate && !mapping.validate(value)) {
81 | this.logger.warn(`Invalid value for ${mapping.env}: ${value}`);
82 | continue;
83 | }
84 |
85 | // Set the value in the config object
86 | this.setNestedProperty(config, mapping.path, value);
87 | }
88 |
89 | return config;
90 | }
91 |
92 | /**
93 | * Get runtime state from environment variables
94 | */
95 | getRuntimeState(): Record<string, string> {
96 | const state: Record<string, string> = {};
97 |
98 | for (const mapping of this.mappings) {
99 | if (!mapping.isRuntimeState) continue;
100 |
101 | const value = process.env[mapping.env];
102 | if (value) {
103 | const key = mapping.path[mapping.path.length - 1];
104 | state[key] = value;
105 | }
106 | }
107 |
108 | return state;
109 | }
110 |
111 | /**
112 | * Helper to set a nested property in an object
113 | */
114 | private setNestedProperty(
115 | obj: any,
116 | path: readonly string[],
117 | value: any
118 | ): void {
119 | const lastKey = path[path.length - 1];
120 | const keys = path.slice(0, -1);
121 |
122 | let current = obj;
123 | for (const key of keys) {
124 | if (!current[key]) {
125 | current[key] = {};
126 | }
127 | current = current[key];
128 | }
129 |
130 | current[lastKey] = value;
131 | }
132 |
133 | /**
134 | * Check if an environment variable is set
135 | */
136 | hasEnvVar(envName: string): boolean {
137 | return envName in process.env && process.env[envName] !== undefined;
138 | }
139 |
140 | /**
141 | * Get all environment variables that match our prefix
142 | */
143 | getAllTaskmasterEnvVars(): Record<string, string> {
144 | const vars: Record<string, string> = {};
145 | const prefix = 'TASKMASTER_';
146 |
147 | for (const [key, value] of Object.entries(process.env)) {
148 | if (key.startsWith(prefix) && value !== undefined) {
149 | vars[key] = value;
150 | }
151 | }
152 |
153 | return vars;
154 | }
155 |
156 | /**
157 | * Add a custom mapping
158 | */
159 | addMapping(mapping: EnvMapping): void {
160 | this.mappings.push(mapping);
161 | }
162 |
163 | /**
164 | * Get current mappings
165 | */
166 | getMappings(): EnvMapping[] {
167 | return [...this.mappings];
168 | }
169 | }
170 |
```
--------------------------------------------------------------------------------
/.taskmaster/docs/tdd-workflow-phase-0-spike.md:
--------------------------------------------------------------------------------
```markdown
1 | # Phase 0: Spike - Autonomous TDD Workflow ✅ COMPLETE
2 |
3 | ## Objective
4 | Validate feasibility and build foundational understanding before full implementation.
5 |
6 | ## Status
7 | **COMPLETED** - All deliverables implemented and validated.
8 |
9 | See `apps/cli/src/commands/autopilot.command.ts` for implementation.
10 |
11 | ## Scope
12 | - Implement CLI skeleton `tm autopilot` with dry-run mode
13 | - Show planned steps from a real task with subtasks
14 | - Detect test runner from package.json
15 | - Detect git state and render preflight report
16 |
17 | ## Deliverables
18 |
19 | ### 1. CLI Command Skeleton
20 | - Create `apps/cli/src/commands/autopilot.command.ts`
21 | - Support `tm autopilot <taskId>` command
22 | - Implement `--dry-run` flag
23 | - Basic help text and usage information
24 |
25 | ### 2. Preflight Detection System
26 | - Detect test runner from package.json (npm test, pnpm test, etc.)
27 | - Check git working tree state (clean/dirty)
28 | - Validate required tools are available (git, gh, node/npm)
29 | - Detect default branch
30 |
31 | ### 3. Dry-Run Execution Plan Display
32 | Display planned execution for a task including:
33 | - Preflight checks status
34 | - Branch name that would be created
35 | - Tag that would be set
36 | - List of subtasks in execution order
37 | - For each subtask:
38 | - RED phase: test file that would be created
39 | - GREEN phase: implementation files that would be modified
40 | - COMMIT: commit message that would be used
41 | - Finalization steps: test suite run, coverage check, push, PR creation
42 |
43 | ### 4. Task Loading & Validation
44 | - Load task from TaskMaster state
45 | - Validate task exists and has subtasks
46 | - If no subtasks, show message about needing to expand first
47 | - Show dependency order for subtasks
48 |
49 | ## Example Output
50 |
51 | ```bash
52 | $ tm autopilot 42 --dry-run
53 |
54 | Autopilot Plan for Task #42 [analytics]: User metrics tracking
55 | ─────────────────────────────────────────────────────────────
56 |
57 | Preflight Checks:
58 | ✓ Working tree is clean
59 | ✓ Test command detected: npm test
60 | ✓ Tools available: git, gh, node, npm
61 | ✓ Current branch: main (will create new branch)
62 | ✓ Task has 3 subtasks ready to execute
63 |
64 | Branch & Tag:
65 | → Will create branch: analytics/task-42-user-metrics
66 | → Will set active tag: analytics
67 |
68 | Execution Plan (3 subtasks):
69 |
70 | 1. Subtask 42.1: Add metrics schema
71 | RED: Generate tests → src/__tests__/schema.test.js
72 | GREEN: Implement code → src/schema.js
73 | COMMIT: "feat(metrics): add metrics schema (task 42.1)"
74 |
75 | 2. Subtask 42.2: Add collection endpoint [depends on 42.1]
76 | RED: Generate tests → src/api/__tests__/metrics.test.js
77 | GREEN: Implement code → src/api/metrics.js
78 | COMMIT: "feat(metrics): add collection endpoint (task 42.2)"
79 |
80 | 3. Subtask 42.3: Add dashboard widget [depends on 42.2]
81 | RED: Generate tests → src/components/__tests__/MetricsWidget.test.jsx
82 | GREEN: Implement code → src/components/MetricsWidget.jsx
83 | COMMIT: "feat(metrics): add dashboard widget (task 42.3)"
84 |
85 | Finalization:
86 | → Run full test suite with coverage (threshold: 80%)
87 | → Push branch to origin (will confirm)
88 | → Create PR targeting main
89 |
90 | Estimated commits: 3
91 | Estimated duration: ~20-30 minutes (depends on implementation complexity)
92 |
93 | Run without --dry-run to execute.
94 | ```
95 |
96 | ## Success Criteria
97 | - Dry-run output is clear and matches expected workflow
98 | - Preflight detection works correctly on the project repo
99 | - Task loading integrates with existing TaskMaster state
100 | - No actual git operations or file modifications occur in dry-run mode
101 |
102 | ## Out of Scope
103 | - Actual test generation
104 | - Actual code implementation
105 | - Git operations (branch creation, commits, push)
106 | - PR creation
107 | - Test execution
108 |
109 | ## Implementation Notes
110 | - Reuse existing `TaskService` from `packages/tm-core`
111 | - Use existing git utilities from `scripts/modules/utils/git-utils.js`
112 | - Load task/subtask data from `.taskmaster/tasks/tasks.json`
113 | - Detect test command via package.json → scripts.test field
114 |
115 | ## Dependencies
116 | - Existing TaskMaster CLI structure
117 | - Existing task storage format
118 | - Git utilities
119 |
120 | ## Estimated Effort
121 | 2-3 days
122 |
123 | ## Validation
124 | Test dry-run mode with:
125 | - Task with 1 subtask
126 | - Task with multiple subtasks
127 | - Task with dependencies between subtasks
128 | - Task without subtasks (should show warning)
129 | - Dirty git working tree (should warn)
130 | - Missing tools (should error with helpful message)
131 |
```
--------------------------------------------------------------------------------
/src/profiles/cursor.js:
--------------------------------------------------------------------------------
```javascript
1 | // Cursor conversion profile for rule-transformer
2 | import path from 'path';
3 | import fs from 'fs';
4 | import { log } from '../../scripts/modules/utils.js';
5 | import { createProfile } from './base-profile.js';
6 |
7 | // Helper copy; use cpSync when available, fallback to manual recursion
8 | function copyRecursiveSync(src, dest) {
9 | if (fs.cpSync) {
10 | try {
11 | fs.cpSync(src, dest, { recursive: true, force: true });
12 | return;
13 | } catch (err) {
14 | throw new Error(`Failed to copy ${src} to ${dest}: ${err.message}`);
15 | }
16 | }
17 | const exists = fs.existsSync(src);
18 | let stats = null;
19 | let isDirectory = false;
20 |
21 | if (exists) {
22 | try {
23 | stats = fs.statSync(src);
24 | isDirectory = stats.isDirectory();
25 | } catch (err) {
26 | // Handle TOCTOU race condition - treat as non-existent/not-a-directory
27 | isDirectory = false;
28 | }
29 | }
30 |
31 | if (isDirectory) {
32 | try {
33 | if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
34 | for (const child of fs.readdirSync(src)) {
35 | copyRecursiveSync(path.join(src, child), path.join(dest, child));
36 | }
37 | } catch (err) {
38 | throw new Error(
39 | `Failed to copy directory ${src} to ${dest}: ${err.message}`
40 | );
41 | }
42 | } else {
43 | try {
44 | // ensure parent exists for file copies
45 | fs.mkdirSync(path.dirname(dest), { recursive: true });
46 | fs.copyFileSync(src, dest);
47 | } catch (err) {
48 | throw new Error(`Failed to copy file ${src} to ${dest}: ${err.message}`);
49 | }
50 | }
51 | }
52 |
53 | // Helper function to recursively remove directory
54 | function removeDirectoryRecursive(dirPath) {
55 | if (fs.existsSync(dirPath)) {
56 | try {
57 | fs.rmSync(dirPath, { recursive: true, force: true });
58 | return true;
59 | } catch (err) {
60 | log('error', `Failed to remove directory ${dirPath}: ${err.message}`);
61 | return false;
62 | }
63 | }
64 | return true;
65 | }
66 |
67 | // Resolve the Cursor profile directory from either project root, profile root, or rules dir
68 | function resolveCursorProfileDir(baseDir) {
69 | const base = path.basename(baseDir);
70 | // If called with .../.cursor/rules -> return .../.cursor
71 | if (base === 'rules' && path.basename(path.dirname(baseDir)) === '.cursor') {
72 | return path.dirname(baseDir);
73 | }
74 | // If called with .../.cursor -> return as-is
75 | if (base === '.cursor') return baseDir;
76 | // Otherwise assume project root and append .cursor
77 | return path.join(baseDir, '.cursor');
78 | }
79 |
80 | // Lifecycle functions for Cursor profile
81 | function onAddRulesProfile(targetDir, assetsDir) {
82 | // Copy commands directory recursively
83 | const commandsSourceDir = path.join(assetsDir, 'claude', 'commands');
84 | const profileDir = resolveCursorProfileDir(targetDir);
85 | const commandsDestDir = path.join(profileDir, 'commands');
86 |
87 | if (!fs.existsSync(commandsSourceDir)) {
88 | log(
89 | 'warn',
90 | `[Cursor] Source commands directory does not exist: ${commandsSourceDir}`
91 | );
92 | return;
93 | }
94 |
95 | try {
96 | // Ensure fresh state to avoid stale command files
97 | try {
98 | fs.rmSync(commandsDestDir, { recursive: true, force: true });
99 | log(
100 | 'debug',
101 | `[Cursor] Removed existing commands directory: ${commandsDestDir}`
102 | );
103 | } catch (deleteErr) {
104 | // Directory might not exist, which is fine
105 | log(
106 | 'debug',
107 | `[Cursor] Commands directory did not exist or could not be removed: ${deleteErr.message}`
108 | );
109 | }
110 |
111 | copyRecursiveSync(commandsSourceDir, commandsDestDir);
112 | log('debug', `[Cursor] Copied commands directory to ${commandsDestDir}`);
113 | } catch (err) {
114 | log(
115 | 'error',
116 | `[Cursor] An error occurred during commands copy: ${err.message}`
117 | );
118 | }
119 | }
120 |
121 | function onRemoveRulesProfile(targetDir) {
122 | // Remove .cursor/commands directory recursively
123 | const profileDir = resolveCursorProfileDir(targetDir);
124 | const commandsDir = path.join(profileDir, 'commands');
125 | if (removeDirectoryRecursive(commandsDir)) {
126 | log(
127 | 'debug',
128 | `[Cursor] Ensured commands directory removed at ${commandsDir}`
129 | );
130 | }
131 | }
132 |
133 | // Create and export cursor profile using the base factory
134 | export const cursorProfile = createProfile({
135 | name: 'cursor',
136 | displayName: 'Cursor',
137 | url: 'cursor.so',
138 | docsUrl: 'docs.cursor.com',
139 | targetExtension: '.mdc', // Cursor keeps .mdc extension
140 | supportsRulesSubdirectories: true,
141 | onAdd: onAddRulesProfile,
142 | onRemove: onRemoveRulesProfile
143 | });
144 |
145 | // Export lifecycle functions separately to avoid naming conflicts
146 | export { onAddRulesProfile, onRemoveRulesProfile };
147 |
```
--------------------------------------------------------------------------------
/docs/scripts/models-json-to-markdown.js:
--------------------------------------------------------------------------------
```javascript
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = path.dirname(__filename);
7 |
8 | const supportedModelsPath = path.join(
9 | __dirname,
10 | '..',
11 | 'modules',
12 | 'supported-models.json'
13 | );
14 | const outputMarkdownPath = path.join(
15 | __dirname,
16 | '..',
17 | '..',
18 | 'docs',
19 | 'models.md'
20 | );
21 |
22 | function formatCost(cost) {
23 | if (cost === null || cost === undefined) {
24 | return '—';
25 | }
26 | return cost;
27 | }
28 |
29 | function formatSweScore(score) {
30 | if (score === null || score === undefined || score === 0) {
31 | return '—';
32 | }
33 | return score.toString();
34 | }
35 |
36 | function generateMarkdownTable(title, models) {
37 | if (!models || models.length === 0) {
38 | return `## ${title}\n\nNo models in this category.\n\n`;
39 | }
40 | let table = `## ${title}\n\n`;
41 | table += '| Provider | Model Name | SWE Score | Input Cost | Output Cost |\n';
42 | table += '|---|---|---|---|---|\n';
43 | models.forEach((model) => {
44 | table += `| ${model.provider} | ${model.modelName} | ${formatSweScore(model.sweScore)} | ${formatCost(model.inputCost)} | ${formatCost(model.outputCost)} |\n`;
45 | });
46 | table += '\n';
47 | return table;
48 | }
49 |
50 | function generateUnsupportedTable(models) {
51 | if (!models || models.length === 0) {
52 | return '## Unsupported Models\n\nNo unsupported models found.\n\n';
53 | }
54 | let table = '## Unsupported Models\n\n';
55 | table += '| Provider | Model Name | Reason |\n';
56 | table += '|---|---|---|\n';
57 | models.forEach((model) => {
58 | table += `| ${model.provider} | ${model.modelName} | ${model.reason || '—'} |\n`;
59 | });
60 | table += '\n';
61 | return table;
62 | }
63 |
64 | function main() {
65 | try {
66 | const correctSupportedModelsPath = path.join(
67 | __dirname,
68 | '..',
69 | '..',
70 | 'scripts',
71 | 'modules',
72 | 'supported-models.json'
73 | );
74 | const correctOutputMarkdownPath = path.join(__dirname, '..', 'models.md');
75 |
76 | const supportedModelsContent = fs.readFileSync(
77 | correctSupportedModelsPath,
78 | 'utf8'
79 | );
80 | const supportedModels = JSON.parse(supportedModelsContent);
81 |
82 | const mainModels = [];
83 | const researchModels = [];
84 | const fallbackModels = [];
85 | const unsupportedModels = [];
86 |
87 | for (const provider in supportedModels) {
88 | if (Object.hasOwnProperty.call(supportedModels, provider)) {
89 | const models = supportedModels[provider];
90 | models.forEach((model) => {
91 | const isSupported = model.supported !== false; // default to true if missing
92 | if (isSupported) {
93 | const modelEntry = {
94 | provider: provider,
95 | modelName: model.id,
96 | sweScore: model.swe_score,
97 | inputCost: model.cost_per_1m_tokens
98 | ? model.cost_per_1m_tokens.input
99 | : null,
100 | outputCost: model.cost_per_1m_tokens
101 | ? model.cost_per_1m_tokens.output
102 | : null
103 | };
104 | if (model.allowed_roles && model.allowed_roles.includes('main')) {
105 | mainModels.push(modelEntry);
106 | }
107 | if (
108 | model.allowed_roles &&
109 | model.allowed_roles.includes('research')
110 | ) {
111 | researchModels.push(modelEntry);
112 | }
113 | if (
114 | model.allowed_roles &&
115 | model.allowed_roles.includes('fallback')
116 | ) {
117 | fallbackModels.push(modelEntry);
118 | }
119 | } else {
120 | unsupportedModels.push({
121 | provider: provider,
122 | modelName: model.id,
123 | reason: model.reason || 'Not specified'
124 | });
125 | }
126 | });
127 | }
128 | }
129 |
130 | const date = new Date();
131 | const monthNames = [
132 | 'January',
133 | 'February',
134 | 'March',
135 | 'April',
136 | 'May',
137 | 'June',
138 | 'July',
139 | 'August',
140 | 'September',
141 | 'October',
142 | 'November',
143 | 'December'
144 | ];
145 | const formattedDate = `${monthNames[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
146 |
147 | let markdownContent = `# Available Models as of ${formattedDate}\n\n`;
148 | markdownContent += generateMarkdownTable('Main Models', mainModels);
149 | markdownContent += generateMarkdownTable('Research Models', researchModels);
150 | markdownContent += generateMarkdownTable('Fallback Models', fallbackModels);
151 | markdownContent += generateUnsupportedTable(unsupportedModels);
152 |
153 | fs.writeFileSync(correctOutputMarkdownPath, markdownContent, 'utf8');
154 | console.log(`Successfully updated ${correctOutputMarkdownPath}`);
155 | } catch (error) {
156 | console.error('Error transforming models.json to models.md:', error);
157 | process.exit(1);
158 | }
159 | }
160 |
161 | main();
162 |
```
--------------------------------------------------------------------------------
/tests/unit/prompts/expand-task-prompt.test.js:
--------------------------------------------------------------------------------
```javascript
1 | import { PromptManager } from '../../../scripts/modules/prompt-manager.js';
2 | import { ExpandTaskResponseSchema } from '../../../src/schemas/expand-task.js';
3 | import { SubtaskSchema } from '../../../src/schemas/base-schemas.js';
4 |
5 | describe('expand-task prompt template', () => {
6 | let promptManager;
7 |
8 | beforeEach(() => {
9 | promptManager = new PromptManager();
10 | });
11 |
12 | const testTask = {
13 | id: 1,
14 | title: 'Setup AWS Infrastructure',
15 | description: 'Provision core AWS services',
16 | details: 'Create VPC, subnets, and security groups'
17 | };
18 |
19 | const baseParams = {
20 | task: testTask,
21 | subtaskCount: 3,
22 | nextSubtaskId: 1,
23 | additionalContext: '',
24 | complexityReasoningContext: '',
25 | gatheredContext: '',
26 | useResearch: false,
27 | expansionPrompt: undefined
28 | };
29 |
30 | test('default variant includes task context', () => {
31 | const { userPrompt } = promptManager.loadPrompt(
32 | 'expand-task',
33 | baseParams,
34 | 'default'
35 | );
36 |
37 | expect(userPrompt).toContain(testTask.title);
38 | expect(userPrompt).toContain(testTask.description);
39 | expect(userPrompt).toContain(testTask.details);
40 | expect(userPrompt).toContain('Task ID: 1');
41 | });
42 |
43 | test('research variant includes task context', () => {
44 | const params = { ...baseParams, useResearch: true };
45 | const { userPrompt } = promptManager.loadPrompt(
46 | 'expand-task',
47 | params,
48 | 'research'
49 | );
50 |
51 | expect(userPrompt).toContain(testTask.title);
52 | expect(userPrompt).toContain(testTask.description);
53 | expect(userPrompt).toContain(testTask.details);
54 | expect(userPrompt).toContain('Parent Task:');
55 | expect(userPrompt).toContain('ID: 1');
56 | });
57 |
58 | test('complexity-report variant includes task context', () => {
59 | const params = {
60 | ...baseParams,
61 | expansionPrompt: 'Focus on security best practices',
62 | complexityReasoningContext: 'High complexity due to security requirements'
63 | };
64 | const { userPrompt } = promptManager.loadPrompt(
65 | 'expand-task',
66 | params,
67 | 'complexity-report'
68 | );
69 |
70 | // The fix ensures task context is included
71 | expect(userPrompt).toContain('Parent Task:');
72 | expect(userPrompt).toContain(`ID: ${testTask.id}`);
73 | expect(userPrompt).toContain(`Title: ${testTask.title}`);
74 | expect(userPrompt).toContain(`Description: ${testTask.description}`);
75 | expect(userPrompt).toContain(`Current details: ${testTask.details}`);
76 |
77 | // Also includes the expansion prompt
78 | expect(userPrompt).toContain(params.expansionPrompt);
79 | expect(userPrompt).toContain(params.complexityReasoningContext);
80 | });
81 |
82 | test('ExpandTaskResponseSchema defines required subtask fields', () => {
83 | // Test the schema definition directly instead of weak substring matching
84 | const schema = ExpandTaskResponseSchema;
85 | const subtasksSchema = schema.shape.subtasks;
86 | const subtaskSchema = subtasksSchema.element;
87 |
88 | // Verify the schema has the required fields
89 | expect(subtaskSchema).toBe(SubtaskSchema);
90 | expect(SubtaskSchema.shape).toHaveProperty('id');
91 | expect(SubtaskSchema.shape).toHaveProperty('title');
92 | expect(SubtaskSchema.shape).toHaveProperty('description');
93 | expect(SubtaskSchema.shape).toHaveProperty('dependencies');
94 | expect(SubtaskSchema.shape).toHaveProperty('details');
95 | expect(SubtaskSchema.shape).toHaveProperty('status');
96 | expect(SubtaskSchema.shape).toHaveProperty('testStrategy');
97 | });
98 |
99 | test('complexity-report variant fails without task context regression test', () => {
100 | // This test ensures we don't regress to the old behavior where
101 | // complexity-report variant only used expansionPrompt without task context
102 | const params = {
103 | ...baseParams,
104 | expansionPrompt: 'Generic expansion prompt'
105 | };
106 |
107 | const { userPrompt } = promptManager.loadPrompt(
108 | 'expand-task',
109 | params,
110 | 'complexity-report'
111 | );
112 |
113 | // Count occurrences of task-specific content
114 | const titleOccurrences = (
115 | userPrompt.match(new RegExp(testTask.title, 'g')) || []
116 | ).length;
117 | const descriptionOccurrences = (
118 | userPrompt.match(new RegExp(testTask.description, 'g')) || []
119 | ).length;
120 |
121 | // Should have at least one occurrence of title and description
122 | expect(titleOccurrences).toBeGreaterThanOrEqual(1);
123 | expect(descriptionOccurrences).toBeGreaterThanOrEqual(1);
124 |
125 | // Should not be ONLY the expansion prompt
126 | expect(userPrompt.length).toBeGreaterThan(
127 | params.expansionPrompt.length + 100
128 | );
129 | });
130 | });
131 |
```
--------------------------------------------------------------------------------
/tests/unit/profiles/kiro-integration.test.js:
--------------------------------------------------------------------------------
```javascript
1 | import { jest } from '@jest/globals';
2 | import fs from 'fs';
3 | import path from 'path';
4 | import os from 'os';
5 |
6 | // Mock external modules
7 | jest.mock('child_process', () => ({
8 | execSync: jest.fn()
9 | }));
10 |
11 | // Mock console methods
12 | jest.mock('console', () => ({
13 | log: jest.fn(),
14 | info: jest.fn(),
15 | warn: jest.fn(),
16 | error: jest.fn(),
17 | clear: jest.fn()
18 | }));
19 |
20 | describe('Kiro Integration', () => {
21 | let tempDir;
22 |
23 | beforeEach(() => {
24 | jest.clearAllMocks();
25 |
26 | // Create a temporary directory for testing
27 | tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
28 |
29 | // Spy on fs methods
30 | jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
31 | jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
32 | if (filePath.toString().includes('mcp.json')) {
33 | return JSON.stringify({ mcpServers: {} }, null, 2);
34 | }
35 | return '{}';
36 | });
37 | jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
38 | jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
39 | });
40 |
41 | afterEach(() => {
42 | // Clean up the temporary directory
43 | try {
44 | fs.rmSync(tempDir, { recursive: true, force: true });
45 | } catch (err) {
46 | console.error(`Error cleaning up: ${err.message}`);
47 | }
48 | });
49 |
50 | // Test function that simulates the createProjectStructure behavior for Kiro files
51 | function mockCreateKiroStructure() {
52 | // This function simulates the actual kiro profile creation logic
53 | // It explicitly calls the mocked fs methods to ensure consistency with the test environment
54 |
55 | // Simulate directory creation calls - these will call the mocked mkdirSync
56 | fs.mkdirSync(path.join(tempDir, '.kiro'), { recursive: true });
57 | fs.mkdirSync(path.join(tempDir, '.kiro', 'steering'), { recursive: true });
58 | fs.mkdirSync(path.join(tempDir, '.kiro', 'settings'), { recursive: true });
59 |
60 | // Create MCP config file at .kiro/settings/mcp.json
61 | // This will call the mocked writeFileSync
62 | fs.writeFileSync(
63 | path.join(tempDir, '.kiro', 'settings', 'mcp.json'),
64 | JSON.stringify({ mcpServers: {} }, null, 2)
65 | );
66 |
67 | // Create kiro rule files in steering directory
68 | // All these will call the mocked writeFileSync
69 | fs.writeFileSync(
70 | path.join(tempDir, '.kiro', 'steering', 'kiro_rules.md'),
71 | '# Kiro Rules\n\nKiro-specific rules and instructions.'
72 | );
73 | fs.writeFileSync(
74 | path.join(tempDir, '.kiro', 'steering', 'dev_workflow.md'),
75 | '# Development Workflow\n\nDevelopment workflow instructions.'
76 | );
77 | fs.writeFileSync(
78 | path.join(tempDir, '.kiro', 'steering', 'self_improve.md'),
79 | '# Self Improvement\n\nSelf improvement guidelines.'
80 | );
81 | fs.writeFileSync(
82 | path.join(tempDir, '.kiro', 'steering', 'taskmaster.md'),
83 | '# Task Master\n\nTask Master integration instructions.'
84 | );
85 | }
86 |
87 | test('creates all required .kiro directories', () => {
88 | // Act
89 | mockCreateKiroStructure();
90 |
91 | // Assert
92 | expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.kiro'), {
93 | recursive: true
94 | });
95 | expect(fs.mkdirSync).toHaveBeenCalledWith(
96 | path.join(tempDir, '.kiro', 'steering'),
97 | {
98 | recursive: true
99 | }
100 | );
101 | expect(fs.mkdirSync).toHaveBeenCalledWith(
102 | path.join(tempDir, '.kiro', 'settings'),
103 | {
104 | recursive: true
105 | }
106 | );
107 | });
108 |
109 | test('creates Kiro mcp.json with mcpServers format', () => {
110 | // Act
111 | mockCreateKiroStructure();
112 |
113 | // Assert
114 | expect(fs.writeFileSync).toHaveBeenCalledWith(
115 | path.join(tempDir, '.kiro', 'settings', 'mcp.json'),
116 | JSON.stringify({ mcpServers: {} }, null, 2)
117 | );
118 | });
119 |
120 | test('creates rule files in steering directory', () => {
121 | // Act
122 | mockCreateKiroStructure();
123 |
124 | // Assert
125 | expect(fs.writeFileSync).toHaveBeenCalledWith(
126 | path.join(tempDir, '.kiro', 'steering', 'kiro_rules.md'),
127 | '# Kiro Rules\n\nKiro-specific rules and instructions.'
128 | );
129 | expect(fs.writeFileSync).toHaveBeenCalledWith(
130 | path.join(tempDir, '.kiro', 'steering', 'dev_workflow.md'),
131 | '# Development Workflow\n\nDevelopment workflow instructions.'
132 | );
133 | expect(fs.writeFileSync).toHaveBeenCalledWith(
134 | path.join(tempDir, '.kiro', 'steering', 'self_improve.md'),
135 | '# Self Improvement\n\nSelf improvement guidelines.'
136 | );
137 | expect(fs.writeFileSync).toHaveBeenCalledWith(
138 | path.join(tempDir, '.kiro', 'steering', 'taskmaster.md'),
139 | '# Task Master\n\nTask Master integration instructions.'
140 | );
141 | });
142 | });
143 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/briefs/briefs-domain.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview Briefs Domain Facade
3 | * Public API for brief-related operations
4 | */
5 |
6 | import {
7 | ERROR_CODES,
8 | TaskMasterError
9 | } from '../../common/errors/task-master-error.js';
10 | import { AuthManager } from '../auth/managers/auth-manager.js';
11 | import type { TaskRepository } from '../tasks/repositories/task-repository.interface.js';
12 | import { BriefService, type TagWithStats } from './services/brief-service.js';
13 | import { BriefUrlParser } from './utils/url-parser.js';
14 |
15 | /**
16 | * Briefs Domain - Unified API for brief operations
17 | * Handles brief switching, matching, and statistics
18 | */
19 | export class BriefsDomain {
20 | private briefService: BriefService;
21 | private authManager: AuthManager;
22 |
23 | constructor() {
24 | this.briefService = new BriefService();
25 | this.authManager = AuthManager.getInstance();
26 | }
27 |
28 | /**
29 | * Resolve a brief by name, ID, URL, or partial ID without updating context
30 | * Returns the full brief object
31 | *
32 | * Supports:
33 | * - Hamster URLs (e.g., https://app.tryhamster.com/home/hamster/briefs/abc123)
34 | * - Full UUID
35 | * - Last 8 characters of UUID
36 | * - Brief name (exact or partial match)
37 | *
38 | * @param input - Raw input: URL, UUID, last 8 chars, or brief name
39 | * @param orgId - Optional organization ID. If not provided, tries to extract from URL or uses current context.
40 | * @returns The resolved brief object
41 | */
42 | async resolveBrief(input: string, orgId?: string): Promise<any> {
43 | // Parse input using dedicated URL parser
44 | const parsed = BriefUrlParser.parse(input);
45 | const briefIdOrName = parsed.briefId || input.trim();
46 |
47 | // Resolve organization ID (priority: parameter > URL > context)
48 | let resolvedOrgId = orgId;
49 |
50 | // Try to extract org slug from URL if not provided
51 | if (!resolvedOrgId && parsed.orgSlug) {
52 | try {
53 | const orgs = await this.authManager.getOrganizations();
54 | const matchingOrg = orgs.find(
55 | (org) =>
56 | org.slug?.toLowerCase() === parsed.orgSlug?.toLowerCase() ||
57 | org.name.toLowerCase() === parsed.orgSlug?.toLowerCase()
58 | );
59 | if (matchingOrg) {
60 | resolvedOrgId = matchingOrg.id;
61 | }
62 | } catch {
63 | // If we can't fetch orgs, fall through to context
64 | }
65 | }
66 |
67 | // Fall back to context if still not resolved
68 | if (!resolvedOrgId) {
69 | resolvedOrgId = this.authManager.getContext()?.orgId;
70 | }
71 |
72 | if (!resolvedOrgId) {
73 | throw new TaskMasterError(
74 | 'No organization selected. Run "tm context org" first.',
75 | ERROR_CODES.CONFIG_ERROR
76 | );
77 | }
78 |
79 | // Fetch all briefs for the org
80 | const briefs = await this.authManager.getBriefs(resolvedOrgId);
81 |
82 | // Find matching brief using service
83 | const matchingBrief = await this.briefService.findBrief(
84 | briefs,
85 | briefIdOrName
86 | );
87 |
88 | this.briefService.validateBriefFound(matchingBrief, briefIdOrName);
89 |
90 | return matchingBrief;
91 | }
92 |
93 | /**
94 | * Switch to a different brief by name or ID
95 | * Validates context, finds matching brief, and updates auth context
96 | */
97 | async switchBrief(briefNameOrId: string): Promise<void> {
98 | // Use resolveBrief to find the brief
99 | const matchingBrief = await this.resolveBrief(briefNameOrId);
100 |
101 | // Update context with the found brief
102 | await this.authManager.updateContext({
103 | briefId: matchingBrief.id,
104 | briefName:
105 | matchingBrief.document?.title || `Brief ${matchingBrief.id.slice(-8)}`,
106 | briefStatus: matchingBrief.status,
107 | briefUpdatedAt: matchingBrief.updatedAt
108 | });
109 | }
110 |
111 | /**
112 | * Get all briefs with detailed statistics including task counts
113 | * Used for API storage to show brief statistics
114 | */
115 | async getBriefsWithStats(
116 | repository: TaskRepository,
117 | projectId: string
118 | ): Promise<{
119 | tags: TagWithStats[];
120 | currentTag: string | null;
121 | totalTags: number;
122 | }> {
123 | const context = this.authManager.getContext();
124 |
125 | if (!context?.orgId) {
126 | throw new TaskMasterError(
127 | 'No organization context available',
128 | ERROR_CODES.MISSING_CONFIGURATION,
129 | {
130 | operation: 'getBriefsWithStats',
131 | userMessage:
132 | 'No organization selected. Please authenticate first using: tm auth login'
133 | }
134 | );
135 | }
136 |
137 | // Get all briefs for the organization (through auth manager)
138 | const briefs = await this.authManager.getBriefs(context.orgId);
139 |
140 | // Use BriefService to calculate stats
141 | return this.briefService.getTagsWithStats(
142 | briefs,
143 | context.briefId,
144 | repository,
145 | projectId
146 | );
147 | }
148 | }
149 |
```
--------------------------------------------------------------------------------
/packages/claude-code-plugin/commands/tm-main.md:
--------------------------------------------------------------------------------
```markdown
1 | # Task Master Command Reference
2 |
3 | Comprehensive command structure for Task Master integration with Claude Code.
4 |
5 | ## Command Organization
6 |
7 | Commands are organized hierarchically to match Task Master's CLI structure while providing enhanced Claude Code integration.
8 |
9 | ## Project Setup & Configuration
10 |
11 | ### `/taskmaster:init`
12 | - `init-project` - Initialize new project (handles PRD files intelligently)
13 | - `init-project-quick` - Quick setup with auto-confirmation (-y flag)
14 |
15 | ### `/taskmaster:models`
16 | - `view-models` - View current AI model configuration
17 | - `setup-models` - Interactive model configuration
18 | - `set-main` - Set primary generation model
19 | - `set-research` - Set research model
20 | - `set-fallback` - Set fallback model
21 |
22 | ## Task Generation
23 |
24 | ### `/taskmaster:parse-prd`
25 | - `parse-prd` - Generate tasks from PRD document
26 | - `parse-prd-with-research` - Enhanced parsing with research mode
27 |
28 | ### `/taskmaster:generate`
29 | - `generate-tasks` - Create individual task files from tasks.json
30 |
31 | ## Task Management
32 |
33 | ### `/taskmaster:list`
34 | - `list-tasks` - Smart listing with natural language filters
35 | - `list-tasks-with-subtasks` - Include subtasks in hierarchical view
36 | - `list-tasks-by-status` - Filter by specific status
37 |
38 | ### `/taskmaster:set-status`
39 | - `to-pending` - Reset task to pending
40 | - `to-in-progress` - Start working on task
41 | - `to-done` - Mark task complete
42 | - `to-review` - Submit for review
43 | - `to-deferred` - Defer task
44 | - `to-cancelled` - Cancel task
45 |
46 | ### `/taskmaster:sync-readme`
47 | - `sync-readme` - Export tasks to README.md with formatting
48 |
49 | ### `/taskmaster:update`
50 | - `update-task` - Update tasks with natural language
51 | - `update-tasks-from-id` - Update multiple tasks from a starting point
52 | - `update-single-task` - Update specific task
53 |
54 | ### `/taskmaster:add-task`
55 | - `add-task` - Add new task with AI assistance
56 |
57 | ### `/taskmaster:remove-task`
58 | - `remove-task` - Remove task with confirmation
59 |
60 | ## Subtask Management
61 |
62 | ### `/taskmaster:add-subtask`
63 | - `add-subtask` - Add new subtask to parent
64 | - `convert-task-to-subtask` - Convert existing task to subtask
65 |
66 | ### `/taskmaster:remove-subtask`
67 | - `remove-subtask` - Remove subtask (with optional conversion)
68 |
69 | ### `/taskmaster:clear-subtasks`
70 | - `clear-subtasks` - Clear subtasks from specific task
71 | - `clear-all-subtasks` - Clear all subtasks globally
72 |
73 | ## Task Analysis & Breakdown
74 |
75 | ### `/taskmaster:analyze-complexity`
76 | - `analyze-complexity` - Analyze and generate expansion recommendations
77 |
78 | ### `/taskmaster:complexity-report`
79 | - `complexity-report` - Display complexity analysis report
80 |
81 | ### `/taskmaster:expand`
82 | - `expand-task` - Break down specific task
83 | - `expand-all-tasks` - Expand all eligible tasks
84 | - `with-research` - Enhanced expansion
85 |
86 | ## Task Navigation
87 |
88 | ### `/taskmaster:next`
89 | - `next-task` - Intelligent next task recommendation
90 |
91 | ### `/taskmaster:show`
92 | - `show-task` - Display detailed task information
93 |
94 | ### `/taskmaster:status`
95 | - `project-status` - Comprehensive project dashboard
96 |
97 | ## Dependency Management
98 |
99 | ### `/taskmaster:add-dependency`
100 | - `add-dependency` - Add task dependency
101 |
102 | ### `/taskmaster:remove-dependency`
103 | - `remove-dependency` - Remove task dependency
104 |
105 | ### `/taskmaster:validate-dependencies`
106 | - `validate-dependencies` - Check for dependency issues
107 |
108 | ### `/taskmaster:fix-dependencies`
109 | - `fix-dependencies` - Automatically fix dependency problems
110 |
111 | ## Workflows & Automation
112 |
113 | ### `/taskmaster:workflows`
114 | - `smart-workflow` - Context-aware intelligent workflow execution
115 | - `command-pipeline` - Chain multiple commands together
116 | - `auto-implement-tasks` - Advanced auto-implementation with code generation
117 |
118 | ## Utilities
119 |
120 | ### `/taskmaster:utils`
121 | - `analyze-project` - Deep project analysis and insights
122 |
123 | ### `/taskmaster:setup`
124 | - `install-taskmaster` - Comprehensive installation guide
125 | - `quick-install-taskmaster` - One-line global installation
126 |
127 | ## Usage Patterns
128 |
129 | ### Natural Language
130 | Most commands accept natural language arguments:
131 | ```
132 | /taskmaster:add-task create user authentication system
133 | /taskmaster:update mark all API tasks as high priority
134 | /taskmaster:list show blocked tasks
135 | ```
136 |
137 | ### ID-Based Commands
138 | Commands requiring IDs intelligently parse from $ARGUMENTS:
139 | ```
140 | /taskmaster:show 45
141 | /taskmaster:expand 23
142 | /taskmaster:set-status/to-done 67
143 | ```
144 |
145 | ### Smart Defaults
146 | Commands provide intelligent defaults and suggestions based on context.
```
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - next
8 | pull_request:
9 | workflow_dispatch:
10 |
11 | concurrency:
12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
13 | cancel-in-progress: true
14 |
15 | permissions:
16 | contents: read
17 |
18 | env:
19 | DO_NOT_TRACK: 1
20 | NODE_ENV: development
21 |
22 | jobs:
23 | # Fast checks that can run in parallel
24 | format-check:
25 | name: Format Check
26 | runs-on: ubuntu-latest
27 | steps:
28 | - uses: actions/checkout@v4
29 | with:
30 | fetch-depth: 2
31 |
32 | - uses: actions/setup-node@v4
33 | with:
34 | node-version: 20
35 | cache: "npm"
36 |
37 | - name: Install dependencies
38 | run: npm install --frozen-lockfile --prefer-offline
39 | timeout-minutes: 5
40 |
41 | - name: Format Check
42 | run: npm run format-check
43 | env:
44 | FORCE_COLOR: 1
45 |
46 | changeset-validation:
47 | name: Validate Changesets
48 | runs-on: ubuntu-latest
49 | steps:
50 | - uses: actions/checkout@v4
51 | with:
52 | fetch-depth: 0
53 |
54 | - uses: dorny/paths-filter@v3
55 | id: changes
56 | with:
57 | filters: |
58 | changesets:
59 | - '.changeset/**'
60 | - '.github/scripts/validate-changesets.mjs'
61 |
62 | - uses: actions/setup-node@v4
63 | if: steps.changes.outputs.changesets == 'true'
64 | with:
65 | node-version: 20
66 | cache: "npm"
67 |
68 | - name: Validate changeset package references
69 | if: steps.changes.outputs.changesets == 'true'
70 | run: |
71 | # Validate that changesets only reference public packages
72 | # This catches issues like using @tm/cli instead of task-master-ai
73 | node .github/scripts/validate-changesets.mjs
74 | env:
75 | FORCE_COLOR: 1
76 |
77 | typecheck:
78 | name: Typecheck
79 | timeout-minutes: 10
80 | runs-on: ubuntu-latest
81 | steps:
82 | - uses: actions/checkout@v4
83 | with:
84 | fetch-depth: 2
85 |
86 | - uses: actions/setup-node@v4
87 | with:
88 | node-version: 20
89 | cache: "npm"
90 |
91 | - name: Install dependencies
92 | run: npm install --frozen-lockfile --prefer-offline
93 | timeout-minutes: 5
94 |
95 | - name: Typecheck
96 | run: npm run turbo:typecheck
97 | env:
98 | FORCE_COLOR: 1
99 |
100 | # Build job to ensure everything compiles
101 | build:
102 | name: Build
103 | runs-on: ubuntu-latest
104 | steps:
105 | - uses: actions/checkout@v4
106 | with:
107 | fetch-depth: 2
108 |
109 | - uses: actions/setup-node@v4
110 | with:
111 | node-version: 20
112 | cache: "npm"
113 |
114 | - name: Install dependencies
115 | run: npm install --frozen-lockfile --prefer-offline
116 | timeout-minutes: 5
117 |
118 | - name: Build
119 | run: npm run turbo:build
120 | env:
121 | NODE_ENV: production
122 | FORCE_COLOR: 1
123 | TM_PUBLIC_BASE_DOMAIN: ${{ secrets.TM_PUBLIC_BASE_DOMAIN }}
124 | TM_PUBLIC_SUPABASE_URL: ${{ secrets.TM_PUBLIC_SUPABASE_URL }}
125 | TM_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.TM_PUBLIC_SUPABASE_ANON_KEY }}
126 |
127 | - name: Upload build artifacts
128 | uses: actions/upload-artifact@v4
129 | with:
130 | name: build-artifacts
131 | path: dist/
132 | retention-days: 1
133 |
134 | test:
135 | name: Test
136 | timeout-minutes: 15
137 | runs-on: ubuntu-latest
138 | needs: [format-check, typecheck, build, changeset-validation]
139 | if: always() && !cancelled() && !contains(needs.*.result, 'failure')
140 | steps:
141 | - uses: actions/checkout@v4
142 | with:
143 | fetch-depth: 2
144 |
145 | - uses: actions/setup-node@v4
146 | with:
147 | node-version: 20
148 | cache: "npm"
149 |
150 | - name: Install dependencies
151 | run: npm install --frozen-lockfile --prefer-offline
152 | timeout-minutes: 5
153 |
154 | - name: Download build artifacts
155 | uses: actions/download-artifact@v4
156 | with:
157 | name: build-artifacts
158 | path: dist/
159 |
160 | - name: Run Tests
161 | run: |
162 | npm run test:coverage -- --coverageThreshold '{"global":{"branches":0,"functions":0,"lines":0,"statements":0}}' --detectOpenHandles --forceExit
163 | env:
164 | NODE_ENV: test
165 | CI: true
166 | FORCE_COLOR: 1
167 |
168 | - name: Upload Test Results
169 | if: always()
170 | uses: actions/upload-artifact@v4
171 | with:
172 | name: test-results
173 | path: |
174 | test-results
175 | coverage
176 | junit.xml
177 | retention-days: 30
178 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/create-tag-from-branch.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * create-tag-from-branch.js
3 | * Direct function implementation for creating tags from git branches
4 | */
5 |
6 | import { createTagFromBranch } from '../../../../scripts/modules/task-manager/tag-management.js';
7 | import {
8 | getCurrentBranch,
9 | isGitRepository
10 | } from '../../../../scripts/modules/utils/git-utils.js';
11 | import {
12 | enableSilentMode,
13 | disableSilentMode
14 | } from '../../../../scripts/modules/utils.js';
15 | import { createLogWrapper } from '../../tools/utils.js';
16 |
17 | /**
18 | * Direct function wrapper for creating tags from git branches with error handling.
19 | *
20 | * @param {Object} args - Command arguments
21 | * @param {string} args.tasksJsonPath - Path to the tasks.json file (resolved by tool)
22 | * @param {string} [args.branchName] - Git branch name (optional, uses current branch if not provided)
23 | * @param {boolean} [args.copyFromCurrent] - Copy tasks from current tag
24 | * @param {string} [args.copyFromTag] - Copy tasks from specific tag
25 | * @param {string} [args.description] - Custom description for the tag
26 | * @param {boolean} [args.autoSwitch] - Automatically switch to the new tag
27 | * @param {string} [args.projectRoot] - Project root path
28 | * @param {Object} log - Logger object
29 | * @param {Object} context - Additional context (session)
30 | * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
31 | */
32 | export async function createTagFromBranchDirect(args, log, context = {}) {
33 | // Destructure expected args
34 | const {
35 | tasksJsonPath,
36 | branchName,
37 | copyFromCurrent,
38 | copyFromTag,
39 | description,
40 | autoSwitch,
41 | projectRoot
42 | } = args;
43 | const { session } = context;
44 |
45 | // Enable silent mode to prevent console logs from interfering with JSON response
46 | enableSilentMode();
47 |
48 | // Create logger wrapper using the utility
49 | const mcpLog = createLogWrapper(log);
50 |
51 | try {
52 | // Check if tasksJsonPath was provided
53 | if (!tasksJsonPath) {
54 | log.error('createTagFromBranchDirect called without tasksJsonPath');
55 | disableSilentMode();
56 | return {
57 | success: false,
58 | error: {
59 | code: 'MISSING_ARGUMENT',
60 | message: 'tasksJsonPath is required'
61 | }
62 | };
63 | }
64 |
65 | // Check if projectRoot was provided
66 | if (!projectRoot) {
67 | log.error('createTagFromBranchDirect called without projectRoot');
68 | disableSilentMode();
69 | return {
70 | success: false,
71 | error: {
72 | code: 'MISSING_ARGUMENT',
73 | message: 'projectRoot is required'
74 | }
75 | };
76 | }
77 |
78 | // Check if we're in a git repository
79 | if (!(await isGitRepository(projectRoot))) {
80 | log.error('Not in a git repository');
81 | disableSilentMode();
82 | return {
83 | success: false,
84 | error: {
85 | code: 'NOT_GIT_REPOSITORY',
86 | message: 'Not in a git repository. Cannot create tag from branch.'
87 | }
88 | };
89 | }
90 |
91 | // Determine branch name
92 | let targetBranch = branchName;
93 | if (!targetBranch) {
94 | targetBranch = await getCurrentBranch(projectRoot);
95 | if (!targetBranch) {
96 | log.error('Could not determine current git branch');
97 | disableSilentMode();
98 | return {
99 | success: false,
100 | error: {
101 | code: 'NO_CURRENT_BRANCH',
102 | message: 'Could not determine current git branch'
103 | }
104 | };
105 | }
106 | }
107 |
108 | log.info(`Creating tag from git branch: ${targetBranch}`);
109 |
110 | // Prepare options
111 | const options = {
112 | copyFromCurrent: copyFromCurrent || false,
113 | copyFromTag,
114 | description:
115 | description || `Tag created from git branch "${targetBranch}"`,
116 | autoSwitch: autoSwitch || false
117 | };
118 |
119 | // Call the createTagFromBranch function
120 | const result = await createTagFromBranch(
121 | tasksJsonPath,
122 | targetBranch,
123 | options,
124 | {
125 | session,
126 | mcpLog,
127 | projectRoot
128 | },
129 | 'json' // outputFormat - use 'json' to suppress CLI UI
130 | );
131 |
132 | // Restore normal logging
133 | disableSilentMode();
134 |
135 | return {
136 | success: true,
137 | data: {
138 | branchName: result.branchName,
139 | tagName: result.tagName,
140 | created: result.created,
141 | mappingUpdated: result.mappingUpdated,
142 | autoSwitched: result.autoSwitched,
143 | message: `Successfully created tag "${result.tagName}" from branch "${result.branchName}"`
144 | }
145 | };
146 | } catch (error) {
147 | // Make sure to restore normal logging even if there's an error
148 | disableSilentMode();
149 |
150 | log.error(`Error in createTagFromBranchDirect: ${error.message}`);
151 | return {
152 | success: false,
153 | error: {
154 | code: error.code || 'CREATE_TAG_FROM_BRANCH_ERROR',
155 | message: error.message
156 | }
157 | };
158 | }
159 | }
160 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/add-subtask.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Direct function wrapper for addSubtask
3 | */
4 |
5 | import { addSubtask } from '../../../../scripts/modules/task-manager.js';
6 | import {
7 | enableSilentMode,
8 | disableSilentMode
9 | } from '../../../../scripts/modules/utils.js';
10 |
11 | /**
12 | * Add a subtask to an existing task
13 | * @param {Object} args - Function arguments
14 | * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
15 | * @param {string} args.id - Parent task ID
16 | * @param {string} [args.taskId] - Existing task ID to convert to subtask (optional)
17 | * @param {string} [args.title] - Title for new subtask (when creating a new subtask)
18 | * @param {string} [args.description] - Description for new subtask
19 | * @param {string} [args.details] - Implementation details for new subtask
20 | * @param {string} [args.status] - Status for new subtask (default: 'pending')
21 | * @param {string} [args.dependencies] - Comma-separated list of dependency IDs
22 | * @param {boolean} [args.skipGenerate] - Skip regenerating task files
23 | * @param {string} [args.projectRoot] - Project root directory
24 | * @param {string} [args.tag] - Tag for the task
25 | * @param {Object} log - Logger object
26 | * @returns {Promise<{success: boolean, data?: Object, error?: string}>}
27 | */
28 | export async function addSubtaskDirect(args, log) {
29 | // Destructure expected args
30 | const {
31 | tasksJsonPath,
32 | id,
33 | taskId,
34 | title,
35 | description,
36 | details,
37 | status,
38 | dependencies: dependenciesStr,
39 | skipGenerate,
40 | projectRoot,
41 | tag
42 | } = args;
43 | try {
44 | log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
45 |
46 | // Check if tasksJsonPath was provided
47 | if (!tasksJsonPath) {
48 | log.error('addSubtaskDirect called without tasksJsonPath');
49 | return {
50 | success: false,
51 | error: {
52 | code: 'MISSING_ARGUMENT',
53 | message: 'tasksJsonPath is required'
54 | }
55 | };
56 | }
57 |
58 | if (!id) {
59 | return {
60 | success: false,
61 | error: {
62 | code: 'INPUT_VALIDATION_ERROR',
63 | message: 'Parent task ID is required'
64 | }
65 | };
66 | }
67 |
68 | // Either taskId or title must be provided
69 | if (!taskId && !title) {
70 | return {
71 | success: false,
72 | error: {
73 | code: 'INPUT_VALIDATION_ERROR',
74 | message: 'Either taskId or title must be provided'
75 | }
76 | };
77 | }
78 |
79 | // Use provided path
80 | const tasksPath = tasksJsonPath;
81 |
82 | // Parse dependencies if provided
83 | let dependencies = [];
84 | if (dependenciesStr) {
85 | dependencies = dependenciesStr.split(',').map((depId) => {
86 | // Handle both regular IDs and dot notation
87 | return depId.includes('.') ? depId.trim() : parseInt(depId.trim(), 10);
88 | });
89 | }
90 |
91 | // Convert existingTaskId to a number if provided
92 | const existingTaskId = taskId ? parseInt(taskId, 10) : null;
93 |
94 | // Convert parent ID to a number
95 | const parentId = parseInt(id, 10);
96 |
97 | // Determine if we should generate files
98 | const generateFiles = !skipGenerate;
99 |
100 | // Enable silent mode to prevent console logs from interfering with JSON response
101 | enableSilentMode();
102 |
103 | const context = { projectRoot, tag };
104 |
105 | // Case 1: Convert existing task to subtask
106 | if (existingTaskId) {
107 | log.info(`Converting task ${existingTaskId} to a subtask of ${parentId}`);
108 | const result = await addSubtask(
109 | tasksPath,
110 | parentId,
111 | existingTaskId,
112 | null,
113 | generateFiles,
114 | context
115 | );
116 |
117 | // Restore normal logging
118 | disableSilentMode();
119 |
120 | return {
121 | success: true,
122 | data: {
123 | message: `Task ${existingTaskId} successfully converted to a subtask of task ${parentId}`,
124 | subtask: result
125 | }
126 | };
127 | }
128 | // Case 2: Create new subtask
129 | else {
130 | log.info(`Creating new subtask for parent task ${parentId}`);
131 |
132 | const newSubtaskData = {
133 | title: title,
134 | description: description || '',
135 | details: details || '',
136 | status: status || 'pending',
137 | dependencies: dependencies
138 | };
139 |
140 | const result = await addSubtask(
141 | tasksPath,
142 | parentId,
143 | null,
144 | newSubtaskData,
145 | generateFiles,
146 | context
147 | );
148 |
149 | // Restore normal logging
150 | disableSilentMode();
151 |
152 | return {
153 | success: true,
154 | data: {
155 | message: `New subtask ${parentId}.${result.id} successfully created`,
156 | subtask: result
157 | }
158 | };
159 | }
160 | } catch (error) {
161 | // Make sure to restore normal logging even if there's an error
162 | disableSilentMode();
163 |
164 | log.error(`Error in addSubtaskDirect: ${error.message}`);
165 | return {
166 | success: false,
167 | error: {
168 | code: 'CORE_FUNCTION_ERROR',
169 | message: error.message
170 | }
171 | };
172 | }
173 | }
174 |
```