This is page 14 of 52. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── README.md ├── .claude │ ├── agents │ │ ├── task-checker.md │ │ ├── task-executor.md │ │ └── task-orchestrator.md │ ├── commands │ │ ├── dedupe.md │ │ └── tm │ │ ├── add-dependency │ │ │ └── add-dependency.md │ │ ├── add-subtask │ │ │ ├── add-subtask.md │ │ │ └── convert-task-to-subtask.md │ │ ├── add-task │ │ │ └── add-task.md │ │ ├── analyze-complexity │ │ │ └── analyze-complexity.md │ │ ├── complexity-report │ │ │ └── complexity-report.md │ │ ├── expand │ │ │ ├── expand-all-tasks.md │ │ │ └── expand-task.md │ │ ├── fix-dependencies │ │ │ └── fix-dependencies.md │ │ ├── generate │ │ │ └── generate-tasks.md │ │ ├── help.md │ │ ├── init │ │ │ ├── init-project-quick.md │ │ │ └── init-project.md │ │ ├── learn.md │ │ ├── list │ │ │ ├── list-tasks-by-status.md │ │ │ ├── list-tasks-with-subtasks.md │ │ │ └── list-tasks.md │ │ ├── models │ │ │ ├── setup-models.md │ │ │ └── view-models.md │ │ ├── next │ │ │ └── next-task.md │ │ ├── parse-prd │ │ │ ├── parse-prd-with-research.md │ │ │ └── parse-prd.md │ │ ├── remove-dependency │ │ │ └── remove-dependency.md │ │ ├── remove-subtask │ │ │ └── remove-subtask.md │ │ ├── remove-subtasks │ │ │ ├── remove-all-subtasks.md │ │ │ └── remove-subtasks.md │ │ ├── remove-task │ │ │ └── remove-task.md │ │ ├── set-status │ │ │ ├── to-cancelled.md │ │ │ ├── to-deferred.md │ │ │ ├── to-done.md │ │ │ ├── to-in-progress.md │ │ │ ├── to-pending.md │ │ │ └── to-review.md │ │ ├── setup │ │ │ ├── install-taskmaster.md │ │ │ └── quick-install-taskmaster.md │ │ ├── show │ │ │ └── show-task.md │ │ ├── status │ │ │ └── project-status.md │ │ ├── sync-readme │ │ │ └── sync-readme.md │ │ ├── tm-main.md │ │ ├── update │ │ │ ├── update-single-task.md │ │ │ ├── update-task.md │ │ │ └── update-tasks-from-id.md │ │ ├── utils │ │ │ └── analyze-project.md │ │ ├── validate-dependencies │ │ │ └── validate-dependencies.md │ │ └── workflows │ │ ├── auto-implement-tasks.md │ │ ├── command-pipeline.md │ │ └── smart-workflow.md │ └── TM_COMMANDS_GUIDE.md ├── .coderabbit.yaml ├── .cursor │ ├── mcp.json │ └── rules │ ├── ai_providers.mdc │ ├── ai_services.mdc │ ├── architecture.mdc │ ├── changeset.mdc │ ├── commands.mdc │ ├── context_gathering.mdc │ ├── cursor_rules.mdc │ ├── dependencies.mdc │ ├── dev_workflow.mdc │ ├── git_workflow.mdc │ ├── glossary.mdc │ ├── mcp.mdc │ ├── new_features.mdc │ ├── self_improve.mdc │ ├── tags.mdc │ ├── taskmaster.mdc │ ├── tasks.mdc │ ├── telemetry.mdc │ ├── test_workflow.mdc │ ├── tests.mdc │ ├── ui.mdc │ └── utilities.mdc ├── .cursorignore ├── .env.example ├── .github │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ ├── enhancements---feature-requests.md │ │ └── feedback.md │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bugfix.md │ │ ├── config.yml │ │ ├── feature.md │ │ └── integration.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── scripts │ │ ├── auto-close-duplicates.mjs │ │ ├── backfill-duplicate-comments.mjs │ │ ├── check-pre-release-mode.mjs │ │ ├── parse-metrics.mjs │ │ ├── release.mjs │ │ ├── tag-extension.mjs │ │ └── utils.mjs │ └── workflows │ ├── auto-close-duplicates.yml │ ├── backfill-duplicate-comments.yml │ ├── ci.yml │ ├── claude-dedupe-issues.yml │ ├── claude-docs-trigger.yml │ ├── claude-docs-updater.yml │ ├── claude-issue-triage.yml │ ├── claude.yml │ ├── extension-ci.yml │ ├── extension-release.yml │ ├── log-issue-events.yml │ ├── pre-release.yml │ ├── release-check.yml │ ├── release.yml │ ├── update-models-md.yml │ └── weekly-metrics-discord.yml ├── .gitignore ├── .kiro │ ├── hooks │ │ ├── tm-code-change-task-tracker.kiro.hook │ │ ├── tm-complexity-analyzer.kiro.hook │ │ ├── tm-daily-standup-assistant.kiro.hook │ │ ├── tm-git-commit-task-linker.kiro.hook │ │ ├── tm-pr-readiness-checker.kiro.hook │ │ ├── tm-task-dependency-auto-progression.kiro.hook │ │ └── tm-test-success-task-completer.kiro.hook │ ├── settings │ │ └── mcp.json │ └── steering │ ├── dev_workflow.md │ ├── kiro_rules.md │ ├── self_improve.md │ ├── taskmaster_hooks_workflow.md │ └── taskmaster.md ├── .manypkg.json ├── .mcp.json ├── .npmignore ├── .nvmrc ├── .taskmaster │ ├── CLAUDE.md │ ├── config.json │ ├── docs │ │ ├── MIGRATION-ROADMAP.md │ │ ├── prd-tm-start.txt │ │ ├── prd.txt │ │ ├── README.md │ │ ├── research │ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md │ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md │ │ │ ├── 2025-06-14_test-save-functionality.md │ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md │ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md │ │ ├── task-template-importing-prd.txt │ │ ├── test-prd.txt │ │ └── tm-core-phase-1.txt │ ├── reports │ │ ├── task-complexity-report_cc-kiro-hooks.json │ │ ├── task-complexity-report_test-prd-tag.json │ │ ├── task-complexity-report_tm-core-phase-1.json │ │ ├── task-complexity-report.json │ │ └── tm-core-complexity.json │ ├── state.json │ ├── tasks │ │ ├── task_001_tm-start.txt │ │ ├── task_002_tm-start.txt │ │ ├── task_003_tm-start.txt │ │ ├── task_004_tm-start.txt │ │ ├── task_007_tm-start.txt │ │ └── tasks.json │ └── templates │ └── example_prd.txt ├── .vscode │ ├── extensions.json │ └── settings.json ├── apps │ ├── cli │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── commands │ │ │ │ ├── auth.command.ts │ │ │ │ ├── context.command.ts │ │ │ │ ├── list.command.ts │ │ │ │ ├── set-status.command.ts │ │ │ │ ├── show.command.ts │ │ │ │ └── start.command.ts │ │ │ ├── index.ts │ │ │ ├── ui │ │ │ │ ├── components │ │ │ │ │ ├── dashboard.component.ts │ │ │ │ │ ├── header.component.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── next-task.component.ts │ │ │ │ │ ├── suggested-steps.component.ts │ │ │ │ │ └── task-detail.component.ts │ │ │ │ └── index.ts │ │ │ └── utils │ │ │ ├── auto-update.ts │ │ │ └── ui.ts │ │ └── tsconfig.json │ ├── docs │ │ ├── archive │ │ │ ├── ai-client-utils-example.mdx │ │ │ ├── ai-development-workflow.mdx │ │ │ ├── command-reference.mdx │ │ │ ├── configuration.mdx │ │ │ ├── cursor-setup.mdx │ │ │ ├── examples.mdx │ │ │ └── Installation.mdx │ │ ├── best-practices │ │ │ ├── advanced-tasks.mdx │ │ │ ├── configuration-advanced.mdx │ │ │ └── index.mdx │ │ ├── capabilities │ │ │ ├── cli-root-commands.mdx │ │ │ ├── index.mdx │ │ │ ├── mcp.mdx │ │ │ └── task-structure.mdx │ │ ├── CHANGELOG.md │ │ ├── docs.json │ │ ├── favicon.svg │ │ ├── getting-started │ │ │ ├── contribute.mdx │ │ │ ├── faq.mdx │ │ │ └── quick-start │ │ │ ├── configuration-quick.mdx │ │ │ ├── execute-quick.mdx │ │ │ ├── installation.mdx │ │ │ ├── moving-forward.mdx │ │ │ ├── prd-quick.mdx │ │ │ ├── quick-start.mdx │ │ │ ├── requirements.mdx │ │ │ ├── rules-quick.mdx │ │ │ └── tasks-quick.mdx │ │ ├── introduction.mdx │ │ ├── licensing.md │ │ ├── logo │ │ │ ├── dark.svg │ │ │ ├── light.svg │ │ │ └── task-master-logo.png │ │ ├── package.json │ │ ├── README.md │ │ ├── style.css │ │ ├── vercel.json │ │ └── whats-new.mdx │ └── extension │ ├── .vscodeignore │ ├── assets │ │ ├── banner.png │ │ ├── icon-dark.svg │ │ ├── icon-light.svg │ │ ├── icon.png │ │ ├── screenshots │ │ │ ├── kanban-board.png │ │ │ └── task-details.png │ │ └── sidebar-icon.svg │ ├── CHANGELOG.md │ ├── components.json │ ├── docs │ │ ├── extension-CI-setup.md │ │ └── extension-development-guide.md │ ├── esbuild.js │ ├── LICENSE │ ├── package.json │ ├── package.mjs │ ├── package.publish.json │ ├── README.md │ ├── src │ │ ├── components │ │ │ ├── ConfigView.tsx │ │ │ ├── constants.ts │ │ │ ├── TaskDetails │ │ │ │ ├── AIActionsSection.tsx │ │ │ │ ├── DetailsSection.tsx │ │ │ │ ├── PriorityBadge.tsx │ │ │ │ ├── SubtasksSection.tsx │ │ │ │ ├── TaskMetadataSidebar.tsx │ │ │ │ └── useTaskDetails.ts │ │ │ ├── TaskDetailsView.tsx │ │ │ ├── TaskMasterLogo.tsx │ │ │ └── ui │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── CollapsibleSection.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── label.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── separator.tsx │ │ │ ├── shadcn-io │ │ │ │ └── kanban │ │ │ │ └── index.tsx │ │ │ └── textarea.tsx │ │ ├── extension.ts │ │ ├── index.ts │ │ ├── lib │ │ │ └── utils.ts │ │ ├── services │ │ │ ├── config-service.ts │ │ │ ├── error-handler.ts │ │ │ ├── notification-preferences.ts │ │ │ ├── polling-service.ts │ │ │ ├── polling-strategies.ts │ │ │ ├── sidebar-webview-manager.ts │ │ │ ├── task-repository.ts │ │ │ ├── terminal-manager.ts │ │ │ └── webview-manager.ts │ │ ├── test │ │ │ └── extension.test.ts │ │ ├── utils │ │ │ ├── configManager.ts │ │ │ ├── connectionManager.ts │ │ │ ├── errorHandler.ts │ │ │ ├── event-emitter.ts │ │ │ ├── logger.ts │ │ │ ├── mcpClient.ts │ │ │ ├── notificationPreferences.ts │ │ │ └── task-master-api │ │ │ ├── cache │ │ │ │ └── cache-manager.ts │ │ │ ├── index.ts │ │ │ ├── mcp-client.ts │ │ │ ├── transformers │ │ │ │ └── task-transformer.ts │ │ │ └── types │ │ │ └── index.ts │ │ └── webview │ │ ├── App.tsx │ │ ├── components │ │ │ ├── AppContent.tsx │ │ │ ├── EmptyState.tsx │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── PollingStatus.tsx │ │ │ ├── PriorityBadge.tsx │ │ │ ├── SidebarView.tsx │ │ │ ├── TagDropdown.tsx │ │ │ ├── TaskCard.tsx │ │ │ ├── TaskEditModal.tsx │ │ │ ├── TaskMasterKanban.tsx │ │ │ ├── ToastContainer.tsx │ │ │ └── ToastNotification.tsx │ │ ├── constants │ │ │ └── index.ts │ │ ├── contexts │ │ │ └── VSCodeContext.tsx │ │ ├── hooks │ │ │ ├── useTaskQueries.ts │ │ │ ├── useVSCodeMessages.ts │ │ │ └── useWebviewHeight.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── providers │ │ │ └── QueryProvider.tsx │ │ ├── reducers │ │ │ └── appReducer.ts │ │ ├── sidebar.tsx │ │ ├── types │ │ │ └── index.ts │ │ └── utils │ │ ├── logger.ts │ │ └── toast.ts │ └── tsconfig.json ├── assets │ ├── .windsurfrules │ ├── AGENTS.md │ ├── claude │ │ ├── agents │ │ │ ├── task-checker.md │ │ │ ├── task-executor.md │ │ │ └── task-orchestrator.md │ │ ├── commands │ │ │ └── tm │ │ │ ├── add-dependency │ │ │ │ └── add-dependency.md │ │ │ ├── add-subtask │ │ │ │ ├── add-subtask.md │ │ │ │ └── convert-task-to-subtask.md │ │ │ ├── add-task │ │ │ │ └── add-task.md │ │ │ ├── analyze-complexity │ │ │ │ └── analyze-complexity.md │ │ │ ├── clear-subtasks │ │ │ │ ├── clear-all-subtasks.md │ │ │ │ └── clear-subtasks.md │ │ │ ├── complexity-report │ │ │ │ └── complexity-report.md │ │ │ ├── expand │ │ │ │ ├── expand-all-tasks.md │ │ │ │ └── expand-task.md │ │ │ ├── fix-dependencies │ │ │ │ └── fix-dependencies.md │ │ │ ├── generate │ │ │ │ └── generate-tasks.md │ │ │ ├── help.md │ │ │ ├── init │ │ │ │ ├── init-project-quick.md │ │ │ │ └── init-project.md │ │ │ ├── learn.md │ │ │ ├── list │ │ │ │ ├── list-tasks-by-status.md │ │ │ │ ├── list-tasks-with-subtasks.md │ │ │ │ └── list-tasks.md │ │ │ ├── models │ │ │ │ ├── setup-models.md │ │ │ │ └── view-models.md │ │ │ ├── next │ │ │ │ └── next-task.md │ │ │ ├── parse-prd │ │ │ │ ├── parse-prd-with-research.md │ │ │ │ └── parse-prd.md │ │ │ ├── remove-dependency │ │ │ │ └── remove-dependency.md │ │ │ ├── remove-subtask │ │ │ │ └── remove-subtask.md │ │ │ ├── remove-subtasks │ │ │ │ ├── remove-all-subtasks.md │ │ │ │ └── remove-subtasks.md │ │ │ ├── remove-task │ │ │ │ └── remove-task.md │ │ │ ├── set-status │ │ │ │ ├── to-cancelled.md │ │ │ │ ├── to-deferred.md │ │ │ │ ├── to-done.md │ │ │ │ ├── to-in-progress.md │ │ │ │ ├── to-pending.md │ │ │ │ └── to-review.md │ │ │ ├── setup │ │ │ │ ├── install-taskmaster.md │ │ │ │ └── quick-install-taskmaster.md │ │ │ ├── show │ │ │ │ └── show-task.md │ │ │ ├── status │ │ │ │ └── project-status.md │ │ │ ├── sync-readme │ │ │ │ └── sync-readme.md │ │ │ ├── tm-main.md │ │ │ ├── update │ │ │ │ ├── update-single-task.md │ │ │ │ ├── update-task.md │ │ │ │ └── update-tasks-from-id.md │ │ │ ├── utils │ │ │ │ └── analyze-project.md │ │ │ ├── validate-dependencies │ │ │ │ └── validate-dependencies.md │ │ │ └── workflows │ │ │ ├── auto-implement-tasks.md │ │ │ ├── command-pipeline.md │ │ │ └── smart-workflow.md │ │ └── TM_COMMANDS_GUIDE.md │ ├── config.json │ ├── env.example │ ├── example_prd.txt │ ├── gitignore │ ├── kiro-hooks │ │ ├── tm-code-change-task-tracker.kiro.hook │ │ ├── tm-complexity-analyzer.kiro.hook │ │ ├── tm-daily-standup-assistant.kiro.hook │ │ ├── tm-git-commit-task-linker.kiro.hook │ │ ├── tm-pr-readiness-checker.kiro.hook │ │ ├── tm-task-dependency-auto-progression.kiro.hook │ │ └── tm-test-success-task-completer.kiro.hook │ ├── roocode │ │ ├── .roo │ │ │ ├── rules-architect │ │ │ │ └── architect-rules │ │ │ ├── rules-ask │ │ │ │ └── ask-rules │ │ │ ├── rules-code │ │ │ │ └── code-rules │ │ │ ├── rules-debug │ │ │ │ └── debug-rules │ │ │ ├── rules-orchestrator │ │ │ │ └── orchestrator-rules │ │ │ └── rules-test │ │ │ └── test-rules │ │ └── .roomodes │ ├── rules │ │ ├── cursor_rules.mdc │ │ ├── dev_workflow.mdc │ │ ├── self_improve.mdc │ │ ├── taskmaster_hooks_workflow.mdc │ │ └── taskmaster.mdc │ └── scripts_README.md ├── bin │ └── task-master.js ├── biome.json ├── CHANGELOG.md ├── CLAUDE.md ├── context │ ├── chats │ │ ├── add-task-dependencies-1.md │ │ └── max-min-tokens.txt.md │ ├── fastmcp-core.txt │ ├── fastmcp-docs.txt │ ├── MCP_INTEGRATION.md │ ├── mcp-js-sdk-docs.txt │ ├── mcp-protocol-repo.txt │ ├── mcp-protocol-schema-03262025.json │ └── mcp-protocol-spec.txt ├── CONTRIBUTING.md ├── docs │ ├── CLI-COMMANDER-PATTERN.md │ ├── command-reference.md │ ├── configuration.md │ ├── contributor-docs │ │ └── testing-roo-integration.md │ ├── cross-tag-task-movement.md │ ├── examples │ │ └── claude-code-usage.md │ ├── examples.md │ ├── licensing.md │ ├── mcp-provider-guide.md │ ├── mcp-provider.md │ ├── migration-guide.md │ ├── models.md │ ├── providers │ │ └── gemini-cli.md │ ├── README.md │ ├── scripts │ │ └── models-json-to-markdown.js │ ├── task-structure.md │ └── tutorial.md ├── images │ └── logo.png ├── index.js ├── jest.config.js ├── jest.resolver.cjs ├── LICENSE ├── llms-install.md ├── mcp-server │ ├── server.js │ └── src │ ├── core │ │ ├── __tests__ │ │ │ └── context-manager.test.js │ │ ├── context-manager.js │ │ ├── direct-functions │ │ │ ├── add-dependency.js │ │ │ ├── add-subtask.js │ │ │ ├── add-tag.js │ │ │ ├── add-task.js │ │ │ ├── analyze-task-complexity.js │ │ │ ├── cache-stats.js │ │ │ ├── clear-subtasks.js │ │ │ ├── complexity-report.js │ │ │ ├── copy-tag.js │ │ │ ├── create-tag-from-branch.js │ │ │ ├── delete-tag.js │ │ │ ├── expand-all-tasks.js │ │ │ ├── expand-task.js │ │ │ ├── fix-dependencies.js │ │ │ ├── generate-task-files.js │ │ │ ├── initialize-project.js │ │ │ ├── list-tags.js │ │ │ ├── list-tasks.js │ │ │ ├── models.js │ │ │ ├── move-task-cross-tag.js │ │ │ ├── move-task.js │ │ │ ├── next-task.js │ │ │ ├── parse-prd.js │ │ │ ├── remove-dependency.js │ │ │ ├── remove-subtask.js │ │ │ ├── remove-task.js │ │ │ ├── rename-tag.js │ │ │ ├── research.js │ │ │ ├── response-language.js │ │ │ ├── rules.js │ │ │ ├── scope-down.js │ │ │ ├── scope-up.js │ │ │ ├── set-task-status.js │ │ │ ├── show-task.js │ │ │ ├── update-subtask-by-id.js │ │ │ ├── update-task-by-id.js │ │ │ ├── update-tasks.js │ │ │ ├── use-tag.js │ │ │ └── validate-dependencies.js │ │ ├── task-master-core.js │ │ └── utils │ │ ├── env-utils.js │ │ └── path-utils.js │ ├── custom-sdk │ │ ├── errors.js │ │ ├── index.js │ │ ├── json-extractor.js │ │ ├── language-model.js │ │ ├── message-converter.js │ │ └── schema-converter.js │ ├── index.js │ ├── logger.js │ ├── providers │ │ └── mcp-provider.js │ └── tools │ ├── add-dependency.js │ ├── add-subtask.js │ ├── add-tag.js │ ├── add-task.js │ ├── analyze.js │ ├── clear-subtasks.js │ ├── complexity-report.js │ ├── copy-tag.js │ ├── delete-tag.js │ ├── expand-all.js │ ├── expand-task.js │ ├── fix-dependencies.js │ ├── generate.js │ ├── get-operation-status.js │ ├── get-task.js │ ├── get-tasks.js │ ├── index.js │ ├── initialize-project.js │ ├── list-tags.js │ ├── models.js │ ├── move-task.js │ ├── next-task.js │ ├── parse-prd.js │ ├── remove-dependency.js │ ├── remove-subtask.js │ ├── remove-task.js │ ├── rename-tag.js │ ├── research.js │ ├── response-language.js │ ├── rules.js │ ├── scope-down.js │ ├── scope-up.js │ ├── set-task-status.js │ ├── update-subtask.js │ ├── update-task.js │ ├── update.js │ ├── use-tag.js │ ├── utils.js │ └── validate-dependencies.js ├── mcp-test.js ├── output.json ├── package-lock.json ├── package.json ├── packages │ ├── build-config │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ └── tsdown.base.ts │ │ └── tsconfig.json │ └── tm-core │ ├── .gitignore │ ├── CHANGELOG.md │ ├── docs │ │ └── listTasks-architecture.md │ ├── package.json │ ├── POC-STATUS.md │ ├── README.md │ ├── src │ │ ├── auth │ │ │ ├── auth-manager.test.ts │ │ │ ├── auth-manager.ts │ │ │ ├── config.ts │ │ │ ├── credential-store.test.ts │ │ │ ├── credential-store.ts │ │ │ ├── index.ts │ │ │ ├── oauth-service.ts │ │ │ ├── supabase-session-storage.ts │ │ │ └── types.ts │ │ ├── clients │ │ │ ├── index.ts │ │ │ └── supabase-client.ts │ │ ├── config │ │ │ ├── config-manager.spec.ts │ │ │ ├── config-manager.ts │ │ │ ├── index.ts │ │ │ └── services │ │ │ ├── config-loader.service.spec.ts │ │ │ ├── config-loader.service.ts │ │ │ ├── config-merger.service.spec.ts │ │ │ ├── config-merger.service.ts │ │ │ ├── config-persistence.service.spec.ts │ │ │ ├── config-persistence.service.ts │ │ │ ├── environment-config-provider.service.spec.ts │ │ │ ├── environment-config-provider.service.ts │ │ │ ├── index.ts │ │ │ ├── runtime-state-manager.service.spec.ts │ │ │ └── runtime-state-manager.service.ts │ │ ├── constants │ │ │ └── index.ts │ │ ├── entities │ │ │ └── task.entity.ts │ │ ├── errors │ │ │ ├── index.ts │ │ │ └── task-master-error.ts │ │ ├── executors │ │ │ ├── base-executor.ts │ │ │ ├── claude-executor.ts │ │ │ ├── executor-factory.ts │ │ │ ├── executor-service.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── interfaces │ │ │ ├── ai-provider.interface.ts │ │ │ ├── configuration.interface.ts │ │ │ ├── index.ts │ │ │ └── storage.interface.ts │ │ ├── logger │ │ │ ├── factory.ts │ │ │ ├── index.ts │ │ │ └── logger.ts │ │ ├── mappers │ │ │ └── TaskMapper.ts │ │ ├── parser │ │ │ └── index.ts │ │ ├── providers │ │ │ ├── ai │ │ │ │ ├── base-provider.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── repositories │ │ │ ├── supabase-task-repository.ts │ │ │ └── task-repository.interface.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── organization.service.ts │ │ │ ├── task-execution-service.ts │ │ │ └── task-service.ts │ │ ├── storage │ │ │ ├── api-storage.ts │ │ │ ├── file-storage │ │ │ │ ├── file-operations.ts │ │ │ │ ├── file-storage.ts │ │ │ │ ├── format-handler.ts │ │ │ │ ├── index.ts │ │ │ │ └── path-resolver.ts │ │ │ ├── index.ts │ │ │ └── storage-factory.ts │ │ ├── subpath-exports.test.ts │ │ ├── task-master-core.ts │ │ ├── types │ │ │ ├── database.types.ts │ │ │ ├── index.ts │ │ │ └── legacy.ts │ │ └── utils │ │ ├── id-generator.ts │ │ └── index.ts │ ├── tests │ │ ├── integration │ │ │ └── list-tasks.test.ts │ │ ├── mocks │ │ │ └── mock-provider.ts │ │ ├── setup.ts │ │ └── unit │ │ ├── base-provider.test.ts │ │ ├── executor.test.ts │ │ └── smoke.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── README-task-master.md ├── README.md ├── scripts │ ├── dev.js │ ├── init.js │ ├── modules │ │ ├── ai-services-unified.js │ │ ├── commands.js │ │ ├── config-manager.js │ │ ├── dependency-manager.js │ │ ├── index.js │ │ ├── prompt-manager.js │ │ ├── supported-models.json │ │ ├── sync-readme.js │ │ ├── task-manager │ │ │ ├── add-subtask.js │ │ │ ├── add-task.js │ │ │ ├── analyze-task-complexity.js │ │ │ ├── clear-subtasks.js │ │ │ ├── expand-all-tasks.js │ │ │ ├── expand-task.js │ │ │ ├── find-next-task.js │ │ │ ├── generate-task-files.js │ │ │ ├── is-task-dependent.js │ │ │ ├── list-tasks.js │ │ │ ├── migrate.js │ │ │ ├── models.js │ │ │ ├── move-task.js │ │ │ ├── parse-prd │ │ │ │ ├── index.js │ │ │ │ ├── parse-prd-config.js │ │ │ │ ├── parse-prd-helpers.js │ │ │ │ ├── parse-prd-non-streaming.js │ │ │ │ ├── parse-prd-streaming.js │ │ │ │ └── parse-prd.js │ │ │ ├── remove-subtask.js │ │ │ ├── remove-task.js │ │ │ ├── research.js │ │ │ ├── response-language.js │ │ │ ├── scope-adjustment.js │ │ │ ├── set-task-status.js │ │ │ ├── tag-management.js │ │ │ ├── task-exists.js │ │ │ ├── update-single-task-status.js │ │ │ ├── update-subtask-by-id.js │ │ │ ├── update-task-by-id.js │ │ │ └── update-tasks.js │ │ ├── task-manager.js │ │ ├── ui.js │ │ ├── update-config-tokens.js │ │ ├── utils │ │ │ ├── contextGatherer.js │ │ │ ├── fuzzyTaskSearch.js │ │ │ └── git-utils.js │ │ └── utils.js │ ├── task-complexity-report.json │ ├── test-claude-errors.js │ └── test-claude.js ├── src │ ├── ai-providers │ │ ├── anthropic.js │ │ ├── azure.js │ │ ├── base-provider.js │ │ ├── bedrock.js │ │ ├── claude-code.js │ │ ├── custom-sdk │ │ │ ├── claude-code │ │ │ │ ├── errors.js │ │ │ │ ├── index.js │ │ │ │ ├── json-extractor.js │ │ │ │ ├── language-model.js │ │ │ │ ├── message-converter.js │ │ │ │ └── types.js │ │ │ └── grok-cli │ │ │ ├── errors.js │ │ │ ├── index.js │ │ │ ├── json-extractor.js │ │ │ ├── language-model.js │ │ │ ├── message-converter.js │ │ │ └── types.js │ │ ├── gemini-cli.js │ │ ├── google-vertex.js │ │ ├── google.js │ │ ├── grok-cli.js │ │ ├── groq.js │ │ ├── index.js │ │ ├── ollama.js │ │ ├── openai.js │ │ ├── openrouter.js │ │ ├── perplexity.js │ │ └── xai.js │ ├── constants │ │ ├── commands.js │ │ ├── paths.js │ │ ├── profiles.js │ │ ├── providers.js │ │ ├── rules-actions.js │ │ ├── task-priority.js │ │ └── task-status.js │ ├── profiles │ │ ├── amp.js │ │ ├── base-profile.js │ │ ├── claude.js │ │ ├── cline.js │ │ ├── codex.js │ │ ├── cursor.js │ │ ├── gemini.js │ │ ├── index.js │ │ ├── kilo.js │ │ ├── kiro.js │ │ ├── opencode.js │ │ ├── roo.js │ │ ├── trae.js │ │ ├── vscode.js │ │ ├── windsurf.js │ │ └── zed.js │ ├── progress │ │ ├── base-progress-tracker.js │ │ ├── cli-progress-factory.js │ │ ├── parse-prd-tracker.js │ │ ├── progress-tracker-builder.js │ │ └── tracker-ui.js │ ├── prompts │ │ ├── add-task.json │ │ ├── analyze-complexity.json │ │ ├── expand-task.json │ │ ├── parse-prd.json │ │ ├── README.md │ │ ├── research.json │ │ ├── schemas │ │ │ ├── parameter.schema.json │ │ │ ├── prompt-template.schema.json │ │ │ ├── README.md │ │ │ └── variant.schema.json │ │ ├── update-subtask.json │ │ ├── update-task.json │ │ └── update-tasks.json │ ├── provider-registry │ │ └── index.js │ ├── task-master.js │ ├── ui │ │ ├── confirm.js │ │ ├── indicators.js │ │ └── parse-prd.js │ └── utils │ ├── asset-resolver.js │ ├── create-mcp-config.js │ ├── format.js │ ├── getVersion.js │ ├── logger-utils.js │ ├── manage-gitignore.js │ ├── path-utils.js │ ├── profiles.js │ ├── rule-transformer.js │ ├── stream-parser.js │ └── timeout-manager.js ├── test-clean-tags.js ├── test-config-manager.js ├── test-prd.txt ├── test-tag-functions.js ├── test-version-check-full.js ├── test-version-check.js ├── tests │ ├── e2e │ │ ├── e2e_helpers.sh │ │ ├── parse_llm_output.cjs │ │ ├── run_e2e.sh │ │ ├── run_fallback_verification.sh │ │ └── test_llm_analysis.sh │ ├── fixture │ │ └── test-tasks.json │ ├── fixtures │ │ ├── .taskmasterconfig │ │ ├── sample-claude-response.js │ │ ├── sample-prd.txt │ │ └── sample-tasks.js │ ├── integration │ │ ├── claude-code-optional.test.js │ │ ├── cli │ │ │ ├── commands.test.js │ │ │ ├── complex-cross-tag-scenarios.test.js │ │ │ └── move-cross-tag.test.js │ │ ├── manage-gitignore.test.js │ │ ├── mcp-server │ │ │ └── direct-functions.test.js │ │ ├── move-task-cross-tag.integration.test.js │ │ ├── move-task-simple.integration.test.js │ │ └── profiles │ │ ├── amp-init-functionality.test.js │ │ ├── claude-init-functionality.test.js │ │ ├── cline-init-functionality.test.js │ │ ├── codex-init-functionality.test.js │ │ ├── cursor-init-functionality.test.js │ │ ├── gemini-init-functionality.test.js │ │ ├── opencode-init-functionality.test.js │ │ ├── roo-files-inclusion.test.js │ │ ├── roo-init-functionality.test.js │ │ ├── rules-files-inclusion.test.js │ │ ├── trae-init-functionality.test.js │ │ ├── vscode-init-functionality.test.js │ │ └── windsurf-init-functionality.test.js │ ├── manual │ │ ├── progress │ │ │ ├── parse-prd-analysis.js │ │ │ ├── test-parse-prd.js │ │ │ └── TESTING_GUIDE.md │ │ └── prompts │ │ ├── prompt-test.js │ │ └── README.md │ ├── README.md │ ├── setup.js │ └── unit │ ├── ai-providers │ │ ├── claude-code.test.js │ │ ├── custom-sdk │ │ │ └── claude-code │ │ │ └── language-model.test.js │ │ ├── gemini-cli.test.js │ │ ├── mcp-components.test.js │ │ └── openai.test.js │ ├── ai-services-unified.test.js │ ├── commands.test.js │ ├── config-manager.test.js │ ├── config-manager.test.mjs │ ├── dependency-manager.test.js │ ├── init.test.js │ ├── initialize-project.test.js │ ├── kebab-case-validation.test.js │ ├── manage-gitignore.test.js │ ├── mcp │ │ └── tools │ │ ├── __mocks__ │ │ │ └── move-task.js │ │ ├── add-task.test.js │ │ ├── analyze-complexity.test.js │ │ ├── expand-all.test.js │ │ ├── get-tasks.test.js │ │ ├── initialize-project.test.js │ │ ├── move-task-cross-tag-options.test.js │ │ ├── move-task-cross-tag.test.js │ │ └── remove-task.test.js │ ├── mcp-providers │ │ ├── mcp-components.test.js │ │ └── mcp-provider.test.js │ ├── parse-prd.test.js │ ├── profiles │ │ ├── amp-integration.test.js │ │ ├── claude-integration.test.js │ │ ├── cline-integration.test.js │ │ ├── codex-integration.test.js │ │ ├── cursor-integration.test.js │ │ ├── gemini-integration.test.js │ │ ├── kilo-integration.test.js │ │ ├── kiro-integration.test.js │ │ ├── mcp-config-validation.test.js │ │ ├── opencode-integration.test.js │ │ ├── profile-safety-check.test.js │ │ ├── roo-integration.test.js │ │ ├── rule-transformer-cline.test.js │ │ ├── rule-transformer-cursor.test.js │ │ ├── rule-transformer-gemini.test.js │ │ ├── rule-transformer-kilo.test.js │ │ ├── rule-transformer-kiro.test.js │ │ ├── rule-transformer-opencode.test.js │ │ ├── rule-transformer-roo.test.js │ │ ├── rule-transformer-trae.test.js │ │ ├── rule-transformer-vscode.test.js │ │ ├── rule-transformer-windsurf.test.js │ │ ├── rule-transformer-zed.test.js │ │ ├── rule-transformer.test.js │ │ ├── selective-profile-removal.test.js │ │ ├── subdirectory-support.test.js │ │ ├── trae-integration.test.js │ │ ├── vscode-integration.test.js │ │ ├── windsurf-integration.test.js │ │ └── zed-integration.test.js │ ├── progress │ │ └── base-progress-tracker.test.js │ ├── prompt-manager.test.js │ ├── prompts │ │ └── expand-task-prompt.test.js │ ├── providers │ │ └── provider-registry.test.js │ ├── scripts │ │ └── modules │ │ ├── commands │ │ │ ├── move-cross-tag.test.js │ │ │ └── README.md │ │ ├── dependency-manager │ │ │ ├── circular-dependencies.test.js │ │ │ ├── cross-tag-dependencies.test.js │ │ │ └── fix-dependencies-command.test.js │ │ ├── task-manager │ │ │ ├── add-subtask.test.js │ │ │ ├── add-task.test.js │ │ │ ├── analyze-task-complexity.test.js │ │ │ ├── clear-subtasks.test.js │ │ │ ├── complexity-report-tag-isolation.test.js │ │ │ ├── expand-all-tasks.test.js │ │ │ ├── expand-task.test.js │ │ │ ├── find-next-task.test.js │ │ │ ├── generate-task-files.test.js │ │ │ ├── list-tasks.test.js │ │ │ ├── move-task-cross-tag.test.js │ │ │ ├── move-task.test.js │ │ │ ├── parse-prd.test.js │ │ │ ├── remove-subtask.test.js │ │ │ ├── remove-task.test.js │ │ │ ├── research.test.js │ │ │ ├── scope-adjustment.test.js │ │ │ ├── set-task-status.test.js │ │ │ ├── setup.js │ │ │ ├── update-single-task-status.test.js │ │ │ ├── update-subtask-by-id.test.js │ │ │ ├── update-task-by-id.test.js │ │ │ └── update-tasks.test.js │ │ ├── ui │ │ │ └── cross-tag-error-display.test.js │ │ └── utils-tag-aware-paths.test.js │ ├── task-finder.test.js │ ├── task-manager │ │ ├── clear-subtasks.test.js │ │ ├── move-task.test.js │ │ ├── tag-boundary.test.js │ │ └── tag-management.test.js │ ├── task-master.test.js │ ├── ui │ │ └── indicators.test.js │ ├── ui.test.js │ ├── utils-strip-ansi.test.js │ └── utils.test.js ├── tsconfig.json ├── tsdown.config.ts └── turbo.json ``` # Files -------------------------------------------------------------------------------- /scripts/modules/task-manager/set-task-status.js: -------------------------------------------------------------------------------- ```javascript 1 | import path from 'path'; 2 | import chalk from 'chalk'; 3 | import boxen from 'boxen'; 4 | 5 | import { 6 | log, 7 | readJSON, 8 | writeJSON, 9 | findTaskById, 10 | ensureTagMetadata 11 | } from '../utils.js'; 12 | import { displayBanner } from '../ui.js'; 13 | import { validateTaskDependencies } from '../dependency-manager.js'; 14 | import { getDebugFlag } from '../config-manager.js'; 15 | import updateSingleTaskStatus from './update-single-task-status.js'; 16 | import generateTaskFiles from './generate-task-files.js'; 17 | import { 18 | isValidTaskStatus, 19 | TASK_STATUS_OPTIONS 20 | } from '../../../src/constants/task-status.js'; 21 | 22 | /** 23 | * Set the status of a task 24 | * @param {string} tasksPath - Path to the tasks.json file 25 | * @param {string} taskIdInput - Task ID(s) to update 26 | * @param {string} newStatus - New status 27 | * @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot for tag resolution) 28 | * @param {string} [options.projectRoot] - Project root path 29 | * @param {string} [options.tag] - Optional tag to override current tag resolution 30 | * @param {string} [options.mcpLog] - MCP logger object 31 | * @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode 32 | */ 33 | async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) { 34 | const { projectRoot, tag } = options; 35 | try { 36 | if (!isValidTaskStatus(newStatus)) { 37 | throw new Error( 38 | `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}` 39 | ); 40 | } 41 | // Determine if we're in MCP mode by checking for mcpLog 42 | const isMcpMode = !!options?.mcpLog; 43 | 44 | // Only display UI elements if not in MCP mode 45 | if (!isMcpMode) { 46 | console.log( 47 | boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), { 48 | padding: 1, 49 | borderColor: 'blue', 50 | borderStyle: 'round' 51 | }) 52 | ); 53 | } 54 | 55 | log('info', `Reading tasks from ${tasksPath}...`); 56 | 57 | // Read the raw data without tag resolution to preserve tagged structure 58 | let rawData = readJSON(tasksPath, projectRoot, tag); // No tag parameter 59 | 60 | // Handle the case where readJSON returns resolved data with _rawTaggedData 61 | if (rawData && rawData._rawTaggedData) { 62 | // Use the raw tagged data and discard the resolved view 63 | rawData = rawData._rawTaggedData; 64 | } 65 | 66 | // Ensure the tag exists in the raw data 67 | if (!rawData || !rawData[tag] || !Array.isArray(rawData[tag].tasks)) { 68 | throw new Error( 69 | `Invalid tasks file or tag "${tag}" not found at ${tasksPath}` 70 | ); 71 | } 72 | 73 | // Get the tasks for the current tag 74 | const data = { 75 | tasks: rawData[tag].tasks, 76 | tag, 77 | _rawTaggedData: rawData 78 | }; 79 | 80 | if (!data || !data.tasks) { 81 | throw new Error(`No valid tasks found in ${tasksPath}`); 82 | } 83 | 84 | // Handle multiple task IDs (comma-separated) 85 | const taskIds = taskIdInput.split(',').map((id) => id.trim()); 86 | const updatedTasks = []; 87 | 88 | // Update each task and capture old status for display 89 | for (const id of taskIds) { 90 | // Capture old status before updating 91 | let oldStatus = 'unknown'; 92 | 93 | if (id.includes('.')) { 94 | // Handle subtask 95 | const [parentId, subtaskId] = id 96 | .split('.') 97 | .map((id) => parseInt(id, 10)); 98 | const parentTask = data.tasks.find((t) => t.id === parentId); 99 | if (parentTask?.subtasks) { 100 | const subtask = parentTask.subtasks.find((st) => st.id === subtaskId); 101 | oldStatus = subtask?.status || 'pending'; 102 | } 103 | } else { 104 | // Handle regular task 105 | const taskId = parseInt(id, 10); 106 | const task = data.tasks.find((t) => t.id === taskId); 107 | oldStatus = task?.status || 'pending'; 108 | } 109 | 110 | await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode); 111 | updatedTasks.push({ id, oldStatus, newStatus }); 112 | } 113 | 114 | // Update the raw data structure with the modified tasks 115 | rawData[tag].tasks = data.tasks; 116 | 117 | // Ensure the tag has proper metadata 118 | ensureTagMetadata(rawData[tag], { 119 | description: `Tasks for ${tag} context` 120 | }); 121 | 122 | // Write the updated raw data back to the file 123 | // The writeJSON function will automatically filter out _rawTaggedData 124 | writeJSON(tasksPath, rawData, projectRoot, tag); 125 | 126 | // Validate dependencies after status update 127 | log('info', 'Validating dependencies after status update...'); 128 | validateTaskDependencies(data.tasks); 129 | 130 | // Generate individual task files 131 | // log('info', 'Regenerating task files...'); 132 | // await generateTaskFiles(tasksPath, path.dirname(tasksPath), { 133 | // mcpLog: options.mcpLog 134 | // }); 135 | 136 | // Display success message - only in CLI mode 137 | if (!isMcpMode) { 138 | for (const updateInfo of updatedTasks) { 139 | const { id, oldStatus, newStatus: updatedStatus } = updateInfo; 140 | 141 | console.log( 142 | boxen( 143 | chalk.white.bold(`Successfully updated task ${id} status:`) + 144 | '\n' + 145 | `From: ${chalk.yellow(oldStatus)}\n` + 146 | `To: ${chalk.green(updatedStatus)}`, 147 | { padding: 1, borderColor: 'green', borderStyle: 'round' } 148 | ) 149 | ); 150 | } 151 | } 152 | 153 | // Return success value for programmatic use 154 | return { 155 | success: true, 156 | updatedTasks: updatedTasks.map(({ id, oldStatus, newStatus }) => ({ 157 | id, 158 | oldStatus, 159 | newStatus 160 | })) 161 | }; 162 | } catch (error) { 163 | log('error', `Error setting task status: ${error.message}`); 164 | 165 | // Only show error UI in CLI mode 166 | if (!options?.mcpLog) { 167 | console.error(chalk.red(`Error: ${error.message}`)); 168 | 169 | // Pass session to getDebugFlag 170 | if (getDebugFlag(options?.session)) { 171 | // Use getter 172 | console.error(error); 173 | } 174 | 175 | process.exit(1); 176 | } else { 177 | // In MCP mode, throw the error for the caller to handle 178 | throw error; 179 | } 180 | } 181 | } 182 | 183 | export default setTaskStatus; 184 | ``` -------------------------------------------------------------------------------- /tests/unit/scripts/modules/dependency-manager/fix-dependencies-command.test.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * Unit test to ensure fixDependenciesCommand writes JSON with the correct 3 | * projectRoot and tag arguments so that tag data is preserved. 4 | */ 5 | 6 | import { jest } from '@jest/globals'; 7 | 8 | // Mock process.exit to prevent test termination 9 | const mockProcessExit = jest.fn(); 10 | const originalExit = process.exit; 11 | process.exit = mockProcessExit; 12 | 13 | // Mock utils.js BEFORE importing the module under test 14 | jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({ 15 | readJSON: jest.fn(), 16 | writeJSON: jest.fn(), 17 | log: jest.fn(), 18 | findProjectRoot: jest.fn(() => '/mock/project/root'), 19 | getCurrentTag: jest.fn(() => 'master'), 20 | taskExists: jest.fn(() => true), 21 | formatTaskId: jest.fn((id) => id), 22 | findCycles: jest.fn(() => []), 23 | traverseDependencies: jest.fn((sourceTasks, allTasks, options = {}) => []), 24 | isSilentMode: jest.fn(() => true), 25 | resolveTag: jest.fn(() => 'master'), 26 | getTasksForTag: jest.fn(() => []), 27 | setTasksForTag: jest.fn(), 28 | enableSilentMode: jest.fn(), 29 | disableSilentMode: jest.fn(), 30 | isEmpty: jest.fn((value) => { 31 | if (value === null || value === undefined) return true; 32 | if (Array.isArray(value)) return value.length === 0; 33 | if (typeof value === 'object' && value !== null) 34 | return Object.keys(value).length === 0; 35 | return false; // Not an array or object 36 | }), 37 | resolveEnvVariable: jest.fn() 38 | })); 39 | 40 | // Mock ui.js 41 | jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({ 42 | displayBanner: jest.fn(), 43 | formatDependenciesWithStatus: jest.fn() 44 | })); 45 | 46 | // Mock task-manager.js 47 | jest.unstable_mockModule( 48 | '../../../../../scripts/modules/task-manager.js', 49 | () => ({ 50 | generateTaskFiles: jest.fn() 51 | }) 52 | ); 53 | 54 | // Mock external libraries 55 | jest.unstable_mockModule('chalk', () => ({ 56 | default: { 57 | green: jest.fn((text) => text), 58 | cyan: jest.fn((text) => text), 59 | bold: jest.fn((text) => text) 60 | } 61 | })); 62 | 63 | jest.unstable_mockModule('boxen', () => ({ 64 | default: jest.fn((text) => text) 65 | })); 66 | 67 | // Import the mocked modules 68 | const { readJSON, writeJSON, log, taskExists } = await import( 69 | '../../../../../scripts/modules/utils.js' 70 | ); 71 | 72 | // Import the module under test 73 | const { fixDependenciesCommand } = await import( 74 | '../../../../../scripts/modules/dependency-manager.js' 75 | ); 76 | 77 | describe('fixDependenciesCommand tag preservation', () => { 78 | beforeEach(() => { 79 | jest.clearAllMocks(); 80 | mockProcessExit.mockClear(); 81 | }); 82 | 83 | afterAll(() => { 84 | // Restore original process.exit 85 | process.exit = originalExit; 86 | }); 87 | 88 | it('calls writeJSON with projectRoot and tag parameters when changes are made', async () => { 89 | const tasksPath = '/mock/tasks.json'; 90 | const projectRoot = '/mock/project/root'; 91 | const tag = 'master'; 92 | 93 | // Mock data WITH dependency issues to trigger writeJSON 94 | const tasksDataWithIssues = { 95 | tasks: [ 96 | { 97 | id: 1, 98 | title: 'Task 1', 99 | dependencies: [999] // Non-existent dependency to trigger fix 100 | }, 101 | { 102 | id: 2, 103 | title: 'Task 2', 104 | dependencies: [] 105 | } 106 | ], 107 | tag: 'master', 108 | _rawTaggedData: { 109 | master: { 110 | tasks: [ 111 | { 112 | id: 1, 113 | title: 'Task 1', 114 | dependencies: [999] 115 | } 116 | ] 117 | } 118 | } 119 | }; 120 | 121 | readJSON.mockReturnValue(tasksDataWithIssues); 122 | taskExists.mockReturnValue(false); // Make dependency invalid to trigger fix 123 | 124 | await fixDependenciesCommand(tasksPath, { 125 | context: { projectRoot, tag } 126 | }); 127 | 128 | // Verify readJSON was called with correct parameters 129 | expect(readJSON).toHaveBeenCalledWith(tasksPath, projectRoot, tag); 130 | 131 | // Verify writeJSON was called (should be triggered by removing invalid dependency) 132 | expect(writeJSON).toHaveBeenCalled(); 133 | 134 | // Check the writeJSON call parameters 135 | const writeJSONCalls = writeJSON.mock.calls; 136 | const lastWriteCall = writeJSONCalls[writeJSONCalls.length - 1]; 137 | const [calledPath, _data, calledProjectRoot, calledTag] = lastWriteCall; 138 | 139 | expect(calledPath).toBe(tasksPath); 140 | expect(calledProjectRoot).toBe(projectRoot); 141 | expect(calledTag).toBe(tag); 142 | 143 | // Verify process.exit was NOT called (meaning the function succeeded) 144 | expect(mockProcessExit).not.toHaveBeenCalled(); 145 | }); 146 | 147 | it('does not call writeJSON when no changes are needed', async () => { 148 | const tasksPath = '/mock/tasks.json'; 149 | const projectRoot = '/mock/project/root'; 150 | const tag = 'master'; 151 | 152 | // Mock data WITHOUT dependency issues (no changes needed) 153 | const cleanTasksData = { 154 | tasks: [ 155 | { 156 | id: 1, 157 | title: 'Task 1', 158 | dependencies: [] // Clean, no issues 159 | } 160 | ], 161 | tag: 'master' 162 | }; 163 | 164 | readJSON.mockReturnValue(cleanTasksData); 165 | taskExists.mockReturnValue(true); // All dependencies exist 166 | 167 | await fixDependenciesCommand(tasksPath, { 168 | context: { projectRoot, tag } 169 | }); 170 | 171 | // Verify readJSON was called 172 | expect(readJSON).toHaveBeenCalledWith(tasksPath, projectRoot, tag); 173 | 174 | // Verify writeJSON was NOT called (no changes needed) 175 | expect(writeJSON).not.toHaveBeenCalled(); 176 | 177 | // Verify process.exit was NOT called 178 | expect(mockProcessExit).not.toHaveBeenCalled(); 179 | }); 180 | 181 | it('handles early exit when no valid tasks found', async () => { 182 | const tasksPath = '/mock/tasks.json'; 183 | 184 | // Mock invalid data to trigger early exit 185 | readJSON.mockReturnValue(null); 186 | 187 | await fixDependenciesCommand(tasksPath, { 188 | context: { projectRoot: '/mock', tag: 'master' } 189 | }); 190 | 191 | // Verify readJSON was called 192 | expect(readJSON).toHaveBeenCalled(); 193 | 194 | // Verify writeJSON was NOT called (early exit) 195 | expect(writeJSON).not.toHaveBeenCalled(); 196 | 197 | // Verify process.exit WAS called due to invalid data 198 | expect(mockProcessExit).toHaveBeenCalledWith(1); 199 | }); 200 | }); 201 | ``` -------------------------------------------------------------------------------- /src/profiles/zed.js: -------------------------------------------------------------------------------- ```javascript 1 | // Zed profile for rule-transformer 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import { isSilentMode, log } from '../../scripts/modules/utils.js'; 5 | import { createProfile } from './base-profile.js'; 6 | 7 | /** 8 | * Transform standard MCP config format to Zed format 9 | * @param {Object} mcpConfig - Standard MCP configuration object 10 | * @returns {Object} - Transformed Zed configuration object 11 | */ 12 | function transformToZedFormat(mcpConfig) { 13 | const zedConfig = {}; 14 | 15 | // Transform mcpServers to context_servers 16 | if (mcpConfig.mcpServers) { 17 | zedConfig['context_servers'] = mcpConfig.mcpServers; 18 | } 19 | 20 | // Preserve any other existing settings 21 | for (const [key, value] of Object.entries(mcpConfig)) { 22 | if (key !== 'mcpServers') { 23 | zedConfig[key] = value; 24 | } 25 | } 26 | 27 | return zedConfig; 28 | } 29 | 30 | // Lifecycle functions for Zed profile 31 | function onAddRulesProfile(targetDir, assetsDir) { 32 | // MCP transformation will be handled in onPostConvertRulesProfile 33 | // File copying is handled by the base profile via fileMap 34 | } 35 | 36 | function onRemoveRulesProfile(targetDir) { 37 | // Clean up .rules (Zed uses .rules directly in root) 38 | const userRulesFile = path.join(targetDir, '.rules'); 39 | 40 | try { 41 | // Remove Task Master .rules 42 | if (fs.existsSync(userRulesFile)) { 43 | fs.rmSync(userRulesFile, { force: true }); 44 | log('debug', `[Zed] Removed ${userRulesFile}`); 45 | } 46 | } catch (err) { 47 | log('error', `[Zed] Failed to remove Zed instructions: ${err.message}`); 48 | } 49 | 50 | // MCP Removal: Remove context_servers section 51 | const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json'); 52 | 53 | if (!fs.existsSync(mcpConfigPath)) { 54 | log('debug', '[Zed] No .zed/settings.json found to clean up'); 55 | return; 56 | } 57 | 58 | try { 59 | // Read the current config 60 | const configContent = fs.readFileSync(mcpConfigPath, 'utf8'); 61 | const config = JSON.parse(configContent); 62 | 63 | // Check if it has the context_servers section and task-master-ai server 64 | if ( 65 | config['context_servers'] && 66 | config['context_servers']['task-master-ai'] 67 | ) { 68 | // Remove task-master-ai server 69 | delete config['context_servers']['task-master-ai']; 70 | 71 | // Check if there are other MCP servers in context_servers 72 | const remainingServers = Object.keys(config['context_servers']); 73 | 74 | if (remainingServers.length === 0) { 75 | // No other servers, remove entire context_servers section 76 | delete config['context_servers']; 77 | log('debug', '[Zed] Removed empty context_servers section'); 78 | } 79 | 80 | // Check if config is now empty 81 | const remainingKeys = Object.keys(config); 82 | 83 | if (remainingKeys.length === 0) { 84 | // Config is empty, remove entire file 85 | fs.rmSync(mcpConfigPath, { force: true }); 86 | log('info', '[Zed] Removed empty settings.json file'); 87 | 88 | // Check if .zed directory is empty 89 | const zedDirPath = path.join(targetDir, '.zed'); 90 | if (fs.existsSync(zedDirPath)) { 91 | const remainingContents = fs.readdirSync(zedDirPath); 92 | if (remainingContents.length === 0) { 93 | fs.rmSync(zedDirPath, { recursive: true, force: true }); 94 | log('debug', '[Zed] Removed empty .zed directory'); 95 | } 96 | } 97 | } else { 98 | // Write back the modified config 99 | fs.writeFileSync( 100 | mcpConfigPath, 101 | JSON.stringify(config, null, '\t') + '\n' 102 | ); 103 | log( 104 | 'info', 105 | '[Zed] Removed TaskMaster from settings.json, preserved other configurations' 106 | ); 107 | } 108 | } else { 109 | log('debug', '[Zed] TaskMaster not found in context_servers'); 110 | } 111 | } catch (error) { 112 | log('error', `[Zed] Failed to clean up settings.json: ${error.message}`); 113 | } 114 | } 115 | 116 | function onPostConvertRulesProfile(targetDir, assetsDir) { 117 | // Handle .rules setup (same as onAddRulesProfile) 118 | onAddRulesProfile(targetDir, assetsDir); 119 | 120 | // Transform MCP config to Zed format 121 | const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json'); 122 | 123 | if (!fs.existsSync(mcpConfigPath)) { 124 | log('debug', '[Zed] No .zed/settings.json found to transform'); 125 | return; 126 | } 127 | 128 | try { 129 | // Read the generated standard MCP config 130 | const mcpConfigContent = fs.readFileSync(mcpConfigPath, 'utf8'); 131 | const mcpConfig = JSON.parse(mcpConfigContent); 132 | 133 | // Check if it's already in Zed format (has context_servers) 134 | if (mcpConfig['context_servers']) { 135 | log( 136 | 'info', 137 | '[Zed] settings.json already in Zed format, skipping transformation' 138 | ); 139 | return; 140 | } 141 | 142 | // Transform to Zed format 143 | const zedConfig = transformToZedFormat(mcpConfig); 144 | 145 | // Add "source": "custom" to task-master-ai server for Zed 146 | if ( 147 | zedConfig['context_servers'] && 148 | zedConfig['context_servers']['task-master-ai'] 149 | ) { 150 | zedConfig['context_servers']['task-master-ai'].source = 'custom'; 151 | } 152 | 153 | // Write back the transformed config with proper formatting 154 | fs.writeFileSync( 155 | mcpConfigPath, 156 | JSON.stringify(zedConfig, null, '\t') + '\n' 157 | ); 158 | 159 | log('info', '[Zed] Transformed settings.json to Zed format'); 160 | log('debug', '[Zed] Renamed mcpServers to context_servers'); 161 | } catch (error) { 162 | log('error', `[Zed] Failed to transform settings.json: ${error.message}`); 163 | } 164 | } 165 | 166 | // Create and export zed profile using the base factory 167 | export const zedProfile = createProfile({ 168 | name: 'zed', 169 | displayName: 'Zed', 170 | url: 'zed.dev', 171 | docsUrl: 'zed.dev/docs', 172 | profileDir: '.zed', 173 | rulesDir: '.', 174 | mcpConfig: true, 175 | mcpConfigName: 'settings.json', 176 | includeDefaultRules: false, 177 | fileMap: { 178 | 'AGENTS.md': '.rules' 179 | }, 180 | onAdd: onAddRulesProfile, 181 | onRemove: onRemoveRulesProfile, 182 | onPostConvert: onPostConvertRulesProfile 183 | }); 184 | 185 | // Export lifecycle functions separately to avoid naming conflicts 186 | export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile }; 187 | ``` -------------------------------------------------------------------------------- /apps/cli/src/utils/auto-update.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * @fileoverview Auto-update utilities for task-master-ai CLI 3 | */ 4 | 5 | import { spawn } from 'child_process'; 6 | import https from 'https'; 7 | import chalk from 'chalk'; 8 | import ora from 'ora'; 9 | import boxen from 'boxen'; 10 | 11 | export interface UpdateInfo { 12 | currentVersion: string; 13 | latestVersion: string; 14 | needsUpdate: boolean; 15 | } 16 | 17 | /** 18 | * Get current version from build-time injected environment variable 19 | */ 20 | function getCurrentVersion(): string { 21 | // Version is injected at build time via TM_PUBLIC_VERSION 22 | const version = process.env.TM_PUBLIC_VERSION; 23 | if (version && version !== 'unknown') { 24 | return version; 25 | } 26 | 27 | // Fallback for development or if injection failed 28 | console.warn('Could not read version from TM_PUBLIC_VERSION, using fallback'); 29 | return '0.0.0'; 30 | } 31 | 32 | /** 33 | * Compare semantic versions with proper pre-release handling 34 | * @param v1 - First version 35 | * @param v2 - Second version 36 | * @returns -1 if v1 < v2, 0 if v1 = v2, 1 if v1 > v2 37 | */ 38 | export function compareVersions(v1: string, v2: string): number { 39 | const toParts = (v: string) => { 40 | const [core, pre = ''] = v.split('-', 2); 41 | const nums = core.split('.').map((n) => Number.parseInt(n, 10) || 0); 42 | return { nums, pre }; 43 | }; 44 | 45 | const a = toParts(v1); 46 | const b = toParts(v2); 47 | const len = Math.max(a.nums.length, b.nums.length); 48 | 49 | // Compare numeric parts 50 | for (let i = 0; i < len; i++) { 51 | const d = (a.nums[i] || 0) - (b.nums[i] || 0); 52 | if (d !== 0) return d < 0 ? -1 : 1; 53 | } 54 | 55 | // Handle pre-release comparison 56 | if (a.pre && !b.pre) return -1; // prerelease < release 57 | if (!a.pre && b.pre) return 1; // release > prerelease 58 | if (a.pre === b.pre) return 0; // same or both empty 59 | return a.pre < b.pre ? -1 : 1; // basic prerelease tie-break 60 | } 61 | 62 | /** 63 | * Check for newer version of task-master-ai 64 | */ 65 | export async function checkForUpdate( 66 | currentVersionOverride?: string 67 | ): Promise<UpdateInfo> { 68 | const currentVersion = currentVersionOverride || getCurrentVersion(); 69 | 70 | return new Promise((resolve) => { 71 | const options = { 72 | hostname: 'registry.npmjs.org', 73 | path: '/task-master-ai', 74 | method: 'GET', 75 | headers: { 76 | Accept: 'application/vnd.npm.install-v1+json', 77 | 'User-Agent': `task-master-ai/${currentVersion}` 78 | } 79 | }; 80 | 81 | const req = https.request(options, (res) => { 82 | let data = ''; 83 | 84 | res.on('data', (chunk) => { 85 | data += chunk; 86 | }); 87 | 88 | res.on('end', () => { 89 | try { 90 | if (res.statusCode !== 200) 91 | throw new Error(`npm registry status ${res.statusCode}`); 92 | const npmData = JSON.parse(data); 93 | const latestVersion = npmData['dist-tags']?.latest || currentVersion; 94 | 95 | const needsUpdate = 96 | compareVersions(currentVersion, latestVersion) < 0; 97 | 98 | resolve({ 99 | currentVersion, 100 | latestVersion, 101 | needsUpdate 102 | }); 103 | } catch (error) { 104 | resolve({ 105 | currentVersion, 106 | latestVersion: currentVersion, 107 | needsUpdate: false 108 | }); 109 | } 110 | }); 111 | }); 112 | 113 | req.on('error', () => { 114 | resolve({ 115 | currentVersion, 116 | latestVersion: currentVersion, 117 | needsUpdate: false 118 | }); 119 | }); 120 | 121 | req.setTimeout(3000, () => { 122 | req.destroy(); 123 | resolve({ 124 | currentVersion, 125 | latestVersion: currentVersion, 126 | needsUpdate: false 127 | }); 128 | }); 129 | 130 | req.end(); 131 | }); 132 | } 133 | 134 | /** 135 | * Display upgrade notification message 136 | */ 137 | export function displayUpgradeNotification( 138 | currentVersion: string, 139 | latestVersion: string 140 | ) { 141 | const message = boxen( 142 | `${chalk.blue.bold('Update Available!')} ${chalk.dim(currentVersion)} → ${chalk.green(latestVersion)}\n\n` + 143 | `Auto-updating to the latest version with new features and bug fixes...`, 144 | { 145 | padding: 1, 146 | margin: { top: 1, bottom: 1 }, 147 | borderColor: 'yellow', 148 | borderStyle: 'round' 149 | } 150 | ); 151 | 152 | console.log(message); 153 | } 154 | 155 | /** 156 | * Automatically update task-master-ai to the latest version 157 | */ 158 | export async function performAutoUpdate( 159 | latestVersion: string 160 | ): Promise<boolean> { 161 | if (process.env.TASKMASTER_SKIP_AUTO_UPDATE === '1' || process.env.CI) { 162 | console.log( 163 | chalk.dim('Skipping auto-update (TASKMASTER_SKIP_AUTO_UPDATE/CI).') 164 | ); 165 | return false; 166 | } 167 | const spinner = ora({ 168 | text: chalk.blue( 169 | `Updating task-master-ai to version ${chalk.green(latestVersion)}` 170 | ), 171 | spinner: 'dots', 172 | color: 'blue' 173 | }).start(); 174 | 175 | return new Promise((resolve) => { 176 | const updateProcess = spawn( 177 | 'npm', 178 | [ 179 | 'install', 180 | '-g', 181 | `task-master-ai@${latestVersion}`, 182 | '--no-fund', 183 | '--no-audit', 184 | '--loglevel=warn' 185 | ], 186 | { 187 | stdio: ['ignore', 'pipe', 'pipe'] 188 | } 189 | ); 190 | 191 | let errorOutput = ''; 192 | 193 | updateProcess.stdout.on('data', () => { 194 | // Update spinner text with progress 195 | spinner.text = chalk.blue( 196 | `Installing task-master-ai@${latestVersion}...` 197 | ); 198 | }); 199 | 200 | updateProcess.stderr.on('data', (data) => { 201 | errorOutput += data.toString(); 202 | }); 203 | 204 | updateProcess.on('close', (code) => { 205 | if (code === 0) { 206 | spinner.succeed( 207 | chalk.green( 208 | `Successfully updated to version ${chalk.bold(latestVersion)}` 209 | ) 210 | ); 211 | console.log( 212 | chalk.dim('Please restart your command to use the new version.') 213 | ); 214 | resolve(true); 215 | } else { 216 | spinner.fail(chalk.red('Auto-update failed')); 217 | console.log( 218 | chalk.cyan( 219 | `Please run manually: npm install -g task-master-ai@${latestVersion}` 220 | ) 221 | ); 222 | if (errorOutput) { 223 | console.log(chalk.dim(`Error: ${errorOutput.trim()}`)); 224 | } 225 | resolve(false); 226 | } 227 | }); 228 | 229 | updateProcess.on('error', (error) => { 230 | spinner.fail(chalk.red('Auto-update failed')); 231 | console.log(chalk.red('Error:'), error.message); 232 | console.log( 233 | chalk.cyan( 234 | `Please run manually: npm install -g task-master-ai@${latestVersion}` 235 | ) 236 | ); 237 | resolve(false); 238 | }); 239 | }); 240 | } 241 | ``` -------------------------------------------------------------------------------- /apps/docs/best-practices/advanced-tasks.mdx: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: Advanced Tasks 3 | sidebarTitle: "Advanced Tasks" 4 | --- 5 | 6 | ## AI-Driven Development Workflow 7 | 8 | The Cursor agent is pre-configured (via the rules file) to follow this workflow: 9 | 10 | ### 1. Task Discovery and Selection 11 | 12 | Ask the agent to list available tasks: 13 | 14 | ``` 15 | What tasks are available to work on next? 16 | ``` 17 | 18 | ``` 19 | Can you show me tasks 1, 3, and 5 to understand their current status? 20 | ``` 21 | 22 | The agent will: 23 | 24 | - Run `task-master list` to see all tasks 25 | - Run `task-master next` to determine the next task to work on 26 | - Run `task-master show 1,3,5` to display multiple tasks with interactive options 27 | - Analyze dependencies to determine which tasks are ready to be worked on 28 | - Prioritize tasks based on priority level and ID order 29 | - Suggest the next task(s) to implement 30 | 31 | ### 2. Task Implementation 32 | 33 | When implementing a task, the agent will: 34 | 35 | - Reference the task's details section for implementation specifics 36 | - Consider dependencies on previous tasks 37 | - Follow the project's coding standards 38 | - Create appropriate tests based on the task's testStrategy 39 | 40 | You can ask: 41 | 42 | ``` 43 | Let's implement task 3. What does it involve? 44 | ``` 45 | 46 | ### 2.1. Viewing Multiple Tasks 47 | 48 | For efficient context gathering and batch operations: 49 | 50 | ``` 51 | Show me tasks 5, 7, and 9 so I can plan my implementation approach. 52 | ``` 53 | 54 | The agent will: 55 | 56 | - Run `task-master show 5,7,9` to display a compact summary table 57 | - Show task status, priority, and progress indicators 58 | - Provide an interactive action menu with batch operations 59 | - Allow you to perform group actions like marking multiple tasks as in-progress 60 | 61 | ### 3. Task Verification 62 | 63 | Before marking a task as complete, verify it according to: 64 | 65 | - The task's specified testStrategy 66 | - Any automated tests in the codebase 67 | - Manual verification if required 68 | 69 | ### 4. Task Completion 70 | 71 | When a task is completed, tell the agent: 72 | 73 | ``` 74 | Task 3 is now complete. Please update its status. 75 | ``` 76 | 77 | The agent will execute: 78 | 79 | ```bash 80 | task-master set-status --id=3 --status=done 81 | ``` 82 | 83 | ### 5. Handling Implementation Drift 84 | 85 | If during implementation, you discover that: 86 | 87 | - The current approach differs significantly from what was planned 88 | - Future tasks need to be modified due to current implementation choices 89 | - New dependencies or requirements have emerged 90 | 91 | Tell the agent: 92 | 93 | ``` 94 | We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks (from ID 4) to reflect this change? 95 | ``` 96 | 97 | The agent will execute: 98 | 99 | ```bash 100 | task-master update --from=4 --prompt="Now we are using MongoDB instead of PostgreSQL." 101 | 102 | # OR, if research is needed to find best practices for MongoDB: 103 | task-master update --from=4 --prompt="Update to use MongoDB, researching best practices" --research 104 | ``` 105 | 106 | This will rewrite or re-scope subsequent tasks in tasks.json while preserving completed work. 107 | 108 | ### 6. Reorganizing Tasks 109 | 110 | If you need to reorganize your task structure: 111 | 112 | ``` 113 | I think subtask 5.2 would fit better as part of task 7 instead. Can you move it there? 114 | ``` 115 | 116 | The agent will execute: 117 | 118 | ```bash 119 | task-master move --from=5.2 --to=7.3 120 | ``` 121 | 122 | You can reorganize tasks in various ways: 123 | 124 | - Moving a standalone task to become a subtask: `--from=5 --to=7` 125 | - Moving a subtask to become a standalone task: `--from=5.2 --to=7` 126 | - Moving a subtask to a different parent: `--from=5.2 --to=7.3` 127 | - Reordering subtasks within the same parent: `--from=5.2 --to=5.4` 128 | - Moving a task to a new ID position: `--from=5 --to=25` (even if task 25 doesn't exist yet) 129 | - Moving multiple tasks at once: `--from=10,11,12 --to=16,17,18` (must have same number of IDs, Taskmaster will look through each position) 130 | 131 | When moving tasks to new IDs: 132 | 133 | - The system automatically creates placeholder tasks for non-existent destination IDs 134 | - This prevents accidental data loss during reorganization 135 | - Any tasks that depend on moved tasks will have their dependencies updated 136 | - When moving a parent task, all its subtasks are automatically moved with it and renumbered 137 | 138 | This is particularly useful as your project understanding evolves and you need to refine your task structure. 139 | 140 | ### 7. Resolving Merge Conflicts with Tasks 141 | 142 | When working with a team, you might encounter merge conflicts in your tasks.json file if multiple team members create tasks on different branches. The move command makes resolving these conflicts straightforward: 143 | 144 | ``` 145 | I just merged the main branch and there's a conflict with tasks.json. My teammates created tasks 10-15 while I created tasks 10-12 on my branch. Can you help me resolve this? 146 | ``` 147 | 148 | The agent will help you: 149 | 150 | 1. Keep your teammates' tasks (10-15) 151 | 2. Move your tasks to new positions to avoid conflicts: 152 | 153 | ```bash 154 | # Move your tasks to new positions (e.g., 16-18) 155 | task-master move --from=10 --to=16 156 | task-master move --from=11 --to=17 157 | task-master move --from=12 --to=18 158 | ``` 159 | 160 | This approach preserves everyone's work while maintaining a clean task structure, making it much easier to handle task conflicts than trying to manually merge JSON files. 161 | 162 | ### 8. Breaking Down Complex Tasks 163 | 164 | For complex tasks that need more granularity: 165 | 166 | ``` 167 | Task 5 seems complex. Can you break it down into subtasks? 168 | ``` 169 | 170 | The agent will execute: 171 | 172 | ```bash 173 | task-master expand --id=5 --num=3 174 | ``` 175 | 176 | You can provide additional context: 177 | 178 | ``` 179 | Please break down task 5 with a focus on security considerations. 180 | ``` 181 | 182 | The agent will execute: 183 | 184 | ```bash 185 | task-master expand --id=5 --prompt="Focus on security aspects" 186 | ``` 187 | 188 | You can also expand all pending tasks: 189 | 190 | ``` 191 | Please break down all pending tasks into subtasks. 192 | ``` 193 | 194 | The agent will execute: 195 | 196 | ```bash 197 | task-master expand --all 198 | ``` 199 | 200 | For research-backed subtask generation using the configured research model: 201 | 202 | ``` 203 | Please break down task 5 using research-backed generation. 204 | ``` 205 | 206 | The agent will execute: 207 | 208 | ```bash 209 | task-master expand --id=5 --research 210 | ``` ``` -------------------------------------------------------------------------------- /packages/tm-core/src/storage/file-storage/format-handler.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * @fileoverview Format handler for task storage files 3 | */ 4 | 5 | import type { Task, TaskMetadata } from '../../types/index.js'; 6 | 7 | export interface FileStorageData { 8 | tasks: Task[]; 9 | metadata: TaskMetadata; 10 | } 11 | 12 | export type FileFormat = 'legacy' | 'standard'; 13 | 14 | /** 15 | * Handles format detection and conversion between legacy and standard task file formats 16 | */ 17 | export class FormatHandler { 18 | /** 19 | * Detect the format of the raw data 20 | */ 21 | detectFormat(data: any): FileFormat { 22 | if (!data || typeof data !== 'object') { 23 | return 'standard'; 24 | } 25 | 26 | const keys = Object.keys(data); 27 | 28 | // Check if this uses the legacy format with tag keys 29 | // Legacy format has keys that are not 'tasks' or 'metadata' 30 | const hasLegacyFormat = keys.some( 31 | (key) => key !== 'tasks' && key !== 'metadata' 32 | ); 33 | 34 | return hasLegacyFormat ? 'legacy' : 'standard'; 35 | } 36 | 37 | /** 38 | * Extract tasks from data for a specific tag 39 | */ 40 | extractTasks(data: any, tag: string): Task[] { 41 | if (!data) { 42 | return []; 43 | } 44 | 45 | const format = this.detectFormat(data); 46 | 47 | if (format === 'legacy') { 48 | return this.extractTasksFromLegacy(data, tag); 49 | } 50 | 51 | return this.extractTasksFromStandard(data); 52 | } 53 | 54 | /** 55 | * Extract tasks from legacy format 56 | */ 57 | private extractTasksFromLegacy(data: any, tag: string): Task[] { 58 | // First check if the requested tag exists 59 | if (tag in data) { 60 | const tagData = data[tag]; 61 | return tagData?.tasks || []; 62 | } 63 | 64 | // If we're looking for 'master' tag but it doesn't exist, try the first available tag 65 | const availableKeys = Object.keys(data).filter( 66 | (key) => key !== 'tasks' && key !== 'metadata' 67 | ); 68 | if (tag === 'master' && availableKeys.length > 0) { 69 | const firstTag = availableKeys[0]; 70 | const tagData = data[firstTag]; 71 | return tagData?.tasks || []; 72 | } 73 | 74 | return []; 75 | } 76 | 77 | /** 78 | * Extract tasks from standard format 79 | */ 80 | private extractTasksFromStandard(data: any): Task[] { 81 | return data?.tasks || []; 82 | } 83 | 84 | /** 85 | * Extract metadata from data for a specific tag 86 | */ 87 | extractMetadata(data: any, tag: string): TaskMetadata | null { 88 | if (!data) { 89 | return null; 90 | } 91 | 92 | const format = this.detectFormat(data); 93 | 94 | if (format === 'legacy') { 95 | return this.extractMetadataFromLegacy(data, tag); 96 | } 97 | 98 | return this.extractMetadataFromStandard(data); 99 | } 100 | 101 | /** 102 | * Extract metadata from legacy format 103 | */ 104 | private extractMetadataFromLegacy( 105 | data: any, 106 | tag: string 107 | ): TaskMetadata | null { 108 | if (tag in data) { 109 | const tagData = data[tag]; 110 | // Generate metadata if not present in legacy format 111 | if (!tagData?.metadata && tagData?.tasks) { 112 | return this.generateMetadataFromTasks(tagData.tasks, tag); 113 | } 114 | return tagData?.metadata || null; 115 | } 116 | 117 | // If we're looking for 'master' tag but it doesn't exist, try the first available tag 118 | const availableKeys = Object.keys(data).filter( 119 | (key) => key !== 'tasks' && key !== 'metadata' 120 | ); 121 | if (tag === 'master' && availableKeys.length > 0) { 122 | const firstTag = availableKeys[0]; 123 | const tagData = data[firstTag]; 124 | if (!tagData?.metadata && tagData?.tasks) { 125 | return this.generateMetadataFromTasks(tagData.tasks, firstTag); 126 | } 127 | return tagData?.metadata || null; 128 | } 129 | 130 | return null; 131 | } 132 | 133 | /** 134 | * Extract metadata from standard format 135 | */ 136 | private extractMetadataFromStandard(data: any): TaskMetadata | null { 137 | return data?.metadata || null; 138 | } 139 | 140 | /** 141 | * Extract all available tags from the single tasks.json file 142 | */ 143 | extractTags(data: any): string[] { 144 | if (!data) { 145 | return []; 146 | } 147 | 148 | const format = this.detectFormat(data); 149 | 150 | if (format === 'legacy') { 151 | // Return all tag keys from legacy format 152 | const keys = Object.keys(data); 153 | return keys.filter((key) => key !== 'tasks' && key !== 'metadata'); 154 | } 155 | 156 | // Standard format - just has 'master' tag 157 | return ['master']; 158 | } 159 | 160 | /** 161 | * Convert tasks and metadata to the appropriate format for saving 162 | */ 163 | convertToSaveFormat( 164 | tasks: Task[], 165 | metadata: TaskMetadata, 166 | existingData: any, 167 | tag: string 168 | ): any { 169 | const resolvedTag = tag || 'master'; 170 | 171 | // Normalize task IDs to strings 172 | const normalizedTasks = this.normalizeTasks(tasks); 173 | 174 | // Check if existing file uses legacy format 175 | if (existingData && this.detectFormat(existingData) === 'legacy') { 176 | return this.convertToLegacyFormat(normalizedTasks, metadata, resolvedTag); 177 | } 178 | 179 | // Use standard format for new files 180 | return this.convertToStandardFormat(normalizedTasks, metadata, tag); 181 | } 182 | 183 | /** 184 | * Convert to legacy format 185 | */ 186 | private convertToLegacyFormat( 187 | tasks: Task[], 188 | metadata: TaskMetadata, 189 | tag: string 190 | ): any { 191 | return { 192 | [tag]: { 193 | tasks, 194 | metadata: { 195 | ...metadata, 196 | tags: [tag] 197 | } 198 | } 199 | }; 200 | } 201 | 202 | /** 203 | * Convert to standard format 204 | */ 205 | private convertToStandardFormat( 206 | tasks: Task[], 207 | metadata: TaskMetadata, 208 | tag?: string 209 | ): FileStorageData { 210 | return { 211 | tasks, 212 | metadata: { 213 | ...metadata, 214 | tags: tag ? [tag] : [] 215 | } 216 | }; 217 | } 218 | 219 | /** 220 | * Normalize task IDs - keep Task IDs as strings, Subtask IDs as numbers 221 | */ 222 | private normalizeTasks(tasks: Task[]): Task[] { 223 | return tasks.map((task) => ({ 224 | ...task, 225 | id: String(task.id), // Task IDs are strings 226 | dependencies: task.dependencies?.map((dep) => String(dep)) || [], 227 | subtasks: 228 | task.subtasks?.map((subtask) => ({ 229 | ...subtask, 230 | id: Number(subtask.id), // Subtask IDs are numbers 231 | parentId: String(subtask.parentId) // Parent ID is string (Task ID) 232 | })) || [] 233 | })); 234 | } 235 | 236 | /** 237 | * Generate metadata from tasks when not present 238 | */ 239 | private generateMetadataFromTasks(tasks: Task[], tag: string): TaskMetadata { 240 | return { 241 | version: '1.0.0', 242 | lastModified: new Date().toISOString(), 243 | taskCount: tasks.length, 244 | completedCount: tasks.filter((t: any) => t.status === 'done').length, 245 | tags: [tag] 246 | }; 247 | } 248 | } 249 | ``` -------------------------------------------------------------------------------- /packages/tm-core/src/config/services/config-merger.service.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * @fileoverview Unit tests for ConfigMerger service 3 | */ 4 | 5 | import { describe, it, expect, beforeEach } from 'vitest'; 6 | import { ConfigMerger, CONFIG_PRECEDENCE } from './config-merger.service.js'; 7 | 8 | describe('ConfigMerger', () => { 9 | let merger: ConfigMerger; 10 | 11 | beforeEach(() => { 12 | merger = new ConfigMerger(); 13 | }); 14 | 15 | describe('addSource', () => { 16 | it('should add configuration source', () => { 17 | const source = { 18 | name: 'test', 19 | config: { test: true }, 20 | precedence: 1 21 | }; 22 | 23 | merger.addSource(source); 24 | const sources = merger.getSources(); 25 | 26 | expect(sources).toHaveLength(1); 27 | expect(sources[0]).toEqual(source); 28 | }); 29 | 30 | it('should add multiple sources', () => { 31 | merger.addSource({ name: 'source1', config: {}, precedence: 1 }); 32 | merger.addSource({ name: 'source2', config: {}, precedence: 2 }); 33 | 34 | expect(merger.getSources()).toHaveLength(2); 35 | }); 36 | }); 37 | 38 | describe('clearSources', () => { 39 | it('should remove all configuration sources', () => { 40 | merger.addSource({ name: 'test', config: {}, precedence: 1 }); 41 | merger.clearSources(); 42 | 43 | expect(merger.getSources()).toHaveLength(0); 44 | }); 45 | }); 46 | 47 | describe('merge', () => { 48 | it('should merge configurations based on precedence', () => { 49 | merger.addSource({ 50 | name: 'low', 51 | config: { a: 1, b: 2 }, 52 | precedence: 1 53 | }); 54 | 55 | merger.addSource({ 56 | name: 'high', 57 | config: { a: 3, c: 4 }, 58 | precedence: 2 59 | }); 60 | 61 | const result = merger.merge(); 62 | 63 | expect(result).toEqual({ 64 | a: 3, // High precedence wins 65 | b: 2, // Only in low 66 | c: 4 // Only in high 67 | }); 68 | }); 69 | 70 | it('should deep merge nested objects', () => { 71 | merger.addSource({ 72 | name: 'base', 73 | config: { 74 | models: { main: 'model1', fallback: 'model2' }, 75 | storage: { type: 'file' as const } 76 | }, 77 | precedence: 1 78 | }); 79 | 80 | merger.addSource({ 81 | name: 'override', 82 | config: { 83 | models: { main: 'model3' }, 84 | storage: { encoding: 'utf8' as const } 85 | }, 86 | precedence: 2 87 | }); 88 | 89 | const result = merger.merge(); 90 | 91 | expect(result).toEqual({ 92 | models: { 93 | main: 'model3', // Overridden 94 | fallback: 'model2' // Preserved 95 | }, 96 | storage: { 97 | type: 'file', // Preserved 98 | encoding: 'utf8' // Added 99 | } 100 | }); 101 | }); 102 | 103 | it('should handle arrays by replacement', () => { 104 | merger.addSource({ 105 | name: 'base', 106 | config: { items: [1, 2, 3] }, 107 | precedence: 1 108 | }); 109 | 110 | merger.addSource({ 111 | name: 'override', 112 | config: { items: [4, 5] }, 113 | precedence: 2 114 | }); 115 | 116 | const result = merger.merge(); 117 | 118 | expect(result.items).toEqual([4, 5]); // Arrays are replaced, not merged 119 | }); 120 | 121 | it('should ignore null and undefined values', () => { 122 | merger.addSource({ 123 | name: 'base', 124 | config: { a: 1, b: 2 }, 125 | precedence: 1 126 | }); 127 | 128 | merger.addSource({ 129 | name: 'override', 130 | config: { a: null, b: undefined, c: 3 } as any, 131 | precedence: 2 132 | }); 133 | 134 | const result = merger.merge(); 135 | 136 | expect(result).toEqual({ 137 | a: 1, // null ignored 138 | b: 2, // undefined ignored 139 | c: 3 // new value added 140 | }); 141 | }); 142 | 143 | it('should return empty object when no sources', () => { 144 | const result = merger.merge(); 145 | expect(result).toEqual({}); 146 | }); 147 | 148 | it('should use CONFIG_PRECEDENCE constants correctly', () => { 149 | merger.addSource({ 150 | name: 'defaults', 151 | config: { level: 'default' }, 152 | precedence: CONFIG_PRECEDENCE.DEFAULTS 153 | }); 154 | 155 | merger.addSource({ 156 | name: 'local', 157 | config: { level: 'local' }, 158 | precedence: CONFIG_PRECEDENCE.LOCAL 159 | }); 160 | 161 | merger.addSource({ 162 | name: 'environment', 163 | config: { level: 'env' }, 164 | precedence: CONFIG_PRECEDENCE.ENVIRONMENT 165 | }); 166 | 167 | const result = merger.merge(); 168 | 169 | expect(result.level).toBe('env'); // Highest precedence wins 170 | }); 171 | }); 172 | 173 | describe('getSources', () => { 174 | it('should return sources sorted by precedence (highest first)', () => { 175 | merger.addSource({ name: 'low', config: {}, precedence: 1 }); 176 | merger.addSource({ name: 'high', config: {}, precedence: 3 }); 177 | merger.addSource({ name: 'medium', config: {}, precedence: 2 }); 178 | 179 | const sources = merger.getSources(); 180 | 181 | expect(sources[0].name).toBe('high'); 182 | expect(sources[1].name).toBe('medium'); 183 | expect(sources[2].name).toBe('low'); 184 | }); 185 | 186 | it('should return a copy of sources array', () => { 187 | merger.addSource({ name: 'test', config: {}, precedence: 1 }); 188 | 189 | const sources1 = merger.getSources(); 190 | const sources2 = merger.getSources(); 191 | 192 | expect(sources1).not.toBe(sources2); // Different array instances 193 | expect(sources1).toEqual(sources2); // Same content 194 | }); 195 | }); 196 | 197 | describe('hasSource', () => { 198 | it('should return true when source exists', () => { 199 | merger.addSource({ name: 'test', config: {}, precedence: 1 }); 200 | 201 | expect(merger.hasSource('test')).toBe(true); 202 | }); 203 | 204 | it('should return false when source does not exist', () => { 205 | expect(merger.hasSource('nonexistent')).toBe(false); 206 | }); 207 | }); 208 | 209 | describe('removeSource', () => { 210 | it('should remove source by name and return true', () => { 211 | merger.addSource({ name: 'test', config: {}, precedence: 1 }); 212 | merger.addSource({ name: 'keep', config: {}, precedence: 2 }); 213 | 214 | const removed = merger.removeSource('test'); 215 | 216 | expect(removed).toBe(true); 217 | expect(merger.hasSource('test')).toBe(false); 218 | expect(merger.hasSource('keep')).toBe(true); 219 | }); 220 | 221 | it('should return false when source does not exist', () => { 222 | const removed = merger.removeSource('nonexistent'); 223 | 224 | expect(removed).toBe(false); 225 | }); 226 | 227 | it('should handle removing all sources', () => { 228 | merger.addSource({ name: 'test1', config: {}, precedence: 1 }); 229 | merger.addSource({ name: 'test2', config: {}, precedence: 2 }); 230 | 231 | merger.removeSource('test1'); 232 | merger.removeSource('test2'); 233 | 234 | expect(merger.getSources()).toHaveLength(0); 235 | }); 236 | }); 237 | }); 238 | ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/add-task.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * add-task.js 3 | * Direct function implementation for adding a new task 4 | */ 5 | 6 | import { addTask } from '../../../../scripts/modules/task-manager.js'; 7 | import { 8 | enableSilentMode, 9 | disableSilentMode 10 | } from '../../../../scripts/modules/utils.js'; 11 | import { createLogWrapper } from '../../tools/utils.js'; 12 | 13 | /** 14 | * Direct function wrapper for adding a new task with error handling. 15 | * 16 | * @param {Object} args - Command arguments 17 | * @param {string} [args.prompt] - Description of the task to add (required if not using manual fields) 18 | * @param {string} [args.title] - Task title (for manual task creation) 19 | * @param {string} [args.description] - Task description (for manual task creation) 20 | * @param {string} [args.details] - Implementation details (for manual task creation) 21 | * @param {string} [args.testStrategy] - Test strategy (for manual task creation) 22 | * @param {string} [args.dependencies] - Comma-separated list of task IDs this task depends on 23 | * @param {string} [args.priority='medium'] - Task priority (high, medium, low) 24 | * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool) 25 | * @param {boolean} [args.research=false] - Whether to use research capabilities for task creation 26 | * @param {string} [args.projectRoot] - Project root path 27 | * @param {string} [args.tag] - Tag for the task (optional) 28 | * @param {Object} log - Logger object 29 | * @param {Object} context - Additional context (session) 30 | * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } } 31 | */ 32 | export async function addTaskDirect(args, log, context = {}) { 33 | // Destructure expected args (including research and projectRoot) 34 | const { 35 | tasksJsonPath, 36 | prompt, 37 | dependencies, 38 | priority, 39 | research, 40 | projectRoot, 41 | tag 42 | } = args; 43 | const { session } = context; // Destructure session from context 44 | 45 | // Enable silent mode to prevent console logs from interfering with JSON response 46 | enableSilentMode(); 47 | 48 | // Create logger wrapper using the utility 49 | const mcpLog = createLogWrapper(log); 50 | 51 | try { 52 | // Check if tasksJsonPath was provided 53 | if (!tasksJsonPath) { 54 | log.error('addTaskDirect called without tasksJsonPath'); 55 | disableSilentMode(); // Disable before returning 56 | return { 57 | success: false, 58 | error: { 59 | code: 'MISSING_ARGUMENT', 60 | message: 'tasksJsonPath is required' 61 | } 62 | }; 63 | } 64 | 65 | // Use provided path 66 | const tasksPath = tasksJsonPath; 67 | 68 | // Check if this is manual task creation or AI-driven task creation 69 | const isManualCreation = args.title && args.description; 70 | 71 | // Check required parameters 72 | if (!args.prompt && !isManualCreation) { 73 | log.error( 74 | 'Missing required parameters: either prompt or title+description must be provided' 75 | ); 76 | disableSilentMode(); 77 | return { 78 | success: false, 79 | error: { 80 | code: 'MISSING_PARAMETER', 81 | message: 82 | 'Either the prompt parameter or both title and description parameters are required for adding a task' 83 | } 84 | }; 85 | } 86 | 87 | // Extract and prepare parameters 88 | const taskDependencies = Array.isArray(dependencies) 89 | ? dependencies // Already an array if passed directly 90 | : dependencies // Check if dependencies exist and are a string 91 | ? String(dependencies) 92 | .split(',') 93 | .map((id) => parseInt(id.trim(), 10)) // Split, trim, and parse 94 | : []; // Default to empty array if null/undefined 95 | const taskPriority = priority || 'medium'; // Default priority 96 | 97 | let manualTaskData = null; 98 | let newTaskId; 99 | let telemetryData; 100 | let tagInfo; 101 | 102 | if (isManualCreation) { 103 | // Create manual task data object 104 | manualTaskData = { 105 | title: args.title, 106 | description: args.description, 107 | details: args.details || '', 108 | testStrategy: args.testStrategy || '' 109 | }; 110 | 111 | log.info( 112 | `Adding new task manually with title: "${args.title}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}` 113 | ); 114 | 115 | // Call the addTask function with manual task data 116 | const result = await addTask( 117 | tasksPath, 118 | null, // prompt is null for manual creation 119 | taskDependencies, 120 | taskPriority, 121 | { 122 | session, 123 | mcpLog, 124 | projectRoot, 125 | commandName: 'add-task', 126 | outputType: 'mcp', 127 | tag 128 | }, 129 | 'json', // outputFormat 130 | manualTaskData, // Pass the manual task data 131 | false // research flag is false for manual creation 132 | ); 133 | newTaskId = result.newTaskId; 134 | telemetryData = result.telemetryData; 135 | tagInfo = result.tagInfo; 136 | } else { 137 | // AI-driven task creation 138 | log.info( 139 | `Adding new task with prompt: "${prompt}", dependencies: [${taskDependencies.join(', ')}], priority: ${taskPriority}, research: ${research}` 140 | ); 141 | 142 | // Call the addTask function, passing the research flag 143 | const result = await addTask( 144 | tasksPath, 145 | prompt, // Use the prompt for AI creation 146 | taskDependencies, 147 | taskPriority, 148 | { 149 | session, 150 | mcpLog, 151 | projectRoot, 152 | commandName: 'add-task', 153 | outputType: 'mcp', 154 | tag 155 | }, 156 | 'json', // outputFormat 157 | null, // manualTaskData is null for AI creation 158 | research // Pass the research flag 159 | ); 160 | newTaskId = result.newTaskId; 161 | telemetryData = result.telemetryData; 162 | tagInfo = result.tagInfo; 163 | } 164 | 165 | // Restore normal logging 166 | disableSilentMode(); 167 | 168 | return { 169 | success: true, 170 | data: { 171 | taskId: newTaskId, 172 | message: `Successfully added new task #${newTaskId}`, 173 | telemetryData: telemetryData, 174 | tagInfo: tagInfo 175 | } 176 | }; 177 | } catch (error) { 178 | // Make sure to restore normal logging even if there's an error 179 | disableSilentMode(); 180 | 181 | log.error(`Error in addTaskDirect: ${error.message}`); 182 | // Add specific error code checks if needed 183 | return { 184 | success: false, 185 | error: { 186 | code: error.code || 'ADD_TASK_ERROR', // Use error code if available 187 | message: error.message 188 | } 189 | }; 190 | } 191 | } 192 | ``` -------------------------------------------------------------------------------- /scripts/modules/sync-readme.js: -------------------------------------------------------------------------------- ```javascript 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import { log, findProjectRoot } from './utils.js'; 5 | import { getProjectName } from './config-manager.js'; 6 | import listTasks from './task-manager/list-tasks.js'; 7 | 8 | /** 9 | * Creates a basic README structure if one doesn't exist 10 | * @param {string} projectName - Name of the project 11 | * @returns {string} - Basic README content 12 | */ 13 | function createBasicReadme(projectName) { 14 | return `# ${projectName} 15 | 16 | This project is managed using Task Master. 17 | 18 | `; 19 | } 20 | 21 | /** 22 | * Create UTM tracking URL for task-master.dev 23 | * @param {string} projectRoot - The project root path 24 | * @returns {string} - UTM tracked URL 25 | */ 26 | function createTaskMasterUrl(projectRoot) { 27 | // Get the actual folder name from the project root path 28 | const folderName = path.basename(projectRoot); 29 | 30 | // Clean folder name for UTM (replace spaces/special chars with hyphens) 31 | const cleanFolderName = folderName 32 | .toLowerCase() 33 | .replace(/[^a-z0-9]/g, '-') 34 | .replace(/-+/g, '-') 35 | .replace(/^-|-$/g, ''); 36 | 37 | const utmParams = new URLSearchParams({ 38 | utm_source: 'github-readme', 39 | utm_medium: 'readme-export', 40 | utm_campaign: cleanFolderName || 'task-sync', 41 | utm_content: 'task-export-link' 42 | }); 43 | 44 | return `https://task-master.dev?${utmParams.toString()}`; 45 | } 46 | 47 | /** 48 | * Create the start marker with metadata 49 | * @param {Object} options - Export options 50 | * @returns {string} - Formatted start marker 51 | */ 52 | function createStartMarker(options) { 53 | const { timestamp, withSubtasks, status, projectRoot } = options; 54 | 55 | // Format status filter text 56 | const statusText = status 57 | ? `Status filter: ${status}` 58 | : 'Status filter: none'; 59 | const subtasksText = withSubtasks ? 'with subtasks' : 'without subtasks'; 60 | 61 | // Create the export info content 62 | const exportInfo = 63 | `🎯 **Taskmaster Export** - ${timestamp}\n` + 64 | `📋 Export: ${subtasksText} • ${statusText}\n` + 65 | `🔗 Powered by [Task Master](${createTaskMasterUrl(projectRoot)})`; 66 | 67 | // Create a markdown box using code blocks and emojis to mimic our UI style 68 | const boxContent = 69 | `<!-- TASKMASTER_EXPORT_START -->\n` + 70 | `> ${exportInfo.split('\n').join('\n> ')}\n\n`; 71 | 72 | return boxContent; 73 | } 74 | 75 | /** 76 | * Create the end marker 77 | * @returns {string} - Formatted end marker 78 | */ 79 | function createEndMarker() { 80 | return ( 81 | `\n> 📋 **End of Taskmaster Export** - Tasks are synced from your project using the \`sync-readme\` command.\n` + 82 | `<!-- TASKMASTER_EXPORT_END -->\n` 83 | ); 84 | } 85 | 86 | /** 87 | * Syncs the current task list to README.md at the project root 88 | * @param {string} projectRoot - Path to the project root directory 89 | * @param {Object} options - Options for syncing 90 | * @param {boolean} options.withSubtasks - Include subtasks in the output (default: false) 91 | * @param {string} options.status - Filter by status (e.g., 'pending', 'done') 92 | * @param {string} options.tasksPath - Custom path to tasks.json 93 | * @returns {boolean} - True if sync was successful, false otherwise 94 | * TODO: Add tag support - this is not currently supported how we want to handle this - Parthy 95 | */ 96 | export async function syncTasksToReadme(projectRoot = null, options = {}) { 97 | try { 98 | const actualProjectRoot = projectRoot || findProjectRoot() || '.'; 99 | const { withSubtasks = false, status, tasksPath, tag } = options; 100 | 101 | // Get current tasks using the list-tasks functionality with markdown-readme format 102 | const tasksOutput = await listTasks( 103 | tasksPath || 104 | path.join(actualProjectRoot, '.taskmaster', 'tasks', 'tasks.json'), 105 | status, 106 | null, 107 | withSubtasks, 108 | 'markdown-readme', 109 | { projectRoot, tag } 110 | ); 111 | 112 | if (!tasksOutput) { 113 | console.log(chalk.red('❌ Failed to generate task output')); 114 | return false; 115 | } 116 | 117 | // Generate timestamp and metadata 118 | const timestamp = 119 | new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; 120 | const projectName = getProjectName(actualProjectRoot); 121 | 122 | // Create the export markers with metadata 123 | const startMarker = createStartMarker({ 124 | timestamp, 125 | withSubtasks, 126 | status, 127 | projectRoot: actualProjectRoot 128 | }); 129 | 130 | const endMarker = createEndMarker(); 131 | 132 | // Create the complete task section 133 | const taskSection = startMarker + tasksOutput + endMarker; 134 | 135 | // Read current README content 136 | const readmePath = path.join(actualProjectRoot, 'README.md'); 137 | let readmeContent = ''; 138 | try { 139 | readmeContent = fs.readFileSync(readmePath, 'utf8'); 140 | } catch (err) { 141 | if (err.code === 'ENOENT') { 142 | // Create basic README if it doesn't exist 143 | readmeContent = createBasicReadme(projectName); 144 | } else { 145 | throw err; 146 | } 147 | } 148 | 149 | // Check if export markers exist and replace content between them 150 | const startComment = '<!-- TASKMASTER_EXPORT_START -->'; 151 | const endComment = '<!-- TASKMASTER_EXPORT_END -->'; 152 | 153 | let updatedContent; 154 | const startIndex = readmeContent.indexOf(startComment); 155 | const endIndex = readmeContent.indexOf(endComment); 156 | 157 | if (startIndex !== -1 && endIndex !== -1) { 158 | // Replace existing task section 159 | const beforeTasks = readmeContent.substring(0, startIndex); 160 | const afterTasks = readmeContent.substring(endIndex + endComment.length); 161 | updatedContent = beforeTasks + taskSection + afterTasks; 162 | } else { 163 | // Append to end of README 164 | updatedContent = readmeContent + '\n' + taskSection; 165 | } 166 | 167 | // Write updated content to README 168 | fs.writeFileSync(readmePath, updatedContent, 'utf8'); 169 | 170 | console.log(chalk.green('✅ Successfully synced tasks to README.md')); 171 | console.log( 172 | chalk.cyan( 173 | `📋 Export details: ${withSubtasks ? 'with' : 'without'} subtasks${status ? `, status: ${status}` : ''}` 174 | ) 175 | ); 176 | console.log(chalk.gray(`📍 Location: ${readmePath}`)); 177 | 178 | return true; 179 | } catch (error) { 180 | console.log(chalk.red('❌ Failed to sync tasks to README:'), error.message); 181 | log('error', `README sync error: ${error.message}`); 182 | return false; 183 | } 184 | } 185 | 186 | export default syncTasksToReadme; 187 | ``` -------------------------------------------------------------------------------- /tests/fixtures/sample-prd.txt: -------------------------------------------------------------------------------- ``` 1 | <context> 2 | # Overview 3 | This document outlines the requirements for a minimal web-based URL Shortener application. The application allows users to input a long URL and receive a shorter, alias URL that redirects to the original destination. This serves as a basic example of a micro-SaaS product. It's intended for anyone needing to create shorter links for sharing. The value is in providing a simple, functional utility accessible via a web browser. 4 | 5 | # Core Features 6 | 1. **URL Input & Shortening:** A user interface with an input field for pasting a long URL and a button to trigger the shortening process. 7 | - *Why:* The primary function for the user interaction. 8 | - *How:* A React component with a text input and a submit button. Clicking the button sends the long URL to a backend API. 9 | 2. **Short URL Display:** After successful shortening, the application displays the newly generated short URL to the user. 10 | - *Why:* Provides the result of the core function to the user. 11 | - *How:* The React frontend updates to show the short URL returned by the API (e.g., `http://your-domain.com/aB3cD`). Include a "copy to clipboard" button for convenience. 12 | 3. **URL Redirection:** Accessing a generated short URL in a browser redirects the user to the original long URL. 13 | - *Why:* The fundamental purpose of the shortened link. 14 | * *How:* A backend API endpoint handles requests to `/:shortCode`. It looks up the code in a data store and issues an HTTP redirect (301 or 302) to the corresponding long URL. 15 | 4. **Basic Persistence:** Short URL mappings (short code -> long URL) persist across requests. 16 | - *Why:* Short URLs need to remain functional after creation. 17 | * *How:* A simple backend data store (e.g., initially an in-memory object for testing, then potentially a JSON file or simple database) holds the mappings. 18 | 19 | # User Experience 20 | - **User Persona:** Anyone wanting to shorten a long web link. 21 | - **Key User Flow:** User visits the web app -> Pastes a long URL into the input field -> Clicks "Shorten" -> Sees the generated short URL -> Copies the short URL -> (Later) Uses the short URL in a browser and gets redirected. 22 | - **UI/UX Considerations:** Clean, minimal single-page interface. Clear input field, prominent button, easy-to-read display of the short URL, copy button. Basic validation feedback (e.g., "Invalid URL", "Success!"). 23 | </context> 24 | <PRD> 25 | # Technical Architecture 26 | - **System Components:** 27 | - Frontend: Single Page Application (SPA) built with Vite + React. 28 | - Backend: Simple API server (e.g., Node.js with Express). 29 | - **Data Model:** A key-value store mapping `shortCode` (string) to `longUrl` (string). 30 | - **APIs & Integrations:** 31 | - Backend API: 32 | - `POST /api/shorten`: Accepts `{ longUrl: string }` in the request body. Generates a unique `shortCode`, stores the mapping, returns `{ shortUrl: string }`. 33 | - `GET /:shortCode`: Looks up `shortCode`. If found, performs HTTP redirect to `longUrl`. If not found, returns 404. 34 | - **Infrastructure:** Frontend can be hosted on static hosting. Backend needs a simple server environment (Node.js). 35 | - **Libraries:** 36 | - Frontend: `react`, `react-dom`, `axios` (or `fetch` API) for API calls. Consider a simple state management solution if needed (e.g., `useState`, `useContext`). 37 | - Backend: `express`, `nanoid` (or similar for short code generation). 38 | 39 | # Development Roadmap 40 | - **MVP Requirements:** 41 | 1. Setup Vite + React project. 42 | 2. Create basic React UI components (InputForm, ResultDisplay). 43 | 3. Setup basic Node.js/Express backend server. 44 | 4. Implement backend data storage module (start with in-memory object). 45 | 5. Implement unique short code generation logic (e.g., using `nanoid`). 46 | 6. Implement backend `POST /api/shorten` endpoint logic. 47 | 7. Implement backend `GET /:shortCode` redirect logic. 48 | 8. Implement frontend logic to take input, call `POST /api/shorten`, and display the result. 49 | 9. Basic frontend input validation (check if likely a URL). 50 | - **Future Enhancements:** User accounts, custom short codes, analytics (click tracking), using a persistent database, error handling improvements, UI styling. (Out of scope for MVP). 51 | 52 | # Logical Dependency Chain 53 | 1. Vite + React Project Setup. 54 | 2. Basic Backend Server Setup (Express). 55 | 3. Backend Storage Module (in-memory first). 56 | 4. Short Code Generation Logic. 57 | 5. Implement `POST /api/shorten` endpoint (depends on 3 & 4). 58 | 6. Implement `GET /:shortCode` endpoint (depends on 3). 59 | 7. Frontend UI Components. 60 | 8. Frontend logic to call `POST /api/shorten` (depends on 5 & 7). 61 | 9. Frontend display logic (depends on 7 & 8). 62 | *Goal is to get the backend API working first, then build the frontend to consume it.* 63 | 64 | # Risks and Mitigations 65 | - **Risk:** Short code collisions (generating the same code twice). 66 | - **Mitigation (MVP):** Use a library like `nanoid` with sufficient length to make collisions highly improbable for a simple service. Add a retry loop in generation if a collision *is* detected (check if code exists before storing). 67 | - **Risk:** Storing invalid or malicious URLs. 68 | - **Mitigation (MVP):** Basic URL validation on the frontend (simple regex) and potentially on the backend. Sanitize input. Advanced checks are out of scope. 69 | - **Risk:** Scalability of in-memory store. 70 | - **Mitigation (MVP):** Acceptable for MVP. Acknowledge need for persistent database (JSON file, Redis, SQL/NoSQL DB) for future enhancement. 71 | 72 | # Appendix 73 | - Example Data Store (in-memory object): 74 | ```javascript 75 | // backend/storage.js 76 | const urlMap = { 77 | 'aB3cD': 'https://very-long-url-example.com/with/path/and/query?params=true', 78 | 'xY7zW': 'https://another-example.org/' 79 | }; 80 | // ... functions to get/set URLs ... 81 | ``` 82 | </PRD> ``` -------------------------------------------------------------------------------- /packages/tm-core/src/types/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Core type definitions for Task Master 3 | */ 4 | 5 | /** 6 | * Storage type options 7 | * - 'file': Local file system storage 8 | * - 'api': Remote API storage (Hamster integration) 9 | * - 'auto': Automatically detect based on auth status 10 | */ 11 | export type StorageType = 'file' | 'api' | 'auto'; 12 | 13 | // ============================================================================ 14 | // Type Literals 15 | // ============================================================================ 16 | 17 | /** 18 | * Task status values 19 | */ 20 | export type TaskStatus = 21 | | 'pending' 22 | | 'in-progress' 23 | | 'done' 24 | | 'deferred' 25 | | 'cancelled' 26 | | 'blocked' 27 | | 'review' 28 | | 'completed'; 29 | 30 | /** 31 | * Task priority levels 32 | */ 33 | export type TaskPriority = 'low' | 'medium' | 'high' | 'critical'; 34 | 35 | /** 36 | * Task complexity levels 37 | */ 38 | export type TaskComplexity = 'simple' | 'moderate' | 'complex' | 'very-complex'; 39 | 40 | // ============================================================================ 41 | // Core Interfaces 42 | // ============================================================================ 43 | 44 | /** 45 | * Placeholder task interface for temporary/minimal task objects 46 | */ 47 | export interface PlaceholderTask { 48 | id: string; 49 | title: string; 50 | status: TaskStatus; 51 | priority: TaskPriority; 52 | } 53 | 54 | /** 55 | * Base task interface 56 | */ 57 | export interface Task { 58 | id: string; 59 | title: string; 60 | description: string; 61 | status: TaskStatus; 62 | priority: TaskPriority; 63 | dependencies: string[]; 64 | details: string; 65 | testStrategy: string; 66 | subtasks: Subtask[]; 67 | 68 | // Optional enhanced properties 69 | createdAt?: string; 70 | updatedAt?: string; 71 | effort?: number; 72 | actualEffort?: number; 73 | tags?: string[]; 74 | assignee?: string; 75 | complexity?: TaskComplexity; 76 | } 77 | 78 | /** 79 | * Subtask interface extending Task with numeric ID 80 | */ 81 | export interface Subtask extends Omit<Task, 'id' | 'subtasks'> { 82 | id: number; 83 | parentId: string; 84 | subtasks?: never; // Subtasks cannot have their own subtasks 85 | } 86 | 87 | /** 88 | * Task metadata for tracking overall project state 89 | */ 90 | export interface TaskMetadata { 91 | version: string; 92 | lastModified: string; 93 | taskCount: number; 94 | completedCount: number; 95 | projectName?: string; 96 | description?: string; 97 | tags?: string[]; 98 | } 99 | 100 | /** 101 | * Task collection with metadata 102 | */ 103 | export interface TaskCollection { 104 | tasks: Task[]; 105 | metadata: TaskMetadata; 106 | } 107 | 108 | /** 109 | * Task tag for organizing tasks 110 | */ 111 | export interface TaskTag { 112 | name: string; 113 | tasks: string[]; // Task IDs belonging to this tag 114 | metadata: Record<string, any>; 115 | } 116 | 117 | // ============================================================================ 118 | // Utility Types 119 | // ============================================================================ 120 | 121 | /** 122 | * Type for creating a new task (without generated fields) 123 | */ 124 | export type CreateTask = Omit< 125 | Task, 126 | 'id' | 'createdAt' | 'updatedAt' | 'subtasks' 127 | > & { 128 | subtasks?: Omit<Subtask, 'id' | 'parentId' | 'createdAt' | 'updatedAt'>[]; 129 | }; 130 | 131 | /** 132 | * Type for updating a task (all fields optional except ID) 133 | */ 134 | export type UpdateTask = Partial<Omit<Task, 'id'>> & { 135 | id: string; 136 | }; 137 | 138 | /** 139 | * Type for task filters 140 | */ 141 | export interface TaskFilter { 142 | status?: TaskStatus | TaskStatus[]; 143 | priority?: TaskPriority | TaskPriority[]; 144 | tags?: string[]; 145 | hasSubtasks?: boolean; 146 | search?: string; 147 | assignee?: string; 148 | complexity?: TaskComplexity | TaskComplexity[]; 149 | } 150 | 151 | /** 152 | * Type for sort options 153 | */ 154 | export interface TaskSortOptions { 155 | field: keyof Task; 156 | direction: 'asc' | 'desc'; 157 | } 158 | 159 | // ============================================================================ 160 | // Type Guards 161 | // ============================================================================ 162 | 163 | /** 164 | * Type guard to check if a value is a valid TaskStatus 165 | */ 166 | export function isTaskStatus(value: unknown): value is TaskStatus { 167 | return ( 168 | typeof value === 'string' && 169 | [ 170 | 'pending', 171 | 'in-progress', 172 | 'done', 173 | 'deferred', 174 | 'cancelled', 175 | 'blocked', 176 | 'review' 177 | ].includes(value) 178 | ); 179 | } 180 | 181 | /** 182 | * Type guard to check if a value is a valid TaskPriority 183 | */ 184 | export function isTaskPriority(value: unknown): value is TaskPriority { 185 | return ( 186 | typeof value === 'string' && 187 | ['low', 'medium', 'high', 'critical'].includes(value) 188 | ); 189 | } 190 | 191 | /** 192 | * Type guard to check if a value is a valid TaskComplexity 193 | */ 194 | export function isTaskComplexity(value: unknown): value is TaskComplexity { 195 | return ( 196 | typeof value === 'string' && 197 | ['simple', 'moderate', 'complex', 'very-complex'].includes(value) 198 | ); 199 | } 200 | 201 | /** 202 | * Type guard to check if an object is a Task 203 | */ 204 | export function isTask(obj: unknown): obj is Task { 205 | if (!obj || typeof obj !== 'object') return false; 206 | const task = obj as Record<string, unknown>; 207 | 208 | return ( 209 | typeof task.id === 'string' && 210 | typeof task.title === 'string' && 211 | typeof task.description === 'string' && 212 | isTaskStatus(task.status) && 213 | isTaskPriority(task.priority) && 214 | Array.isArray(task.dependencies) && 215 | typeof task.details === 'string' && 216 | typeof task.testStrategy === 'string' && 217 | Array.isArray(task.subtasks) 218 | ); 219 | } 220 | 221 | /** 222 | * Type guard to check if an object is a Subtask 223 | */ 224 | export function isSubtask(obj: unknown): obj is Subtask { 225 | if (!obj || typeof obj !== 'object') return false; 226 | const subtask = obj as Record<string, unknown>; 227 | 228 | return ( 229 | typeof subtask.id === 'number' && 230 | typeof subtask.parentId === 'string' && 231 | typeof subtask.title === 'string' && 232 | typeof subtask.description === 'string' && 233 | isTaskStatus(subtask.status) && 234 | isTaskPriority(subtask.priority) && 235 | !('subtasks' in subtask) 236 | ); 237 | } 238 | 239 | // ============================================================================ 240 | // Deprecated Types (for backwards compatibility) 241 | // ============================================================================ 242 | 243 | /** 244 | * @deprecated Use TaskStatus instead 245 | */ 246 | export type Status = TaskStatus; 247 | 248 | /** 249 | * @deprecated Use TaskPriority instead 250 | */ 251 | export type Priority = TaskPriority; 252 | 253 | /** 254 | * @deprecated Use TaskComplexity instead 255 | */ 256 | export type Complexity = TaskComplexity; 257 | ``` -------------------------------------------------------------------------------- /.claude/agents/task-checker.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | name: task-checker 3 | description: Use this agent to verify that tasks marked as 'review' have been properly implemented according to their specifications. This agent performs quality assurance by checking implementations against requirements, running tests, and ensuring best practices are followed. <example>Context: A task has been marked as 'review' after implementation. user: 'Check if task 118 was properly implemented' assistant: 'I'll use the task-checker agent to verify the implementation meets all requirements.' <commentary>Tasks in 'review' status need verification before being marked as 'done'.</commentary></example> <example>Context: Multiple tasks are in review status. user: 'Verify all tasks that are ready for review' assistant: 'I'll deploy the task-checker to verify all tasks in review status.' <commentary>The checker ensures quality before tasks are marked complete.</commentary></example> 4 | model: sonnet 5 | color: yellow 6 | --- 7 | 8 | You are a Quality Assurance specialist that rigorously verifies task implementations against their specifications. Your role is to ensure that tasks marked as 'review' meet all requirements before they can be marked as 'done'. 9 | 10 | ## Core Responsibilities 11 | 12 | 1. **Task Specification Review** 13 | - Retrieve task details using MCP tool `mcp__task-master-ai__get_task` 14 | - Understand the requirements, test strategy, and success criteria 15 | - Review any subtasks and their individual requirements 16 | 17 | 2. **Implementation Verification** 18 | - Use `Read` tool to examine all created/modified files 19 | - Use `Bash` tool to run compilation and build commands 20 | - Use `Grep` tool to search for required patterns and implementations 21 | - Verify file structure matches specifications 22 | - Check that all required methods/functions are implemented 23 | 24 | 3. **Test Execution** 25 | - Run tests specified in the task's testStrategy 26 | - Execute build commands (npm run build, tsc --noEmit, etc.) 27 | - Verify no compilation errors or warnings 28 | - Check for runtime errors where applicable 29 | - Test edge cases mentioned in requirements 30 | 31 | 4. **Code Quality Assessment** 32 | - Verify code follows project conventions 33 | - Check for proper error handling 34 | - Ensure TypeScript typing is strict (no 'any' unless justified) 35 | - Verify documentation/comments where required 36 | - Check for security best practices 37 | 38 | 5. **Dependency Validation** 39 | - Verify all task dependencies were actually completed 40 | - Check integration points with dependent tasks 41 | - Ensure no breaking changes to existing functionality 42 | 43 | ## Verification Workflow 44 | 45 | 1. **Retrieve Task Information** 46 | ``` 47 | Use mcp__task-master-ai__get_task to get full task details 48 | Note the implementation requirements and test strategy 49 | ``` 50 | 51 | 2. **Check File Existence** 52 | ```bash 53 | # Verify all required files exist 54 | ls -la [expected directories] 55 | # Read key files to verify content 56 | ``` 57 | 58 | 3. **Verify Implementation** 59 | - Read each created/modified file 60 | - Check against requirements checklist 61 | - Verify all subtasks are complete 62 | 63 | 4. **Run Tests** 64 | ```bash 65 | # TypeScript compilation 66 | cd [project directory] && npx tsc --noEmit 67 | 68 | # Run specified tests 69 | npm test [specific test files] 70 | 71 | # Build verification 72 | npm run build 73 | ``` 74 | 75 | 5. **Generate Verification Report** 76 | 77 | ## Output Format 78 | 79 | ```yaml 80 | verification_report: 81 | task_id: [ID] 82 | status: PASS | FAIL | PARTIAL 83 | score: [1-10] 84 | 85 | requirements_met: 86 | - ✅ [Requirement that was satisfied] 87 | - ✅ [Another satisfied requirement] 88 | 89 | issues_found: 90 | - ❌ [Issue description] 91 | - ⚠️ [Warning or minor issue] 92 | 93 | files_verified: 94 | - path: [file path] 95 | status: [created/modified/verified] 96 | issues: [any problems found] 97 | 98 | tests_run: 99 | - command: [test command] 100 | result: [pass/fail] 101 | output: [relevant output] 102 | 103 | recommendations: 104 | - [Specific fix needed] 105 | - [Improvement suggestion] 106 | 107 | verdict: | 108 | [Clear statement on whether task should be marked 'done' or sent back to 'pending'] 109 | [If FAIL: Specific list of what must be fixed] 110 | [If PASS: Confirmation that all requirements are met] 111 | ``` 112 | 113 | ## Decision Criteria 114 | 115 | **Mark as PASS (ready for 'done'):** 116 | - All required files exist and contain expected content 117 | - All tests pass successfully 118 | - No compilation or build errors 119 | - All subtasks are complete 120 | - Core requirements are met 121 | - Code quality is acceptable 122 | 123 | **Mark as PARTIAL (may proceed with warnings):** 124 | - Core functionality is implemented 125 | - Minor issues that don't block functionality 126 | - Missing nice-to-have features 127 | - Documentation could be improved 128 | - Tests pass but coverage could be better 129 | 130 | **Mark as FAIL (must return to 'pending'):** 131 | - Required files are missing 132 | - Compilation or build errors 133 | - Tests fail 134 | - Core requirements not met 135 | - Security vulnerabilities detected 136 | - Breaking changes to existing code 137 | 138 | ## Important Guidelines 139 | 140 | - **BE THOROUGH**: Check every requirement systematically 141 | - **BE SPECIFIC**: Provide exact file paths and line numbers for issues 142 | - **BE FAIR**: Distinguish between critical issues and minor improvements 143 | - **BE CONSTRUCTIVE**: Provide clear guidance on how to fix issues 144 | - **BE EFFICIENT**: Focus on requirements, not perfection 145 | 146 | ## Tools You MUST Use 147 | 148 | - `Read`: Examine implementation files (READ-ONLY) 149 | - `Bash`: Run tests and verification commands 150 | - `Grep`: Search for patterns in code 151 | - `mcp__task-master-ai__get_task`: Get task details 152 | - **NEVER use Write/Edit** - you only verify, not fix 153 | 154 | ## Integration with Workflow 155 | 156 | You are the quality gate between 'review' and 'done' status: 157 | 1. Task-executor implements and marks as 'review' 158 | 2. You verify and report PASS/FAIL 159 | 3. Claude either marks as 'done' (PASS) or 'pending' (FAIL) 160 | 4. If FAIL, task-executor re-implements based on your report 161 | 162 | Your verification ensures high quality and prevents accumulation of technical debt. ``` -------------------------------------------------------------------------------- /assets/claude/agents/task-checker.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | name: task-checker 3 | description: Use this agent to verify that tasks marked as 'review' have been properly implemented according to their specifications. This agent performs quality assurance by checking implementations against requirements, running tests, and ensuring best practices are followed. <example>Context: A task has been marked as 'review' after implementation. user: 'Check if task 118 was properly implemented' assistant: 'I'll use the task-checker agent to verify the implementation meets all requirements.' <commentary>Tasks in 'review' status need verification before being marked as 'done'.</commentary></example> <example>Context: Multiple tasks are in review status. user: 'Verify all tasks that are ready for review' assistant: 'I'll deploy the task-checker to verify all tasks in review status.' <commentary>The checker ensures quality before tasks are marked complete.</commentary></example> 4 | model: sonnet 5 | color: yellow 6 | --- 7 | 8 | You are a Quality Assurance specialist that rigorously verifies task implementations against their specifications. Your role is to ensure that tasks marked as 'review' meet all requirements before they can be marked as 'done'. 9 | 10 | ## Core Responsibilities 11 | 12 | 1. **Task Specification Review** 13 | - Retrieve task details using MCP tool `mcp__task-master-ai__get_task` 14 | - Understand the requirements, test strategy, and success criteria 15 | - Review any subtasks and their individual requirements 16 | 17 | 2. **Implementation Verification** 18 | - Use `Read` tool to examine all created/modified files 19 | - Use `Bash` tool to run compilation and build commands 20 | - Use `Grep` tool to search for required patterns and implementations 21 | - Verify file structure matches specifications 22 | - Check that all required methods/functions are implemented 23 | 24 | 3. **Test Execution** 25 | - Run tests specified in the task's testStrategy 26 | - Execute build commands (npm run build, tsc --noEmit, etc.) 27 | - Verify no compilation errors or warnings 28 | - Check for runtime errors where applicable 29 | - Test edge cases mentioned in requirements 30 | 31 | 4. **Code Quality Assessment** 32 | - Verify code follows project conventions 33 | - Check for proper error handling 34 | - Ensure TypeScript typing is strict (no 'any' unless justified) 35 | - Verify documentation/comments where required 36 | - Check for security best practices 37 | 38 | 5. **Dependency Validation** 39 | - Verify all task dependencies were actually completed 40 | - Check integration points with dependent tasks 41 | - Ensure no breaking changes to existing functionality 42 | 43 | ## Verification Workflow 44 | 45 | 1. **Retrieve Task Information** 46 | ``` 47 | Use mcp__task-master-ai__get_task to get full task details 48 | Note the implementation requirements and test strategy 49 | ``` 50 | 51 | 2. **Check File Existence** 52 | ```bash 53 | # Verify all required files exist 54 | ls -la [expected directories] 55 | # Read key files to verify content 56 | ``` 57 | 58 | 3. **Verify Implementation** 59 | - Read each created/modified file 60 | - Check against requirements checklist 61 | - Verify all subtasks are complete 62 | 63 | 4. **Run Tests** 64 | ```bash 65 | # TypeScript compilation 66 | cd [project directory] && npx tsc --noEmit 67 | 68 | # Run specified tests 69 | npm test [specific test files] 70 | 71 | # Build verification 72 | npm run build 73 | ``` 74 | 75 | 5. **Generate Verification Report** 76 | 77 | ## Output Format 78 | 79 | ```yaml 80 | verification_report: 81 | task_id: [ID] 82 | status: PASS | FAIL | PARTIAL 83 | score: [1-10] 84 | 85 | requirements_met: 86 | - ✅ [Requirement that was satisfied] 87 | - ✅ [Another satisfied requirement] 88 | 89 | issues_found: 90 | - ❌ [Issue description] 91 | - ⚠️ [Warning or minor issue] 92 | 93 | files_verified: 94 | - path: [file path] 95 | status: [created/modified/verified] 96 | issues: [any problems found] 97 | 98 | tests_run: 99 | - command: [test command] 100 | result: [pass/fail] 101 | output: [relevant output] 102 | 103 | recommendations: 104 | - [Specific fix needed] 105 | - [Improvement suggestion] 106 | 107 | verdict: | 108 | [Clear statement on whether task should be marked 'done' or sent back to 'pending'] 109 | [If FAIL: Specific list of what must be fixed] 110 | [If PASS: Confirmation that all requirements are met] 111 | ``` 112 | 113 | ## Decision Criteria 114 | 115 | **Mark as PASS (ready for 'done'):** 116 | - All required files exist and contain expected content 117 | - All tests pass successfully 118 | - No compilation or build errors 119 | - All subtasks are complete 120 | - Core requirements are met 121 | - Code quality is acceptable 122 | 123 | **Mark as PARTIAL (may proceed with warnings):** 124 | - Core functionality is implemented 125 | - Minor issues that don't block functionality 126 | - Missing nice-to-have features 127 | - Documentation could be improved 128 | - Tests pass but coverage could be better 129 | 130 | **Mark as FAIL (must return to 'pending'):** 131 | - Required files are missing 132 | - Compilation or build errors 133 | - Tests fail 134 | - Core requirements not met 135 | - Security vulnerabilities detected 136 | - Breaking changes to existing code 137 | 138 | ## Important Guidelines 139 | 140 | - **BE THOROUGH**: Check every requirement systematically 141 | - **BE SPECIFIC**: Provide exact file paths and line numbers for issues 142 | - **BE FAIR**: Distinguish between critical issues and minor improvements 143 | - **BE CONSTRUCTIVE**: Provide clear guidance on how to fix issues 144 | - **BE EFFICIENT**: Focus on requirements, not perfection 145 | 146 | ## Tools You MUST Use 147 | 148 | - `Read`: Examine implementation files (READ-ONLY) 149 | - `Bash`: Run tests and verification commands 150 | - `Grep`: Search for patterns in code 151 | - `mcp__task-master-ai__get_task`: Get task details 152 | - **NEVER use Write/Edit** - you only verify, not fix 153 | 154 | ## Integration with Workflow 155 | 156 | You are the quality gate between 'review' and 'done' status: 157 | 1. Task-executor implements and marks as 'review' 158 | 2. You verify and report PASS/FAIL 159 | 3. Claude either marks as 'done' (PASS) or 'pending' (FAIL) 160 | 4. If FAIL, task-executor re-implements based on your report 161 | 162 | Your verification ensures high quality and prevents accumulation of technical debt. ``` -------------------------------------------------------------------------------- /apps/docs/capabilities/cli-root-commands.mdx: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: CLI Commands 3 | sidebarTitle: "CLI Commands" 4 | --- 5 | 6 | 7 | <AccordionGroup> 8 | <Accordion title="Parse PRD"> 9 | ```bash 10 | # Parse a PRD file and generate tasks 11 | task-master parse-prd <prd-file.txt> 12 | 13 | # Limit the number of tasks generated 14 | task-master parse-prd <prd-file.txt> --num-tasks=10 15 | ``` 16 | </Accordion> 17 | 18 | <Accordion title="List Tasks"> 19 | ```bash 20 | # List all tasks 21 | task-master list 22 | 23 | # List tasks with a specific status 24 | task-master list --status=<status> 25 | 26 | # List tasks with subtasks 27 | task-master list --with-subtasks 28 | 29 | # List tasks with a specific status and include subtasks 30 | task-master list --status=<status> --with-subtasks 31 | ``` 32 | </Accordion> 33 | 34 | <Accordion title="Show Next Task"> 35 | ```bash 36 | # Show the next task to work on based on dependencies and status 37 | task-master next 38 | ``` 39 | </Accordion> 40 | 41 | <Accordion title="Show Specific Task"> 42 | ```bash 43 | # Show details of a specific task 44 | task-master show <id> 45 | # or 46 | task-master show --id=<id> 47 | 48 | # View a specific subtask (e.g., subtask 2 of task 1) 49 | task-master show 1.2 50 | ``` 51 | </Accordion> 52 | 53 | <Accordion title="Update Tasks"> 54 | ```bash 55 | # Update tasks from a specific ID and provide context 56 | task-master update --from=<id> --prompt="<prompt>" 57 | ``` 58 | </Accordion> 59 | 60 | <Accordion title="Update a Specific Task"> 61 | ```bash 62 | # Update a single task by ID with new information 63 | task-master update-task --id=<id> --prompt="<prompt>" 64 | 65 | # Use research-backed updates with Perplexity AI 66 | task-master update-task --id=<id> --prompt="<prompt>" --research 67 | ``` 68 | </Accordion> 69 | 70 | <Accordion title="Update a Subtask"> 71 | ```bash 72 | # Append additional information to a specific subtask 73 | task-master update-subtask --id=<parentId.subtaskId> --prompt="<prompt>" 74 | 75 | # Example: Add details about API rate limiting to subtask 2 of task 5 76 | task-master update-subtask --id=5.2 --prompt="Add rate limiting of 100 requests per minute" 77 | 78 | # Use research-backed updates with Perplexity AI 79 | task-master update-subtask --id=<parentId.subtaskId> --prompt="<prompt>" --research 80 | ``` 81 | 82 | Unlike the `update-task` command which replaces task information, the `update-subtask` command _appends_ new information to the existing subtask details, marking it with a timestamp. This is useful for iteratively enhancing subtasks while preserving the original content. 83 | </Accordion> 84 | 85 | <Accordion title="Generate Task Files"> 86 | ```bash 87 | # Generate individual task files from tasks.json 88 | task-master generate 89 | ``` 90 | </Accordion> 91 | 92 | <Accordion title="Set Task Status"> 93 | ```bash 94 | # Set status of a single task 95 | task-master set-status --id=<id> --status=<status> 96 | 97 | # Set status for multiple tasks 98 | task-master set-status --id=1,2,3 --status=<status> 99 | 100 | # Set status for subtasks 101 | task-master set-status --id=1.1,1.2 --status=<status> 102 | ``` 103 | 104 | When marking a task as "done", all of its subtasks will automatically be marked as "done" as well. 105 | </Accordion> 106 | 107 | <Accordion title="Expand Tasks"> 108 | ```bash 109 | # Expand a specific task with subtasks 110 | task-master expand --id=<id> --num=<number> 111 | 112 | # Expand with additional context 113 | task-master expand --id=<id> --prompt="<context>" 114 | 115 | # Expand all pending tasks 116 | task-master expand --all 117 | 118 | # Force regeneration of subtasks for tasks that already have them 119 | task-master expand --all --force 120 | 121 | # Research-backed subtask generation for a specific task 122 | task-master expand --id=<id> --research 123 | 124 | # Research-backed generation for all tasks 125 | task-master expand --all --research 126 | ``` 127 | </Accordion> 128 | 129 | <Accordion title="Clear Subtasks"> 130 | ```bash 131 | # Clear subtasks from a specific task 132 | task-master clear-subtasks --id=<id> 133 | 134 | # Clear subtasks from multiple tasks 135 | task-master clear-subtasks --id=1,2,3 136 | 137 | # Clear subtasks from all tasks 138 | task-master clear-subtasks --all 139 | ``` 140 | </Accordion> 141 | 142 | <Accordion title="Analyze Task Complexity"> 143 | ```bash 144 | # Analyze complexity of all tasks 145 | task-master analyze-complexity 146 | 147 | # Save report to a custom location 148 | task-master analyze-complexity --output=my-report.json 149 | 150 | # Use a specific LLM model 151 | task-master analyze-complexity --model=claude-3-opus-20240229 152 | 153 | # Set a custom complexity threshold (1-10) 154 | task-master analyze-complexity --threshold=6 155 | 156 | # Use an alternative tasks file 157 | task-master analyze-complexity --file=custom-tasks.json 158 | 159 | # Use Perplexity AI for research-backed complexity analysis 160 | task-master analyze-complexity --research 161 | ``` 162 | </Accordion> 163 | 164 | <Accordion title="View Complexity Report"> 165 | ```bash 166 | # Display the task complexity analysis report 167 | task-master complexity-report 168 | 169 | # View a report at a custom location 170 | task-master complexity-report --file=my-report.json 171 | ``` 172 | </Accordion> 173 | 174 | <Accordion title="Managing Task Dependencies"> 175 | ```bash 176 | # Add a dependency to a task 177 | task-master add-dependency --id=<id> --depends-on=<id> 178 | 179 | # Remove a dependency from a task 180 | task-master remove-dependency --id=<id> --depends-on=<id> 181 | 182 | # Validate dependencies without fixing them 183 | task-master validate-dependencies 184 | 185 | # Find and fix invalid dependencies automatically 186 | task-master fix-dependencies 187 | ``` 188 | </Accordion> 189 | 190 | <Accordion title="Add a New Task"> 191 | ```bash 192 | # Add a new task using AI 193 | task-master add-task --prompt="Description of the new task" 194 | 195 | # Add a task with dependencies 196 | task-master add-task --prompt="Description" --dependencies=1,2,3 197 | 198 | # Add a task with priority 199 | task-master add-task --prompt="Description" --priority=high 200 | ``` 201 | </Accordion> 202 | 203 | <Accordion title="Initialize a Project"> 204 | ```bash 205 | # Initialize a new project with Task Master structure 206 | task-master init 207 | ``` 208 | </Accordion> 209 | </AccordionGroup> 210 | ``` -------------------------------------------------------------------------------- /tests/unit/ui/indicators.test.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * Unit tests for indicators module (priority and complexity indicators) 3 | */ 4 | import { jest } from '@jest/globals'; 5 | 6 | // Mock chalk using unstable_mockModule for ESM compatibility 7 | jest.unstable_mockModule('chalk', () => ({ 8 | default: { 9 | red: jest.fn((str) => str), 10 | yellow: jest.fn((str) => str), 11 | green: jest.fn((str) => str), 12 | white: jest.fn((str) => str), 13 | hex: jest.fn(() => jest.fn((str) => str)) 14 | } 15 | })); 16 | 17 | // Import after mocking 18 | const { 19 | getMcpPriorityIndicators, 20 | getCliPriorityIndicators, 21 | getPriorityIndicators, 22 | getPriorityIndicator, 23 | getStatusBarPriorityIndicators, 24 | getPriorityColors, 25 | getCliComplexityIndicators, 26 | getStatusBarComplexityIndicators, 27 | getComplexityColors, 28 | getComplexityIndicator 29 | } = await import('../../../src/ui/indicators.js'); 30 | 31 | describe('Priority Indicators', () => { 32 | describe('getMcpPriorityIndicators', () => { 33 | it('should return emoji indicators for MCP context', () => { 34 | const indicators = getMcpPriorityIndicators(); 35 | expect(indicators).toEqual({ 36 | high: '🔴', 37 | medium: '🟠', 38 | low: '🟢' 39 | }); 40 | }); 41 | }); 42 | 43 | describe('getCliPriorityIndicators', () => { 44 | it('should return colored dot indicators for CLI context', () => { 45 | const indicators = getCliPriorityIndicators(); 46 | expect(indicators).toHaveProperty('high'); 47 | expect(indicators).toHaveProperty('medium'); 48 | expect(indicators).toHaveProperty('low'); 49 | // Since chalk is mocked, we're just verifying structure 50 | expect(indicators.high).toContain('●'); 51 | }); 52 | }); 53 | 54 | describe('getPriorityIndicators', () => { 55 | it('should return MCP indicators when isMcp is true', () => { 56 | const indicators = getPriorityIndicators(true); 57 | expect(indicators).toEqual({ 58 | high: '🔴', 59 | medium: '🟠', 60 | low: '🟢' 61 | }); 62 | }); 63 | 64 | it('should return CLI indicators when isMcp is false', () => { 65 | const indicators = getPriorityIndicators(false); 66 | expect(indicators).toHaveProperty('high'); 67 | expect(indicators).toHaveProperty('medium'); 68 | expect(indicators).toHaveProperty('low'); 69 | }); 70 | 71 | it('should default to CLI indicators when no parameter provided', () => { 72 | const indicators = getPriorityIndicators(); 73 | expect(indicators).toHaveProperty('high'); 74 | expect(indicators.high).toContain('●'); 75 | }); 76 | }); 77 | 78 | describe('getPriorityIndicator', () => { 79 | it('should return correct MCP indicator for valid priority', () => { 80 | expect(getPriorityIndicator('high', true)).toBe('🔴'); 81 | expect(getPriorityIndicator('medium', true)).toBe('🟠'); 82 | expect(getPriorityIndicator('low', true)).toBe('🟢'); 83 | }); 84 | 85 | it('should return correct CLI indicator for valid priority', () => { 86 | const highIndicator = getPriorityIndicator('high', false); 87 | const mediumIndicator = getPriorityIndicator('medium', false); 88 | const lowIndicator = getPriorityIndicator('low', false); 89 | 90 | expect(highIndicator).toContain('●'); 91 | expect(mediumIndicator).toContain('●'); 92 | expect(lowIndicator).toContain('●'); 93 | }); 94 | 95 | it('should return medium indicator for invalid priority', () => { 96 | expect(getPriorityIndicator('invalid', true)).toBe('🟠'); 97 | expect(getPriorityIndicator(null, true)).toBe('🟠'); 98 | expect(getPriorityIndicator(undefined, true)).toBe('🟠'); 99 | }); 100 | 101 | it('should default to CLI context when isMcp not provided', () => { 102 | const indicator = getPriorityIndicator('high'); 103 | expect(indicator).toContain('●'); 104 | }); 105 | }); 106 | }); 107 | 108 | describe('Complexity Indicators', () => { 109 | describe('getCliComplexityIndicators', () => { 110 | it('should return colored dot indicators for complexity levels', () => { 111 | const indicators = getCliComplexityIndicators(); 112 | expect(indicators).toHaveProperty('high'); 113 | expect(indicators).toHaveProperty('medium'); 114 | expect(indicators).toHaveProperty('low'); 115 | expect(indicators.high).toContain('●'); 116 | }); 117 | }); 118 | 119 | describe('getStatusBarComplexityIndicators', () => { 120 | it('should return single character indicators for status bars', () => { 121 | const indicators = getStatusBarComplexityIndicators(); 122 | // Since chalk is mocked, we need to check for the actual characters 123 | expect(indicators.high).toContain('⋮'); 124 | expect(indicators.medium).toContain(':'); 125 | expect(indicators.low).toContain('.'); 126 | }); 127 | }); 128 | 129 | describe('getComplexityColors', () => { 130 | it('should return complexity color functions', () => { 131 | const colors = getComplexityColors(); 132 | expect(colors).toHaveProperty('high'); 133 | expect(colors).toHaveProperty('medium'); 134 | expect(colors).toHaveProperty('low'); 135 | // Verify they are functions (mocked chalk functions) 136 | expect(typeof colors.high).toBe('function'); 137 | }); 138 | }); 139 | 140 | describe('getComplexityIndicator', () => { 141 | it('should return high indicator for scores >= 7', () => { 142 | const cliIndicators = getCliComplexityIndicators(); 143 | expect(getComplexityIndicator(7)).toBe(cliIndicators.high); 144 | expect(getComplexityIndicator(8)).toBe(cliIndicators.high); 145 | expect(getComplexityIndicator(10)).toBe(cliIndicators.high); 146 | }); 147 | 148 | it('should return low indicator for scores <= 3', () => { 149 | const cliIndicators = getCliComplexityIndicators(); 150 | expect(getComplexityIndicator(1)).toBe(cliIndicators.low); 151 | expect(getComplexityIndicator(2)).toBe(cliIndicators.low); 152 | expect(getComplexityIndicator(3)).toBe(cliIndicators.low); 153 | }); 154 | 155 | it('should return medium indicator for scores 4-6', () => { 156 | const cliIndicators = getCliComplexityIndicators(); 157 | expect(getComplexityIndicator(4)).toBe(cliIndicators.medium); 158 | expect(getComplexityIndicator(5)).toBe(cliIndicators.medium); 159 | expect(getComplexityIndicator(6)).toBe(cliIndicators.medium); 160 | }); 161 | 162 | it('should return status bar indicators when statusBar is true', () => { 163 | const statusBarIndicators = getStatusBarComplexityIndicators(); 164 | expect(getComplexityIndicator(8, true)).toBe(statusBarIndicators.high); 165 | expect(getComplexityIndicator(5, true)).toBe(statusBarIndicators.medium); 166 | expect(getComplexityIndicator(2, true)).toBe(statusBarIndicators.low); 167 | }); 168 | }); 169 | }); 170 | ``` -------------------------------------------------------------------------------- /apps/extension/src/utils/task-master-api/cache/cache-manager.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Cache Manager 3 | * Handles all caching logic with LRU eviction and analytics 4 | */ 5 | 6 | import type { ExtensionLogger } from '../../logger'; 7 | import type { CacheAnalytics, CacheConfig, CacheEntry } from '../types'; 8 | 9 | export class CacheManager { 10 | private cache = new Map<string, CacheEntry>(); 11 | private analytics: CacheAnalytics = { 12 | hits: 0, 13 | misses: 0, 14 | evictions: 0, 15 | refreshes: 0, 16 | totalSize: 0, 17 | averageAccessTime: 0, 18 | hitRate: 0 19 | }; 20 | private backgroundRefreshTimer?: NodeJS.Timeout; 21 | 22 | constructor( 23 | private config: CacheConfig & { cacheDuration: number }, 24 | private logger: ExtensionLogger 25 | ) { 26 | if (config.enableBackgroundRefresh) { 27 | this.initializeBackgroundRefresh(); 28 | } 29 | } 30 | 31 | /** 32 | * Get data from cache if not expired 33 | */ 34 | get(key: string): any { 35 | const startTime = Date.now(); 36 | const cached = this.cache.get(key); 37 | 38 | if (cached) { 39 | const isExpired = 40 | Date.now() - cached.timestamp >= 41 | (cached.ttl || this.config.cacheDuration); 42 | 43 | if (!isExpired) { 44 | // Update access statistics 45 | cached.accessCount++; 46 | cached.lastAccessed = Date.now(); 47 | 48 | if (this.config.enableAnalytics) { 49 | this.analytics.hits++; 50 | } 51 | 52 | const accessTime = Date.now() - startTime; 53 | this.logger.debug( 54 | `Cache hit for ${key} (${accessTime}ms, ${cached.accessCount} accesses)` 55 | ); 56 | return cached.data; 57 | } else { 58 | // Remove expired entry 59 | this.cache.delete(key); 60 | this.logger.debug(`Cache entry expired and removed: ${key}`); 61 | } 62 | } 63 | 64 | if (this.config.enableAnalytics) { 65 | this.analytics.misses++; 66 | } 67 | 68 | this.logger.debug(`Cache miss for ${key}`); 69 | return null; 70 | } 71 | 72 | /** 73 | * Set data in cache with LRU eviction 74 | */ 75 | set( 76 | key: string, 77 | data: any, 78 | options?: { ttl?: number; tags?: string[] } 79 | ): void { 80 | const now = Date.now(); 81 | const dataSize = this.estimateDataSize(data); 82 | 83 | // Create cache entry 84 | const entry: CacheEntry = { 85 | data, 86 | timestamp: now, 87 | accessCount: 1, 88 | lastAccessed: now, 89 | size: dataSize, 90 | ttl: options?.ttl, 91 | tags: options?.tags || [key.split('_')[0]] 92 | }; 93 | 94 | // Check if we need to evict entries (LRU strategy) 95 | if (this.cache.size >= this.config.maxSize) { 96 | this.evictLRUEntries(Math.max(1, Math.floor(this.config.maxSize * 0.1))); 97 | } 98 | 99 | this.cache.set(key, entry); 100 | this.logger.debug( 101 | `Cached data for ${key} (size: ${dataSize} bytes, TTL: ${entry.ttl || this.config.cacheDuration}ms)` 102 | ); 103 | 104 | // Trigger prefetch if enabled 105 | if (this.config.enablePrefetch) { 106 | this.scheduleRelatedDataPrefetch(key, data); 107 | } 108 | } 109 | 110 | /** 111 | * Clear cache entries matching a pattern 112 | */ 113 | clearPattern(pattern: string): void { 114 | let evictedCount = 0; 115 | for (const key of this.cache.keys()) { 116 | if (key.includes(pattern)) { 117 | this.cache.delete(key); 118 | evictedCount++; 119 | } 120 | } 121 | 122 | if (evictedCount > 0) { 123 | this.analytics.evictions += evictedCount; 124 | this.logger.debug( 125 | `Evicted ${evictedCount} cache entries matching pattern: ${pattern}` 126 | ); 127 | } 128 | } 129 | 130 | /** 131 | * Clear all cached data 132 | */ 133 | clear(): void { 134 | this.cache.clear(); 135 | this.resetAnalytics(); 136 | } 137 | 138 | /** 139 | * Get cache analytics 140 | */ 141 | getAnalytics(): CacheAnalytics { 142 | this.updateAnalytics(); 143 | return { ...this.analytics }; 144 | } 145 | 146 | /** 147 | * Get frequently accessed entries for background refresh 148 | */ 149 | getRefreshCandidates(): Array<[string, CacheEntry]> { 150 | return Array.from(this.cache.entries()) 151 | .filter(([key, entry]) => { 152 | const age = Date.now() - entry.timestamp; 153 | const isNearExpiration = age > this.config.cacheDuration * 0.7; 154 | const isFrequentlyAccessed = entry.accessCount >= 3; 155 | return ( 156 | isNearExpiration && isFrequentlyAccessed && key.includes('get_tasks') 157 | ); 158 | }) 159 | .sort((a, b) => b[1].accessCount - a[1].accessCount) 160 | .slice(0, 5); 161 | } 162 | 163 | /** 164 | * Update refresh count for analytics 165 | */ 166 | incrementRefreshes(): void { 167 | this.analytics.refreshes++; 168 | } 169 | 170 | /** 171 | * Cleanup resources 172 | */ 173 | destroy(): void { 174 | if (this.backgroundRefreshTimer) { 175 | clearInterval(this.backgroundRefreshTimer); 176 | this.backgroundRefreshTimer = undefined; 177 | } 178 | this.clear(); 179 | } 180 | 181 | private initializeBackgroundRefresh(): void { 182 | if (this.backgroundRefreshTimer) { 183 | clearInterval(this.backgroundRefreshTimer); 184 | } 185 | 186 | const interval = this.config.refreshInterval; 187 | this.backgroundRefreshTimer = setInterval(() => { 188 | // Background refresh is handled by the main API class 189 | // This just maintains the timer 190 | }, interval); 191 | 192 | this.logger.debug( 193 | `Cache background refresh initialized with ${interval}ms interval` 194 | ); 195 | } 196 | 197 | private evictLRUEntries(count: number): void { 198 | const entries = Array.from(this.cache.entries()) 199 | .sort((a, b) => a[1].lastAccessed - b[1].lastAccessed) 200 | .slice(0, count); 201 | 202 | for (const [key] of entries) { 203 | this.cache.delete(key); 204 | this.analytics.evictions++; 205 | } 206 | 207 | if (entries.length > 0) { 208 | this.logger.debug(`Evicted ${entries.length} LRU cache entries`); 209 | } 210 | } 211 | 212 | private estimateDataSize(data: any): number { 213 | try { 214 | return JSON.stringify(data).length * 2; // Rough estimate 215 | } catch { 216 | return 1000; // Default fallback 217 | } 218 | } 219 | 220 | private scheduleRelatedDataPrefetch(key: string, data: any): void { 221 | if (key.includes('get_tasks') && Array.isArray(data)) { 222 | this.logger.debug( 223 | `Scheduled prefetch for ${data.length} tasks related to ${key}` 224 | ); 225 | } 226 | } 227 | 228 | private resetAnalytics(): void { 229 | this.analytics = { 230 | hits: 0, 231 | misses: 0, 232 | evictions: 0, 233 | refreshes: 0, 234 | totalSize: 0, 235 | averageAccessTime: 0, 236 | hitRate: 0 237 | }; 238 | } 239 | 240 | private updateAnalytics(): void { 241 | const total = this.analytics.hits + this.analytics.misses; 242 | this.analytics.hitRate = total > 0 ? this.analytics.hits / total : 0; 243 | this.analytics.totalSize = this.cache.size; 244 | 245 | if (this.cache.size > 0) { 246 | const totalAccessTime = Array.from(this.cache.values()).reduce( 247 | (sum, entry) => sum + (entry.lastAccessed - entry.timestamp), 248 | 0 249 | ); 250 | this.analytics.averageAccessTime = totalAccessTime / this.cache.size; 251 | } 252 | } 253 | } 254 | ``` -------------------------------------------------------------------------------- /packages/tm-core/POC-STATUS.md: -------------------------------------------------------------------------------- ```markdown 1 | # GetTaskList POC Status 2 | 3 | ## ✅ What We've Accomplished 4 | 5 | We've successfully implemented a complete end-to-end proof of concept for the `getTaskList` functionality with improved separation of concerns: 6 | 7 | ### 1. Clean Architecture Layers with Proper Separation 8 | 9 | #### Configuration Layer (ConfigManager) 10 | - Single source of truth for configuration 11 | - Manages active tag and storage settings 12 | - Handles config.json persistence 13 | - Determines storage type (file vs API) 14 | 15 | #### Service Layer (TaskService) 16 | - Core business logic and operations 17 | - `getTaskList()` method that coordinates between ConfigManager and Storage 18 | - Handles all filtering and task processing 19 | - Manages storage lifecycle 20 | 21 | #### Facade Layer (TaskMasterCore) 22 | - Simplified API for consumers 23 | - Delegates to TaskService for operations 24 | - Backwards compatible `listTasks()` method 25 | - New `getTaskList()` method (preferred naming) 26 | 27 | #### Domain Layer (Entities) 28 | - `TaskEntity` with business logic 29 | - Validation and status transitions 30 | - Dependency checking (`canComplete()`) 31 | 32 | #### Infrastructure Layer (Storage) 33 | - `IStorage` interface for abstraction 34 | - `FileStorage` for local files (handles 'master' tag correctly) 35 | - `ApiStorage` for Hamster integration 36 | - `StorageFactory` for automatic selection 37 | - **NO business logic** - only persistence 38 | 39 | ### 2. Storage Abstraction Benefits 40 | 41 | ```typescript 42 | // Same API works with different backends 43 | const fileCore = createTaskMasterCore(path, { 44 | storage: { type: 'file' } 45 | }); 46 | 47 | const apiCore = createTaskMasterCore(path, { 48 | storage: { 49 | type: 'api', 50 | apiEndpoint: 'https://hamster.ai', 51 | apiAccessToken: 'xxx' 52 | } 53 | }); 54 | 55 | // Identical usage 56 | const result = await core.listTasks({ 57 | filter: { status: 'pending' } 58 | }); 59 | ``` 60 | 61 | ### 3. Type Safety Throughout 62 | 63 | - Full TypeScript implementation 64 | - Comprehensive interfaces 65 | - Type-safe filters and options 66 | - Proper error types 67 | 68 | ### 4. Testing Coverage 69 | 70 | - 50 tests passing 71 | - Unit tests for core components 72 | - Integration tests for listTasks 73 | - Mock implementations for testing 74 | 75 | ## 📊 Architecture Validation 76 | 77 | ### ✅ Separation of Concerns 78 | - **CLI** handles UI/formatting only 79 | - **tm-core** handles business logic 80 | - **Storage** handles persistence 81 | - Each layer is independently testable 82 | 83 | ### ✅ Extensibility 84 | - Easy to add new storage types (database, S3, etc.) 85 | - New filters can be added to `TaskFilter` 86 | - AI providers follow same pattern (BaseProvider) 87 | 88 | ### ✅ Error Handling 89 | - Consistent `TaskMasterError` with codes 90 | - Context preservation 91 | - User-friendly messages 92 | 93 | ### ✅ Performance Considerations 94 | - File locking for concurrent access 95 | - Atomic writes with temp files 96 | - Retry logic with exponential backoff 97 | - Request timeout handling 98 | 99 | ## 🔄 Integration Path 100 | 101 | ### Current CLI Structure 102 | ```javascript 103 | // scripts/modules/task-manager/list-tasks.js 104 | listTasks(tasksPath, statusFilter, reportPath, withSubtasks, outputFormat, context) 105 | // Directly reads files, handles all logic 106 | ``` 107 | 108 | ### New Integration Structure 109 | ```javascript 110 | // Using tm-core with proper separation of concerns 111 | const tmCore = createTaskMasterCore(projectPath, config); 112 | const result = await tmCore.getTaskList(options); 113 | // CLI only handles formatting result for display 114 | 115 | // Under the hood: 116 | // 1. ConfigManager determines active tag and storage type 117 | // 2. TaskService uses storage to fetch tasks for the tag 118 | // 3. TaskService applies business logic and filters 119 | // 4. Storage only handles reading/writing - no business logic 120 | ``` 121 | 122 | ## 📈 Metrics 123 | 124 | ### Code Quality 125 | - **Clean Code**: Methods under 40 lines ✅ 126 | - **Single Responsibility**: Each class has one purpose ✅ 127 | - **DRY**: No code duplication ✅ 128 | - **Type Coverage**: 100% TypeScript ✅ 129 | 130 | ### Test Coverage 131 | - **Unit Tests**: BaseProvider, TaskEntity ✅ 132 | - **Integration Tests**: Full listTasks flow ✅ 133 | - **Storage Tests**: File and API operations ✅ 134 | 135 | ## 🎯 POC Success Criteria 136 | 137 | | Criteria | Status | Notes | 138 | |----------|--------|-------| 139 | | Clean architecture | ✅ | Clear layer separation | 140 | | Storage abstraction | ✅ | File + API storage working | 141 | | Type safety | ✅ | Full TypeScript | 142 | | Error handling | ✅ | Comprehensive error system | 143 | | Testing | ✅ | 50 tests passing | 144 | | Performance | ✅ | Optimized with caching, batching | 145 | | Documentation | ✅ | Architecture docs created | 146 | 147 | ## 🚀 Next Steps 148 | 149 | ### Immediate (Complete ListTasks Integration) 150 | 1. Create npm script to test integration example 151 | 2. Add mock Hamster API for testing 152 | 3. Create migration guide for CLI 153 | 154 | ### Phase 1 Remaining Work 155 | Based on this POC success, implement remaining operations: 156 | - `addTask()` - Add new tasks 157 | - `updateTask()` - Update existing tasks 158 | - `deleteTask()` - Remove tasks 159 | - `expandTask()` - Break into subtasks 160 | - Tag management operations 161 | 162 | ### Phase 2 (AI Integration) 163 | - Complete AI provider implementations 164 | - Task generation from PRD 165 | - Task complexity analysis 166 | - Auto-expansion of tasks 167 | 168 | ## 💡 Lessons Learned 169 | 170 | ### What Worked Well 171 | 1. **Separation of Concerns** - ConfigManager, TaskService, and Storage have clear responsibilities 172 | 2. **Storage Factory Pattern** - Clean abstraction for multiple backends 173 | 3. **Entity Pattern** - Business logic encapsulation 174 | 4. **Template Method Pattern** - BaseProvider for AI providers 175 | 5. **Comprehensive Error Handling** - TaskMasterError with context 176 | 177 | ### Improvements Made 178 | 1. Migrated from Jest to Vitest (faster) 179 | 2. Replaced ESLint/Prettier with Biome (unified tooling) 180 | 3. Fixed conflicting interface definitions 181 | 4. Added proper TypeScript exports 182 | 5. **Better Architecture** - Separated configuration, business logic, and persistence 183 | 6. **Proper Tag Handling** - 'master' tag maps correctly to tasks.json 184 | 7. **Clean Storage Layer** - Removed business logic from storage 185 | 186 | ## ✨ Conclusion 187 | 188 | The ListTasks POC successfully validates our architecture. The structure is: 189 | - **Clean and maintainable** 190 | - **Properly abstracted** 191 | - **Well-tested** 192 | - **Ready for extension** 193 | 194 | We can confidently proceed with implementing the remaining functionality following this same pattern. ``` -------------------------------------------------------------------------------- /apps/docs/archive/command-reference.mdx: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Task Master Commands" 3 | description: "A comprehensive reference of all available Task Master commands" 4 | --- 5 | 6 | <AccordionGroup> 7 | <Accordion title="Parse PRD"> 8 | ```bash 9 | # Parse a PRD file and generate tasks 10 | task-master parse-prd <prd-file.txt> 11 | 12 | # Limit the number of tasks generated 13 | task-master parse-prd <prd-file.txt> --num-tasks=10 14 | ``` 15 | </Accordion> 16 | 17 | <Accordion title="List Tasks"> 18 | ```bash 19 | # List all tasks 20 | task-master list 21 | 22 | # List tasks with a specific status 23 | task-master list --status=<status> 24 | 25 | # List tasks with subtasks 26 | task-master list --with-subtasks 27 | 28 | # List tasks with a specific status and include subtasks 29 | task-master list --status=<status> --with-subtasks 30 | ``` 31 | </Accordion> 32 | 33 | <Accordion title="Show Next Task"> 34 | ```bash 35 | # Show the next task to work on based on dependencies and status 36 | task-master next 37 | ``` 38 | </Accordion> 39 | 40 | <Accordion title="Show Specific Task"> 41 | ```bash 42 | # Show details of a specific task 43 | task-master show <id> 44 | # or 45 | task-master show --id=<id> 46 | 47 | # View a specific subtask (e.g., subtask 2 of task 1) 48 | task-master show 1.2 49 | ``` 50 | </Accordion> 51 | 52 | <Accordion title="Update Tasks"> 53 | ```bash 54 | # Update tasks from a specific ID and provide context 55 | task-master update --from=<id> --prompt="<prompt>" 56 | ``` 57 | </Accordion> 58 | 59 | <Accordion title="Update a Specific Task"> 60 | ```bash 61 | # Update a single task by ID with new information 62 | task-master update-task --id=<id> --prompt="<prompt>" 63 | 64 | # Use research-backed updates with Perplexity AI 65 | task-master update-task --id=<id> --prompt="<prompt>" --research 66 | ``` 67 | </Accordion> 68 | 69 | <Accordion title="Update a Subtask"> 70 | ```bash 71 | # Append additional information to a specific subtask 72 | task-master update-subtask --id=<parentId.subtaskId> --prompt="<prompt>" 73 | 74 | # Example: Add details about API rate limiting to subtask 2 of task 5 75 | task-master update-subtask --id=5.2 --prompt="Add rate limiting of 100 requests per minute" 76 | 77 | # Use research-backed updates with Perplexity AI 78 | task-master update-subtask --id=<parentId.subtaskId> --prompt="<prompt>" --research 79 | ``` 80 | 81 | Unlike the `update-task` command which replaces task information, the `update-subtask` command _appends_ new information to the existing subtask details, marking it with a timestamp. This is useful for iteratively enhancing subtasks while preserving the original content. 82 | </Accordion> 83 | 84 | <Accordion title="Generate Task Files"> 85 | ```bash 86 | # Generate individual task files from tasks.json 87 | task-master generate 88 | ``` 89 | </Accordion> 90 | 91 | <Accordion title="Set Task Status"> 92 | ```bash 93 | # Set status of a single task 94 | task-master set-status --id=<id> --status=<status> 95 | 96 | # Set status for multiple tasks 97 | task-master set-status --id=1,2,3 --status=<status> 98 | 99 | # Set status for subtasks 100 | task-master set-status --id=1.1,1.2 --status=<status> 101 | ``` 102 | 103 | When marking a task as "done", all of its subtasks will automatically be marked as "done" as well. 104 | </Accordion> 105 | 106 | <Accordion title="Expand Tasks"> 107 | ```bash 108 | # Expand a specific task with subtasks 109 | task-master expand --id=<id> --num=<number> 110 | 111 | # Expand with additional context 112 | task-master expand --id=<id> --prompt="<context>" 113 | 114 | # Expand all pending tasks 115 | task-master expand --all 116 | 117 | # Force regeneration of subtasks for tasks that already have them 118 | task-master expand --all --force 119 | 120 | # Research-backed subtask generation for a specific task 121 | task-master expand --id=<id> --research 122 | 123 | # Research-backed generation for all tasks 124 | task-master expand --all --research 125 | ``` 126 | </Accordion> 127 | 128 | <Accordion title="Clear Subtasks"> 129 | ```bash 130 | # Clear subtasks from a specific task 131 | task-master clear-subtasks --id=<id> 132 | 133 | # Clear subtasks from multiple tasks 134 | task-master clear-subtasks --id=1,2,3 135 | 136 | # Clear subtasks from all tasks 137 | task-master clear-subtasks --all 138 | ``` 139 | </Accordion> 140 | 141 | <Accordion title="Analyze Task Complexity"> 142 | ```bash 143 | # Analyze complexity of all tasks 144 | task-master analyze-complexity 145 | 146 | # Save report to a custom location 147 | task-master analyze-complexity --output=my-report.json 148 | 149 | # Use a specific LLM model 150 | task-master analyze-complexity --model=claude-3-opus-20240229 151 | 152 | # Set a custom complexity threshold (1-10) 153 | task-master analyze-complexity --threshold=6 154 | 155 | # Use an alternative tasks file 156 | task-master analyze-complexity --file=custom-tasks.json 157 | 158 | # Use Perplexity AI for research-backed complexity analysis 159 | task-master analyze-complexity --research 160 | ``` 161 | </Accordion> 162 | 163 | <Accordion title="View Complexity Report"> 164 | ```bash 165 | # Display the task complexity analysis report 166 | task-master complexity-report 167 | 168 | # View a report at a custom location 169 | task-master complexity-report --file=my-report.json 170 | ``` 171 | </Accordion> 172 | 173 | <Accordion title="Managing Task Dependencies"> 174 | ```bash 175 | # Add a dependency to a task 176 | task-master add-dependency --id=<id> --depends-on=<id> 177 | 178 | # Remove a dependency from a task 179 | task-master remove-dependency --id=<id> --depends-on=<id> 180 | 181 | # Validate dependencies without fixing them 182 | task-master validate-dependencies 183 | 184 | # Find and fix invalid dependencies automatically 185 | task-master fix-dependencies 186 | ``` 187 | </Accordion> 188 | 189 | <Accordion title="Add a New Task"> 190 | ```bash 191 | # Add a new task using AI 192 | task-master add-task --prompt="Description of the new task" 193 | 194 | # Add a task with dependencies 195 | task-master add-task --prompt="Description" --dependencies=1,2,3 196 | 197 | # Add a task with priority 198 | task-master add-task --prompt="Description" --priority=high 199 | ``` 200 | </Accordion> 201 | 202 | <Accordion title="Initialize a Project"> 203 | ```bash 204 | # Initialize a new project with Task Master structure 205 | task-master init 206 | ``` 207 | </Accordion> 208 | </AccordionGroup> 209 | ```