This is page 38 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
--------------------------------------------------------------------------------
/tests/integration/cli/complex-cross-tag-scenarios.test.js:
--------------------------------------------------------------------------------
```javascript
1 | import { jest } from '@jest/globals';
2 | import { execSync } from 'child_process';
3 | import fs from 'fs';
4 | import path from 'path';
5 | import os from 'os';
6 | import { fileURLToPath } from 'url';
7 |
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 |
11 | describe('Complex Cross-Tag Scenarios', () => {
12 | let testDir;
13 | let tasksPath;
14 |
15 | // Define binPath once for the entire test suite
16 | const binPath = path.join(
17 | __dirname,
18 | '..',
19 | '..',
20 | '..',
21 | 'dist',
22 | 'task-master.js'
23 | );
24 |
25 | beforeEach(() => {
26 | // Create test directory in OS temp directory to isolate from project
27 | testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tm-test-'));
28 | process.chdir(testDir);
29 | // Keep integration timings deterministic
30 | process.env.TASKMASTER_SKIP_AUTO_UPDATE = '1';
31 |
32 | // Initialize task-master
33 | execSync(`node ${binPath} init --yes`, {
34 | stdio: 'pipe'
35 | });
36 |
37 | // Create test tasks with complex dependencies in the correct tagged format
38 | const complexTasks = {
39 | master: {
40 | tasks: [
41 | {
42 | id: 1,
43 | title: 'Setup Project',
44 | description: 'Initialize the project structure',
45 | status: 'done',
46 | priority: 'high',
47 | dependencies: [],
48 | details: 'Create basic project structure',
49 | testStrategy: 'Verify project structure exists',
50 | subtasks: []
51 | },
52 | {
53 | id: 2,
54 | title: 'Database Schema',
55 | description: 'Design and implement database schema',
56 | status: 'pending',
57 | priority: 'high',
58 | dependencies: [1],
59 | details: 'Create database tables and relationships',
60 | testStrategy: 'Run database migrations',
61 | subtasks: [
62 | {
63 | id: '2.1',
64 | title: 'User Table',
65 | description: 'Create user table',
66 | status: 'pending',
67 | priority: 'medium',
68 | dependencies: [],
69 | details: 'Design user table schema',
70 | testStrategy: 'Test user creation'
71 | },
72 | {
73 | id: '2.2',
74 | title: 'Product Table',
75 | description: 'Create product table',
76 | status: 'pending',
77 | priority: 'medium',
78 | dependencies: ['2.1'],
79 | details: 'Design product table schema',
80 | testStrategy: 'Test product creation'
81 | }
82 | ]
83 | },
84 | {
85 | id: 3,
86 | title: 'API Development',
87 | description: 'Develop REST API endpoints',
88 | status: 'pending',
89 | priority: 'high',
90 | dependencies: [2],
91 | details: 'Create API endpoints for CRUD operations',
92 | testStrategy: 'Test API endpoints',
93 | subtasks: []
94 | },
95 | {
96 | id: 4,
97 | title: 'Frontend Development',
98 | description: 'Develop user interface',
99 | status: 'pending',
100 | priority: 'medium',
101 | dependencies: [3],
102 | details: 'Create React components and pages',
103 | testStrategy: 'Test UI components',
104 | subtasks: []
105 | },
106 | {
107 | id: 5,
108 | title: 'Testing',
109 | description: 'Comprehensive testing',
110 | status: 'pending',
111 | priority: 'medium',
112 | dependencies: [4],
113 | details: 'Write unit and integration tests',
114 | testStrategy: 'Run test suite',
115 | subtasks: []
116 | }
117 | ],
118 | metadata: {
119 | created: new Date().toISOString(),
120 | description: 'Test tasks for complex cross-tag scenarios'
121 | }
122 | }
123 | };
124 |
125 | // Write tasks to file
126 | tasksPath = path.join(testDir, '.taskmaster', 'tasks', 'tasks.json');
127 | fs.writeFileSync(tasksPath, JSON.stringify(complexTasks, null, 2));
128 | });
129 |
130 | afterEach(() => {
131 | // Change back to project root before cleanup
132 | try {
133 | process.chdir(global.projectRoot || path.resolve(__dirname, '../../..'));
134 | } catch (error) {
135 | // If we can't change directory, try a known safe directory
136 | process.chdir(require('os').homedir());
137 | }
138 |
139 | // Cleanup test directory
140 | if (testDir && fs.existsSync(testDir)) {
141 | fs.rmSync(testDir, { recursive: true, force: true });
142 | }
143 | delete process.env.TASKMASTER_SKIP_AUTO_UPDATE;
144 | });
145 |
146 | describe('Circular Dependency Detection', () => {
147 | it('should detect and prevent circular dependencies', () => {
148 | // Create a circular dependency scenario
149 | const circularTasks = {
150 | backlog: {
151 | tasks: [
152 | {
153 | id: 1,
154 | title: 'Task 1',
155 | status: 'pending',
156 | dependencies: [2],
157 | subtasks: []
158 | },
159 | {
160 | id: 2,
161 | title: 'Task 2',
162 | status: 'pending',
163 | dependencies: [3],
164 | subtasks: []
165 | },
166 | {
167 | id: 3,
168 | title: 'Task 3',
169 | status: 'pending',
170 | dependencies: [1],
171 | subtasks: []
172 | }
173 | ],
174 | metadata: {
175 | created: new Date().toISOString(),
176 | description: 'Backlog tasks with circular dependencies'
177 | }
178 | },
179 | 'in-progress': {
180 | tasks: [],
181 | metadata: {
182 | created: new Date().toISOString(),
183 | description: 'In-progress tasks'
184 | }
185 | }
186 | };
187 |
188 | fs.writeFileSync(tasksPath, JSON.stringify(circularTasks, null, 2));
189 |
190 | // Try to move task 1 - should fail due to circular dependency
191 | expect(() => {
192 | execSync(
193 | `node ${binPath} move --from=1 --from-tag=backlog --to-tag=in-progress`,
194 | { stdio: 'pipe' }
195 | );
196 | }).toThrow();
197 |
198 | // Check that the move was not performed
199 | const tasksAfter = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
200 | expect(tasksAfter.backlog.tasks.find((t) => t.id === 1)).toBeDefined();
201 | expect(
202 | tasksAfter['in-progress'].tasks.find((t) => t.id === 1)
203 | ).toBeUndefined();
204 | });
205 | });
206 |
207 | describe('Complex Dependency Chains', () => {
208 | it('should handle deep dependency chains correctly', () => {
209 | // Create a deep dependency chain
210 | const deepChainTasks = {
211 | master: {
212 | tasks: [
213 | {
214 | id: 1,
215 | title: 'Task 1',
216 | status: 'pending',
217 | dependencies: [2],
218 | subtasks: []
219 | },
220 | {
221 | id: 2,
222 | title: 'Task 2',
223 | status: 'pending',
224 | dependencies: [3],
225 | subtasks: []
226 | },
227 | {
228 | id: 3,
229 | title: 'Task 3',
230 | status: 'pending',
231 | dependencies: [4],
232 | subtasks: []
233 | },
234 | {
235 | id: 4,
236 | title: 'Task 4',
237 | status: 'pending',
238 | dependencies: [5],
239 | subtasks: []
240 | },
241 | {
242 | id: 5,
243 | title: 'Task 5',
244 | status: 'pending',
245 | dependencies: [],
246 | subtasks: []
247 | }
248 | ],
249 | metadata: {
250 | created: new Date().toISOString(),
251 | description: 'Deep dependency chain tasks'
252 | }
253 | },
254 | 'in-progress': {
255 | tasks: [],
256 | metadata: {
257 | created: new Date().toISOString(),
258 | description: 'In-progress tasks'
259 | }
260 | }
261 | };
262 |
263 | fs.writeFileSync(tasksPath, JSON.stringify(deepChainTasks, null, 2));
264 |
265 | // Move task 1 with dependencies - should move entire chain
266 | execSync(
267 | `node ${binPath} move --from=1 --from-tag=master --to-tag=in-progress --with-dependencies`,
268 | { stdio: 'pipe' }
269 | );
270 |
271 | // Verify all tasks in the chain were moved
272 | const tasksAfter = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
273 | expect(tasksAfter.master.tasks.find((t) => t.id === 1)).toBeUndefined();
274 | expect(tasksAfter.master.tasks.find((t) => t.id === 2)).toBeUndefined();
275 | expect(tasksAfter.master.tasks.find((t) => t.id === 3)).toBeUndefined();
276 | expect(tasksAfter.master.tasks.find((t) => t.id === 4)).toBeUndefined();
277 | expect(tasksAfter.master.tasks.find((t) => t.id === 5)).toBeUndefined();
278 |
279 | expect(
280 | tasksAfter['in-progress'].tasks.find((t) => t.id === 1)
281 | ).toBeDefined();
282 | expect(
283 | tasksAfter['in-progress'].tasks.find((t) => t.id === 2)
284 | ).toBeDefined();
285 | expect(
286 | tasksAfter['in-progress'].tasks.find((t) => t.id === 3)
287 | ).toBeDefined();
288 | expect(
289 | tasksAfter['in-progress'].tasks.find((t) => t.id === 4)
290 | ).toBeDefined();
291 | expect(
292 | tasksAfter['in-progress'].tasks.find((t) => t.id === 5)
293 | ).toBeDefined();
294 | });
295 | });
296 |
297 | describe('Subtask Movement Restrictions', () => {
298 | it('should prevent direct subtask movement between tags', () => {
299 | // Try to move a subtask directly
300 | expect(() => {
301 | execSync(
302 | `node ${binPath} move --from=2.1 --from-tag=master --to-tag=in-progress`,
303 | { stdio: 'pipe' }
304 | );
305 | }).toThrow();
306 |
307 | // Verify subtask was not moved
308 | const tasksAfter = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
309 | const task2 = tasksAfter.master.tasks.find((t) => t.id === 2);
310 | expect(task2).toBeDefined();
311 | expect(task2.subtasks.find((s) => s.id === '2.1')).toBeDefined();
312 | });
313 |
314 | it('should allow moving parent task with all subtasks', () => {
315 | // Move parent task with dependencies (includes subtasks)
316 | execSync(
317 | `node ${binPath} move --from=2 --from-tag=master --to-tag=in-progress --with-dependencies`,
318 | { stdio: 'pipe' }
319 | );
320 |
321 | // Verify parent and subtasks were moved
322 | const tasksAfter = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
323 | expect(tasksAfter.master.tasks.find((t) => t.id === 2)).toBeUndefined();
324 | const movedTask2 = tasksAfter['in-progress'].tasks.find(
325 | (t) => t.id === 2
326 | );
327 | expect(movedTask2).toBeDefined();
328 | expect(movedTask2.subtasks).toHaveLength(2);
329 | });
330 | });
331 |
332 | describe('Large Task Set Performance', () => {
333 | it('should handle large task sets efficiently', () => {
334 | // Create a large task set (50 tasks)
335 | const largeTaskSet = {
336 | master: {
337 | tasks: [],
338 | metadata: {
339 | created: new Date().toISOString(),
340 | description: 'Large task set for performance testing'
341 | }
342 | },
343 | 'in-progress': {
344 | tasks: [],
345 | metadata: {
346 | created: new Date().toISOString(),
347 | description: 'In-progress tasks'
348 | }
349 | }
350 | };
351 |
352 | // Add 25 tasks to master with dependencies
353 | for (let i = 1; i <= 25; i++) {
354 | largeTaskSet.master.tasks.push({
355 | id: i,
356 | title: `Task ${i}`,
357 | status: 'pending',
358 | dependencies: i > 1 ? [i - 1] : [],
359 | subtasks: []
360 | });
361 | }
362 |
363 | // Add 25 tasks to in-progress (ensure no ID conflict with master)
364 | for (let i = 26; i <= 50; i++) {
365 | largeTaskSet['in-progress'].tasks.push({
366 | id: i,
367 | title: `Task ${i}`,
368 | status: 'in-progress',
369 | dependencies: [],
370 | subtasks: []
371 | });
372 | }
373 |
374 | fs.writeFileSync(tasksPath, JSON.stringify(largeTaskSet, null, 2));
375 | // Execute move; correctness is validated below (no timing assertion)
376 | execSync(
377 | `node ${binPath} move --from=25 --from-tag=master --to-tag=in-progress --with-dependencies`,
378 | { stdio: 'pipe' }
379 | );
380 |
381 | // Verify the move was successful
382 | const tasksAfter = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
383 |
384 | // Verify all tasks in the dependency chain were moved
385 | for (let i = 1; i <= 25; i++) {
386 | expect(tasksAfter.master.tasks.find((t) => t.id === i)).toBeUndefined();
387 | expect(
388 | tasksAfter['in-progress'].tasks.find((t) => t.id === i)
389 | ).toBeDefined();
390 | }
391 |
392 | // Verify in-progress still has its original tasks (26-50)
393 | for (let i = 26; i <= 50; i++) {
394 | expect(
395 | tasksAfter['in-progress'].tasks.find((t) => t.id === i)
396 | ).toBeDefined();
397 | }
398 |
399 | // Final count check
400 | expect(tasksAfter['in-progress'].tasks).toHaveLength(50); // 25 moved + 25 original
401 | });
402 | });
403 |
404 | describe('Error Recovery and Edge Cases', () => {
405 | it('should handle invalid task IDs gracefully', () => {
406 | expect(() => {
407 | execSync(
408 | `node ${binPath} move --from=999 --from-tag=master --to-tag=in-progress`,
409 | { stdio: 'pipe' }
410 | );
411 | }).toThrow();
412 | });
413 |
414 | it('should handle invalid tag names gracefully', () => {
415 | expect(() => {
416 | execSync(
417 | `node ${binPath} move --from=1 --from-tag=invalid-tag --to-tag=in-progress`,
418 | { stdio: 'pipe' }
419 | );
420 | }).toThrow();
421 | });
422 |
423 | it('should handle same source and target tags', () => {
424 | expect(() => {
425 | execSync(
426 | `node ${binPath} move --from=1 --from-tag=master --to-tag=master`,
427 | { stdio: 'pipe' }
428 | );
429 | }).toThrow();
430 | });
431 |
432 | it('should create target tag if it does not exist', () => {
433 | execSync(
434 | `node ${binPath} move --from=1 --from-tag=master --to-tag=new-tag`,
435 | { stdio: 'pipe' }
436 | );
437 |
438 | const tasksAfter = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
439 | expect(tasksAfter['new-tag']).toBeDefined();
440 | expect(tasksAfter['new-tag'].tasks.find((t) => t.id === 1)).toBeDefined();
441 | });
442 | });
443 |
444 | describe('Multiple Task Movement', () => {
445 | it('should move multiple tasks simultaneously', () => {
446 | // Create tasks for multiple movement test
447 | const multiTaskSet = {
448 | master: {
449 | tasks: [
450 | {
451 | id: 1,
452 | title: 'Task 1',
453 | status: 'pending',
454 | dependencies: [],
455 | subtasks: []
456 | },
457 | {
458 | id: 2,
459 | title: 'Task 2',
460 | status: 'pending',
461 | dependencies: [],
462 | subtasks: []
463 | },
464 | {
465 | id: 3,
466 | title: 'Task 3',
467 | status: 'pending',
468 | dependencies: [],
469 | subtasks: []
470 | }
471 | ],
472 | metadata: {
473 | created: new Date().toISOString(),
474 | description: 'Tasks for multiple movement test'
475 | }
476 | },
477 | 'in-progress': {
478 | tasks: [],
479 | metadata: {
480 | created: new Date().toISOString(),
481 | description: 'In-progress tasks'
482 | }
483 | }
484 | };
485 |
486 | fs.writeFileSync(tasksPath, JSON.stringify(multiTaskSet, null, 2));
487 |
488 | // Move multiple tasks
489 | execSync(
490 | `node ${binPath} move --from=1,2,3 --from-tag=master --to-tag=in-progress`,
491 | { stdio: 'pipe' }
492 | );
493 |
494 | // Verify all tasks were moved
495 | const tasksAfter = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
496 | expect(tasksAfter.master.tasks.find((t) => t.id === 1)).toBeUndefined();
497 | expect(tasksAfter.master.tasks.find((t) => t.id === 2)).toBeUndefined();
498 | expect(tasksAfter.master.tasks.find((t) => t.id === 3)).toBeUndefined();
499 |
500 | expect(
501 | tasksAfter['in-progress'].tasks.find((t) => t.id === 1)
502 | ).toBeDefined();
503 | expect(
504 | tasksAfter['in-progress'].tasks.find((t) => t.id === 2)
505 | ).toBeDefined();
506 | expect(
507 | tasksAfter['in-progress'].tasks.find((t) => t.id === 3)
508 | ).toBeDefined();
509 | });
510 | });
511 | });
512 |
```
--------------------------------------------------------------------------------
/docs/providers/codex-cli.md:
--------------------------------------------------------------------------------
```markdown
1 | # Codex CLI Provider
2 |
3 | The `codex-cli` provider integrates Task Master with OpenAI's Codex CLI via the community AI SDK provider [`ai-sdk-provider-codex-cli`](https://github.com/ben-vargas/ai-sdk-provider-codex-cli). It uses your ChatGPT subscription (OAuth) via `codex login`, with optional `OPENAI_CODEX_API_KEY` support.
4 |
5 | ## Why Use Codex CLI?
6 |
7 | The primary benefits of using the `codex-cli` provider include:
8 |
9 | - **Use Latest OpenAI Models**: Access to cutting-edge models like GPT-5 and GPT-5-Codex via ChatGPT subscription
10 | - **OAuth Authentication**: No API key management needed - authenticate once with `codex login`
11 | - **Built-in Tool Execution**: Native support for command execution, file changes, MCP tools, and web search
12 | - **Native JSON Schema Support**: Structured output generation without post-processing
13 | - **Approval/Sandbox Modes**: Fine-grained control over command execution and filesystem access for safety
14 |
15 | ## Quickstart
16 |
17 | Get up and running with Codex CLI in 3 steps:
18 |
19 | ```bash
20 | # 1. Install Codex CLI globally
21 | npm install -g @openai/codex
22 |
23 | # 2. Authenticate with your ChatGPT account
24 | codex login
25 |
26 | # 3. Configure Task Master to use Codex CLI
27 | task-master models --set-main gpt-5-codex --codex-cli
28 | ```
29 |
30 | ## Requirements
31 |
32 | - **Node.js**: >= 18.0.0
33 | - **Codex CLI**: >= 0.42.0 (>= 0.44.0 recommended)
34 | - **ChatGPT Subscription**: Required for OAuth access (Plus, Pro, Business, Edu, or Enterprise)
35 | - **Task Master**: >= 0.27.3 (version with Codex CLI support)
36 |
37 | ### Checking Your Versions
38 |
39 | ```bash
40 | # Check Node.js version
41 | node --version
42 |
43 | # Check Codex CLI version
44 | codex --version
45 |
46 | # Check Task Master version
47 | task-master --version
48 | ```
49 |
50 | ## Installation
51 |
52 | ### Install Codex CLI
53 |
54 | ```bash
55 | # Install globally via npm
56 | npm install -g @openai/codex
57 |
58 | # Verify installation
59 | codex --version
60 | ```
61 |
62 | Expected output: `v0.44.0` or higher
63 |
64 | ### Install Task Master (if not already installed)
65 |
66 | ```bash
67 | # Install globally
68 | npm install -g task-master-ai
69 |
70 | # Or install in your project
71 | npm install --save-dev task-master-ai
72 | ```
73 |
74 | ## Authentication
75 |
76 | ### OAuth Authentication (Primary Method - Recommended)
77 |
78 | The Codex CLI provider is designed to use OAuth authentication with your ChatGPT subscription:
79 |
80 | ```bash
81 | # Launch Codex CLI and authenticate
82 | codex login
83 | ```
84 |
85 | This will:
86 | 1. Open a browser window for OAuth authentication
87 | 2. Prompt you to log in with your ChatGPT account
88 | 3. Store authentication credentials locally
89 | 4. Allow Task Master to automatically use these credentials
90 |
91 | To verify your authentication:
92 | ```bash
93 | # Open interactive Codex CLI
94 | codex
95 |
96 | # Use /about command to see auth status
97 | /about
98 | ```
99 |
100 | ### Optional: API Key Method
101 |
102 | While OAuth is the primary and recommended method, you can optionally use an OpenAI API key:
103 |
104 | ```bash
105 | # In your .env file
106 | OPENAI_CODEX_API_KEY=sk-your-openai-api-key-here
107 | ```
108 |
109 | **Important Notes**:
110 | - The API key will **only** be injected when explicitly provided
111 | - OAuth authentication is always preferred when available
112 | - Using an API key doesn't provide access to subscription-only models like GPT-5-Codex
113 | - For full OpenAI API access with non-subscription models, consider using the standard `openai` provider instead
114 | - `OPENAI_CODEX_API_KEY` is specific to the codex-cli provider to avoid conflicts with the `openai` provider's `OPENAI_API_KEY`
115 |
116 | ## Available Models
117 |
118 | The Codex CLI provider supports only models available through ChatGPT subscription:
119 |
120 | | Model ID | Description | Max Input Tokens | Max Output Tokens |
121 | |----------|-------------|------------------|-------------------|
122 | | `gpt-5` | Latest GPT-5 model | 272K | 128K |
123 | | `gpt-5-codex` | GPT-5 optimized for agentic software engineering | 272K | 128K |
124 |
125 | **Note**: These models are only available via OAuth subscription through Codex CLI (ChatGPT Plus, Pro, Business, Edu, or Enterprise plans). For other OpenAI models, use the standard `openai` provider with an API key.
126 |
127 | **Research Capabilities**: Both GPT-5 models support web search tools, making them suitable for the `research` role in addition to `main` and `fallback` roles.
128 |
129 | ## Configuration
130 |
131 | ### Basic Configuration
132 |
133 | Add Codex CLI to your `.taskmaster/config.json`:
134 |
135 | ```json
136 | {
137 | "models": {
138 | "main": {
139 | "provider": "codex-cli",
140 | "modelId": "gpt-5-codex",
141 | "maxTokens": 128000,
142 | "temperature": 0.2
143 | },
144 | "fallback": {
145 | "provider": "codex-cli",
146 | "modelId": "gpt-5",
147 | "maxTokens": 128000,
148 | "temperature": 0.2
149 | }
150 | }
151 | }
152 | ```
153 |
154 | ### Advanced Configuration with Codex CLI Settings
155 |
156 | The `codexCli` section allows you to customize Codex CLI behavior:
157 |
158 | ```json
159 | {
160 | "models": {
161 | "main": {
162 | "provider": "codex-cli",
163 | "modelId": "gpt-5-codex",
164 | "maxTokens": 128000,
165 | "temperature": 0.2
166 | }
167 | },
168 | "codexCli": {
169 | "allowNpx": true,
170 | "skipGitRepoCheck": true,
171 | "approvalMode": "on-failure",
172 | "sandboxMode": "workspace-write",
173 | "verbose": false
174 | }
175 | }
176 | ```
177 |
178 | ### Codex CLI Settings Reference
179 |
180 | #### Core Settings
181 |
182 | - **`allowNpx`** (boolean, default: `false`)
183 | - Allow fallback to `npx @openai/codex` if the CLI is not found on PATH
184 | - Useful for CI environments or systems without global npm installations
185 | - Example: `"allowNpx": true`
186 |
187 | - **`skipGitRepoCheck`** (boolean, default: `false`)
188 | - Skip git repository safety check before execution
189 | - Recommended for CI environments or non-repository usage
190 | - Example: `"skipGitRepoCheck": true`
191 |
192 | #### Execution Control
193 |
194 | - **`approvalMode`** (string)
195 | - Controls when to require user approval for command execution
196 | - Options:
197 | - `"untrusted"`: Require approval for all commands
198 | - `"on-failure"`: Only require approval after a command fails (default)
199 | - `"on-request"`: Approve only when explicitly requested
200 | - `"never"`: Never require approval (use with caution)
201 | - Example: `"approvalMode": "on-failure"`
202 |
203 | - **`sandboxMode`** (string)
204 | - Controls filesystem access permissions
205 | - Options:
206 | - `"read-only"`: Read-only access to filesystem
207 | - `"workspace-write"`: Allow writes to workspace directory (default)
208 | - `"danger-full-access"`: Full filesystem access (use with extreme caution)
209 | - Example: `"sandboxMode": "workspace-write"`
210 |
211 | #### Path and Environment
212 |
213 | - **`codexPath`** (string, optional)
214 | - Custom path to Codex CLI executable
215 | - Useful when Codex is installed in a non-standard location
216 | - Example: `"codexPath": "/usr/local/bin/codex"`
217 |
218 | - **`cwd`** (string, optional)
219 | - Working directory for Codex CLI execution
220 | - Defaults to current working directory
221 | - Example: `"cwd": "/path/to/project"`
222 |
223 | - **`env`** (object, optional)
224 | - Additional environment variables for Codex CLI
225 | - Example: `"env": { "DEBUG": "true" }`
226 |
227 | #### Advanced Settings
228 |
229 | - **`fullAuto`** (boolean, optional)
230 | - Fully automatic mode (equivalent to `--full-auto` flag)
231 | - Bypasses most approvals for fully automated workflows
232 | - Example: `"fullAuto": true`
233 |
234 | - **`dangerouslyBypassApprovalsAndSandbox`** (boolean, optional)
235 | - Bypass all safety checks including approvals and sandbox
236 | - **WARNING**: Use with extreme caution - can execute arbitrary code
237 | - Example: `"dangerouslyBypassApprovalsAndSandbox": false`
238 |
239 | - **`color`** (string, optional)
240 | - Force color handling in Codex CLI output
241 | - Options: `"always"`, `"never"`, `"auto"`
242 | - Example: `"color": "auto"`
243 |
244 | - **`outputLastMessageFile`** (string, optional)
245 | - Write last agent message to specified file
246 | - Useful for debugging or logging
247 | - Example: `"outputLastMessageFile": "./last-message.txt"`
248 |
249 | - **`verbose`** (boolean, optional)
250 | - Enable verbose provider logging
251 | - Helpful for debugging issues
252 | - Example: `"verbose": true`
253 |
254 | ### Command-Specific Settings
255 |
256 | Override settings for specific Task Master commands:
257 |
258 | ```json
259 | {
260 | "codexCli": {
261 | "allowNpx": true,
262 | "approvalMode": "on-failure",
263 | "commandSpecific": {
264 | "parse-prd": {
265 | "approvalMode": "never",
266 | "verbose": true
267 | },
268 | "expand": {
269 | "sandboxMode": "read-only"
270 | },
271 | "add-task": {
272 | "approvalMode": "untrusted"
273 | }
274 | }
275 | }
276 | }
277 | ```
278 |
279 | ## Usage
280 |
281 | ### Setting Codex CLI Models
282 |
283 | ```bash
284 | # Set Codex CLI for main role
285 | task-master models --set-main gpt-5-codex --codex-cli
286 |
287 | # Set Codex CLI for fallback role
288 | task-master models --set-fallback gpt-5 --codex-cli
289 |
290 | # Set Codex CLI for research role
291 | task-master models --set-research gpt-5 --codex-cli
292 |
293 | # Verify configuration
294 | task-master models
295 | ```
296 |
297 | ### Using Codex CLI with Task Master Commands
298 |
299 | Once configured, use Task Master commands as normal:
300 |
301 | ```bash
302 | # Parse a PRD with Codex CLI
303 | task-master parse-prd my-requirements.txt
304 |
305 | # Analyze project complexity
306 | task-master analyze-complexity --research
307 |
308 | # Expand a task into subtasks
309 | task-master expand --id=1.2
310 |
311 | # Add a new task with AI assistance
312 | task-master add-task --prompt="Implement user authentication" --research
313 | ```
314 |
315 | The provider will automatically use your OAuth credentials when Codex CLI is configured.
316 |
317 | ## Codebase Features
318 |
319 | The Codex CLI provider is **codebase-capable**, meaning it can analyze and interact with your project files. This enables advanced features like:
320 |
321 | - **Code Analysis**: Understanding your project structure and dependencies
322 | - **Intelligent Suggestions**: Context-aware task recommendations
323 | - **File Operations**: Reading and analyzing project files for better task generation
324 | - **Pattern Recognition**: Identifying common patterns and best practices in your codebase
325 |
326 | ### Enabling Codebase Analysis
327 |
328 | Codebase analysis is automatically enabled when:
329 | 1. Your provider is set to `codex-cli`
330 | 2. `enableCodebaseAnalysis` is `true` in your global configuration (default)
331 |
332 | To verify or configure:
333 |
334 | ```json
335 | {
336 | "global": {
337 | "enableCodebaseAnalysis": true
338 | }
339 | }
340 | ```
341 |
342 | ## Troubleshooting
343 |
344 | ### "codex: command not found" Error
345 |
346 | **Symptoms**: Task Master reports that the Codex CLI is not found.
347 |
348 | **Solutions**:
349 | 1. **Install Codex CLI globally**:
350 | ```bash
351 | npm install -g @openai/codex
352 | ```
353 |
354 | 2. **Verify installation**:
355 | ```bash
356 | codex --version
357 | ```
358 |
359 | 3. **Alternative: Enable npx fallback**:
360 | ```json
361 | {
362 | "codexCli": {
363 | "allowNpx": true
364 | }
365 | }
366 | ```
367 |
368 | ### "Not logged in" Errors
369 |
370 | **Symptoms**: Authentication errors when trying to use Codex CLI.
371 |
372 | **Solutions**:
373 | 1. **Authenticate with OAuth**:
374 | ```bash
375 | codex login
376 | ```
377 |
378 | 2. **Verify authentication status**:
379 | ```bash
380 | codex
381 | # Then use /about command
382 | ```
383 |
384 | 3. **Re-authenticate if needed**:
385 | ```bash
386 | # Logout first
387 | codex
388 | # Use /auth command to change auth method
389 |
390 | # Then login again
391 | codex login
392 | ```
393 |
394 | ### "Old version" Warnings
395 |
396 | **Symptoms**: Warnings about Codex CLI version being outdated.
397 |
398 | **Solutions**:
399 | 1. **Check current version**:
400 | ```bash
401 | codex --version
402 | ```
403 |
404 | 2. **Upgrade to latest version**:
405 | ```bash
406 | npm install -g @openai/codex@latest
407 | ```
408 |
409 | 3. **Verify upgrade**:
410 | ```bash
411 | codex --version
412 | ```
413 | Should show >= 0.44.0
414 |
415 | ### "Model not available" Errors
416 |
417 | **Symptoms**: Error indicating the requested model is not available.
418 |
419 | **Causes and Solutions**:
420 |
421 | 1. **Using unsupported model**:
422 | - Only `gpt-5` and `gpt-5-codex` are available via Codex CLI
423 | - For other OpenAI models, use the standard `openai` provider
424 |
425 | 2. **Subscription not active**:
426 | - Verify your ChatGPT subscription is active
427 | - Check subscription status at <https://platform.openai.com>
428 |
429 | 3. **Wrong provider selected**:
430 | - Verify you're using `--codex-cli` flag when setting models
431 | - Check `.taskmaster/config.json` shows `"provider": "codex-cli"`
432 |
433 | ### API Key Not Being Used
434 |
435 | **Symptoms**: You've set `OPENAI_CODEX_API_KEY` but it's not being used.
436 |
437 | **Expected Behavior**:
438 | - OAuth authentication is always preferred
439 | - API key is only injected when explicitly provided
440 | - API key doesn't grant access to subscription-only models
441 |
442 | **Solutions**:
443 | 1. **Verify OAuth is working**:
444 | ```bash
445 | codex
446 | # Check /about for auth status
447 | ```
448 |
449 | 2. **If you want to force API key usage**:
450 | - This is not recommended with Codex CLI
451 | - Consider using the standard `openai` provider instead
452 |
453 | 3. **Verify .env file is being loaded**:
454 | ```bash
455 | # Check if .env exists in project root
456 | ls -la .env
457 |
458 | # Verify OPENAI_CODEX_API_KEY is set
459 | grep OPENAI_CODEX_API_KEY .env
460 | ```
461 |
462 | ### Approval/Sandbox Issues
463 |
464 | **Symptoms**: Commands are blocked or filesystem access is denied.
465 |
466 | **Solutions**:
467 |
468 | 1. **Adjust approval mode**:
469 | ```json
470 | {
471 | "codexCli": {
472 | "approvalMode": "on-request"
473 | }
474 | }
475 | ```
476 |
477 | 2. **Adjust sandbox mode**:
478 | ```json
479 | {
480 | "codexCli": {
481 | "sandboxMode": "workspace-write"
482 | }
483 | }
484 | ```
485 |
486 | 3. **For fully automated workflows** (use cautiously):
487 | ```json
488 | {
489 | "codexCli": {
490 | "fullAuto": true
491 | }
492 | }
493 | ```
494 |
495 | ## Important Notes
496 |
497 | - **OAuth subscription required**: No API key needed for basic operation, but requires active ChatGPT subscription
498 | - **Limited model selection**: Only `gpt-5` and `gpt-5-codex` available via OAuth
499 | - **Pricing information**: Not available for OAuth models (shows as "Unknown" in cost calculations)
500 | - **No automatic dependency**: The `@openai/codex` package is not added to Task Master's dependencies - install it globally or enable `allowNpx`
501 | - **Codebase analysis**: Automatically enabled when using `codex-cli` provider
502 | - **Safety first**: Default settings prioritize safety with `approvalMode: "on-failure"` and `sandboxMode: "workspace-write"`
503 |
504 | ## See Also
505 |
506 | - [Configuration Guide](../configuration.md#codex-cli-provider) - Complete Codex CLI configuration reference
507 | - [Command Reference](../command-reference.md) - Using `--codex-cli` flag with commands
508 | - [Gemini CLI Provider](./gemini-cli.md) - Similar CLI-based provider for Google Gemini
509 | - [Claude Code Integration](../claude-code-integration.md) - Another CLI-based provider
510 | - [ai-sdk-provider-codex-cli](https://github.com/ben-vargas/ai-sdk-provider-codex-cli) - Source code for the provider package
511 |
```
--------------------------------------------------------------------------------
/scripts/modules/task-manager/update-subtask-by-id.js:
--------------------------------------------------------------------------------
```javascript
1 | import fs from 'fs';
2 | import path from 'path';
3 | import chalk from 'chalk';
4 | import boxen from 'boxen';
5 | import Table from 'cli-table3';
6 |
7 | import {
8 | getStatusWithColor,
9 | startLoadingIndicator,
10 | stopLoadingIndicator,
11 | displayAiUsageSummary
12 | } from '../ui.js';
13 | import {
14 | log as consoleLog,
15 | readJSON,
16 | writeJSON,
17 | truncate,
18 | isSilentMode,
19 | findProjectRoot,
20 | flattenTasksWithSubtasks
21 | } from '../utils.js';
22 | import { generateTextService } from '../ai-services-unified.js';
23 | import { getDebugFlag, hasCodebaseAnalysis } from '../config-manager.js';
24 | import { getPromptManager } from '../prompt-manager.js';
25 | import generateTaskFiles from './generate-task-files.js';
26 | import { ContextGatherer } from '../utils/contextGatherer.js';
27 | import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
28 | import { tryUpdateViaRemote } from '@tm/bridge';
29 |
30 | /**
31 | * Update a subtask by appending additional timestamped information using the unified AI service.
32 | * @param {string} tasksPath - Path to the tasks.json file
33 | * @param {string} subtaskId - ID of the subtask to update in format "parentId.subtaskId"
34 | * @param {string} prompt - Prompt for generating additional information
35 | * @param {boolean} [useResearch=false] - Whether to use the research AI role.
36 | * @param {Object} context - Context object containing session and mcpLog.
37 | * @param {Object} [context.session] - Session object from MCP server.
38 | * @param {Object} [context.mcpLog] - MCP logger object.
39 | * @param {string} [context.projectRoot] - Project root path (needed for AI service key resolution).
40 | * @param {string} [context.tag] - Tag for the task
41 | * @param {string} [outputFormat='text'] - Output format ('text' or 'json'). Automatically 'json' if mcpLog is present.
42 | * @returns {Promise<Object|null>} - The updated subtask or null if update failed.
43 | */
44 | async function updateSubtaskById(
45 | tasksPath,
46 | subtaskId,
47 | prompt,
48 | useResearch = false,
49 | context = {},
50 | outputFormat = context.mcpLog ? 'json' : 'text'
51 | ) {
52 | const { session, mcpLog, projectRoot: providedProjectRoot, tag } = context;
53 | const logFn = mcpLog || consoleLog;
54 | const isMCP = !!mcpLog;
55 |
56 | // Report helper
57 | const report = (level, ...args) => {
58 | if (isMCP) {
59 | if (typeof logFn[level] === 'function') logFn[level](...args);
60 | else logFn.info(...args);
61 | } else if (!isSilentMode()) {
62 | logFn(level, ...args);
63 | }
64 | };
65 |
66 | let loadingIndicator = null;
67 |
68 | try {
69 | report('info', `Updating subtask ${subtaskId} with prompt: "${prompt}"`);
70 |
71 | if (
72 | !subtaskId ||
73 | typeof subtaskId !== 'string' ||
74 | !subtaskId.includes('.')
75 | ) {
76 | throw new Error(
77 | `Invalid subtask ID format: ${subtaskId}. Subtask ID must be in format "parentId.subtaskId"`
78 | );
79 | }
80 |
81 | if (!prompt || typeof prompt !== 'string' || prompt.trim() === '') {
82 | throw new Error(
83 | 'Prompt cannot be empty. Please provide context for the subtask update.'
84 | );
85 | }
86 |
87 | if (!fs.existsSync(tasksPath)) {
88 | throw new Error(`Tasks file not found at path: ${tasksPath}`);
89 | }
90 |
91 | const projectRoot = providedProjectRoot || findProjectRoot();
92 | if (!projectRoot) {
93 | throw new Error('Could not determine project root directory');
94 | }
95 |
96 | // --- BRIDGE: Try remote update first (API storage) ---
97 | // In API storage, subtask IDs like "1.2" or "TAS-49.1" are just regular task IDs
98 | // So update-subtask and update-task work identically
99 | const remoteResult = await tryUpdateViaRemote({
100 | taskId: subtaskId,
101 | prompt,
102 | projectRoot,
103 | tag,
104 | appendMode: true, // Subtask updates are always append mode
105 | useResearch,
106 | isMCP,
107 | outputFormat,
108 | report
109 | });
110 |
111 | // If remote handled it, return the result
112 | if (remoteResult) {
113 | return {
114 | updatedSubtask: { id: subtaskId },
115 | telemetryData: remoteResult.telemetryData,
116 | tagInfo: remoteResult.tagInfo
117 | };
118 | }
119 | // Otherwise fall through to file-based logic below
120 | // --- End BRIDGE ---
121 |
122 | const data = readJSON(tasksPath, projectRoot, tag);
123 | if (!data || !data.tasks) {
124 | throw new Error(
125 | `No valid tasks found in ${tasksPath}. The file may be corrupted or have an invalid format.`
126 | );
127 | }
128 |
129 | const [parentIdStr, subtaskIdStr] = subtaskId.split('.');
130 | const parentId = parseInt(parentIdStr, 10);
131 | const subtaskIdNum = parseInt(subtaskIdStr, 10);
132 |
133 | if (
134 | Number.isNaN(parentId) ||
135 | parentId <= 0 ||
136 | Number.isNaN(subtaskIdNum) ||
137 | subtaskIdNum <= 0
138 | ) {
139 | throw new Error(
140 | `Invalid subtask ID format: ${subtaskId}. Both parent ID and subtask ID must be positive integers.`
141 | );
142 | }
143 |
144 | const parentTask = data.tasks.find((task) => task.id === parentId);
145 | if (!parentTask) {
146 | throw new Error(
147 | `Parent task with ID ${parentId} not found. Please verify the task ID and try again.`
148 | );
149 | }
150 |
151 | if (!parentTask.subtasks || !Array.isArray(parentTask.subtasks)) {
152 | throw new Error(`Parent task ${parentId} has no subtasks.`);
153 | }
154 |
155 | const subtaskIndex = parentTask.subtasks.findIndex(
156 | (st) => st.id === subtaskIdNum
157 | );
158 | if (subtaskIndex === -1) {
159 | throw new Error(
160 | `Subtask with ID ${subtaskId} not found. Please verify the subtask ID and try again.`
161 | );
162 | }
163 |
164 | const subtask = parentTask.subtasks[subtaskIndex];
165 |
166 | // --- Context Gathering ---
167 | let gatheredContext = '';
168 | try {
169 | const contextGatherer = new ContextGatherer(projectRoot, tag);
170 | const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
171 | const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update-subtask');
172 | const searchQuery = `${parentTask.title} ${subtask.title} ${prompt}`;
173 | const searchResults = fuzzySearch.findRelevantTasks(searchQuery, {
174 | maxResults: 5,
175 | includeSelf: true
176 | });
177 | const relevantTaskIds = fuzzySearch.getTaskIds(searchResults);
178 |
179 | const finalTaskIds = [
180 | ...new Set([subtaskId.toString(), ...relevantTaskIds])
181 | ];
182 |
183 | if (finalTaskIds.length > 0) {
184 | const contextResult = await contextGatherer.gather({
185 | tasks: finalTaskIds,
186 | format: 'research'
187 | });
188 | gatheredContext = contextResult.context || '';
189 | }
190 | } catch (contextError) {
191 | report('warn', `Could not gather context: ${contextError.message}`);
192 | }
193 | // --- End Context Gathering ---
194 |
195 | if (outputFormat === 'text') {
196 | const table = new Table({
197 | head: [
198 | chalk.cyan.bold('ID'),
199 | chalk.cyan.bold('Title'),
200 | chalk.cyan.bold('Status')
201 | ],
202 | colWidths: [10, 55, 10]
203 | });
204 | table.push([
205 | subtaskId,
206 | truncate(subtask.title, 52),
207 | getStatusWithColor(subtask.status)
208 | ]);
209 | console.log(
210 | boxen(chalk.white.bold(`Updating Subtask #${subtaskId}`), {
211 | padding: 1,
212 | borderColor: 'blue',
213 | borderStyle: 'round',
214 | margin: { top: 1, bottom: 0 }
215 | })
216 | );
217 | console.log(table.toString());
218 | loadingIndicator = startLoadingIndicator(
219 | useResearch
220 | ? 'Updating subtask with research...'
221 | : 'Updating subtask...'
222 | );
223 | }
224 |
225 | let generatedContentString = '';
226 | let newlyAddedSnippet = '';
227 | let aiServiceResponse = null;
228 |
229 | try {
230 | const parentContext = {
231 | id: parentTask.id,
232 | title: parentTask.title
233 | };
234 | const prevSubtask =
235 | subtaskIndex > 0
236 | ? {
237 | id: `${parentTask.id}.${parentTask.subtasks[subtaskIndex - 1].id}`,
238 | title: parentTask.subtasks[subtaskIndex - 1].title,
239 | status: parentTask.subtasks[subtaskIndex - 1].status
240 | }
241 | : undefined;
242 | const nextSubtask =
243 | subtaskIndex < parentTask.subtasks.length - 1
244 | ? {
245 | id: `${parentTask.id}.${parentTask.subtasks[subtaskIndex + 1].id}`,
246 | title: parentTask.subtasks[subtaskIndex + 1].title,
247 | status: parentTask.subtasks[subtaskIndex + 1].status
248 | }
249 | : undefined;
250 |
251 | // Build prompts using PromptManager
252 | const promptManager = getPromptManager();
253 |
254 | const promptParams = {
255 | parentTask: parentContext,
256 | prevSubtask: prevSubtask,
257 | nextSubtask: nextSubtask,
258 | currentDetails: subtask.details || '(No existing details)',
259 | updatePrompt: prompt,
260 | useResearch: useResearch,
261 | gatheredContext: gatheredContext || '',
262 | hasCodebaseAnalysis: hasCodebaseAnalysis(
263 | useResearch,
264 | projectRoot,
265 | session
266 | ),
267 | projectRoot: projectRoot
268 | };
269 |
270 | const variantKey = useResearch ? 'research' : 'default';
271 | const { systemPrompt, userPrompt } = await promptManager.loadPrompt(
272 | 'update-subtask',
273 | promptParams,
274 | variantKey
275 | );
276 |
277 | const role = useResearch ? 'research' : 'main';
278 | report('info', `Using AI text service with role: ${role}`);
279 |
280 | aiServiceResponse = await generateTextService({
281 | prompt: userPrompt,
282 | systemPrompt: systemPrompt,
283 | role,
284 | session,
285 | projectRoot,
286 | maxRetries: 2,
287 | commandName: 'update-subtask',
288 | outputType: isMCP ? 'mcp' : 'cli'
289 | });
290 |
291 | if (
292 | aiServiceResponse &&
293 | aiServiceResponse.mainResult &&
294 | typeof aiServiceResponse.mainResult === 'string'
295 | ) {
296 | generatedContentString = aiServiceResponse.mainResult;
297 | } else {
298 | generatedContentString = '';
299 | report(
300 | 'warn',
301 | 'AI service response did not contain expected text string.'
302 | );
303 | }
304 |
305 | if (outputFormat === 'text' && loadingIndicator) {
306 | stopLoadingIndicator(loadingIndicator);
307 | loadingIndicator = null;
308 | }
309 | } catch (aiError) {
310 | report('error', `AI service call failed: ${aiError.message}`);
311 | if (outputFormat === 'text' && loadingIndicator) {
312 | stopLoadingIndicator(loadingIndicator);
313 | loadingIndicator = null;
314 | }
315 | throw aiError;
316 | }
317 |
318 | if (generatedContentString && generatedContentString.trim()) {
319 | // Check if the string is not empty
320 | const timestamp = new Date().toISOString();
321 | const formattedBlock = `<info added on ${timestamp}>\n${generatedContentString.trim()}\n</info added on ${timestamp}>`;
322 | newlyAddedSnippet = formattedBlock; // <--- ADD THIS LINE: Store for display
323 |
324 | subtask.details =
325 | (subtask.details ? subtask.details + '\n' : '') + formattedBlock;
326 | } else {
327 | report(
328 | 'warn',
329 | 'AI response was empty or whitespace after trimming. Original details remain unchanged.'
330 | );
331 | newlyAddedSnippet = 'No new details were added by the AI.';
332 | }
333 |
334 | const updatedSubtask = parentTask.subtasks[subtaskIndex];
335 |
336 | if (outputFormat === 'text' && getDebugFlag(session)) {
337 | console.log(
338 | '>>> DEBUG: Subtask details AFTER AI update:',
339 | updatedSubtask.details
340 | );
341 | }
342 |
343 | if (updatedSubtask.description) {
344 | if (prompt.length < 100) {
345 | if (outputFormat === 'text' && getDebugFlag(session)) {
346 | console.log(
347 | '>>> DEBUG: Subtask description BEFORE append:',
348 | updatedSubtask.description
349 | );
350 | }
351 | updatedSubtask.description += ` [Updated: ${new Date().toLocaleDateString()}]`;
352 | if (outputFormat === 'text' && getDebugFlag(session)) {
353 | console.log(
354 | '>>> DEBUG: Subtask description AFTER append:',
355 | updatedSubtask.description
356 | );
357 | }
358 | }
359 | }
360 |
361 | if (outputFormat === 'text' && getDebugFlag(session)) {
362 | console.log('>>> DEBUG: About to call writeJSON with updated data...');
363 | }
364 | writeJSON(tasksPath, data, projectRoot, tag);
365 | if (outputFormat === 'text' && getDebugFlag(session)) {
366 | console.log('>>> DEBUG: writeJSON call completed.');
367 | }
368 |
369 | report('success', `Successfully updated subtask ${subtaskId}`);
370 | // Updated function call to make sure if uncommented it will generate the task files for the updated subtask based on the tag
371 | // await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
372 | // tag: tag,
373 | // projectRoot: projectRoot
374 | // });
375 |
376 | if (outputFormat === 'text') {
377 | if (loadingIndicator) {
378 | stopLoadingIndicator(loadingIndicator);
379 | loadingIndicator = null;
380 | }
381 | console.log(
382 | boxen(
383 | chalk.green(`Successfully updated subtask #${subtaskId}`) +
384 | '\n\n' +
385 | chalk.white.bold('Title:') +
386 | ' ' +
387 | updatedSubtask.title +
388 | '\n\n' +
389 | chalk.white.bold('Newly Added Snippet:') +
390 | '\n' +
391 | chalk.white(newlyAddedSnippet),
392 | { padding: 1, borderColor: 'green', borderStyle: 'round' }
393 | )
394 | );
395 | }
396 |
397 | if (outputFormat === 'text' && aiServiceResponse.telemetryData) {
398 | displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli');
399 | }
400 |
401 | return {
402 | updatedSubtask: updatedSubtask,
403 | telemetryData: aiServiceResponse.telemetryData,
404 | tagInfo: aiServiceResponse.tagInfo
405 | };
406 | } catch (error) {
407 | if (outputFormat === 'text' && loadingIndicator) {
408 | stopLoadingIndicator(loadingIndicator);
409 | loadingIndicator = null;
410 | }
411 | report('error', `Error updating subtask: ${error.message}`);
412 | if (outputFormat === 'text') {
413 | console.error(chalk.red(`Error: ${error.message}`));
414 | if (error.message?.includes('ANTHROPIC_API_KEY')) {
415 | console.log(
416 | chalk.yellow('\nTo fix this issue, set your Anthropic API key:')
417 | );
418 | console.log(' export ANTHROPIC_API_KEY=your_api_key_here');
419 | } else if (error.message?.includes('PERPLEXITY_API_KEY')) {
420 | console.log(chalk.yellow('\nTo fix this issue:'));
421 | console.log(
422 | ' 1. Set your Perplexity API key: export PERPLEXITY_API_KEY=your_api_key_here'
423 | );
424 | console.log(
425 | ' 2. Or run without the research flag: task-master update-subtask --id=<id> --prompt="..."'
426 | );
427 | } else if (error.message?.includes('overloaded')) {
428 | console.log(
429 | chalk.yellow(
430 | '\nAI model overloaded, and fallback failed or was unavailable:'
431 | )
432 | );
433 | console.log(' 1. Try again in a few minutes.');
434 | console.log(' 2. Ensure PERPLEXITY_API_KEY is set for fallback.');
435 | } else if (error.message?.includes('not found')) {
436 | console.log(chalk.yellow('\nTo fix this issue:'));
437 | console.log(
438 | ' 1. Run task-master list --with-subtasks to see all available subtask IDs'
439 | );
440 | console.log(
441 | ' 2. Use a valid subtask ID with the --id parameter in format "parentId.subtaskId"'
442 | );
443 | } else if (
444 | error.message?.includes('empty stream response') ||
445 | error.message?.includes('AI did not return a valid text string')
446 | ) {
447 | console.log(
448 | chalk.yellow(
449 | '\nThe AI model returned an empty or invalid response. This might be due to the prompt or API issues. Try rephrasing or trying again later.'
450 | )
451 | );
452 | }
453 | if (getDebugFlag(session)) {
454 | console.error(error);
455 | }
456 | } else {
457 | throw error;
458 | }
459 | return null;
460 | }
461 | }
462 |
463 | export default updateSubtaskById;
464 |
```
--------------------------------------------------------------------------------
/.taskmaster/docs/tdd-workflow-phase-3-extensibility-guardrails.md:
--------------------------------------------------------------------------------
```markdown
1 | # Phase 3: Extensibility + Guardrails - Autonomous TDD Workflow
2 |
3 | ## Objective
4 | Add multi-language/framework support, enhanced safety guardrails, TUI interface, and extensibility for IDE/editor integration.
5 |
6 | ## Scope
7 | - Multi-language test runner support (pytest, go test, etc.)
8 | - Enhanced safety: diff preview, confirmation gates, minimal-change prompts
9 | - Optional TUI panel with tmux integration
10 | - State-based extension API for IDE integration
11 | - Parallel subtask execution (experimental)
12 |
13 | ## Deliverables
14 |
15 | ### 1. Multi-Language Test Runner Support
16 |
17 | **Extend TestRunnerAdapter:**
18 | ```typescript
19 | class TestRunnerAdapter {
20 | // Existing methods...
21 |
22 | async detectLanguage(): Promise<Language>
23 | async detectFramework(language: Language): Promise<Framework>
24 | async getFrameworkAdapter(framework: Framework): Promise<FrameworkAdapter>
25 | }
26 |
27 | enum Language {
28 | JavaScript = 'javascript',
29 | TypeScript = 'typescript',
30 | Python = 'python',
31 | Go = 'go',
32 | Rust = 'rust'
33 | }
34 |
35 | enum Framework {
36 | Vitest = 'vitest',
37 | Jest = 'jest',
38 | Pytest = 'pytest',
39 | GoTest = 'gotest',
40 | CargoTest = 'cargotest'
41 | }
42 |
43 | interface FrameworkAdapter {
44 | runTargeted(pattern: string): Promise<TestResults>
45 | runAll(): Promise<TestResults>
46 | parseCoverage(output: string): Promise<CoverageReport>
47 | getTestFilePattern(): string
48 | getTestFileExtension(): string
49 | }
50 | ```
51 |
52 | **Framework-specific adapters:**
53 |
54 | **PytestAdapter** (`packages/tm-core/src/services/test-adapters/pytest-adapter.ts`):
55 | ```typescript
56 | class PytestAdapter implements FrameworkAdapter {
57 | async runTargeted(pattern: string): Promise<TestResults> {
58 | const output = await exec(`pytest ${pattern} --json-report`)
59 | return this.parseResults(output)
60 | }
61 |
62 | async runAll(): Promise<TestResults> {
63 | const output = await exec('pytest --cov --json-report')
64 | return this.parseResults(output)
65 | }
66 |
67 | parseCoverage(output: string): Promise<CoverageReport> {
68 | // Parse pytest-cov XML output
69 | }
70 |
71 | getTestFilePattern(): string {
72 | return '**/test_*.py'
73 | }
74 |
75 | getTestFileExtension(): string {
76 | return '.py'
77 | }
78 | }
79 | ```
80 |
81 | **GoTestAdapter** (`packages/tm-core/src/services/test-adapters/gotest-adapter.ts`):
82 | ```typescript
83 | class GoTestAdapter implements FrameworkAdapter {
84 | async runTargeted(pattern: string): Promise<TestResults> {
85 | const output = await exec(`go test ${pattern} -json`)
86 | return this.parseResults(output)
87 | }
88 |
89 | async runAll(): Promise<TestResults> {
90 | const output = await exec('go test ./... -coverprofile=coverage.out -json')
91 | return this.parseResults(output)
92 | }
93 |
94 | parseCoverage(output: string): Promise<CoverageReport> {
95 | // Parse go test coverage output
96 | }
97 |
98 | getTestFilePattern(): string {
99 | return '**/*_test.go'
100 | }
101 |
102 | getTestFileExtension(): string {
103 | return '_test.go'
104 | }
105 | }
106 | ```
107 |
108 | **Detection Logic:**
109 | ```typescript
110 | async function detectFramework(): Promise<Framework> {
111 | // Check for package.json
112 | if (await exists('package.json')) {
113 | const pkg = await readJSON('package.json')
114 | if (pkg.devDependencies?.vitest) return Framework.Vitest
115 | if (pkg.devDependencies?.jest) return Framework.Jest
116 | }
117 |
118 | // Check for Python files
119 | if (await exists('pytest.ini') || await exists('setup.py')) {
120 | return Framework.Pytest
121 | }
122 |
123 | // Check for Go files
124 | if (await exists('go.mod')) {
125 | return Framework.GoTest
126 | }
127 |
128 | // Check for Rust files
129 | if (await exists('Cargo.toml')) {
130 | return Framework.CargoTest
131 | }
132 |
133 | throw new Error('Could not detect test framework')
134 | }
135 | ```
136 |
137 | ### 2. Enhanced Safety Guardrails
138 |
139 | **Diff Preview Mode:**
140 | ```bash
141 | $ tm autopilot 42 --preview-diffs
142 |
143 | [2/3] Subtask 42.2: Add collection endpoint
144 |
145 | RED ✓ Tests created: src/api/__tests__/metrics.test.js
146 |
147 | GREEN Implementing code...
148 |
149 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
150 | Proposed changes (src/api/metrics.js):
151 |
152 | + import { MetricsSchema } from '../models/schema.js'
153 | +
154 | + export async function createMetric(data) {
155 | + const validated = MetricsSchema.parse(data)
156 | + const result = await db.metrics.create(validated)
157 | + return result
158 | + }
159 |
160 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
161 |
162 | Apply these changes? [Y/n/e(dit)/s(kip)]
163 | Y - Apply and continue
164 | n - Reject and retry GREEN phase
165 | e - Open in editor for manual changes
166 | s - Skip this subtask
167 | ```
168 |
169 | **Minimal Change Enforcement:**
170 |
171 | Add to system prompt:
172 | ```markdown
173 | CRITICAL: Make MINIMAL changes to pass the failing tests.
174 | - Only modify files directly related to the subtask
175 | - Do not refactor existing code unless absolutely necessary
176 | - Do not add features beyond the acceptance criteria
177 | - Keep changes under 50 lines per file when possible
178 | - Prefer composition over modification
179 | ```
180 |
181 | **Change Size Warnings:**
182 | ```bash
183 | ⚠️ Large change detected:
184 | Files modified: 5
185 | Lines changed: +234, -12
186 |
187 | This subtask was expected to be small (~50 lines).
188 | Consider:
189 | - Breaking into smaller subtasks
190 | - Reviewing acceptance criteria
191 | - Checking for unintended changes
192 |
193 | Continue anyway? [y/N]
194 | ```
195 |
196 | ### 3. TUI Interface with tmux
197 |
198 | **Layout:**
199 | ```
200 | ┌──────────────────────────────────┬─────────────────────────────────┐
201 | │ Task Navigator (left) │ Executor Terminal (right) │
202 | │ │ │
203 | │ Project: my-app │ $ tm autopilot --executor-mode │
204 | │ Branch: analytics/task-42 │ > Running subtask 42.2 GREEN... │
205 | │ Tag: analytics │ > Implementing endpoint... │
206 | │ │ > Tests: 3 passed, 0 failed │
207 | │ Tasks: │ > Ready to commit │
208 | │ → 42 [in-progress] User metrics │ │
209 | │ → 42.1 [done] Schema │ [Live output from executor] │
210 | │ → 42.2 [active] Endpoint ◀ │ │
211 | │ → 42.3 [pending] Dashboard │ │
212 | │ │ │
213 | │ [s] start [p] pause [q] quit │ │
214 | └──────────────────────────────────┴─────────────────────────────────┘
215 | ```
216 |
217 | **Implementation:**
218 |
219 | **TUI Navigator** (`apps/cli/src/ui/tui/navigator.ts`):
220 | ```typescript
221 | import blessed from 'blessed'
222 |
223 | class AutopilotTUI {
224 | private screen: blessed.Widgets.Screen
225 | private taskList: blessed.Widgets.ListElement
226 | private statusBox: blessed.Widgets.BoxElement
227 | private executorPane: string // tmux pane ID
228 |
229 | async start(taskId?: string) {
230 | // Create blessed screen
231 | this.screen = blessed.screen()
232 |
233 | // Create task list widget
234 | this.taskList = blessed.list({
235 | label: 'Tasks',
236 | keys: true,
237 | vi: true,
238 | style: { selected: { bg: 'blue' } }
239 | })
240 |
241 | // Spawn tmux pane for executor
242 | this.executorPane = await this.spawnExecutorPane()
243 |
244 | // Watch state file for updates
245 | this.watchStateFile()
246 |
247 | // Handle keybindings
248 | this.setupKeybindings()
249 | }
250 |
251 | private async spawnExecutorPane(): Promise<string> {
252 | const paneId = await exec('tmux split-window -h -P -F "#{pane_id}"')
253 | await exec(`tmux send-keys -t ${paneId} "tm autopilot --executor-mode" Enter`)
254 | return paneId.trim()
255 | }
256 |
257 | private watchStateFile() {
258 | watch('.taskmaster/state/current-run.json', (event, filename) => {
259 | this.updateDisplay()
260 | })
261 | }
262 |
263 | private setupKeybindings() {
264 | this.screen.key(['s'], () => this.startTask())
265 | this.screen.key(['p'], () => this.pauseTask())
266 | this.screen.key(['q'], () => this.quit())
267 | this.screen.key(['up', 'down'], () => this.navigateTasks())
268 | }
269 | }
270 | ```
271 |
272 | **Executor Mode:**
273 | ```bash
274 | $ tm autopilot 42 --executor-mode
275 |
276 | # Runs in executor pane, writes state to shared file
277 | # Left pane reads state file and updates display
278 | ```
279 |
280 | **State File** (`.taskmaster/state/current-run.json`):
281 | ```json
282 | {
283 | "runId": "2025-01-15-142033",
284 | "taskId": "42",
285 | "status": "running",
286 | "currentPhase": "green",
287 | "currentSubtask": "42.2",
288 | "lastOutput": "Implementing endpoint...",
289 | "testsStatus": {
290 | "passed": 3,
291 | "failed": 0
292 | }
293 | }
294 | ```
295 |
296 | ### 4. Extension API for IDE Integration
297 |
298 | **State-based API:**
299 |
300 | Expose run state via JSON files that IDEs can read:
301 | - `.taskmaster/state/current-run.json` - live run state
302 | - `.taskmaster/reports/runs/<runId>/manifest.json` - run metadata
303 | - `.taskmaster/reports/runs/<runId>/log.jsonl` - event stream
304 |
305 | **WebSocket API (optional):**
306 | ```typescript
307 | // packages/tm-core/src/services/autopilot-server.ts
308 | class AutopilotServer {
309 | private wss: WebSocketServer
310 |
311 | start(port: number = 7890) {
312 | this.wss = new WebSocketServer({ port })
313 |
314 | this.wss.on('connection', (ws) => {
315 | // Send current state
316 | ws.send(JSON.stringify(this.getCurrentState()))
317 |
318 | // Stream events
319 | this.orchestrator.on('*', (event) => {
320 | ws.send(JSON.stringify(event))
321 | })
322 | })
323 | }
324 | }
325 | ```
326 |
327 | **Usage from IDE extension:**
328 | ```typescript
329 | // VS Code extension example
330 | const ws = new WebSocket('ws://localhost:7890')
331 |
332 | ws.on('message', (data) => {
333 | const event = JSON.parse(data)
334 |
335 | if (event.type === 'subtask:complete') {
336 | vscode.window.showInformationMessage(
337 | `Subtask ${event.subtaskId} completed`
338 | )
339 | }
340 | })
341 | ```
342 |
343 | ### 5. Parallel Subtask Execution (Experimental)
344 |
345 | **Dependency Analysis:**
346 | ```typescript
347 | class SubtaskScheduler {
348 | async buildDependencyGraph(subtasks: Subtask[]): Promise<DAG> {
349 | const graph = new DAG()
350 |
351 | for (const subtask of subtasks) {
352 | graph.addNode(subtask.id)
353 |
354 | for (const depId of subtask.dependencies) {
355 | graph.addEdge(depId, subtask.id)
356 | }
357 | }
358 |
359 | return graph
360 | }
361 |
362 | async getParallelBatches(graph: DAG): Promise<Subtask[][]> {
363 | const batches: Subtask[][] = []
364 | const completed = new Set<string>()
365 |
366 | while (completed.size < graph.size()) {
367 | const ready = graph.nodes.filter(node =>
368 | !completed.has(node.id) &&
369 | node.dependencies.every(dep => completed.has(dep))
370 | )
371 |
372 | batches.push(ready)
373 | ready.forEach(node => completed.add(node.id))
374 | }
375 |
376 | return batches
377 | }
378 | }
379 | ```
380 |
381 | **Parallel Execution:**
382 | ```bash
383 | $ tm autopilot 42 --parallel
384 |
385 | [Batch 1] Running 2 subtasks in parallel:
386 | → 42.1: Add metrics schema
387 | → 42.4: Add API documentation
388 |
389 | 42.1 RED ✓ Tests created
390 | 42.4 RED ✓ Tests created
391 |
392 | 42.1 GREEN ✓ Implementation complete
393 | 42.4 GREEN ✓ Implementation complete
394 |
395 | 42.1 COMMIT ✓ Committed: a1b2c3d
396 | 42.4 COMMIT ✓ Committed: e5f6g7h
397 |
398 | [Batch 2] Running 2 subtasks in parallel (depend on 42.1):
399 | → 42.2: Add collection endpoint
400 | → 42.3: Add dashboard widget
401 | ...
402 | ```
403 |
404 | **Conflict Detection:**
405 | ```typescript
406 | async function detectConflicts(subtasks: Subtask[]): Promise<Conflict[]> {
407 | const conflicts: Conflict[] = []
408 |
409 | for (let i = 0; i < subtasks.length; i++) {
410 | for (let j = i + 1; j < subtasks.length; j++) {
411 | const filesA = await predictAffectedFiles(subtasks[i])
412 | const filesB = await predictAffectedFiles(subtasks[j])
413 |
414 | const overlap = filesA.filter(f => filesB.includes(f))
415 |
416 | if (overlap.length > 0) {
417 | conflicts.push({
418 | subtasks: [subtasks[i].id, subtasks[j].id],
419 | files: overlap
420 | })
421 | }
422 | }
423 | }
424 |
425 | return conflicts
426 | }
427 | ```
428 |
429 | ### 6. Advanced Configuration
430 |
431 | **Add to `.taskmaster/config.json`:**
432 | ```json
433 | {
434 | "autopilot": {
435 | "safety": {
436 | "previewDiffs": false,
437 | "maxChangeLinesPerFile": 100,
438 | "warnOnLargeChanges": true,
439 | "requireConfirmOnLargeChanges": true
440 | },
441 | "parallel": {
442 | "enabled": false,
443 | "maxConcurrent": 3,
444 | "detectConflicts": true
445 | },
446 | "tui": {
447 | "enabled": false,
448 | "tmuxSession": "taskmaster-autopilot"
449 | },
450 | "api": {
451 | "enabled": false,
452 | "port": 7890,
453 | "allowRemote": false
454 | }
455 | },
456 | "test": {
457 | "frameworks": {
458 | "python": {
459 | "runner": "pytest",
460 | "coverageCommand": "pytest --cov",
461 | "testPattern": "**/test_*.py"
462 | },
463 | "go": {
464 | "runner": "go test",
465 | "coverageCommand": "go test ./... -coverprofile=coverage.out",
466 | "testPattern": "**/*_test.go"
467 | }
468 | }
469 | }
470 | }
471 | ```
472 |
473 | ## CLI Updates
474 |
475 | **New commands:**
476 | ```bash
477 | tm autopilot <taskId> --tui # Launch TUI interface
478 | tm autopilot <taskId> --parallel # Enable parallel execution
479 | tm autopilot <taskId> --preview-diffs # Show diffs before applying
480 | tm autopilot <taskId> --executor-mode # Run as executor pane
481 | tm autopilot-server start # Start WebSocket API
482 | ```
483 |
484 | ## Success Criteria
485 | - Supports Python projects with pytest
486 | - Supports Go projects with go test
487 | - Diff preview prevents unwanted changes
488 | - TUI provides better visibility for long-running tasks
489 | - IDE extensions can integrate via state files or WebSocket
490 | - Parallel execution reduces total time for independent subtasks
491 |
492 | ## Out of Scope
493 | - Full Electron/web GUI
494 | - AI executor selection UI (defer to Phase 4)
495 | - Multi-repository support
496 | - Remote execution on cloud runners
497 |
498 | ## Testing Strategy
499 | - Test with Python project (pytest)
500 | - Test with Go project (go test)
501 | - Test diff preview UI with mock changes
502 | - Test parallel execution with independent subtasks
503 | - Test conflict detection with overlapping file changes
504 | - Test TUI with mock tmux environment
505 |
506 | ## Dependencies
507 | - Phase 2 completed (PR + resumability)
508 | - tmux installed (for TUI)
509 | - blessed or ink library (for TUI rendering)
510 |
511 | ## Estimated Effort
512 | 3-4 weeks
513 |
514 | ## Risks & Mitigations
515 | - **Risk:** Parallel execution causes git conflicts
516 | - **Mitigation:** Conservative conflict detection, sequential fallback
517 |
518 | - **Risk:** TUI adds complexity and maintenance burden
519 | - **Mitigation:** Keep TUI optional, state-based design allows alternatives
520 |
521 | - **Risk:** Framework adapters hard to maintain across versions
522 | - **Mitigation:** Abstract common parsing logic, document adapter interface
523 |
524 | - **Risk:** Diff preview slows down workflow
525 | - **Mitigation:** Make optional, use --preview-diffs flag only when needed
526 |
527 | ## Validation
528 | Test with:
529 | - Python project with pytest and pytest-cov
530 | - Go project with go test
531 | - Large changes requiring confirmation
532 | - Parallel execution with 3+ independent subtasks
533 | - TUI with task selection and live status updates
534 | - VS Code extension reading state files
535 |
```
--------------------------------------------------------------------------------
/tests/manual/progress/test-parse-prd.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * test-parse-prd.js
5 | *
6 | * Comprehensive integration test for parse-prd functionality.
7 | * Tests MCP streaming, CLI streaming, and non-streaming modes.
8 | * Validates token tracking, message formats, and priority indicators across all contexts.
9 | */
10 |
11 | import fs from 'fs';
12 | import path from 'path';
13 | import chalk from 'chalk';
14 | import { fileURLToPath } from 'url';
15 |
16 | // Get current directory
17 | const __filename = fileURLToPath(import.meta.url);
18 | const __dirname = path.dirname(__filename);
19 |
20 | // Get project root (three levels up from tests/manual/progress/)
21 | const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
22 |
23 | // Import the parse-prd function
24 | import parsePRD from '../../../scripts/modules/task-manager/parse-prd/index.js';
25 |
26 | /**
27 | * Mock Progress Reporter for testing
28 | */
29 | class MockProgressReporter {
30 | constructor(enableDebug = true) {
31 | this.enableDebug = enableDebug;
32 | this.progressHistory = [];
33 | this.startTime = Date.now();
34 | }
35 |
36 | async reportProgress(data) {
37 | const timestamp = Date.now() - this.startTime;
38 |
39 | const entry = {
40 | timestamp,
41 | ...data
42 | };
43 |
44 | this.progressHistory.push(entry);
45 |
46 | if (this.enableDebug) {
47 | const percentage = data.total
48 | ? Math.round((data.progress / data.total) * 100)
49 | : 0;
50 | console.log(
51 | chalk.blue(`[${timestamp}ms]`),
52 | chalk.green(`${percentage}%`),
53 | chalk.yellow(data.message)
54 | );
55 | }
56 | }
57 |
58 | getProgressHistory() {
59 | return this.progressHistory;
60 | }
61 |
62 | printSummary() {
63 | console.log(chalk.green('\n=== Progress Summary ==='));
64 | console.log(`Total progress reports: ${this.progressHistory.length}`);
65 | console.log(
66 | `Duration: ${this.progressHistory[this.progressHistory.length - 1]?.timestamp || 0}ms`
67 | );
68 |
69 | this.progressHistory.forEach((entry, index) => {
70 | const percentage = entry.total
71 | ? Math.round((entry.progress / entry.total) * 100)
72 | : 0;
73 | console.log(
74 | `${index + 1}. [${entry.timestamp}ms] ${percentage}% - ${entry.message}`
75 | );
76 | });
77 |
78 | // Check for expected message formats
79 | const hasInitialMessage = this.progressHistory.some(
80 | (entry) =>
81 | entry.message.includes('Starting PRD analysis') &&
82 | entry.message.includes('Input:') &&
83 | entry.message.includes('tokens')
84 | );
85 | // Make regex more flexible to handle potential whitespace variations
86 | const hasTaskMessages = this.progressHistory.some((entry) =>
87 | /^[🔴🟠🟢⚪]{3} Task \d+\/\d+ - .+ \| ~Output: \d+ tokens/u.test(
88 | entry.message.trim()
89 | )
90 | );
91 |
92 | const hasCompletionMessage = this.progressHistory.some(
93 | (entry) =>
94 | entry.message.includes('✅ Task Generation Completed') &&
95 | entry.message.includes('Tokens (I/O):')
96 | );
97 |
98 | console.log(chalk.cyan('\n=== Message Format Validation ==='));
99 | console.log(
100 | `✅ Initial message format: ${hasInitialMessage ? 'PASS' : 'FAIL'}`
101 | );
102 | console.log(`✅ Task message format: ${hasTaskMessages ? 'PASS' : 'FAIL'}`);
103 | console.log(
104 | `✅ Completion message format: ${hasCompletionMessage ? 'PASS' : 'FAIL'}`
105 | );
106 | }
107 | }
108 |
109 | /**
110 | * Mock MCP Logger for testing
111 | */
112 | class MockMCPLogger {
113 | constructor(enableDebug = true) {
114 | this.enableDebug = enableDebug;
115 | this.logs = [];
116 | }
117 |
118 | _log(level, ...args) {
119 | const entry = {
120 | level,
121 | timestamp: Date.now(),
122 | message: args.join(' ')
123 | };
124 | this.logs.push(entry);
125 |
126 | if (this.enableDebug) {
127 | const color =
128 | {
129 | info: chalk.blue,
130 | warn: chalk.yellow,
131 | error: chalk.red,
132 | debug: chalk.gray,
133 | success: chalk.green
134 | }[level] || chalk.white;
135 |
136 | console.log(color(`[${level.toUpperCase()}]`), ...args);
137 | }
138 | }
139 |
140 | info(...args) {
141 | this._log('info', ...args);
142 | }
143 | warn(...args) {
144 | this._log('warn', ...args);
145 | }
146 | error(...args) {
147 | this._log('error', ...args);
148 | }
149 | debug(...args) {
150 | this._log('debug', ...args);
151 | }
152 | success(...args) {
153 | this._log('success', ...args);
154 | }
155 |
156 | getLogs() {
157 | return this.logs;
158 | }
159 | }
160 |
161 | /**
162 | * Get the path to the sample PRD file
163 | */
164 | function getSamplePRDPath() {
165 | return path.resolve(PROJECT_ROOT, 'tests', 'fixtures', 'sample-prd.txt');
166 | }
167 |
168 | /**
169 | * Create a basic test config file
170 | */
171 | function createTestConfig() {
172 | const testConfig = {
173 | models: {
174 | main: {
175 | provider: 'anthropic',
176 | modelId: 'claude-3-5-sonnet',
177 | maxTokens: 64000,
178 | temperature: 0.2
179 | },
180 | research: {
181 | provider: 'perplexity',
182 | modelId: 'sonar-pro',
183 | maxTokens: 8700,
184 | temperature: 0.1
185 | },
186 | fallback: {
187 | provider: 'anthropic',
188 | modelId: 'claude-3-5-sonnet',
189 | maxTokens: 64000,
190 | temperature: 0.2
191 | }
192 | },
193 | global: {
194 | logLevel: 'info',
195 | debug: false,
196 | defaultSubtasks: 5,
197 | defaultPriority: 'medium',
198 | projectName: 'Task Master Test',
199 | ollamaBaseURL: 'http://localhost:11434/api',
200 | bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com'
201 | }
202 | };
203 |
204 | const taskmasterDir = path.join(__dirname, '.taskmaster');
205 | const configPath = path.join(taskmasterDir, 'config.json');
206 |
207 | // Create .taskmaster directory if it doesn't exist
208 | if (!fs.existsSync(taskmasterDir)) {
209 | fs.mkdirSync(taskmasterDir, { recursive: true });
210 | }
211 |
212 | fs.writeFileSync(configPath, JSON.stringify(testConfig, null, 2));
213 | return configPath;
214 | }
215 |
216 | /**
217 | * Setup test files and configuration
218 | */
219 | function setupTestFiles(testName) {
220 | const testPRDPath = getSamplePRDPath();
221 | const testTasksPath = path.join(__dirname, `test-${testName}-tasks.json`);
222 | const configPath = createTestConfig();
223 |
224 | // Clean up existing files
225 | if (fs.existsSync(testTasksPath)) {
226 | fs.unlinkSync(testTasksPath);
227 | }
228 |
229 | return { testPRDPath, testTasksPath, configPath };
230 | }
231 |
232 | /**
233 | * Clean up test files
234 | */
235 | function cleanupTestFiles(testTasksPath, configPath) {
236 | if (fs.existsSync(testTasksPath)) fs.unlinkSync(testTasksPath);
237 | if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
238 | }
239 |
240 | /**
241 | * Run parsePRD with configurable options
242 | */
243 | async function runParsePRD(testPRDPath, testTasksPath, numTasks, options = {}) {
244 | const startTime = Date.now();
245 |
246 | const result = await parsePRD(testPRDPath, testTasksPath, numTasks, {
247 | force: true,
248 | append: false,
249 | research: false,
250 | projectRoot: PROJECT_ROOT,
251 | ...options
252 | });
253 |
254 | const endTime = Date.now();
255 | const duration = endTime - startTime;
256 |
257 | return { result, duration };
258 | }
259 |
260 | /**
261 | * Verify task file existence and structure
262 | */
263 | function verifyTaskResults(testTasksPath) {
264 | if (fs.existsSync(testTasksPath)) {
265 | const tasksData = JSON.parse(fs.readFileSync(testTasksPath, 'utf8'));
266 | console.log(
267 | chalk.green(
268 | `\n✅ Tasks file created with ${tasksData.tasks.length} tasks`
269 | )
270 | );
271 |
272 | // Verify task structure
273 | const firstTask = tasksData.tasks[0];
274 | if (firstTask && firstTask.id && firstTask.title && firstTask.description) {
275 | console.log(chalk.green('✅ Task structure is valid'));
276 | return true;
277 | } else {
278 | console.log(chalk.red('❌ Task structure is invalid'));
279 | return false;
280 | }
281 | } else {
282 | console.log(chalk.red('❌ Tasks file was not created'));
283 | return false;
284 | }
285 | }
286 |
287 | /**
288 | * Print MCP-specific logs and validation
289 | */
290 | function printMCPResults(mcpLogger, progressReporter) {
291 | // Print progress summary
292 | progressReporter.printSummary();
293 |
294 | // Print MCP logs
295 | console.log(chalk.cyan('\n=== MCP Logs ==='));
296 | const logs = mcpLogger.getLogs();
297 | logs.forEach((log, index) => {
298 | const color =
299 | {
300 | info: chalk.blue,
301 | warn: chalk.yellow,
302 | error: chalk.red,
303 | debug: chalk.gray,
304 | success: chalk.green
305 | }[log.level] || chalk.white;
306 | console.log(
307 | `${index + 1}. ${color(`[${log.level.toUpperCase()}]`)} ${log.message}`
308 | );
309 | });
310 |
311 | // Verify MCP-specific message formats (should use emoji indicators)
312 | const hasEmojiIndicators = progressReporter
313 | .getProgressHistory()
314 | .some((entry) => /[🔴🟠🟢]/u.test(entry.message));
315 |
316 | console.log(chalk.cyan('\n=== MCP-Specific Validation ==='));
317 | console.log(
318 | `✅ Emoji priority indicators: ${hasEmojiIndicators ? 'PASS' : 'FAIL'}`
319 | );
320 |
321 | return { hasEmojiIndicators, logs };
322 | }
323 |
324 | /**
325 | * Test MCP streaming with proper MCP context
326 | */
327 | async function testMCPStreaming(numTasks = 10) {
328 | console.log(chalk.cyan('🧪 Testing MCP Streaming Functionality\n'));
329 |
330 | const { testPRDPath, testTasksPath, configPath } = setupTestFiles('mcp');
331 | const progressReporter = new MockProgressReporter(true);
332 | const mcpLogger = new MockMCPLogger(true); // Enable debug for MCP context
333 |
334 | try {
335 | console.log(chalk.yellow('Starting MCP streaming test...'));
336 |
337 | const { result, duration } = await runParsePRD(
338 | testPRDPath,
339 | testTasksPath,
340 | numTasks,
341 | {
342 | reportProgress: progressReporter.reportProgress.bind(progressReporter),
343 | mcpLog: mcpLogger // Add MCP context - this is the key difference
344 | }
345 | );
346 |
347 | console.log(
348 | chalk.green(`\n✅ MCP streaming test completed in ${duration}ms`)
349 | );
350 |
351 | const { hasEmojiIndicators, logs } = printMCPResults(
352 | mcpLogger,
353 | progressReporter
354 | );
355 | const isValidStructure = verifyTaskResults(testTasksPath);
356 |
357 | return {
358 | success: true,
359 | duration,
360 | progressHistory: progressReporter.getProgressHistory(),
361 | mcpLogs: logs,
362 | hasEmojiIndicators,
363 | result
364 | };
365 | } catch (error) {
366 | console.error(chalk.red(`❌ MCP streaming test failed: ${error.message}`));
367 | return {
368 | success: false,
369 | error: error.message
370 | };
371 | } finally {
372 | cleanupTestFiles(testTasksPath, configPath);
373 | }
374 | }
375 |
376 | /**
377 | * Test CLI streaming (no reportProgress)
378 | */
379 | async function testCLIStreaming(numTasks = 10) {
380 | console.log(chalk.cyan('🧪 Testing CLI Streaming (No Progress Reporter)\n'));
381 |
382 | const { testPRDPath, testTasksPath, configPath } = setupTestFiles('cli');
383 |
384 | try {
385 | console.log(chalk.yellow('Starting CLI streaming test...'));
386 |
387 | // No reportProgress provided; CLI text mode uses the default streaming reporter
388 | const { result, duration } = await runParsePRD(
389 | testPRDPath,
390 | testTasksPath,
391 | numTasks
392 | );
393 |
394 | console.log(
395 | chalk.green(`\n✅ CLI streaming test completed in ${duration}ms`)
396 | );
397 |
398 | const isValidStructure = verifyTaskResults(testTasksPath);
399 |
400 | return {
401 | success: true,
402 | duration,
403 | result
404 | };
405 | } catch (error) {
406 | console.error(chalk.red(`❌ CLI streaming test failed: ${error.message}`));
407 | return {
408 | success: false,
409 | error: error.message
410 | };
411 | } finally {
412 | cleanupTestFiles(testTasksPath, configPath);
413 | }
414 | }
415 |
416 | /**
417 | * Test non-streaming functionality
418 | */
419 | async function testNonStreaming(numTasks = 10) {
420 | console.log(chalk.cyan('🧪 Testing Non-Streaming Functionality\n'));
421 |
422 | const { testPRDPath, testTasksPath, configPath } =
423 | setupTestFiles('non-streaming');
424 |
425 | try {
426 | console.log(chalk.yellow('Starting non-streaming test...'));
427 |
428 | // Force non-streaming by not providing reportProgress
429 | const { result, duration } = await runParsePRD(
430 | testPRDPath,
431 | testTasksPath,
432 | numTasks
433 | );
434 |
435 | console.log(
436 | chalk.green(`\n✅ Non-streaming test completed in ${duration}ms`)
437 | );
438 |
439 | const isValidStructure = verifyTaskResults(testTasksPath);
440 |
441 | return {
442 | success: true,
443 | duration,
444 | result
445 | };
446 | } catch (error) {
447 | console.error(chalk.red(`❌ Non-streaming test failed: ${error.message}`));
448 | return {
449 | success: false,
450 | error: error.message
451 | };
452 | } finally {
453 | cleanupTestFiles(testTasksPath, configPath);
454 | }
455 | }
456 |
457 | /**
458 | * Compare results between streaming and non-streaming
459 | */
460 | function compareResults(streamingResult, nonStreamingResult) {
461 | console.log(chalk.cyan('\n=== Results Comparison ==='));
462 |
463 | if (!streamingResult.success || !nonStreamingResult.success) {
464 | console.log(chalk.red('❌ Cannot compare - one or both tests failed'));
465 | return;
466 | }
467 |
468 | console.log(`Streaming duration: ${streamingResult.duration}ms`);
469 | console.log(`Non-streaming duration: ${nonStreamingResult.duration}ms`);
470 |
471 | const durationDiff = Math.abs(
472 | streamingResult.duration - nonStreamingResult.duration
473 | );
474 | const durationDiffPercent = Math.round(
475 | (durationDiff /
476 | Math.max(streamingResult.duration, nonStreamingResult.duration)) *
477 | 100
478 | );
479 |
480 | console.log(
481 | `Duration difference: ${durationDiff}ms (${durationDiffPercent}%)`
482 | );
483 |
484 | if (streamingResult.progressHistory) {
485 | console.log(
486 | `Streaming progress reports: ${streamingResult.progressHistory.length}`
487 | );
488 | }
489 |
490 | console.log(chalk.green('✅ Both methods completed successfully'));
491 | }
492 |
493 | /**
494 | * Main test runner
495 | */
496 | async function main() {
497 | const args = process.argv.slice(2);
498 | const testType = args[0] || 'streaming';
499 | const numTasks = parseInt(args[1]) || 8;
500 |
501 | console.log(chalk.bold.cyan('🚀 Task Master PRD Streaming Tests\n'));
502 | console.log(chalk.blue(`Test type: ${testType}`));
503 | console.log(chalk.blue(`Number of tasks: ${numTasks}\n`));
504 |
505 | try {
506 | switch (testType.toLowerCase()) {
507 | case 'mcp':
508 | case 'mcp-streaming':
509 | await testMCPStreaming(numTasks);
510 | break;
511 |
512 | case 'cli':
513 | case 'cli-streaming':
514 | await testCLIStreaming(numTasks);
515 | break;
516 |
517 | case 'non-streaming':
518 | case 'non':
519 | await testNonStreaming(numTasks);
520 | break;
521 |
522 | case 'both': {
523 | console.log(
524 | chalk.yellow(
525 | 'Running both MCP streaming and non-streaming tests...\n'
526 | )
527 | );
528 | const mcpStreamingResult = await testMCPStreaming(numTasks);
529 | console.log('\n' + '='.repeat(60) + '\n');
530 | const nonStreamingResult = await testNonStreaming(numTasks);
531 | compareResults(mcpStreamingResult, nonStreamingResult);
532 | break;
533 | }
534 |
535 | case 'all': {
536 | console.log(chalk.yellow('Running all test types...\n'));
537 | const mcpResult = await testMCPStreaming(numTasks);
538 | console.log('\n' + '='.repeat(60) + '\n');
539 | const cliResult = await testCLIStreaming(numTasks);
540 | console.log('\n' + '='.repeat(60) + '\n');
541 | const nonStreamResult = await testNonStreaming(numTasks);
542 |
543 | console.log(chalk.cyan('\n=== All Tests Summary ==='));
544 | console.log(
545 | `MCP Streaming: ${mcpResult.success ? '✅ PASS' : '❌ FAIL'} ${mcpResult.hasEmojiIndicators ? '(✅ Emojis)' : '(❌ No Emojis)'}`
546 | );
547 | console.log(
548 | `CLI Streaming: ${cliResult.success ? '✅ PASS' : '❌ FAIL'}`
549 | );
550 | console.log(
551 | `Non-streaming: ${nonStreamResult.success ? '✅ PASS' : '❌ FAIL'}`
552 | );
553 | break;
554 | }
555 |
556 | default:
557 | console.log(chalk.red(`Unknown test type: ${testType}`));
558 | console.log(
559 | chalk.yellow(
560 | 'Available options: mcp-streaming, cli-streaming, non-streaming, both, all'
561 | )
562 | );
563 | process.exit(1);
564 | }
565 |
566 | console.log(chalk.green('\n🎉 Tests completed successfully!'));
567 | } catch (error) {
568 | console.error(chalk.red(`\n❌ Test failed: ${error.message}`));
569 | console.error(chalk.red(error.stack));
570 | process.exit(1);
571 | }
572 | }
573 |
574 | // Run if called directly
575 | if (import.meta.url === `file://${process.argv[1]}`) {
576 | main();
577 | }
578 |
```
--------------------------------------------------------------------------------
/.taskmaster/templates/example_prd_rpg.md:
--------------------------------------------------------------------------------
```markdown
1 | <rpg-method>
2 | # Repository Planning Graph (RPG) Method - PRD Template
3 |
4 | This template teaches you (AI or human) how to create structured, dependency-aware PRDs using the RPG methodology from Microsoft Research. The key insight: separate WHAT (functional) from HOW (structural), then connect them with explicit dependencies.
5 |
6 | ## Core Principles
7 |
8 | 1. **Dual-Semantics**: Think functional (capabilities) AND structural (code organization) separately, then map them
9 | 2. **Explicit Dependencies**: Never assume - always state what depends on what
10 | 3. **Topological Order**: Build foundation first, then layers on top
11 | 4. **Progressive Refinement**: Start broad, refine iteratively
12 |
13 | ## How to Use This Template
14 |
15 | - Follow the instructions in each `<instruction>` block
16 | - Look at `<example>` blocks to see good vs bad patterns
17 | - Fill in the content sections with your project details
18 | - The AI reading this will learn the RPG method by following along
19 | - Task Master will parse the resulting PRD into dependency-aware tasks
20 |
21 | ## Recommended Tools for Creating PRDs
22 |
23 | When using this template to **create** a PRD (not parse it), use **code-context-aware AI assistants** for best results:
24 |
25 | **Why?** The AI needs to understand your existing codebase to make good architectural decisions about modules, dependencies, and integration points.
26 |
27 | **Recommended tools:**
28 | - **Claude Code** (claude-code CLI) - Best for structured reasoning and large contexts
29 | - **Cursor/Windsurf** - IDE integration with full codebase context
30 | - **Gemini CLI** (gemini-cli) - Massive context window for large codebases
31 | - **Codex/Grok CLI** - Strong code generation with context awareness
32 |
33 | **Note:** Once your PRD is created, `task-master parse-prd` works with any configured AI model - it just needs to read the PRD text itself, not your codebase.
34 | </rpg-method>
35 |
36 | ---
37 |
38 | <overview>
39 | <instruction>
40 | Start with the problem, not the solution. Be specific about:
41 | - What pain point exists?
42 | - Who experiences it?
43 | - Why existing solutions don't work?
44 | - What success looks like (measurable outcomes)?
45 |
46 | Keep this section focused - don't jump into implementation details yet.
47 | </instruction>
48 |
49 | ## Problem Statement
50 | [Describe the core problem. Be concrete about user pain points.]
51 |
52 | ## Target Users
53 | [Define personas, their workflows, and what they're trying to achieve.]
54 |
55 | ## Success Metrics
56 | [Quantifiable outcomes. Examples: "80% task completion via autopilot", "< 5% manual intervention rate"]
57 |
58 | </overview>
59 |
60 | ---
61 |
62 | <functional-decomposition>
63 | <instruction>
64 | Now think about CAPABILITIES (what the system DOES), not code structure yet.
65 |
66 | Step 1: Identify high-level capability domains
67 | - Think: "What major things does this system do?"
68 | - Examples: Data Management, Core Processing, Presentation Layer
69 |
70 | Step 2: For each capability, enumerate specific features
71 | - Use explore-exploit strategy:
72 | * Exploit: What features are REQUIRED for core value?
73 | * Explore: What features make this domain COMPLETE?
74 |
75 | Step 3: For each feature, define:
76 | - Description: What it does in one sentence
77 | - Inputs: What data/context it needs
78 | - Outputs: What it produces/returns
79 | - Behavior: Key logic or transformations
80 |
81 | <example type="good">
82 | Capability: Data Validation
83 | Feature: Schema validation
84 | - Description: Validate JSON payloads against defined schemas
85 | - Inputs: JSON object, schema definition
86 | - Outputs: Validation result (pass/fail) + error details
87 | - Behavior: Iterate fields, check types, enforce constraints
88 |
89 | Feature: Business rule validation
90 | - Description: Apply domain-specific validation rules
91 | - Inputs: Validated data object, rule set
92 | - Outputs: Boolean + list of violated rules
93 | - Behavior: Execute rules sequentially, short-circuit on failure
94 | </example>
95 |
96 | <example type="bad">
97 | Capability: validation.js
98 | (Problem: This is a FILE, not a CAPABILITY. Mixing structure into functional thinking.)
99 |
100 | Capability: Validation
101 | Feature: Make sure data is good
102 | (Problem: Too vague. No inputs/outputs. Not actionable.)
103 | </example>
104 | </instruction>
105 |
106 | ## Capability Tree
107 |
108 | ### Capability: [Name]
109 | [Brief description of what this capability domain covers]
110 |
111 | #### Feature: [Name]
112 | - **Description**: [One sentence]
113 | - **Inputs**: [What it needs]
114 | - **Outputs**: [What it produces]
115 | - **Behavior**: [Key logic]
116 |
117 | #### Feature: [Name]
118 | - **Description**:
119 | - **Inputs**:
120 | - **Outputs**:
121 | - **Behavior**:
122 |
123 | ### Capability: [Name]
124 | ...
125 |
126 | </functional-decomposition>
127 |
128 | ---
129 |
130 | <structural-decomposition>
131 | <instruction>
132 | NOW think about code organization. Map capabilities to actual file/folder structure.
133 |
134 | Rules:
135 | 1. Each capability maps to a module (folder or file)
136 | 2. Features within a capability map to functions/classes
137 | 3. Use clear module boundaries - each module has ONE responsibility
138 | 4. Define what each module exports (public interface)
139 |
140 | The goal: Create a clear mapping between "what it does" (functional) and "where it lives" (structural).
141 |
142 | <example type="good">
143 | Capability: Data Validation
144 | → Maps to: src/validation/
145 | ├── schema-validator.js (Schema validation feature)
146 | ├── rule-validator.js (Business rule validation feature)
147 | └── index.js (Public exports)
148 |
149 | Exports:
150 | - validateSchema(data, schema)
151 | - validateRules(data, rules)
152 | </example>
153 |
154 | <example type="bad">
155 | Capability: Data Validation
156 | → Maps to: src/utils.js
157 | (Problem: "utils" is not a clear module boundary. Where do I find validation logic?)
158 |
159 | Capability: Data Validation
160 | → Maps to: src/validation/everything.js
161 | (Problem: One giant file. Features should map to separate files for maintainability.)
162 | </example>
163 | </instruction>
164 |
165 | ## Repository Structure
166 |
167 | ```
168 | project-root/
169 | ├── src/
170 | │ ├── [module-name]/ # Maps to: [Capability Name]
171 | │ │ ├── [file].js # Maps to: [Feature Name]
172 | │ │ └── index.js # Public exports
173 | │ └── [module-name]/
174 | ├── tests/
175 | └── docs/
176 | ```
177 |
178 | ## Module Definitions
179 |
180 | ### Module: [Name]
181 | - **Maps to capability**: [Capability from functional decomposition]
182 | - **Responsibility**: [Single clear purpose]
183 | - **File structure**:
184 | ```
185 | module-name/
186 | ├── feature1.js
187 | ├── feature2.js
188 | └── index.js
189 | ```
190 | - **Exports**:
191 | - `functionName()` - [what it does]
192 | - `ClassName` - [what it does]
193 |
194 | </structural-decomposition>
195 |
196 | ---
197 |
198 | <dependency-graph>
199 | <instruction>
200 | This is THE CRITICAL SECTION for Task Master parsing.
201 |
202 | Define explicit dependencies between modules. This creates the topological order for task execution.
203 |
204 | Rules:
205 | 1. List modules in dependency order (foundation first)
206 | 2. For each module, state what it depends on
207 | 3. Foundation modules should have NO dependencies
208 | 4. Every non-foundation module should depend on at least one other module
209 | 5. Think: "What must EXIST before I can build this module?"
210 |
211 | <example type="good">
212 | Foundation Layer (no dependencies):
213 | - error-handling: No dependencies
214 | - config-manager: No dependencies
215 | - base-types: No dependencies
216 |
217 | Data Layer:
218 | - schema-validator: Depends on [base-types, error-handling]
219 | - data-ingestion: Depends on [schema-validator, config-manager]
220 |
221 | Core Layer:
222 | - algorithm-engine: Depends on [base-types, error-handling]
223 | - pipeline-orchestrator: Depends on [algorithm-engine, data-ingestion]
224 | </example>
225 |
226 | <example type="bad">
227 | - validation: Depends on API
228 | - API: Depends on validation
229 | (Problem: Circular dependency. This will cause build/runtime issues.)
230 |
231 | - user-auth: Depends on everything
232 | (Problem: Too many dependencies. Should be more focused.)
233 | </example>
234 | </instruction>
235 |
236 | ## Dependency Chain
237 |
238 | ### Foundation Layer (Phase 0)
239 | No dependencies - these are built first.
240 |
241 | - **[Module Name]**: [What it provides]
242 | - **[Module Name]**: [What it provides]
243 |
244 | ### [Layer Name] (Phase 1)
245 | - **[Module Name]**: Depends on [[module-from-phase-0], [module-from-phase-0]]
246 | - **[Module Name]**: Depends on [[module-from-phase-0]]
247 |
248 | ### [Layer Name] (Phase 2)
249 | - **[Module Name]**: Depends on [[module-from-phase-1], [module-from-foundation]]
250 |
251 | [Continue building up layers...]
252 |
253 | </dependency-graph>
254 |
255 | ---
256 |
257 | <implementation-roadmap>
258 | <instruction>
259 | Turn the dependency graph into concrete development phases.
260 |
261 | Each phase should:
262 | 1. Have clear entry criteria (what must exist before starting)
263 | 2. Contain tasks that can be parallelized (no inter-dependencies within phase)
264 | 3. Have clear exit criteria (how do we know phase is complete?)
265 | 4. Build toward something USABLE (not just infrastructure)
266 |
267 | Phase ordering follows topological sort of dependency graph.
268 |
269 | <example type="good">
270 | Phase 0: Foundation
271 | Entry: Clean repository
272 | Tasks:
273 | - Implement error handling utilities
274 | - Create base type definitions
275 | - Setup configuration system
276 | Exit: Other modules can import foundation without errors
277 |
278 | Phase 1: Data Layer
279 | Entry: Phase 0 complete
280 | Tasks:
281 | - Implement schema validator (uses: base types, error handling)
282 | - Build data ingestion pipeline (uses: validator, config)
283 | Exit: End-to-end data flow from input to validated output
284 | </example>
285 |
286 | <example type="bad">
287 | Phase 1: Build Everything
288 | Tasks:
289 | - API
290 | - Database
291 | - UI
292 | - Tests
293 | (Problem: No clear focus. Too broad. Dependencies not considered.)
294 | </example>
295 | </instruction>
296 |
297 | ## Development Phases
298 |
299 | ### Phase 0: [Foundation Name]
300 | **Goal**: [What foundational capability this establishes]
301 |
302 | **Entry Criteria**: [What must be true before starting]
303 |
304 | **Tasks**:
305 | - [ ] [Task name] (depends on: [none or list])
306 | - Acceptance criteria: [How we know it's done]
307 | - Test strategy: [What tests prove it works]
308 |
309 | - [ ] [Task name] (depends on: [none or list])
310 |
311 | **Exit Criteria**: [Observable outcome that proves phase complete]
312 |
313 | **Delivers**: [What can users/developers do after this phase?]
314 |
315 | ---
316 |
317 | ### Phase 1: [Layer Name]
318 | **Goal**:
319 |
320 | **Entry Criteria**: Phase 0 complete
321 |
322 | **Tasks**:
323 | - [ ] [Task name] (depends on: [[tasks-from-phase-0]])
324 | - [ ] [Task name] (depends on: [[tasks-from-phase-0]])
325 |
326 | **Exit Criteria**:
327 |
328 | **Delivers**:
329 |
330 | ---
331 |
332 | [Continue with more phases...]
333 |
334 | </implementation-roadmap>
335 |
336 | ---
337 |
338 | <test-strategy>
339 | <instruction>
340 | Define how testing will be integrated throughout development (TDD approach).
341 |
342 | Specify:
343 | 1. Test pyramid ratios (unit vs integration vs e2e)
344 | 2. Coverage requirements
345 | 3. Critical test scenarios
346 | 4. Test generation guidelines for Surgical Test Generator
347 |
348 | This section guides the AI when generating tests during the RED phase of TDD.
349 |
350 | <example type="good">
351 | Critical Test Scenarios for Data Validation module:
352 | - Happy path: Valid data passes all checks
353 | - Edge cases: Empty strings, null values, boundary numbers
354 | - Error cases: Invalid types, missing required fields
355 | - Integration: Validator works with ingestion pipeline
356 | </example>
357 | </instruction>
358 |
359 | ## Test Pyramid
360 |
361 | ```
362 | /\
363 | /E2E\ ← [X]% (End-to-end, slow, comprehensive)
364 | /------\
365 | /Integration\ ← [Y]% (Module interactions)
366 | /------------\
367 | / Unit Tests \ ← [Z]% (Fast, isolated, deterministic)
368 | /----------------\
369 | ```
370 |
371 | ## Coverage Requirements
372 | - Line coverage: [X]% minimum
373 | - Branch coverage: [X]% minimum
374 | - Function coverage: [X]% minimum
375 | - Statement coverage: [X]% minimum
376 |
377 | ## Critical Test Scenarios
378 |
379 | ### [Module/Feature Name]
380 | **Happy path**:
381 | - [Scenario description]
382 | - Expected: [What should happen]
383 |
384 | **Edge cases**:
385 | - [Scenario description]
386 | - Expected: [What should happen]
387 |
388 | **Error cases**:
389 | - [Scenario description]
390 | - Expected: [How system handles failure]
391 |
392 | **Integration points**:
393 | - [What interactions to test]
394 | - Expected: [End-to-end behavior]
395 |
396 | ## Test Generation Guidelines
397 | [Specific instructions for Surgical Test Generator about what to focus on, what patterns to follow, project-specific test conventions]
398 |
399 | </test-strategy>
400 |
401 | ---
402 |
403 | <architecture>
404 | <instruction>
405 | Describe technical architecture, data models, and key design decisions.
406 |
407 | Keep this section AFTER functional/structural decomposition - implementation details come after understanding structure.
408 | </instruction>
409 |
410 | ## System Components
411 | [Major architectural pieces and their responsibilities]
412 |
413 | ## Data Models
414 | [Core data structures, schemas, database design]
415 |
416 | ## Technology Stack
417 | [Languages, frameworks, key libraries]
418 |
419 | **Decision: [Technology/Pattern]**
420 | - **Rationale**: [Why chosen]
421 | - **Trade-offs**: [What we're giving up]
422 | - **Alternatives considered**: [What else we looked at]
423 |
424 | </architecture>
425 |
426 | ---
427 |
428 | <risks>
429 | <instruction>
430 | Identify risks that could derail development and how to mitigate them.
431 |
432 | Categories:
433 | - Technical risks (complexity, unknowns)
434 | - Dependency risks (blocking issues)
435 | - Scope risks (creep, underestimation)
436 | </instruction>
437 |
438 | ## Technical Risks
439 | **Risk**: [Description]
440 | - **Impact**: [High/Medium/Low - effect on project]
441 | - **Likelihood**: [High/Medium/Low]
442 | - **Mitigation**: [How to address]
443 | - **Fallback**: [Plan B if mitigation fails]
444 |
445 | ## Dependency Risks
446 | [External dependencies, blocking issues]
447 |
448 | ## Scope Risks
449 | [Scope creep, underestimation, unclear requirements]
450 |
451 | </risks>
452 |
453 | ---
454 |
455 | <appendix>
456 | ## References
457 | [Papers, documentation, similar systems]
458 |
459 | ## Glossary
460 | [Domain-specific terms]
461 |
462 | ## Open Questions
463 | [Things to resolve during development]
464 | </appendix>
465 |
466 | ---
467 |
468 | <task-master-integration>
469 | # How Task Master Uses This PRD
470 |
471 | When you run `task-master parse-prd <file>.txt`, the parser:
472 |
473 | 1. **Extracts capabilities** → Main tasks
474 | - Each `### Capability:` becomes a top-level task
475 |
476 | 2. **Extracts features** → Subtasks
477 | - Each `#### Feature:` becomes a subtask under its capability
478 |
479 | 3. **Parses dependencies** → Task dependencies
480 | - `Depends on: [X, Y]` sets task.dependencies = ["X", "Y"]
481 |
482 | 4. **Orders by phases** → Task priorities
483 | - Phase 0 tasks = highest priority
484 | - Phase N tasks = lower priority, properly sequenced
485 |
486 | 5. **Uses test strategy** → Test generation context
487 | - Feeds test scenarios to Surgical Test Generator during implementation
488 |
489 | **Result**: A dependency-aware task graph that can be executed in topological order.
490 |
491 | ## Why RPG Structure Matters
492 |
493 | Traditional flat PRDs lead to:
494 | - ❌ Unclear task dependencies
495 | - ❌ Arbitrary task ordering
496 | - ❌ Circular dependencies discovered late
497 | - ❌ Poorly scoped tasks
498 |
499 | RPG-structured PRDs provide:
500 | - ✅ Explicit dependency chains
501 | - ✅ Topological execution order
502 | - ✅ Clear module boundaries
503 | - ✅ Validated task graph before implementation
504 |
505 | ## Tips for Best Results
506 |
507 | 1. **Spend time on dependency graph** - This is the most valuable section for Task Master
508 | 2. **Keep features atomic** - Each feature should be independently testable
509 | 3. **Progressive refinement** - Start broad, use `task-master expand` to break down complex tasks
510 | 4. **Use research mode** - `task-master parse-prd --research` leverages AI for better task generation
511 | </task-master-integration>
512 |
```
--------------------------------------------------------------------------------
/assets/example_prd_rpg.txt:
--------------------------------------------------------------------------------
```
1 | <rpg-method>
2 | # Repository Planning Graph (RPG) Method - PRD Template
3 |
4 | This template teaches you (AI or human) how to create structured, dependency-aware PRDs using the RPG methodology from Microsoft Research. The key insight: separate WHAT (functional) from HOW (structural), then connect them with explicit dependencies.
5 |
6 | ## Core Principles
7 |
8 | 1. **Dual-Semantics**: Think functional (capabilities) AND structural (code organization) separately, then map them
9 | 2. **Explicit Dependencies**: Never assume - always state what depends on what
10 | 3. **Topological Order**: Build foundation first, then layers on top
11 | 4. **Progressive Refinement**: Start broad, refine iteratively
12 |
13 | ## How to Use This Template
14 |
15 | - Follow the instructions in each `<instruction>` block
16 | - Look at `<example>` blocks to see good vs bad patterns
17 | - Fill in the content sections with your project details
18 | - The AI reading this will learn the RPG method by following along
19 | - Task Master will parse the resulting PRD into dependency-aware tasks
20 |
21 | ## Recommended Tools for Creating PRDs
22 |
23 | When using this template to **create** a PRD (not parse it), use **code-context-aware AI assistants** for best results:
24 |
25 | **Why?** The AI needs to understand your existing codebase to make good architectural decisions about modules, dependencies, and integration points.
26 |
27 | **Recommended tools:**
28 | - **Claude Code** (claude-code CLI) - Best for structured reasoning and large contexts
29 | - **Cursor/Windsurf** - IDE integration with full codebase context
30 | - **Gemini CLI** (gemini-cli) - Massive context window for large codebases
31 | - **Codex/Grok CLI** - Strong code generation with context awareness
32 |
33 | **Note:** Once your PRD is created, `task-master parse-prd` works with any configured AI model - it just needs to read the PRD text itself, not your codebase.
34 | </rpg-method>
35 |
36 | ---
37 |
38 | <overview>
39 | <instruction>
40 | Start with the problem, not the solution. Be specific about:
41 | - What pain point exists?
42 | - Who experiences it?
43 | - Why existing solutions don't work?
44 | - What success looks like (measurable outcomes)?
45 |
46 | Keep this section focused - don't jump into implementation details yet.
47 | </instruction>
48 |
49 | ## Problem Statement
50 | [Describe the core problem. Be concrete about user pain points.]
51 |
52 | ## Target Users
53 | [Define personas, their workflows, and what they're trying to achieve.]
54 |
55 | ## Success Metrics
56 | [Quantifiable outcomes. Examples: "80% task completion via autopilot", "< 5% manual intervention rate"]
57 |
58 | </overview>
59 |
60 | ---
61 |
62 | <functional-decomposition>
63 | <instruction>
64 | Now think about CAPABILITIES (what the system DOES), not code structure yet.
65 |
66 | Step 1: Identify high-level capability domains
67 | - Think: "What major things does this system do?"
68 | - Examples: Data Management, Core Processing, Presentation Layer
69 |
70 | Step 2: For each capability, enumerate specific features
71 | - Use explore-exploit strategy:
72 | * Exploit: What features are REQUIRED for core value?
73 | * Explore: What features make this domain COMPLETE?
74 |
75 | Step 3: For each feature, define:
76 | - Description: What it does in one sentence
77 | - Inputs: What data/context it needs
78 | - Outputs: What it produces/returns
79 | - Behavior: Key logic or transformations
80 |
81 | <example type="good">
82 | Capability: Data Validation
83 | Feature: Schema validation
84 | - Description: Validate JSON payloads against defined schemas
85 | - Inputs: JSON object, schema definition
86 | - Outputs: Validation result (pass/fail) + error details
87 | - Behavior: Iterate fields, check types, enforce constraints
88 |
89 | Feature: Business rule validation
90 | - Description: Apply domain-specific validation rules
91 | - Inputs: Validated data object, rule set
92 | - Outputs: Boolean + list of violated rules
93 | - Behavior: Execute rules sequentially, short-circuit on failure
94 | </example>
95 |
96 | <example type="bad">
97 | Capability: validation.js
98 | (Problem: This is a FILE, not a CAPABILITY. Mixing structure into functional thinking.)
99 |
100 | Capability: Validation
101 | Feature: Make sure data is good
102 | (Problem: Too vague. No inputs/outputs. Not actionable.)
103 | </example>
104 | </instruction>
105 |
106 | ## Capability Tree
107 |
108 | ### Capability: [Name]
109 | [Brief description of what this capability domain covers]
110 |
111 | #### Feature: [Name]
112 | - **Description**: [One sentence]
113 | - **Inputs**: [What it needs]
114 | - **Outputs**: [What it produces]
115 | - **Behavior**: [Key logic]
116 |
117 | #### Feature: [Name]
118 | - **Description**:
119 | - **Inputs**:
120 | - **Outputs**:
121 | - **Behavior**:
122 |
123 | ### Capability: [Name]
124 | ...
125 |
126 | </functional-decomposition>
127 |
128 | ---
129 |
130 | <structural-decomposition>
131 | <instruction>
132 | NOW think about code organization. Map capabilities to actual file/folder structure.
133 |
134 | Rules:
135 | 1. Each capability maps to a module (folder or file)
136 | 2. Features within a capability map to functions/classes
137 | 3. Use clear module boundaries - each module has ONE responsibility
138 | 4. Define what each module exports (public interface)
139 |
140 | The goal: Create a clear mapping between "what it does" (functional) and "where it lives" (structural).
141 |
142 | <example type="good">
143 | Capability: Data Validation
144 | → Maps to: src/validation/
145 | ├── schema-validator.js (Schema validation feature)
146 | ├── rule-validator.js (Business rule validation feature)
147 | └── index.js (Public exports)
148 |
149 | Exports:
150 | - validateSchema(data, schema)
151 | - validateRules(data, rules)
152 | </example>
153 |
154 | <example type="bad">
155 | Capability: Data Validation
156 | → Maps to: src/utils.js
157 | (Problem: "utils" is not a clear module boundary. Where do I find validation logic?)
158 |
159 | Capability: Data Validation
160 | → Maps to: src/validation/everything.js
161 | (Problem: One giant file. Features should map to separate files for maintainability.)
162 | </example>
163 | </instruction>
164 |
165 | ## Repository Structure
166 |
167 | ```
168 | project-root/
169 | ├── src/
170 | │ ├── [module-name]/ # Maps to: [Capability Name]
171 | │ │ ├── [file].js # Maps to: [Feature Name]
172 | │ │ └── index.js # Public exports
173 | │ └── [module-name]/
174 | ├── tests/
175 | └── docs/
176 | ```
177 |
178 | ## Module Definitions
179 |
180 | ### Module: [Name]
181 | - **Maps to capability**: [Capability from functional decomposition]
182 | - **Responsibility**: [Single clear purpose]
183 | - **File structure**:
184 | ```
185 | module-name/
186 | ├── feature1.js
187 | ├── feature2.js
188 | └── index.js
189 | ```
190 | - **Exports**:
191 | - `functionName()` - [what it does]
192 | - `ClassName` - [what it does]
193 |
194 | </structural-decomposition>
195 |
196 | ---
197 |
198 | <dependency-graph>
199 | <instruction>
200 | This is THE CRITICAL SECTION for Task Master parsing.
201 |
202 | Define explicit dependencies between modules. This creates the topological order for task execution.
203 |
204 | Rules:
205 | 1. List modules in dependency order (foundation first)
206 | 2. For each module, state what it depends on
207 | 3. Foundation modules should have NO dependencies
208 | 4. Every non-foundation module should depend on at least one other module
209 | 5. Think: "What must EXIST before I can build this module?"
210 |
211 | <example type="good">
212 | Foundation Layer (no dependencies):
213 | - error-handling: No dependencies
214 | - config-manager: No dependencies
215 | - base-types: No dependencies
216 |
217 | Data Layer:
218 | - schema-validator: Depends on [base-types, error-handling]
219 | - data-ingestion: Depends on [schema-validator, config-manager]
220 |
221 | Core Layer:
222 | - algorithm-engine: Depends on [base-types, error-handling]
223 | - pipeline-orchestrator: Depends on [algorithm-engine, data-ingestion]
224 | </example>
225 |
226 | <example type="bad">
227 | - validation: Depends on API
228 | - API: Depends on validation
229 | (Problem: Circular dependency. This will cause build/runtime issues.)
230 |
231 | - user-auth: Depends on everything
232 | (Problem: Too many dependencies. Should be more focused.)
233 | </example>
234 | </instruction>
235 |
236 | ## Dependency Chain
237 |
238 | ### Foundation Layer (Phase 0)
239 | No dependencies - these are built first.
240 |
241 | - **[Module Name]**: [What it provides]
242 | - **[Module Name]**: [What it provides]
243 |
244 | ### [Layer Name] (Phase 1)
245 | - **[Module Name]**: Depends on [[module-from-phase-0], [module-from-phase-0]]
246 | - **[Module Name]**: Depends on [[module-from-phase-0]]
247 |
248 | ### [Layer Name] (Phase 2)
249 | - **[Module Name]**: Depends on [[module-from-phase-1], [module-from-foundation]]
250 |
251 | [Continue building up layers...]
252 |
253 | </dependency-graph>
254 |
255 | ---
256 |
257 | <implementation-roadmap>
258 | <instruction>
259 | Turn the dependency graph into concrete development phases.
260 |
261 | Each phase should:
262 | 1. Have clear entry criteria (what must exist before starting)
263 | 2. Contain tasks that can be parallelized (no inter-dependencies within phase)
264 | 3. Have clear exit criteria (how do we know phase is complete?)
265 | 4. Build toward something USABLE (not just infrastructure)
266 |
267 | Phase ordering follows topological sort of dependency graph.
268 |
269 | <example type="good">
270 | Phase 0: Foundation
271 | Entry: Clean repository
272 | Tasks:
273 | - Implement error handling utilities
274 | - Create base type definitions
275 | - Setup configuration system
276 | Exit: Other modules can import foundation without errors
277 |
278 | Phase 1: Data Layer
279 | Entry: Phase 0 complete
280 | Tasks:
281 | - Implement schema validator (uses: base types, error handling)
282 | - Build data ingestion pipeline (uses: validator, config)
283 | Exit: End-to-end data flow from input to validated output
284 | </example>
285 |
286 | <example type="bad">
287 | Phase 1: Build Everything
288 | Tasks:
289 | - API
290 | - Database
291 | - UI
292 | - Tests
293 | (Problem: No clear focus. Too broad. Dependencies not considered.)
294 | </example>
295 | </instruction>
296 |
297 | ## Development Phases
298 |
299 | ### Phase 0: [Foundation Name]
300 | **Goal**: [What foundational capability this establishes]
301 |
302 | **Entry Criteria**: [What must be true before starting]
303 |
304 | **Tasks**:
305 | - [ ] [Task name] (depends on: [none or list])
306 | - Acceptance criteria: [How we know it's done]
307 | - Test strategy: [What tests prove it works]
308 |
309 | - [ ] [Task name] (depends on: [none or list])
310 |
311 | **Exit Criteria**: [Observable outcome that proves phase complete]
312 |
313 | **Delivers**: [What can users/developers do after this phase?]
314 |
315 | ---
316 |
317 | ### Phase 1: [Layer Name]
318 | **Goal**:
319 |
320 | **Entry Criteria**: Phase 0 complete
321 |
322 | **Tasks**:
323 | - [ ] [Task name] (depends on: [[tasks-from-phase-0]])
324 | - [ ] [Task name] (depends on: [[tasks-from-phase-0]])
325 |
326 | **Exit Criteria**:
327 |
328 | **Delivers**:
329 |
330 | ---
331 |
332 | [Continue with more phases...]
333 |
334 | </implementation-roadmap>
335 |
336 | ---
337 |
338 | <test-strategy>
339 | <instruction>
340 | Define how testing will be integrated throughout development (TDD approach).
341 |
342 | Specify:
343 | 1. Test pyramid ratios (unit vs integration vs e2e)
344 | 2. Coverage requirements
345 | 3. Critical test scenarios
346 | 4. Test generation guidelines for Surgical Test Generator
347 |
348 | This section guides the AI when generating tests during the RED phase of TDD.
349 |
350 | <example type="good">
351 | Critical Test Scenarios for Data Validation module:
352 | - Happy path: Valid data passes all checks
353 | - Edge cases: Empty strings, null values, boundary numbers
354 | - Error cases: Invalid types, missing required fields
355 | - Integration: Validator works with ingestion pipeline
356 | </example>
357 | </instruction>
358 |
359 | ## Test Pyramid
360 |
361 | ```
362 | /\
363 | /E2E\ ← [X]% (End-to-end, slow, comprehensive)
364 | /------\
365 | /Integration\ ← [Y]% (Module interactions)
366 | /------------\
367 | / Unit Tests \ ← [Z]% (Fast, isolated, deterministic)
368 | /----------------\
369 | ```
370 |
371 | ## Coverage Requirements
372 | - Line coverage: [X]% minimum
373 | - Branch coverage: [X]% minimum
374 | - Function coverage: [X]% minimum
375 | - Statement coverage: [X]% minimum
376 |
377 | ## Critical Test Scenarios
378 |
379 | ### [Module/Feature Name]
380 | **Happy path**:
381 | - [Scenario description]
382 | - Expected: [What should happen]
383 |
384 | **Edge cases**:
385 | - [Scenario description]
386 | - Expected: [What should happen]
387 |
388 | **Error cases**:
389 | - [Scenario description]
390 | - Expected: [How system handles failure]
391 |
392 | **Integration points**:
393 | - [What interactions to test]
394 | - Expected: [End-to-end behavior]
395 |
396 | ## Test Generation Guidelines
397 | [Specific instructions for Surgical Test Generator about what to focus on, what patterns to follow, project-specific test conventions]
398 |
399 | </test-strategy>
400 |
401 | ---
402 |
403 | <architecture>
404 | <instruction>
405 | Describe technical architecture, data models, and key design decisions.
406 |
407 | Keep this section AFTER functional/structural decomposition - implementation details come after understanding structure.
408 | </instruction>
409 |
410 | ## System Components
411 | [Major architectural pieces and their responsibilities]
412 |
413 | ## Data Models
414 | [Core data structures, schemas, database design]
415 |
416 | ## Technology Stack
417 | [Languages, frameworks, key libraries]
418 |
419 | **Decision: [Technology/Pattern]**
420 | - **Rationale**: [Why chosen]
421 | - **Trade-offs**: [What we're giving up]
422 | - **Alternatives considered**: [What else we looked at]
423 |
424 | </architecture>
425 |
426 | ---
427 |
428 | <risks>
429 | <instruction>
430 | Identify risks that could derail development and how to mitigate them.
431 |
432 | Categories:
433 | - Technical risks (complexity, unknowns)
434 | - Dependency risks (blocking issues)
435 | - Scope risks (creep, underestimation)
436 | </instruction>
437 |
438 | ## Technical Risks
439 | **Risk**: [Description]
440 | - **Impact**: [High/Medium/Low - effect on project]
441 | - **Likelihood**: [High/Medium/Low]
442 | - **Mitigation**: [How to address]
443 | - **Fallback**: [Plan B if mitigation fails]
444 |
445 | ## Dependency Risks
446 | [External dependencies, blocking issues]
447 |
448 | ## Scope Risks
449 | [Scope creep, underestimation, unclear requirements]
450 |
451 | </risks>
452 |
453 | ---
454 |
455 | <appendix>
456 | ## References
457 | [Papers, documentation, similar systems]
458 |
459 | ## Glossary
460 | [Domain-specific terms]
461 |
462 | ## Open Questions
463 | [Things to resolve during development]
464 | </appendix>
465 |
466 | ---
467 |
468 | <task-master-integration>
469 | # How Task Master Uses This PRD
470 |
471 | When you run `task-master parse-prd <file>.txt`, the parser:
472 |
473 | 1. **Extracts capabilities** → Main tasks
474 | - Each `### Capability:` becomes a top-level task
475 |
476 | 2. **Extracts features** → Subtasks
477 | - Each `#### Feature:` becomes a subtask under its capability
478 |
479 | 3. **Parses dependencies** → Task dependencies
480 | - `Depends on: [X, Y]` sets task.dependencies = ["X", "Y"]
481 |
482 | 4. **Orders by phases** → Task priorities
483 | - Phase 0 tasks = highest priority
484 | - Phase N tasks = lower priority, properly sequenced
485 |
486 | 5. **Uses test strategy** → Test generation context
487 | - Feeds test scenarios to Surgical Test Generator during implementation
488 |
489 | **Result**: A dependency-aware task graph that can be executed in topological order.
490 |
491 | ## Why RPG Structure Matters
492 |
493 | Traditional flat PRDs lead to:
494 | - ❌ Unclear task dependencies
495 | - ❌ Arbitrary task ordering
496 | - ❌ Circular dependencies discovered late
497 | - ❌ Poorly scoped tasks
498 |
499 | RPG-structured PRDs provide:
500 | - ✅ Explicit dependency chains
501 | - ✅ Topological execution order
502 | - ✅ Clear module boundaries
503 | - ✅ Validated task graph before implementation
504 |
505 | ## Tips for Best Results
506 |
507 | 1. **Spend time on dependency graph** - This is the most valuable section for Task Master
508 | 2. **Keep features atomic** - Each feature should be independently testable
509 | 3. **Progressive refinement** - Start broad, use `task-master expand` to break down complex tasks
510 | 4. **Use research mode** - `task-master parse-prd --research` leverages AI for better task generation
511 | </task-master-integration>
512 |
```