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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/docs/contributor-docs/worktree-setup.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Git Worktree Setup for Parallel Development
  2 | 
  3 | Simple git worktree setup for running multiple AI coding assistants in parallel.
  4 | 
  5 | ## Why Worktrees?
  6 | 
  7 | Instead of Docker complexity, use git worktrees to create isolated working directories:
  8 | 
  9 | ✅ **Editor Agnostic** - Works with Cursor, Windsurf, VS Code, Claude Code, etc.
 10 | ✅ **Simple** - No Docker, no containers, just git
 11 | ✅ **Fast** - Instant setup, shared git history
 12 | ✅ **Flexible** - Each worktree can be on a different branch
 13 | ✅ **Task Master Works** - Full access to `.taskmaster/` in each worktree
 14 | 
 15 | ## Quick Start
 16 | 
 17 | ### 1. Create a Worktree
 18 | 
 19 | ```bash
 20 | # Using current branch as base
 21 | ./scripts/create-worktree.sh
 22 | 
 23 | # Or specify a branch name
 24 | ./scripts/create-worktree.sh feature/my-feature
 25 | ```
 26 | 
 27 | This creates a worktree in `../claude-task-master-worktrees/<branch-name>/`
 28 | 
 29 | ### 2. Open in Your Editor
 30 | 
 31 | ```bash
 32 | # Navigate to the worktree
 33 | cd ../claude-task-master-worktrees/auto-main/  # (or whatever branch)
 34 | 
 35 | # Open with your preferred AI editor
 36 | cursor .        # Cursor
 37 | code .          # VS Code
 38 | windsurf .      # Windsurf
 39 | claude          # Claude Code CLI
 40 | ```
 41 | 
 42 | ### 3. Work in Parallel
 43 | 
 44 | **Main directory** (where you are now):
 45 | ```bash
 46 | # Keep working normally
 47 | git checkout main
 48 | cursor .
 49 | ```
 50 | 
 51 | **Worktree directory**:
 52 | ```bash
 53 | cd ../claude-task-master-worktrees/auto-main/
 54 | # Different files, different branch, same git repo
 55 | claude
 56 | ```
 57 | 
 58 | ## Usage Examples
 59 | 
 60 | ### Example 1: Let Claude Work Autonomously
 61 | 
 62 | ```bash
 63 | # Create worktree
 64 | ./scripts/create-worktree.sh auto/taskmaster-work
 65 | 
 66 | # Navigate there
 67 | cd ../claude-task-master-worktrees/auto-taskmaster-work/
 68 | 
 69 | # Start Claude
 70 | claude
 71 | 
 72 | # In Claude session
 73 | > Use task-master to get the next task and complete it
 74 | ```
 75 | 
 76 | **Meanwhile in your main directory:**
 77 | ```bash
 78 | # You keep working normally
 79 | cursor .
 80 | # No conflicts!
 81 | ```
 82 | 
 83 | ### Example 2: Multiple AI Assistants in Parallel
 84 | 
 85 | ```bash
 86 | # Create multiple worktrees
 87 | ./scripts/create-worktree.sh cursor/feature-a
 88 | ./scripts/create-worktree.sh claude/feature-b
 89 | ./scripts/create-worktree.sh windsurf/feature-c
 90 | 
 91 | # Terminal 1
 92 | cd ../claude-task-master-worktrees/cursor-feature-a/
 93 | cursor .
 94 | 
 95 | # Terminal 2
 96 | cd ../claude-task-master-worktrees/claude-feature-b/
 97 | claude
 98 | 
 99 | # Terminal 3
100 | cd ../claude-task-master-worktrees/windsurf-feature-c/
101 | windsurf .
102 | ```
103 | 
104 | ### Example 3: Test vs Implementation
105 | 
106 | ```bash
107 | # Main directory: Write implementation
108 | cursor .
109 | 
110 | # Worktree: Have Claude write tests
111 | cd ../claude-task-master-worktrees/auto-main/
112 | claude -p "Write tests for the recent changes in the main branch"
113 | ```
114 | 
115 | ## How It Works
116 | 
117 | ### Directory Structure
118 | 
119 | ```
120 | /Volumes/Workspace/workspace/contrib/task-master/
121 | ├── claude-task-master/              # Main directory (this one)
122 | │   ├── .git/                        # Shared git repo
123 | │   ├── .taskmaster/                 # Synced via git
124 | │   └── your code...
125 | │
126 | └── claude-task-master-worktrees/    # Worktrees directory
127 |     ├── auto-main/                   # Worktree 1
128 |     │   ├── .git -> (points to main .git)
129 |     │   ├── .taskmaster/             # Same tasks, synced
130 |     │   └── your code... (on branch auto/main)
131 |     │
132 |     └── feature-x/                   # Worktree 2
133 |         ├── .git -> (points to main .git)
134 |         ├── .taskmaster/
135 |         └── your code... (on branch feature/x)
136 | ```
137 | 
138 | ### Shared Git Repository
139 | 
140 | All worktrees share the same `.git`:
141 | - Commits in one worktree are immediately visible in others
142 | - Branches are shared
143 | - Git history is shared
144 | - Only the working files differ
145 | 
146 | ## Task Master in Worktrees
147 | 
148 | Task Master works perfectly in worktrees:
149 | 
150 | ```bash
151 | # In any worktree
152 | task-master list        # Same tasks
153 | task-master next        # Same task queue
154 | task-master show 1.2    # Same task data
155 | 
156 | # Changes are shared (if committed/pushed)
157 | ```
158 | 
159 | ### Recommended Workflow
160 | 
161 | Use **tags** to separate task contexts:
162 | 
163 | ```bash
164 | # Main directory - use default tag
165 | task-master list
166 | 
167 | # Worktree 1 - use separate tag
168 | cd ../claude-task-master-worktrees/auto-main/
169 | task-master add-tag --name=claude-auto
170 | task-master use-tag --name=claude-auto
171 | task-master list  # Shows claude-auto tasks only
172 | ```
173 | 
174 | ## Managing Worktrees
175 | 
176 | ### List All Worktrees
177 | 
178 | ```bash
179 | ./scripts/list-worktrees.sh
180 | 
181 | # Or directly with git
182 | git worktree list
183 | ```
184 | 
185 | ### Remove a Worktree
186 | 
187 | ```bash
188 | # Remove specific worktree
189 | git worktree remove ../claude-task-master-worktrees/auto-main/
190 | 
191 | # Or if there are uncommitted changes, force it
192 | git worktree remove --force ../claude-task-master-worktrees/auto-main/
193 | ```
194 | 
195 | ### Sync Changes Between Worktrees
196 | 
197 | Changes are automatically synced through git:
198 | 
199 | ```bash
200 | # In worktree
201 | git add .
202 | git commit -m "feat: implement feature"
203 | git push
204 | 
205 | # In main directory
206 | git pull
207 | # Changes are now available
208 | ```
209 | 
210 | ## Common Workflows
211 | 
212 | ### 1. Autonomous Claude with Task Master
213 | 
214 | **Setup:**
215 | ```bash
216 | ./scripts/create-worktree.sh auto/claude-work
217 | cd ../claude-task-master-worktrees/auto-claude-work/
218 | ```
219 | 
220 | **Run:**
221 | ```bash
222 | # Copy the autonomous script
223 | cp ../claude-task-master/run-autonomous-tasks.sh .
224 | 
225 | # Run Claude autonomously
226 | ./run-autonomous-tasks.sh
227 | ```
228 | 
229 | **Monitor from main directory:**
230 | ```bash
231 | # In another terminal, in main directory
232 | watch -n 5 "task-master list"
233 | ```
234 | 
235 | ### 2. Code Review Workflow
236 | 
237 | **Main directory:**
238 | ```bash
239 | # You write code
240 | cursor .
241 | git add .
242 | git commit -m "feat: new feature"
243 | ```
244 | 
245 | **Worktree:**
246 | ```bash
247 | cd ../claude-task-master-worktrees/auto-main/
248 | git pull
249 | 
250 | # Have Claude review
251 | claude -p "Review the latest commit and suggest improvements"
252 | ```
253 | 
254 | ### 3. Parallel Feature Development
255 | 
256 | **Worktree 1 (Backend):**
257 | ```bash
258 | ./scripts/create-worktree.sh backend/api
259 | cd ../claude-task-master-worktrees/backend-api/
260 | cursor .
261 | # Work on API
262 | ```
263 | 
264 | **Worktree 2 (Frontend):**
265 | ```bash
266 | ./scripts/create-worktree.sh frontend/ui
267 | cd ../claude-task-master-worktrees/frontend-ui/
268 | windsurf .
269 | # Work on UI
270 | ```
271 | 
272 | **Main directory:**
273 | ```bash
274 | # Monitor and merge
275 | git log --all --graph --oneline
276 | ```
277 | 
278 | ## Tips
279 | 
280 | ### 1. Branch Naming Convention
281 | 
282 | Use prefixes to organize:
283 | - `auto/*` - For autonomous AI work
284 | - `cursor/*` - For Cursor-specific features
285 | - `claude/*` - For Claude-specific features
286 | - `review/*` - For code review worktrees
287 | 
288 | ### 2. Commit Often in Worktrees
289 | 
290 | Worktrees make it easy to try things:
291 | ```bash
292 | # In worktree
293 | git commit -m "experiment: trying approach X"
294 | # If it doesn't work, just delete the worktree
295 | git worktree remove .
296 | ```
297 | 
298 | ### 3. Use Different npm Dependencies
299 | 
300 | Each worktree can have different `node_modules`:
301 | ```bash
302 | # Main directory
303 | npm install
304 | 
305 | # Worktree (different dependencies)
306 | cd ../claude-task-master-worktrees/auto-main/
307 | npm install
308 | # Installs independently
309 | ```
310 | 
311 | ### 4. .env Files
312 | 
313 | Each worktree can have its own `.env`:
314 | ```bash
315 | # Main directory
316 | echo "API_URL=http://localhost:3000" > .env
317 | 
318 | # Worktree
319 | cd ../claude-task-master-worktrees/auto-main/
320 | echo "API_URL=http://localhost:4000" > .env
321 | # Different config!
322 | ```
323 | 
324 | ## Cleanup
325 | 
326 | ### Remove All Worktrees
327 | 
328 | ```bash
329 | # List and manually remove
330 | ./scripts/list-worktrees.sh
331 | 
332 | # Remove each one
333 | git worktree remove ../claude-task-master-worktrees/auto-main/
334 | git worktree remove ../claude-task-master-worktrees/feature-x/
335 | 
336 | # Or remove all at once (careful!)
337 | rm -rf ../claude-task-master-worktrees/
338 | git worktree prune  # Clean up git's worktree metadata
339 | ```
340 | 
341 | ### Delete Remote Branches
342 | 
343 | ```bash
344 | # After merging/done with branches
345 | git branch -d auto/claude-work
346 | git push origin --delete auto/claude-work
347 | ```
348 | 
349 | ## Troubleshooting
350 | 
351 | ### "Cannot create worktree: already exists"
352 | 
353 | ```bash
354 | # Remove the existing worktree first
355 | git worktree remove ../claude-task-master-worktrees/auto-main/
356 | ```
357 | 
358 | ### "Branch already checked out"
359 | 
360 | Git won't let you check out the same branch in multiple worktrees:
361 | ```bash
362 | # Use a different branch name
363 | ./scripts/create-worktree.sh auto/main-2
364 | ```
365 | 
366 | ### Changes Not Syncing
367 | 
368 | Worktrees don't auto-sync files. Use git:
369 | ```bash
370 | # In worktree with changes
371 | git add .
372 | git commit -m "changes"
373 | git push
374 | 
375 | # In other worktree
376 | git pull
377 | ```
378 | 
379 | ### npm install Fails
380 | 
381 | Each worktree needs its own `node_modules`:
382 | ```bash
383 | cd ../claude-task-master-worktrees/auto-main/
384 | npm install
385 | ```
386 | 
387 | ## Comparison to Docker
388 | 
389 | | Feature | Git Worktrees | Docker |
390 | |---------|---------------|--------|
391 | | Setup time | Instant | Minutes (build) |
392 | | Disk usage | Minimal (shared .git) | GBs per container |
393 | | Editor support | Native (any editor) | Limited (need special setup) |
394 | | File sync | Via git | Via volumes (can be slow) |
395 | | Resource usage | None (native) | RAM/CPU overhead |
396 | | Complexity | Simple (just git) | Complex (Dockerfile, compose, etc.) |
397 | | npm install | Per worktree | Per container |
398 | | AI editor support | ✅ All editors work | ⚠️ Need web-based or special config |
399 | 
400 | **TL;DR: Worktrees are simpler, faster, and more flexible for this use case.**
401 | 
402 | ---
403 | 
404 | ## Summary
405 | 
406 | ```bash
407 | # 1. Create worktree
408 | ./scripts/create-worktree.sh auto/claude-work
409 | 
410 | # 2. Open in AI editor
411 | cd ../claude-task-master-worktrees/auto-claude-work/
412 | cursor .   # or claude, windsurf, code, etc.
413 | 
414 | # 3. Work in parallel
415 | # Main directory: You work
416 | # Worktree: AI works
417 | # No conflicts!
418 | ```
419 | 
420 | **Simple, fast, editor-agnostic.** 🚀
421 | 
```

--------------------------------------------------------------------------------
/src/task-master.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * task-master.js
  3 |  * This module provides a centralized path management system for the Task Master application.
  4 |  * It exports the TaskMaster class and the initTaskMaster factory function to create a single,
  5 |  * authoritative source for all critical file and directory paths, resolving circular dependencies.
  6 |  */
  7 | 
  8 | import path from 'path';
  9 | import fs from 'fs';
 10 | import {
 11 | 	TASKMASTER_DIR,
 12 | 	TASKMASTER_TASKS_FILE,
 13 | 	LEGACY_TASKS_FILE,
 14 | 	TASKMASTER_DOCS_DIR,
 15 | 	TASKMASTER_REPORTS_DIR,
 16 | 	TASKMASTER_CONFIG_FILE,
 17 | 	LEGACY_CONFIG_FILE,
 18 | 	COMPLEXITY_REPORT_FILE
 19 | } from './constants/paths.js';
 20 | import { findProjectRoot } from './utils/path-utils.js';
 21 | 
 22 | /**
 23 |  * TaskMaster class manages all the paths for the application.
 24 |  * An instance of this class is created by the initTaskMaster function.
 25 |  */
 26 | export class TaskMaster {
 27 | 	#paths;
 28 | 	#tag;
 29 | 
 30 | 	/**
 31 | 	 * The constructor is intended to be used only by the initTaskMaster factory function.
 32 | 	 * @param {object} paths - A pre-resolved object of all application paths.
 33 | 	 * @param {string|undefined} tag - The current tag.
 34 | 	 */
 35 | 	constructor(paths, tag) {
 36 | 		this.#paths = Object.freeze({ ...paths });
 37 | 		this.#tag = tag;
 38 | 	}
 39 | 
 40 | 	/**
 41 | 	 * @returns {string|null} The absolute path to the project root.
 42 | 	 */
 43 | 	getProjectRoot() {
 44 | 		return this.#paths.projectRoot;
 45 | 	}
 46 | 
 47 | 	/**
 48 | 	 * @returns {string|null} The absolute path to the .taskmaster directory.
 49 | 	 */
 50 | 	getTaskMasterDir() {
 51 | 		return this.#paths.taskMasterDir;
 52 | 	}
 53 | 
 54 | 	/**
 55 | 	 * @returns {string|null} The absolute path to the tasks.json file.
 56 | 	 */
 57 | 	getTasksPath() {
 58 | 		return this.#paths.tasksPath;
 59 | 	}
 60 | 
 61 | 	/**
 62 | 	 * @returns {string|null} The absolute path to the PRD file.
 63 | 	 */
 64 | 	getPrdPath() {
 65 | 		return this.#paths.prdPath;
 66 | 	}
 67 | 
 68 | 	/**
 69 | 	 * @returns {string|null} The absolute path to the complexity report.
 70 | 	 */
 71 | 	getComplexityReportPath() {
 72 | 		if (this.#paths.complexityReportPath) {
 73 | 			return this.#paths.complexityReportPath;
 74 | 		}
 75 | 
 76 | 		const complexityReportFile =
 77 | 			this.getCurrentTag() !== 'master'
 78 | 				? COMPLEXITY_REPORT_FILE.replace(
 79 | 						'.json',
 80 | 						`_${this.getCurrentTag()}.json`
 81 | 					)
 82 | 				: COMPLEXITY_REPORT_FILE;
 83 | 
 84 | 		return path.join(this.#paths.projectRoot, complexityReportFile);
 85 | 	}
 86 | 
 87 | 	/**
 88 | 	 * @returns {string|null} The absolute path to the config.json file.
 89 | 	 */
 90 | 	getConfigPath() {
 91 | 		return this.#paths.configPath;
 92 | 	}
 93 | 
 94 | 	/**
 95 | 	 * @returns {string|null} The absolute path to the state.json file.
 96 | 	 */
 97 | 	getStatePath() {
 98 | 		return this.#paths.statePath;
 99 | 	}
100 | 
101 | 	/**
102 | 	 * @returns {object} A frozen object containing all resolved paths.
103 | 	 */
104 | 	getAllPaths() {
105 | 		return this.#paths;
106 | 	}
107 | 
108 | 	/**
109 | 	 * Gets the current tag from state.json or falls back to defaultTag from config
110 | 	 * @returns {string} The current tag name
111 | 	 */
112 | 	getCurrentTag() {
113 | 		if (this.#tag) {
114 | 			return this.#tag;
115 | 		}
116 | 
117 | 		try {
118 | 			// Try to read current tag from state.json using fs directly
119 | 			if (fs.existsSync(this.#paths.statePath)) {
120 | 				const rawState = fs.readFileSync(this.#paths.statePath, 'utf8');
121 | 				const stateData = JSON.parse(rawState);
122 | 				if (stateData && stateData.currentTag) {
123 | 					return stateData.currentTag;
124 | 				}
125 | 			}
126 | 		} catch (error) {
127 | 			// Ignore errors, fall back to default
128 | 		}
129 | 
130 | 		// Fall back to defaultTag from config using fs directly
131 | 		try {
132 | 			if (fs.existsSync(this.#paths.configPath)) {
133 | 				const rawConfig = fs.readFileSync(this.#paths.configPath, 'utf8');
134 | 				const configData = JSON.parse(rawConfig);
135 | 				if (configData && configData.global && configData.global.defaultTag) {
136 | 					return configData.global.defaultTag;
137 | 				}
138 | 			}
139 | 		} catch (error) {
140 | 			// Ignore errors, use hardcoded default
141 | 		}
142 | 
143 | 		// Final fallback
144 | 		return 'master';
145 | 	}
146 | }
147 | 
148 | /**
149 |  * Initializes a TaskMaster instance with resolved paths.
150 |  * This function centralizes path resolution logic.
151 |  *
152 |  * @param {object} [overrides={}] - An object with possible path overrides.
153 |  * @param {string} [overrides.projectRoot]
154 |  * @param {string} [overrides.tasksPath]
155 |  * @param {string} [overrides.prdPath]
156 |  * @param {string} [overrides.complexityReportPath]
157 |  * @param {string} [overrides.configPath]
158 |  * @param {string} [overrides.statePath]
159 |  * @param {string} [overrides.tag]
160 |  * @returns {TaskMaster} An initialized TaskMaster instance.
161 |  */
162 | export function initTaskMaster(overrides = {}) {
163 | 	const resolvePath = (
164 | 		pathType,
165 | 		override,
166 | 		defaultPaths = [],
167 | 		basePath = null,
168 | 		createParentDirs = false
169 | 	) => {
170 | 		if (typeof override === 'string') {
171 | 			const resolvedPath = path.isAbsolute(override)
172 | 				? override
173 | 				: path.resolve(basePath || process.cwd(), override);
174 | 
175 | 			if (createParentDirs) {
176 | 				// For output paths, create parent directory if it doesn't exist
177 | 				const parentDir = path.dirname(resolvedPath);
178 | 				if (!fs.existsSync(parentDir)) {
179 | 					try {
180 | 						fs.mkdirSync(parentDir, { recursive: true });
181 | 					} catch (error) {
182 | 						throw new Error(
183 | 							`Could not create directory for ${pathType}: ${parentDir}. Error: ${error.message}`
184 | 						);
185 | 					}
186 | 				}
187 | 			} else {
188 | 				// Original validation logic
189 | 				if (!fs.existsSync(resolvedPath)) {
190 | 					throw new Error(
191 | 						`${pathType} override path does not exist: ${resolvedPath}`
192 | 					);
193 | 				}
194 | 			}
195 | 			return resolvedPath;
196 | 		}
197 | 
198 | 		if (override === true) {
199 | 			// Required path - search defaults and fail if not found
200 | 			for (const defaultPath of defaultPaths) {
201 | 				const fullPath = path.isAbsolute(defaultPath)
202 | 					? defaultPath
203 | 					: path.join(basePath || process.cwd(), defaultPath);
204 | 				if (fs.existsSync(fullPath)) {
205 | 					return fullPath;
206 | 				}
207 | 			}
208 | 			throw new Error(
209 | 				`Required ${pathType} not found. Searched: ${defaultPaths.join(', ')}`
210 | 			);
211 | 		}
212 | 
213 | 		// Optional path (override === false/undefined) - search defaults, return null if not found
214 | 		for (const defaultPath of defaultPaths) {
215 | 			const fullPath = path.isAbsolute(defaultPath)
216 | 				? defaultPath
217 | 				: path.join(basePath || process.cwd(), defaultPath);
218 | 			if (fs.existsSync(fullPath)) {
219 | 				return fullPath;
220 | 			}
221 | 		}
222 | 
223 | 		return null;
224 | 	};
225 | 
226 | 	const paths = {};
227 | 
228 | 	// Project Root
229 | 	if (overrides.projectRoot) {
230 | 		const resolvedOverride = path.resolve(overrides.projectRoot);
231 | 		if (!fs.existsSync(resolvedOverride)) {
232 | 			throw new Error(
233 | 				`Project root override path does not exist: ${resolvedOverride}`
234 | 			);
235 | 		}
236 | 
237 | 		const hasTaskmasterDir = fs.existsSync(
238 | 			path.join(resolvedOverride, TASKMASTER_DIR)
239 | 		);
240 | 		const hasLegacyConfig = fs.existsSync(
241 | 			path.join(resolvedOverride, LEGACY_CONFIG_FILE)
242 | 		);
243 | 
244 | 		if (!hasTaskmasterDir && !hasLegacyConfig) {
245 | 			throw new Error(
246 | 				`Project root override is not a valid taskmaster project: ${resolvedOverride}`
247 | 			);
248 | 		}
249 | 
250 | 		paths.projectRoot = resolvedOverride;
251 | 	} else {
252 | 		// findProjectRoot now always returns a value (fallback to cwd)
253 | 		paths.projectRoot = findProjectRoot();
254 | 	}
255 | 
256 | 	// TaskMaster Directory
257 | 	if ('taskMasterDir' in overrides) {
258 | 		paths.taskMasterDir = resolvePath(
259 | 			'taskmaster directory',
260 | 			overrides.taskMasterDir,
261 | 			[TASKMASTER_DIR],
262 | 			paths.projectRoot
263 | 		);
264 | 	} else {
265 | 		paths.taskMasterDir = resolvePath(
266 | 			'taskmaster directory',
267 | 			false,
268 | 			[TASKMASTER_DIR],
269 | 			paths.projectRoot
270 | 		);
271 | 	}
272 | 
273 | 	// Always set default paths first
274 | 	// These can be overridden below if needed
275 | 	paths.configPath = path.join(paths.projectRoot, TASKMASTER_CONFIG_FILE);
276 | 	paths.statePath = path.join(
277 | 		paths.taskMasterDir || path.join(paths.projectRoot, TASKMASTER_DIR),
278 | 		'state.json'
279 | 	);
280 | 	paths.tasksPath = path.join(paths.projectRoot, TASKMASTER_TASKS_FILE);
281 | 
282 | 	// Handle overrides - only validate/resolve if explicitly provided
283 | 	if ('configPath' in overrides) {
284 | 		paths.configPath = resolvePath(
285 | 			'config file',
286 | 			overrides.configPath,
287 | 			[TASKMASTER_CONFIG_FILE, LEGACY_CONFIG_FILE],
288 | 			paths.projectRoot
289 | 		);
290 | 	}
291 | 
292 | 	if ('statePath' in overrides) {
293 | 		paths.statePath = resolvePath(
294 | 			'state file',
295 | 			overrides.statePath,
296 | 			['state.json'],
297 | 			paths.taskMasterDir
298 | 		);
299 | 	}
300 | 
301 | 	if ('tasksPath' in overrides) {
302 | 		paths.tasksPath = resolvePath(
303 | 			'tasks file',
304 | 			overrides.tasksPath,
305 | 			[TASKMASTER_TASKS_FILE, LEGACY_TASKS_FILE],
306 | 			paths.projectRoot
307 | 		);
308 | 	}
309 | 
310 | 	if ('prdPath' in overrides) {
311 | 		paths.prdPath = resolvePath(
312 | 			'PRD file',
313 | 			overrides.prdPath,
314 | 			[
315 | 				path.join(TASKMASTER_DOCS_DIR, 'PRD.md'),
316 | 				path.join(TASKMASTER_DOCS_DIR, 'prd.md'),
317 | 				path.join(TASKMASTER_DOCS_DIR, 'PRD.txt'),
318 | 				path.join(TASKMASTER_DOCS_DIR, 'prd.txt'),
319 | 				path.join('scripts', 'PRD.md'),
320 | 				path.join('scripts', 'prd.md'),
321 | 				path.join('scripts', 'PRD.txt'),
322 | 				path.join('scripts', 'prd.txt'),
323 | 				'PRD.md',
324 | 				'prd.md',
325 | 				'PRD.txt',
326 | 				'prd.txt'
327 | 			],
328 | 			paths.projectRoot
329 | 		);
330 | 	}
331 | 
332 | 	if ('complexityReportPath' in overrides) {
333 | 		paths.complexityReportPath = resolvePath(
334 | 			'complexity report',
335 | 			overrides.complexityReportPath,
336 | 			[
337 | 				path.join(TASKMASTER_REPORTS_DIR, 'task-complexity-report.json'),
338 | 				path.join(TASKMASTER_REPORTS_DIR, 'complexity-report.json'),
339 | 				path.join('scripts', 'task-complexity-report.json'),
340 | 				path.join('scripts', 'complexity-report.json'),
341 | 				'task-complexity-report.json',
342 | 				'complexity-report.json'
343 | 			],
344 | 			paths.projectRoot,
345 | 			true // Enable parent directory creation for output paths
346 | 		);
347 | 	}
348 | 
349 | 	return new TaskMaster(paths, overrides.tag);
350 | }
351 | 
```

--------------------------------------------------------------------------------
/tests/unit/profiles/vscode-integration.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { jest } from '@jest/globals';
  2 | import fs from 'fs';
  3 | import path from 'path';
  4 | import os from 'os';
  5 | // Mock the schema integration functions to avoid chalk issues
  6 | const mockSetupSchemaIntegration = jest.fn();
  7 | 
  8 | import { vscodeProfile } from '../../../src/profiles/vscode.js';
  9 | 
 10 | // Mock external modules
 11 | jest.mock('child_process', () => ({
 12 | 	execSync: jest.fn()
 13 | }));
 14 | 
 15 | // Mock fs/promises
 16 | const mockFsPromises = {
 17 | 	mkdir: jest.fn(),
 18 | 	access: jest.fn(),
 19 | 	copyFile: jest.fn(),
 20 | 	readFile: jest.fn(),
 21 | 	writeFile: jest.fn()
 22 | };
 23 | 
 24 | jest.mock('fs/promises', () => mockFsPromises);
 25 | 
 26 | // Mock console methods
 27 | jest.mock('console', () => ({
 28 | 	log: jest.fn(),
 29 | 	info: jest.fn(),
 30 | 	warn: jest.fn(),
 31 | 	error: jest.fn(),
 32 | 	clear: jest.fn()
 33 | }));
 34 | 
 35 | describe('VS Code Integration', () => {
 36 | 	let tempDir;
 37 | 
 38 | 	beforeEach(() => {
 39 | 		jest.clearAllMocks();
 40 | 
 41 | 		// Create a temporary directory for testing
 42 | 		tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
 43 | 
 44 | 		// Spy on fs methods
 45 | 		jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
 46 | 		jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
 47 | 			if (filePath.toString().includes('mcp.json')) {
 48 | 				return JSON.stringify({
 49 | 					mcpServers: {
 50 | 						'task-master-ai': {
 51 | 							command: 'node',
 52 | 							args: ['mcp-server/src/index.js']
 53 | 						}
 54 | 					}
 55 | 				});
 56 | 			}
 57 | 			if (filePath.toString().includes('instructions')) {
 58 | 				return 'VS Code instruction content';
 59 | 			}
 60 | 			return '{}';
 61 | 		});
 62 | 		jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
 63 | 		jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
 64 | 	});
 65 | 
 66 | 	afterEach(() => {
 67 | 		// Clean up the temporary directory
 68 | 		try {
 69 | 			fs.rmSync(tempDir, { recursive: true, force: true });
 70 | 		} catch (err) {
 71 | 			console.error(`Error cleaning up: ${err.message}`);
 72 | 		}
 73 | 	});
 74 | 
 75 | 	// Test function that simulates the createProjectStructure behavior for VS Code files
 76 | 	function mockCreateVSCodeStructure() {
 77 | 		// Create .vscode directory for MCP configuration
 78 | 		fs.mkdirSync(path.join(tempDir, '.vscode'), { recursive: true });
 79 | 
 80 | 		// Create .github/instructions directory for VS Code custom instructions
 81 | 		fs.mkdirSync(path.join(tempDir, '.github', 'instructions'), {
 82 | 			recursive: true
 83 | 		});
 84 | 		fs.mkdirSync(path.join(tempDir, '.github', 'instructions', 'taskmaster'), {
 85 | 			recursive: true
 86 | 		});
 87 | 
 88 | 		// Create MCP configuration file
 89 | 		const mcpConfig = {
 90 | 			mcpServers: {
 91 | 				'task-master-ai': {
 92 | 					command: 'node',
 93 | 					args: ['mcp-server/src/index.js'],
 94 | 					env: {
 95 | 						PROJECT_ROOT: process.cwd()
 96 | 					}
 97 | 				}
 98 | 			}
 99 | 		};
100 | 		fs.writeFileSync(
101 | 			path.join(tempDir, '.vscode', 'mcp.json'),
102 | 			JSON.stringify(mcpConfig, null, 2)
103 | 		);
104 | 
105 | 		// Create sample instruction files
106 | 		const instructionFiles = [
107 | 			'vscode_rules.md',
108 | 			'dev_workflow.md',
109 | 			'self_improve.md'
110 | 		];
111 | 
112 | 		for (const file of instructionFiles) {
113 | 			const content = `---
114 | description: VS Code instruction for ${file}
115 | applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.jsx"
116 | alwaysApply: true
117 | ---
118 | 
119 | # ${file.replace('.md', '').replace('_', ' ').toUpperCase()}
120 | 
121 | This is a VS Code custom instruction file.`;
122 | 
123 | 			fs.writeFileSync(
124 | 				path.join(tempDir, '.github', 'instructions', file),
125 | 				content
126 | 			);
127 | 		}
128 | 
129 | 		// Create taskmaster subdirectory with additional instructions
130 | 		const taskmasterFiles = ['taskmaster.md', 'commands.md', 'architecture.md'];
131 | 
132 | 		for (const file of taskmasterFiles) {
133 | 			const content = `---
134 | description: Task Master specific instruction for ${file}
135 | applyTo: "**/*.ts,**/*.js"
136 | alwaysApply: true
137 | ---
138 | 
139 | # ${file.replace('.md', '').toUpperCase()}
140 | 
141 | Task Master specific VS Code instruction.`;
142 | 
143 | 			fs.writeFileSync(
144 | 				path.join(tempDir, '.github', 'instructions', 'taskmaster', file),
145 | 				content
146 | 			);
147 | 		}
148 | 	}
149 | 
150 | 	test('creates all required VS Code directories', () => {
151 | 		// Act
152 | 		mockCreateVSCodeStructure();
153 | 
154 | 		// Assert - .vscode directory for MCP config
155 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.vscode'), {
156 | 			recursive: true
157 | 		});
158 | 
159 | 		// Assert - .github/instructions directory for custom instructions
160 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
161 | 			path.join(tempDir, '.github', 'instructions'),
162 | 			{ recursive: true }
163 | 		);
164 | 
165 | 		// Assert - taskmaster subdirectory
166 | 		expect(fs.mkdirSync).toHaveBeenCalledWith(
167 | 			path.join(tempDir, '.github', 'instructions', 'taskmaster'),
168 | 			{ recursive: true }
169 | 		);
170 | 	});
171 | 
172 | 	test('creates VS Code MCP configuration file', () => {
173 | 		// Act
174 | 		mockCreateVSCodeStructure();
175 | 
176 | 		// Assert
177 | 		const expectedMcpPath = path.join(tempDir, '.vscode', 'mcp.json');
178 | 		expect(fs.writeFileSync).toHaveBeenCalledWith(
179 | 			expectedMcpPath,
180 | 			expect.stringContaining('task-master-ai')
181 | 		);
182 | 	});
183 | 
184 | 	test('creates VS Code instruction files with applyTo patterns', () => {
185 | 		// Act
186 | 		mockCreateVSCodeStructure();
187 | 
188 | 		// Assert main instruction files
189 | 		const mainInstructionFiles = [
190 | 			'vscode_rules.md',
191 | 			'dev_workflow.md',
192 | 			'self_improve.md'
193 | 		];
194 | 
195 | 		for (const file of mainInstructionFiles) {
196 | 			const expectedPath = path.join(tempDir, '.github', 'instructions', file);
197 | 			expect(fs.writeFileSync).toHaveBeenCalledWith(
198 | 				expectedPath,
199 | 				expect.stringContaining('applyTo:')
200 | 			);
201 | 		}
202 | 	});
203 | 
204 | 	test('creates taskmaster specific instruction files', () => {
205 | 		// Act
206 | 		mockCreateVSCodeStructure();
207 | 
208 | 		// Assert taskmaster subdirectory files
209 | 		const taskmasterFiles = ['taskmaster.md', 'commands.md', 'architecture.md'];
210 | 
211 | 		for (const file of taskmasterFiles) {
212 | 			const expectedPath = path.join(
213 | 				tempDir,
214 | 				'.github',
215 | 				'instructions',
216 | 				'taskmaster',
217 | 				file
218 | 			);
219 | 			expect(fs.writeFileSync).toHaveBeenCalledWith(
220 | 				expectedPath,
221 | 				expect.stringContaining('applyTo:')
222 | 			);
223 | 		}
224 | 	});
225 | 
226 | 	test('VS Code instruction files use applyTo instead of globs', () => {
227 | 		// Act
228 | 		mockCreateVSCodeStructure();
229 | 
230 | 		// Get all the writeFileSync calls for .md files
231 | 		const mdFileWrites = fs.writeFileSync.mock.calls.filter((call) =>
232 | 			call[0].toString().endsWith('.md')
233 | 		);
234 | 
235 | 		// Assert that all .md files contain applyTo and not globs
236 | 		for (const writeCall of mdFileWrites) {
237 | 			const content = writeCall[1];
238 | 			expect(content).toContain('applyTo:');
239 | 			expect(content).not.toContain('globs:');
240 | 		}
241 | 	});
242 | 
243 | 	test('MCP configuration includes correct structure for VS Code', () => {
244 | 		// Act
245 | 		mockCreateVSCodeStructure();
246 | 
247 | 		// Get the MCP config write call
248 | 		const mcpConfigWrite = fs.writeFileSync.mock.calls.find((call) =>
249 | 			call[0].toString().includes('mcp.json')
250 | 		);
251 | 
252 | 		expect(mcpConfigWrite).toBeDefined();
253 | 
254 | 		const mcpContent = mcpConfigWrite[1];
255 | 		const mcpConfig = JSON.parse(mcpContent);
256 | 
257 | 		// Assert MCP structure
258 | 		expect(mcpConfig).toHaveProperty('mcpServers');
259 | 		expect(mcpConfig.mcpServers).toHaveProperty('task-master-ai');
260 | 		expect(mcpConfig.mcpServers['task-master-ai']).toHaveProperty(
261 | 			'command',
262 | 			'node'
263 | 		);
264 | 		expect(mcpConfig.mcpServers['task-master-ai']).toHaveProperty('args');
265 | 		expect(mcpConfig.mcpServers['task-master-ai'].args).toContain(
266 | 			'mcp-server/src/index.js'
267 | 		);
268 | 	});
269 | 
270 | 	test('directory structure follows VS Code conventions', () => {
271 | 		// Act
272 | 		mockCreateVSCodeStructure();
273 | 
274 | 		// Assert the specific directory structure VS Code expects
275 | 		const expectedDirs = [
276 | 			path.join(tempDir, '.vscode'),
277 | 			path.join(tempDir, '.github', 'instructions'),
278 | 			path.join(tempDir, '.github', 'instructions', 'taskmaster')
279 | 		];
280 | 
281 | 		for (const dir of expectedDirs) {
282 | 			expect(fs.mkdirSync).toHaveBeenCalledWith(dir, { recursive: true });
283 | 		}
284 | 	});
285 | 
286 | 	test('instruction files contain VS Code specific formatting', () => {
287 | 		// Act
288 | 		mockCreateVSCodeStructure();
289 | 
290 | 		// Get a sample instruction file write
291 | 		const instructionWrite = fs.writeFileSync.mock.calls.find((call) =>
292 | 			call[0].toString().includes('vscode_rules.md')
293 | 		);
294 | 
295 | 		expect(instructionWrite).toBeDefined();
296 | 
297 | 		const content = instructionWrite[1];
298 | 
299 | 		// Assert VS Code specific patterns
300 | 		expect(content).toContain('---'); // YAML frontmatter
301 | 		expect(content).toContain('description:');
302 | 		expect(content).toContain('applyTo:');
303 | 		expect(content).toContain('alwaysApply:');
304 | 		expect(content).toContain('**/*.ts'); // File patterns in quotes
305 | 	});
306 | 
307 | 	describe('Schema Integration', () => {
308 | 		beforeEach(() => {
309 | 			jest.clearAllMocks();
310 | 			// Replace the onAddRulesProfile function with our mock
311 | 			vscodeProfile.onAddRulesProfile = mockSetupSchemaIntegration;
312 | 		});
313 | 
314 | 		test('setupSchemaIntegration is called with project root', async () => {
315 | 			// Arrange
316 | 			mockSetupSchemaIntegration.mockResolvedValue();
317 | 
318 | 			// Act
319 | 			await vscodeProfile.onAddRulesProfile(tempDir);
320 | 
321 | 			// Assert
322 | 			expect(mockSetupSchemaIntegration).toHaveBeenCalledWith(tempDir);
323 | 		});
324 | 
325 | 		test('schema integration function exists and is callable', () => {
326 | 			// Assert that the VS Code profile has the schema integration function
327 | 			expect(vscodeProfile.onAddRulesProfile).toBeDefined();
328 | 			expect(typeof vscodeProfile.onAddRulesProfile).toBe('function');
329 | 		});
330 | 
331 | 		test('schema integration handles errors gracefully', async () => {
332 | 			// Arrange
333 | 			mockSetupSchemaIntegration.mockRejectedValue(
334 | 				new Error('Schema setup failed')
335 | 			);
336 | 
337 | 			// Act & Assert - Should propagate the error
338 | 			await expect(vscodeProfile.onAddRulesProfile(tempDir)).rejects.toThrow(
339 | 				'Schema setup failed'
340 | 			);
341 | 		});
342 | 	});
343 | });
344 | 
```

--------------------------------------------------------------------------------
/apps/docs/archive/ai-client-utils-example.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "AI Client Utilities for MCP Tools"
  3 | description: "This document provides examples of how to use the new AI client utilities with AsyncOperationManager in MCP tools."
  4 | ---
  5 | ## Examples
  6 | <AccordionGroup>
  7 |   <Accordion title="Basic Usage with Direct Functions">
  8 |     ```javascript
  9 |     // In your direct function implementation:
 10 |     import {
 11 |         getAnthropicClientForMCP,
 12 |         getModelConfig,
 13 |         handleClaudeError
 14 |     } from '../utils/ai-client-utils.js';
 15 | 
 16 |     export async function someAiOperationDirect(args, log, context) {
 17 |         try {
 18 |             // Initialize Anthropic client with session from context
 19 |             const client = getAnthropicClientForMCP(context.session, log);
 20 | 
 21 |             // Get model configuration with defaults or session overrides
 22 |             const modelConfig = getModelConfig(context.session);
 23 | 
 24 |             // Make API call with proper error handling
 25 |             try {
 26 |                 const response = await client.messages.create({
 27 |                     model: modelConfig.model,
 28 |                     max_tokens: modelConfig.maxTokens,
 29 |                     temperature: modelConfig.temperature,
 30 |                     messages: [{ role: 'user', content: 'Your prompt here' }]
 31 |                 });
 32 | 
 33 |                 return {
 34 |                     success: true,
 35 |                     data: response
 36 |                 };
 37 |             } catch (apiError) {
 38 |                 // Use helper to get user-friendly error message
 39 |                 const friendlyMessage = handleClaudeError(apiError);
 40 | 
 41 |                 return {
 42 |                     success: false,
 43 |                     error: {
 44 |                         code: 'AI_API_ERROR',
 45 |                         message: friendlyMessage
 46 |                     }
 47 |                 };
 48 |             }
 49 |         } catch (error) {
 50 |             // Handle client initialization errors
 51 |             return {
 52 |                 success: false,
 53 |                 error: {
 54 |                     code: 'AI_CLIENT_ERROR',
 55 |                     message: error.message
 56 |                 }
 57 |             };
 58 |         }
 59 |     }
 60 |     ```
 61 |   </Accordion>
 62 | 
 63 |   <Accordion title="Integration with AsyncOperationManager">
 64 |     ```javascript
 65 |     // In your MCP tool implementation:
 66 |     import {
 67 |         AsyncOperationManager,
 68 |         StatusCodes
 69 |     } from '../../utils/async-operation-manager.js';
 70 |     import { someAiOperationDirect } from '../../core/direct-functions/some-ai-operation.js';
 71 | 
 72 |     export async function someAiOperation(args, context) {
 73 |         const { session, mcpLog } = context;
 74 |         const log = mcpLog || console;
 75 | 
 76 |         try {
 77 |             // Create operation description
 78 |             const operationDescription = `AI operation: ${args.someParam}`;
 79 | 
 80 |             // Start async operation
 81 |             const operation = AsyncOperationManager.createOperation(
 82 |                 operationDescription,
 83 |                 async (reportProgress) => {
 84 |                     try {
 85 |                         // Initial progress report
 86 |                         reportProgress({
 87 |                             progress: 0,
 88 |                             status: 'Starting AI operation...'
 89 |                         });
 90 | 
 91 |                         // Call direct function with session and progress reporting
 92 |                         const result = await someAiOperationDirect(args, log, {
 93 |                             reportProgress,
 94 |                             mcpLog: log,
 95 |                             session
 96 |                         });
 97 | 
 98 |                         // Final progress update
 99 |                         reportProgress({
100 |                             progress: 100,
101 |                             status: result.success ? 'Operation completed' : 'Operation failed',
102 |                             result: result.data,
103 |                             error: result.error
104 |                         });
105 | 
106 |                         return result;
107 |                     } catch (error) {
108 |                         // Handle errors in the operation
109 |                         reportProgress({
110 |                             progress: 100,
111 |                             status: 'Operation failed',
112 |                             error: {
113 |                                 message: error.message,
114 |                                 code: error.code || 'OPERATION_FAILED'
115 |                             }
116 |                         });
117 |                         throw error;
118 |                     }
119 |                 }
120 |             );
121 | 
122 |             // Return immediate response with operation ID
123 |             return {
124 |                 status: StatusCodes.ACCEPTED,
125 |                 body: {
126 |                     success: true,
127 |                     message: 'Operation started',
128 |                     operationId: operation.id
129 |                 }
130 |             };
131 |         } catch (error) {
132 |             // Handle errors in the MCP tool
133 |             log.error(`Error in someAiOperation: ${error.message}`);
134 |             return {
135 |                 status: StatusCodes.INTERNAL_SERVER_ERROR,
136 |                 body: {
137 |                     success: false,
138 |                     error: {
139 |                         code: 'OPERATION_FAILED',
140 |                         message: error.message
141 |                     }
142 |                 }
143 |             };
144 |         }
145 |     }
146 |     ```
147 |   </Accordion>
148 | 
149 |   <Accordion title="Using Research Capabilities with Perplexity">
150 |     ```javascript
151 |     // In your direct function:
152 |     import {
153 |         getPerplexityClientForMCP,
154 |         getBestAvailableAIModel
155 |     } from '../utils/ai-client-utils.js';
156 | 
157 |     export async function researchOperationDirect(args, log, context) {
158 |         try {
159 |             // Get the best AI model for this operation based on needs
160 |             const { type, client } = await getBestAvailableAIModel(
161 |                 context.session,
162 |                 { requiresResearch: true },
163 |                 log
164 |             );
165 | 
166 |             // Report which model we're using
167 |             if (context.reportProgress) {
168 |                 await context.reportProgress({
169 |                     progress: 10,
170 |                     status: `Using ${type} model for research...`
171 |                 });
172 |             }
173 | 
174 |             // Make API call based on the model type
175 |             if (type === 'perplexity') {
176 |                 // Call Perplexity
177 |                 const response = await client.chat.completions.create({
178 |                     model: context.session?.env?.PERPLEXITY_MODEL || 'sonar-medium-online',
179 |                     messages: [{ role: 'user', content: args.researchQuery }],
180 |                     temperature: 0.1
181 |                 });
182 | 
183 |                 return {
184 |                     success: true,
185 |                     data: response.choices[0].message.content
186 |                 };
187 |             } else {
188 |                 // Call Claude as fallback
189 |                 // (Implementation depends on specific needs)
190 |                 // ...
191 |             }
192 |         } catch (error) {
193 |             // Handle errors
194 |             return {
195 |                 success: false,
196 |                 error: {
197 |                     code: 'RESEARCH_ERROR',
198 |                     message: error.message
199 |                 }
200 |             };
201 |         }
202 |     }
203 |     ```
204 |   </Accordion>
205 | 
206 |   <Accordion title="Model Configuration Override">
207 |     ```javascript
208 |     // In your direct function:
209 |     import { getModelConfig } from '../utils/ai-client-utils.js';
210 | 
211 |     // Using custom defaults for a specific operation
212 |     const operationDefaults = {
213 |         model: 'claude-3-haiku-20240307', // Faster, smaller model
214 |         maxTokens: 1000, // Lower token limit
215 |         temperature: 0.2 // Lower temperature for more deterministic output
216 |     };
217 | 
218 |     // Get model config with operation-specific defaults
219 |     const modelConfig = getModelConfig(context.session, operationDefaults);
220 | 
221 |     // Now use modelConfig in your API calls
222 |     const response = await client.messages.create({
223 |         model: modelConfig.model,
224 |         max_tokens: modelConfig.maxTokens,
225 |         temperature: modelConfig.temperature
226 |         // Other parameters...
227 |     });
228 |     ```
229 |   </Accordion>
230 | </AccordionGroup>
231 | 
232 | ## Best Practices
233 | 
234 | <AccordionGroup>
235 |   <Accordion title="Error Handling">
236 |     - Always use try/catch blocks around both client initialization and API calls
237 |     - Use `handleClaudeError` to provide user-friendly error messages
238 |     - Return standardized error objects with code and message
239 |   </Accordion>
240 |   
241 |   <Accordion title="Progress Reporting">
242 |     - Report progress at key points (starting, processing, completing)
243 |     - Include meaningful status messages
244 |     - Include error details in progress reports when failures occur
245 |   </Accordion>
246 |   
247 |   <Accordion title="Session Handling">
248 |     - Always pass the session from the context to the AI client getters
249 |     - Use `getModelConfig` to respect user settings from session
250 |   </Accordion>
251 |   
252 |   <Accordion title="Model Selection">
253 |     - Use `getBestAvailableAIModel` when you need to select between different models
254 |     - Set `requiresResearch: true` when you need Perplexity capabilities
255 |   </Accordion>
256 |   
257 |   <Accordion title="AsyncOperationManager Integration">
258 |     - Create descriptive operation names
259 |     - Handle all errors within the operation function
260 |     - Return standardized results from direct functions
261 |     - Return immediate responses with operation IDs
262 |   </Accordion>
263 | </AccordionGroup>
264 | 
```

--------------------------------------------------------------------------------
/.taskmaster/reports/task-complexity-report_tm-core-phase-1.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"meta": {
 3 | 		"generatedAt": "2025-08-06T12:39:03.250Z",
 4 | 		"tasksAnalyzed": 8,
 5 | 		"totalTasks": 11,
 6 | 		"analysisCount": 8,
 7 | 		"thresholdScore": 5,
 8 | 		"projectName": "Taskmaster",
 9 | 		"usedResearch": false
10 | 	},
11 | 	"complexityAnalysis": [
12 | 		{
13 | 			"taskId": 118,
14 | 			"taskTitle": "Create AI Provider Base Architecture",
15 | 			"complexityScore": 7,
16 | 			"recommendedSubtasks": 5,
17 | 			"expansionPrompt": "Break down the implementation of BaseProvider abstract TypeScript class into subtasks focusing on: 1) Converting existing JavaScript base-provider.js to TypeScript with proper interface definitions, 2) Implementing the Template Method pattern with abstract methods, 3) Adding comprehensive error handling and retry logic with exponential backoff, 4) Creating proper TypeScript types for all method signatures and options, 5) Setting up comprehensive unit tests with MockProvider. Consider that the existing codebase uses JavaScript ES modules and Vercel AI SDK, so the TypeScript implementation needs to maintain compatibility while adding type safety.",
18 | 			"reasoning": "This task requires significant architectural work including converting existing JavaScript code to TypeScript, creating new interfaces, implementing design patterns, and ensuring backward compatibility. The existing base-provider.js already implements a sophisticated provider pattern using Vercel AI SDK, so the TypeScript conversion needs careful consideration of type definitions and maintaining existing functionality."
19 | 		},
20 | 		{
21 | 			"taskId": 119,
22 | 			"taskTitle": "Implement Provider Factory with Dynamic Imports",
23 | 			"complexityScore": 5,
24 | 			"recommendedSubtasks": 5,
25 | 			"expansionPrompt": "Break down the Provider Factory implementation into: 1) Creating the ProviderFactory class structure with proper TypeScript typing, 2) Implementing the switch statement for provider selection logic, 3) Adding dynamic imports for each provider to enable tree-shaking, 4) Handling provider instantiation with configuration passing, 5) Implementing comprehensive error handling for module loading failures. Note that the existing codebase already has a provider selection mechanism in the JavaScript files, so ensure the factory pattern integrates smoothly with existing infrastructure.",
26 | 			"reasoning": "This is a moderate complexity task that involves creating a factory pattern with dynamic imports. The existing codebase already has provider management logic, so the main complexity is in creating a clean TypeScript implementation with proper dynamic imports while maintaining compatibility with the existing JavaScript module system."
27 | 		},
28 | 		{
29 | 			"taskId": 120,
30 | 			"taskTitle": "Implement Anthropic Provider",
31 | 			"complexityScore": 6,
32 | 			"recommendedSubtasks": 5,
33 | 			"expansionPrompt": "Implement the AnthropicProvider class in stages: 1) Set up the class structure extending BaseProvider with proper TypeScript imports and type definitions, 2) Implement constructor with Anthropic SDK client initialization and configuration handling, 3) Implement generateCompletion method with proper message format transformation and error handling, 4) Add token calculation methods and utility functions (getName, getModel, getDefaultModel), 5) Implement comprehensive error handling with custom error wrapping and type exports. The existing anthropic.js provider can serve as a reference but needs to be reimplemented to extend the new TypeScript BaseProvider.",
34 | 			"reasoning": "This task involves integrating with an external SDK (@anthropic-ai/sdk) and implementing all abstract methods from BaseProvider. The existing JavaScript implementation provides a good reference, but the TypeScript version needs proper type definitions, error handling, and must work with the new abstract base class architecture."
35 | 		},
36 | 		{
37 | 			"taskId": 121,
38 | 			"taskTitle": "Create Prompt Builder and Task Parser",
39 | 			"complexityScore": 8,
40 | 			"recommendedSubtasks": 5,
41 | 			"expansionPrompt": "Implement PromptBuilder and TaskParser with focus on: 1) Creating PromptBuilder class with template methods for building structured prompts with JSON format instructions, 2) Implementing TaskParser class structure with dependency injection of IAIProvider and IConfiguration, 3) Implementing parsePRD method with file reading, prompt generation, and AI provider integration, 4) Adding task enrichment logic with metadata, validation, and structure verification, 5) Implementing comprehensive error handling for all failure scenarios including file I/O, AI provider errors, and JSON parsing. The existing parse-prd.js provides complex logic that needs to be reimplemented with proper TypeScript types and cleaner architecture.",
42 | 			"reasoning": "This is a complex task that involves multiple components working together: file I/O, AI provider integration, JSON parsing, and data validation. The existing parse-prd.js implementation is quite sophisticated with Zod schemas and complex task processing logic that needs to be reimplemented in TypeScript with proper separation of concerns."
43 | 		},
44 | 		{
45 | 			"taskId": 122,
46 | 			"taskTitle": "Implement Configuration Management",
47 | 			"complexityScore": 6,
48 | 			"recommendedSubtasks": 5,
49 | 			"expansionPrompt": "Create ConfigManager implementation focusing on: 1) Setting up Zod validation schema that matches the IConfiguration interface structure, 2) Implementing ConfigManager constructor with default values merging and storage initialization, 3) Creating validate method with Zod schema parsing and user-friendly error transformation, 4) Implementing type-safe get method using TypeScript generics and keyof operator, 5) Adding getAll method and ensuring proper immutability and module exports. The existing config-manager.js has complex configuration loading logic that can inform the TypeScript implementation but needs cleaner architecture.",
50 | 			"reasoning": "This task involves creating a configuration management system with validation using Zod. The existing JavaScript config-manager.js is quite complex with multiple configuration sources, defaults, and validation logic. The TypeScript version needs to provide a cleaner API while maintaining the flexibility of the current system."
51 | 		},
52 | 		{
53 | 			"taskId": 123,
54 | 			"taskTitle": "Create Utility Functions and Error Handling",
55 | 			"complexityScore": 4,
56 | 			"recommendedSubtasks": 5,
57 | 			"expansionPrompt": "Implement utilities and error handling in stages: 1) Create ID generation module with generateTaskId and generateSubtaskId functions using proper random generation, 2) Implement base TaskMasterError class extending Error with proper TypeScript typing, 3) Add error sanitization methods to prevent sensitive data exposure in production, 4) Implement development-only logging with environment detection, 5) Create specialized error subclasses (FileNotFoundError, ParseError, ValidationError, APIError) with appropriate error codes and formatting.",
58 | 			"reasoning": "This is a relatively straightforward task involving utility functions and error class hierarchies. The main complexity is in ensuring proper error sanitization for production use and creating a well-structured error hierarchy that can be used throughout the application."
59 | 		},
60 | 		{
61 | 			"taskId": 124,
62 | 			"taskTitle": "Implement TaskMasterCore Facade",
63 | 			"complexityScore": 7,
64 | 			"recommendedSubtasks": 5,
65 | 			"expansionPrompt": "Build TaskMasterCore facade implementation: 1) Create class structure with proper TypeScript imports and type definitions for all subsystem interfaces, 2) Implement initialize method for lazy loading AI provider and parser instances based on configuration, 3) Create parsePRD method that coordinates parser, AI provider, and storage subsystems, 4) Implement getTasks and other facade methods for task retrieval and management, 5) Create createTaskMaster factory function and set up all module exports including type re-exports. Ensure proper ESM compatibility with .js extensions in imports.",
66 | 			"reasoning": "This is a complex integration task that brings together all the other components into a cohesive facade. It requires understanding of the facade pattern, proper dependency management, lazy initialization, and careful module export structure for the public API."
67 | 		},
68 | 		{
69 | 			"taskId": 125,
70 | 			"taskTitle": "Create Placeholder Providers and Complete Testing",
71 | 			"complexityScore": 5,
72 | 			"recommendedSubtasks": 5,
73 | 			"expansionPrompt": "Complete the implementation with placeholders and testing: 1) Create OpenAIProvider placeholder class extending BaseProvider with 'not yet implemented' errors, 2) Create GoogleProvider placeholder class with similar structure, 3) Implement MockProvider in tests/mocks directory with configurable responses and behavior simulation, 4) Write comprehensive unit tests for TaskParser covering all methods and edge cases, 5) Create integration tests for the complete parse-prd workflow ensuring 80% code coverage. Follow kebab-case naming convention for test files.",
74 | 			"reasoning": "This task involves creating placeholder implementations and a comprehensive test suite. While the placeholder providers are simple, creating a good MockProvider and comprehensive tests requires understanding the entire system architecture and ensuring all edge cases are covered."
75 | 		}
76 | 	]
77 | }
78 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/services/environment-config-provider.service.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Unit tests for EnvironmentConfigProvider service
  3 |  */
  4 | 
  5 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  6 | import { EnvironmentConfigProvider } from './environment-config-provider.service.js';
  7 | 
  8 | describe('EnvironmentConfigProvider', () => {
  9 | 	let provider: EnvironmentConfigProvider;
 10 | 	const originalEnv = { ...process.env };
 11 | 
 12 | 	beforeEach(() => {
 13 | 		// Clear all TASKMASTER_ env vars
 14 | 		Object.keys(process.env).forEach((key) => {
 15 | 			if (key.startsWith('TASKMASTER_')) {
 16 | 				delete process.env[key];
 17 | 			}
 18 | 		});
 19 | 		provider = new EnvironmentConfigProvider();
 20 | 	});
 21 | 
 22 | 	afterEach(() => {
 23 | 		// Restore original environment
 24 | 		process.env = { ...originalEnv };
 25 | 	});
 26 | 
 27 | 	describe('loadConfig', () => {
 28 | 		it('should load configuration from environment variables', () => {
 29 | 			process.env.TASKMASTER_STORAGE_TYPE = 'api';
 30 | 			process.env.TASKMASTER_API_ENDPOINT = 'https://api.example.com';
 31 | 			process.env.TASKMASTER_MODEL_MAIN = 'gpt-4';
 32 | 
 33 | 			const config = provider.loadConfig();
 34 | 
 35 | 			expect(config).toEqual({
 36 | 				storage: {
 37 | 					type: 'api',
 38 | 					apiEndpoint: 'https://api.example.com'
 39 | 				},
 40 | 				models: {
 41 | 					main: 'gpt-4'
 42 | 				}
 43 | 			});
 44 | 		});
 45 | 
 46 | 		it('should return empty object when no env vars are set', () => {
 47 | 			const config = provider.loadConfig();
 48 | 			expect(config).toEqual({});
 49 | 		});
 50 | 
 51 | 		it('should skip runtime state variables', () => {
 52 | 			process.env.TASKMASTER_TAG = 'feature-branch';
 53 | 			process.env.TASKMASTER_MODEL_MAIN = 'claude-3';
 54 | 
 55 | 			const config = provider.loadConfig();
 56 | 
 57 | 			expect(config).toEqual({
 58 | 				models: { main: 'claude-3' }
 59 | 			});
 60 | 			expect(config).not.toHaveProperty('activeTag');
 61 | 		});
 62 | 
 63 | 		it('should validate storage type values', () => {
 64 | 			// Mock console.warn to check validation
 65 | 			const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
 66 | 
 67 | 			process.env.TASKMASTER_STORAGE_TYPE = 'invalid';
 68 | 
 69 | 			const config = provider.loadConfig();
 70 | 
 71 | 			expect(config).toEqual({});
 72 | 			expect(warnSpy).toHaveBeenCalledWith(
 73 | 				'Invalid value for TASKMASTER_STORAGE_TYPE: invalid'
 74 | 			);
 75 | 
 76 | 			warnSpy.mockRestore();
 77 | 		});
 78 | 
 79 | 		it('should accept valid storage type values', () => {
 80 | 			process.env.TASKMASTER_STORAGE_TYPE = 'file';
 81 | 			let config = provider.loadConfig();
 82 | 			expect(config.storage?.type).toBe('file');
 83 | 
 84 | 			process.env.TASKMASTER_STORAGE_TYPE = 'api';
 85 | 			provider = new EnvironmentConfigProvider(); // Reset provider
 86 | 			config = provider.loadConfig();
 87 | 			expect(config.storage?.type).toBe('api');
 88 | 		});
 89 | 
 90 | 		it('should handle nested configuration paths', () => {
 91 | 			process.env.TASKMASTER_MODEL_MAIN = 'model1';
 92 | 			process.env.TASKMASTER_MODEL_RESEARCH = 'model2';
 93 | 			process.env.TASKMASTER_MODEL_FALLBACK = 'model3';
 94 | 
 95 | 			const config = provider.loadConfig();
 96 | 
 97 | 			expect(config).toEqual({
 98 | 				models: {
 99 | 					main: 'model1',
100 | 					research: 'model2',
101 | 					fallback: 'model3'
102 | 				}
103 | 			});
104 | 		});
105 | 
106 | 		it('should handle custom response language', () => {
107 | 			process.env.TASKMASTER_RESPONSE_LANGUAGE = 'Spanish';
108 | 
109 | 			const config = provider.loadConfig();
110 | 
111 | 			expect(config).toEqual({
112 | 				custom: {
113 | 					responseLanguage: 'Spanish'
114 | 				}
115 | 			});
116 | 		});
117 | 
118 | 		it('should ignore empty string values', () => {
119 | 			process.env.TASKMASTER_MODEL_MAIN = '';
120 | 			process.env.TASKMASTER_MODEL_FALLBACK = 'fallback-model';
121 | 
122 | 			const config = provider.loadConfig();
123 | 
124 | 			expect(config).toEqual({
125 | 				models: {
126 | 					fallback: 'fallback-model'
127 | 				}
128 | 			});
129 | 		});
130 | 	});
131 | 
132 | 	describe('getRuntimeState', () => {
133 | 		it('should extract runtime state variables', () => {
134 | 			process.env.TASKMASTER_TAG = 'develop';
135 | 			process.env.TASKMASTER_MODEL_MAIN = 'model'; // Should not be included
136 | 
137 | 			const state = provider.getRuntimeState();
138 | 
139 | 			expect(state).toEqual({
140 | 				activeTag: 'develop'
141 | 			});
142 | 		});
143 | 
144 | 		it('should return empty object when no runtime state vars', () => {
145 | 			process.env.TASKMASTER_MODEL_MAIN = 'model';
146 | 
147 | 			const state = provider.getRuntimeState();
148 | 
149 | 			expect(state).toEqual({});
150 | 		});
151 | 	});
152 | 
153 | 	describe('hasEnvVar', () => {
154 | 		it('should return true when env var exists', () => {
155 | 			process.env.TASKMASTER_MODEL_MAIN = 'test';
156 | 
157 | 			expect(provider.hasEnvVar('TASKMASTER_MODEL_MAIN')).toBe(true);
158 | 		});
159 | 
160 | 		it('should return false when env var does not exist', () => {
161 | 			expect(provider.hasEnvVar('TASKMASTER_NONEXISTENT')).toBe(false);
162 | 		});
163 | 
164 | 		it('should return false for undefined values', () => {
165 | 			process.env.TASKMASTER_TEST = undefined as any;
166 | 
167 | 			expect(provider.hasEnvVar('TASKMASTER_TEST')).toBe(false);
168 | 		});
169 | 	});
170 | 
171 | 	describe('getAllTaskmasterEnvVars', () => {
172 | 		it('should return all TASKMASTER_ prefixed variables', () => {
173 | 			process.env.TASKMASTER_VAR1 = 'value1';
174 | 			process.env.TASKMASTER_VAR2 = 'value2';
175 | 			process.env.OTHER_VAR = 'other';
176 | 			process.env.TASK_MASTER = 'wrong-prefix';
177 | 
178 | 			const vars = provider.getAllTaskmasterEnvVars();
179 | 
180 | 			expect(vars).toEqual({
181 | 				TASKMASTER_VAR1: 'value1',
182 | 				TASKMASTER_VAR2: 'value2'
183 | 			});
184 | 		});
185 | 
186 | 		it('should return empty object when no TASKMASTER_ vars', () => {
187 | 			process.env.OTHER_VAR = 'value';
188 | 
189 | 			const vars = provider.getAllTaskmasterEnvVars();
190 | 
191 | 			expect(vars).toEqual({});
192 | 		});
193 | 
194 | 		it('should filter out undefined values', () => {
195 | 			process.env.TASKMASTER_DEFINED = 'value';
196 | 			process.env.TASKMASTER_UNDEFINED = undefined as any;
197 | 
198 | 			const vars = provider.getAllTaskmasterEnvVars();
199 | 
200 | 			expect(vars).toEqual({
201 | 				TASKMASTER_DEFINED: 'value'
202 | 			});
203 | 		});
204 | 	});
205 | 
206 | 	describe('custom mappings', () => {
207 | 		it('should use custom mappings when provided', () => {
208 | 			const customMappings = [{ env: 'CUSTOM_VAR', path: ['custom', 'value'] }];
209 | 
210 | 			const customProvider = new EnvironmentConfigProvider(customMappings);
211 | 			process.env.CUSTOM_VAR = 'test-value';
212 | 
213 | 			const config = customProvider.loadConfig();
214 | 
215 | 			expect(config).toEqual({
216 | 				custom: {
217 | 					value: 'test-value'
218 | 				}
219 | 			});
220 | 		});
221 | 
222 | 		it('should add new mapping with addMapping', () => {
223 | 			process.env.NEW_MAPPING = 'new-value';
224 | 
225 | 			provider.addMapping({
226 | 				env: 'NEW_MAPPING',
227 | 				path: ['new', 'mapping']
228 | 			});
229 | 
230 | 			const config = provider.loadConfig();
231 | 
232 | 			expect(config).toHaveProperty('new.mapping', 'new-value');
233 | 		});
234 | 
235 | 		it('should return current mappings with getMappings', () => {
236 | 			const mappings = provider.getMappings();
237 | 
238 | 			expect(mappings).toBeInstanceOf(Array);
239 | 			expect(mappings.length).toBeGreaterThan(0);
240 | 
241 | 			// Check for some expected mappings
242 | 			const envNames = mappings.map((m) => m.env);
243 | 			expect(envNames).toContain('TASKMASTER_STORAGE_TYPE');
244 | 			expect(envNames).toContain('TASKMASTER_MODEL_MAIN');
245 | 			expect(envNames).toContain('TASKMASTER_TAG');
246 | 		});
247 | 
248 | 		it('should return copy of mappings array', () => {
249 | 			const mappings1 = provider.getMappings();
250 | 			const mappings2 = provider.getMappings();
251 | 
252 | 			expect(mappings1).not.toBe(mappings2); // Different instances
253 | 			expect(mappings1).toEqual(mappings2); // Same content
254 | 		});
255 | 	});
256 | 
257 | 	describe('validation', () => {
258 | 		it('should validate values when validator is provided', () => {
259 | 			const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
260 | 
261 | 			process.env.TASKMASTER_STORAGE_TYPE = 'database'; // Invalid
262 | 
263 | 			const config = provider.loadConfig();
264 | 
265 | 			expect(config).toEqual({});
266 | 			expect(warnSpy).toHaveBeenCalledWith(
267 | 				'Invalid value for TASKMASTER_STORAGE_TYPE: database'
268 | 			);
269 | 
270 | 			warnSpy.mockRestore();
271 | 		});
272 | 
273 | 		it('should accept values that pass validation', () => {
274 | 			process.env.TASKMASTER_STORAGE_TYPE = 'file';
275 | 
276 | 			const config = provider.loadConfig();
277 | 
278 | 			expect(config.storage?.type).toBe('file');
279 | 		});
280 | 
281 | 		it('should work with custom validators', () => {
282 | 			const customProvider = new EnvironmentConfigProvider([
283 | 				{
284 | 					env: 'CUSTOM_NUMBER',
285 | 					path: ['custom', 'number'],
286 | 					validate: (v) => !isNaN(Number(v))
287 | 				}
288 | 			]);
289 | 
290 | 			process.env.CUSTOM_NUMBER = '123';
291 | 			let config = customProvider.loadConfig();
292 | 			expect(config.custom?.number).toBe('123');
293 | 
294 | 			process.env.CUSTOM_NUMBER = 'not-a-number';
295 | 			const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
296 | 			customProvider = new EnvironmentConfigProvider([
297 | 				{
298 | 					env: 'CUSTOM_NUMBER',
299 | 					path: ['custom', 'number'],
300 | 					validate: (v) => !isNaN(Number(v))
301 | 				}
302 | 			]);
303 | 			config = customProvider.loadConfig();
304 | 			expect(config).toEqual({});
305 | 			expect(warnSpy).toHaveBeenCalled();
306 | 
307 | 			warnSpy.mockRestore();
308 | 		});
309 | 	});
310 | 
311 | 	describe('edge cases', () => {
312 | 		it('should handle special characters in values', () => {
313 | 			process.env.TASKMASTER_API_ENDPOINT =
314 | 				'https://api.example.com/v1?key=abc&token=xyz';
315 | 			process.env.TASKMASTER_API_TOKEN = 'Bearer abc123!@#$%^&*()';
316 | 
317 | 			const config = provider.loadConfig();
318 | 
319 | 			expect(config.storage?.apiEndpoint).toBe(
320 | 				'https://api.example.com/v1?key=abc&token=xyz'
321 | 			);
322 | 			expect(config.storage?.apiAccessToken).toBe('Bearer abc123!@#$%^&*()');
323 | 		});
324 | 
325 | 		it('should handle whitespace in values', () => {
326 | 			process.env.TASKMASTER_MODEL_MAIN = '  claude-3  ';
327 | 
328 | 			const config = provider.loadConfig();
329 | 
330 | 			// Note: We're not trimming, preserving the value as-is
331 | 			expect(config.models?.main).toBe('  claude-3  ');
332 | 		});
333 | 
334 | 		it('should handle very long values', () => {
335 | 			const longValue = 'a'.repeat(10000);
336 | 			process.env.TASKMASTER_API_TOKEN = longValue;
337 | 
338 | 			const config = provider.loadConfig();
339 | 
340 | 			expect(config.storage?.apiAccessToken).toBe(longValue);
341 | 		});
342 | 	});
343 | });
344 | 
```

--------------------------------------------------------------------------------
/apps/cli/src/commands/export.command.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Export command for exporting tasks to external systems
  3 |  * Provides functionality to export tasks to Hamster briefs
  4 |  */
  5 | 
  6 | import {
  7 | 	AuthManager,
  8 | 	type ExportResult,
  9 | 	type TmCore,
 10 | 	type UserContext,
 11 | 	createTmCore
 12 | } from '@tm/core';
 13 | import chalk from 'chalk';
 14 | import { Command } from 'commander';
 15 | import inquirer from 'inquirer';
 16 | import ora, { Ora } from 'ora';
 17 | import { displayError } from '../utils/error-handler.js';
 18 | import * as ui from '../utils/ui.js';
 19 | import { getProjectRoot } from '../utils/project-root.js';
 20 | 
 21 | /**
 22 |  * Result type from export command
 23 |  */
 24 | export interface ExportCommandResult {
 25 | 	success: boolean;
 26 | 	action: 'export' | 'validate' | 'cancelled';
 27 | 	result?: ExportResult;
 28 | 	message?: string;
 29 | }
 30 | 
 31 | /**
 32 |  * ExportCommand extending Commander's Command class
 33 |  * Handles task export to external systems
 34 |  */
 35 | export class ExportCommand extends Command {
 36 | 	private authManager: AuthManager;
 37 | 	private taskMasterCore?: TmCore;
 38 | 	private lastResult?: ExportCommandResult;
 39 | 
 40 | 	constructor(name?: string) {
 41 | 		super(name || 'export');
 42 | 
 43 | 		// Initialize auth manager
 44 | 		this.authManager = AuthManager.getInstance();
 45 | 
 46 | 		// Configure the command
 47 | 		this.description('Export tasks to external systems (e.g., Hamster briefs)');
 48 | 
 49 | 		// Add options
 50 | 		this.option('--org <id>', 'Organization ID to export to');
 51 | 		this.option('--brief <id>', 'Brief ID to export tasks to');
 52 | 		this.option('--tag <tag>', 'Export tasks from a specific tag');
 53 | 		this.option(
 54 | 			'--status <status>',
 55 | 			'Filter tasks by status (pending, in-progress, done, etc.)'
 56 | 		);
 57 | 		this.option('--exclude-subtasks', 'Exclude subtasks from export');
 58 | 		this.option('-y, --yes', 'Skip confirmation prompt');
 59 | 
 60 | 		// Accept optional positional argument for brief ID or Hamster URL
 61 | 		this.argument('[briefOrUrl]', 'Brief ID or Hamster brief URL');
 62 | 
 63 | 		// Default action
 64 | 		this.action(async (briefOrUrl?: string, options?: any) => {
 65 | 			await this.executeExport(briefOrUrl, options);
 66 | 		});
 67 | 	}
 68 | 
 69 | 	/**
 70 | 	 * Initialize the TmCore
 71 | 	 */
 72 | 	private async initializeServices(): Promise<void> {
 73 | 		if (this.taskMasterCore) {
 74 | 			return;
 75 | 		}
 76 | 
 77 | 		try {
 78 | 			// Initialize TmCore
 79 | 			this.taskMasterCore = await createTmCore({
 80 | 				projectPath: getProjectRoot()
 81 | 			});
 82 | 		} catch (error) {
 83 | 			throw new Error(
 84 | 				`Failed to initialize services: ${(error as Error).message}`
 85 | 			);
 86 | 		}
 87 | 	}
 88 | 
 89 | 	/**
 90 | 	 * Execute the export command
 91 | 	 */
 92 | 	private async executeExport(
 93 | 		briefOrUrl?: string,
 94 | 		options?: any
 95 | 	): Promise<void> {
 96 | 		let spinner: Ora | undefined;
 97 | 
 98 | 		try {
 99 | 			// Check authentication
100 | 			const hasSession = await this.authManager.hasValidSession();
101 | 			if (!hasSession) {
102 | 				ui.displayError('Not authenticated. Run "tm auth login" first.');
103 | 				process.exit(1);
104 | 			}
105 | 
106 | 			// Initialize services
107 | 			await this.initializeServices();
108 | 
109 | 			// Get current context
110 | 			const context = await this.authManager.getContext();
111 | 
112 | 			// Determine org and brief IDs
113 | 			let orgId = options?.org || context?.orgId;
114 | 			let briefId = options?.brief || briefOrUrl || context?.briefId;
115 | 
116 | 			// If a URL/ID was provided as argument, resolve it
117 | 			if (briefOrUrl && !options?.brief) {
118 | 				spinner = ora('Resolving brief...').start();
119 | 				const resolvedBrief = await this.resolveBriefInput(briefOrUrl);
120 | 				if (resolvedBrief) {
121 | 					briefId = resolvedBrief.briefId;
122 | 					orgId = resolvedBrief.orgId;
123 | 					spinner.succeed('Brief resolved');
124 | 				} else {
125 | 					spinner.fail('Could not resolve brief');
126 | 					process.exit(1);
127 | 				}
128 | 			}
129 | 
130 | 			// Validate we have necessary IDs
131 | 			if (!orgId) {
132 | 				ui.displayError(
133 | 					'No organization selected. Run "tm context org" or use --org flag.'
134 | 				);
135 | 				process.exit(1);
136 | 			}
137 | 
138 | 			if (!briefId) {
139 | 				ui.displayError(
140 | 					'No brief specified. Run "tm context brief", provide a brief ID/URL, or use --brief flag.'
141 | 				);
142 | 				process.exit(1);
143 | 			}
144 | 
145 | 			// Confirm export if not auto-confirmed
146 | 			if (!options?.yes) {
147 | 				const confirmed = await this.confirmExport(orgId, briefId, context);
148 | 				if (!confirmed) {
149 | 					ui.displayWarning('Export cancelled');
150 | 					this.lastResult = {
151 | 						success: false,
152 | 						action: 'cancelled',
153 | 						message: 'User cancelled export'
154 | 					};
155 | 					process.exit(0);
156 | 				}
157 | 			}
158 | 
159 | 			// Perform export
160 | 			spinner = ora('Exporting tasks...').start();
161 | 
162 | 			// Use integration domain facade
163 | 			const exportResult = await this.taskMasterCore!.integration.exportTasks({
164 | 				orgId,
165 | 				briefId,
166 | 				tag: options?.tag,
167 | 				status: options?.status,
168 | 				excludeSubtasks: options?.excludeSubtasks || false
169 | 			});
170 | 
171 | 			if (exportResult.success) {
172 | 				spinner.succeed(
173 | 					`Successfully exported ${exportResult.taskCount} task(s) to brief`
174 | 				);
175 | 
176 | 				// Display summary
177 | 				console.log(chalk.cyan('\n📤 Export Summary\n'));
178 | 				console.log(chalk.white(`  Organization: ${orgId}`));
179 | 				console.log(chalk.white(`  Brief: ${briefId}`));
180 | 				console.log(chalk.white(`  Tasks exported: ${exportResult.taskCount}`));
181 | 				if (options?.tag) {
182 | 					console.log(chalk.gray(`  Tag: ${options.tag}`));
183 | 				}
184 | 				if (options?.status) {
185 | 					console.log(chalk.gray(`  Status filter: ${options.status}`));
186 | 				}
187 | 
188 | 				if (exportResult.message) {
189 | 					console.log(chalk.gray(`\n  ${exportResult.message}`));
190 | 				}
191 | 			} else {
192 | 				spinner.fail('Export failed');
193 | 				if (exportResult.error) {
194 | 					console.error(chalk.red(`\n✗ ${exportResult.error.message}`));
195 | 				}
196 | 			}
197 | 
198 | 			this.lastResult = {
199 | 				success: exportResult.success,
200 | 				action: 'export',
201 | 				result: exportResult
202 | 			};
203 | 		} catch (error: any) {
204 | 			if (spinner?.isSpinning) spinner.fail('Export failed');
205 | 			displayError(error);
206 | 		}
207 | 	}
208 | 
209 | 	/**
210 | 	 * Resolve brief input to get brief and org IDs
211 | 	 */
212 | 	private async resolveBriefInput(
213 | 		briefOrUrl: string
214 | 	): Promise<{ briefId: string; orgId: string } | null> {
215 | 		try {
216 | 			// Extract brief ID from input
217 | 			const briefId = this.extractBriefId(briefOrUrl);
218 | 			if (!briefId) {
219 | 				return null;
220 | 			}
221 | 
222 | 			// Fetch brief to get organization
223 | 			const brief = await this.authManager.getBrief(briefId);
224 | 			if (!brief) {
225 | 				ui.displayError('Brief not found or you do not have access');
226 | 				return null;
227 | 			}
228 | 
229 | 			return {
230 | 				briefId: brief.id,
231 | 				orgId: brief.accountId
232 | 			};
233 | 		} catch (error) {
234 | 			console.error(chalk.red(`Failed to resolve brief: ${error}`));
235 | 			return null;
236 | 		}
237 | 	}
238 | 
239 | 	/**
240 | 	 * Extract a brief ID from raw input (ID or URL)
241 | 	 */
242 | 	private extractBriefId(input: string): string | null {
243 | 		const raw = input?.trim() ?? '';
244 | 		if (!raw) return null;
245 | 
246 | 		const parseUrl = (s: string): URL | null => {
247 | 			try {
248 | 				return new URL(s);
249 | 			} catch {}
250 | 			try {
251 | 				return new URL(`https://${s}`);
252 | 			} catch {}
253 | 			return null;
254 | 		};
255 | 
256 | 		const fromParts = (path: string): string | null => {
257 | 			const parts = path.split('/').filter(Boolean);
258 | 			const briefsIdx = parts.lastIndexOf('briefs');
259 | 			const candidate =
260 | 				briefsIdx >= 0 && parts.length > briefsIdx + 1
261 | 					? parts[briefsIdx + 1]
262 | 					: parts[parts.length - 1];
263 | 			return candidate?.trim() || null;
264 | 		};
265 | 
266 | 		// Try URL parsing
267 | 		const url = parseUrl(raw);
268 | 		if (url) {
269 | 			const qId = url.searchParams.get('id') || url.searchParams.get('briefId');
270 | 			const candidate = (qId || fromParts(url.pathname)) ?? null;
271 | 			if (candidate) {
272 | 				if (this.isLikelyId(candidate) || candidate.length >= 8) {
273 | 					return candidate;
274 | 				}
275 | 			}
276 | 		}
277 | 
278 | 		// Check if it looks like a path
279 | 		if (raw.includes('/')) {
280 | 			const candidate = fromParts(raw);
281 | 			if (candidate && (this.isLikelyId(candidate) || candidate.length >= 8)) {
282 | 				return candidate;
283 | 			}
284 | 		}
285 | 
286 | 		// Return raw if it looks like an ID
287 | 		return raw;
288 | 	}
289 | 
290 | 	/**
291 | 	 * Check if a string looks like a brief ID
292 | 	 */
293 | 	private isLikelyId(value: string): boolean {
294 | 		const uuidRegex =
295 | 			/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
296 | 		const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
297 | 		const slugRegex = /^[A-Za-z0-9_-]{16,}$/;
298 | 		return (
299 | 			uuidRegex.test(value) || ulidRegex.test(value) || slugRegex.test(value)
300 | 		);
301 | 	}
302 | 
303 | 	/**
304 | 	 * Confirm export with the user
305 | 	 */
306 | 	private async confirmExport(
307 | 		orgId: string,
308 | 		briefId: string,
309 | 		context: UserContext | null
310 | 	): Promise<boolean> {
311 | 		console.log(chalk.cyan('\n📤 Export Tasks\n'));
312 | 
313 | 		// Show org name if available
314 | 		if (context?.orgName) {
315 | 			console.log(chalk.white(`  Organization: ${context.orgName}`));
316 | 			console.log(chalk.gray(`  ID: ${orgId}`));
317 | 		} else {
318 | 			console.log(chalk.white(`  Organization ID: ${orgId}`));
319 | 		}
320 | 
321 | 		// Show brief info
322 | 		if (context?.briefName) {
323 | 			console.log(chalk.white(`\n  Brief: ${context.briefName}`));
324 | 			console.log(chalk.gray(`  ID: ${briefId}`));
325 | 		} else {
326 | 			console.log(chalk.white(`\n  Brief ID: ${briefId}`));
327 | 		}
328 | 
329 | 		const { confirmed } = await inquirer.prompt([
330 | 			{
331 | 				type: 'confirm',
332 | 				name: 'confirmed',
333 | 				message: 'Do you want to proceed with export?',
334 | 				default: true
335 | 			}
336 | 		]);
337 | 
338 | 		return confirmed;
339 | 	}
340 | 
341 | 	/**
342 | 	 * Get the last export result (useful for testing)
343 | 	 */
344 | 	public getLastResult(): ExportCommandResult | undefined {
345 | 		return this.lastResult;
346 | 	}
347 | 
348 | 	/**
349 | 	 * Clean up resources
350 | 	 */
351 | 	async cleanup(): Promise<void> {
352 | 		// No resources to clean up
353 | 	}
354 | 
355 | 	/**
356 | 	 * Register this command on an existing program
357 | 	 */
358 | 	static register(program: Command, name?: string): ExportCommand {
359 | 		const exportCommand = new ExportCommand(name);
360 | 		program.addCommand(exportCommand);
361 | 		return exportCommand;
362 | 	}
363 | }
364 | 
```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/update-tasks.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Tests for the update-tasks.js module
  3 |  */
  4 | import { jest } from '@jest/globals';
  5 | 
  6 | // Mock the dependencies before importing the module under test
  7 | jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
  8 | 	readJSON: jest.fn(),
  9 | 	writeJSON: jest.fn(),
 10 | 	log: jest.fn(),
 11 | 	CONFIG: {
 12 | 		model: 'mock-claude-model',
 13 | 		maxTokens: 4000,
 14 | 		temperature: 0.7,
 15 | 		debug: false
 16 | 	},
 17 | 	sanitizePrompt: jest.fn((prompt) => prompt),
 18 | 	truncate: jest.fn((text) => text),
 19 | 	isSilentMode: jest.fn(() => false),
 20 | 	findTaskById: jest.fn(),
 21 | 	getCurrentTag: jest.fn(() => 'master'),
 22 | 	ensureTagMetadata: jest.fn((tagObj) => tagObj),
 23 | 	flattenTasksWithSubtasks: jest.fn((tasks) => tasks),
 24 | 	findProjectRoot: jest.fn(() => '/mock/project/root')
 25 | }));
 26 | 
 27 | jest.unstable_mockModule(
 28 | 	'../../../../../scripts/modules/ai-services-unified.js',
 29 | 	() => ({
 30 | 		generateTextService: jest.fn().mockResolvedValue({
 31 | 			mainResult: '[]', // mainResult is the text string directly
 32 | 			telemetryData: {}
 33 | 		}),
 34 | 		generateObjectService: jest.fn().mockResolvedValue({
 35 | 			mainResult: {
 36 | 				tasks: [] // generateObject returns structured data
 37 | 			},
 38 | 			telemetryData: {}
 39 | 		})
 40 | 	})
 41 | );
 42 | 
 43 | jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
 44 | 	getStatusWithColor: jest.fn((status) => status),
 45 | 	startLoadingIndicator: jest.fn(),
 46 | 	stopLoadingIndicator: jest.fn(),
 47 | 	displayAiUsageSummary: jest.fn()
 48 | }));
 49 | 
 50 | jest.unstable_mockModule(
 51 | 	'../../../../../scripts/modules/config-manager.js',
 52 | 	() => ({
 53 | 		getDebugFlag: jest.fn(() => false),
 54 | 		hasCodebaseAnalysis: jest.fn(() => false)
 55 | 	})
 56 | );
 57 | 
 58 | jest.unstable_mockModule(
 59 | 	'../../../../../scripts/modules/task-manager/generate-task-files.js',
 60 | 	() => ({
 61 | 		default: jest.fn().mockResolvedValue()
 62 | 	})
 63 | );
 64 | 
 65 | jest.unstable_mockModule(
 66 | 	'../../../../../scripts/modules/prompt-manager.js',
 67 | 	() => ({
 68 | 		getPromptManager: jest.fn().mockReturnValue({
 69 | 			loadPrompt: jest.fn().mockResolvedValue({
 70 | 				systemPrompt: 'Mocked system prompt',
 71 | 				userPrompt: 'Mocked user prompt'
 72 | 			})
 73 | 		})
 74 | 	})
 75 | );
 76 | 
 77 | jest.unstable_mockModule(
 78 | 	'../../../../../scripts/modules/task-manager/models.js',
 79 | 	() => ({
 80 | 		getModelConfiguration: jest.fn(() => ({
 81 | 			model: 'mock-model',
 82 | 			maxTokens: 4000,
 83 | 			temperature: 0.7
 84 | 		}))
 85 | 	})
 86 | );
 87 | 
 88 | // Import the mocked modules
 89 | const { readJSON, writeJSON, log } = await import(
 90 | 	'../../../../../scripts/modules/utils.js'
 91 | );
 92 | 
 93 | const { generateObjectService } = await import(
 94 | 	'../../../../../scripts/modules/ai-services-unified.js'
 95 | );
 96 | 
 97 | // Import the module under test
 98 | const { default: updateTasks } = await import(
 99 | 	'../../../../../scripts/modules/task-manager/update-tasks.js'
100 | );
101 | 
102 | describe('updateTasks', () => {
103 | 	beforeEach(() => {
104 | 		jest.clearAllMocks();
105 | 	});
106 | 
107 | 	test('should update tasks based on new context', async () => {
108 | 		// Arrange
109 | 		const mockTasksPath = '/mock/path/tasks.json';
110 | 		const mockFromId = 2;
111 | 		const mockPrompt = 'New project direction';
112 | 		const mockInitialTasks = {
113 | 			master: {
114 | 				tasks: [
115 | 					{
116 | 						id: 1,
117 | 						title: 'Old Task 1',
118 | 						status: 'done',
119 | 						details: 'Done details'
120 | 					},
121 | 					{
122 | 						id: 2,
123 | 						title: 'Old Task 2',
124 | 						status: 'pending',
125 | 						details: 'Old details 2'
126 | 					},
127 | 					{
128 | 						id: 3,
129 | 						title: 'Old Task 3',
130 | 						status: 'in-progress',
131 | 						details: 'Old details 3'
132 | 					}
133 | 				]
134 | 			}
135 | 		};
136 | 
137 | 		const mockUpdatedTasks = [
138 | 			{
139 | 				id: 2,
140 | 				title: 'Updated Task 2',
141 | 				status: 'pending',
142 | 				details: 'New details 2 based on direction',
143 | 				description: 'Updated description',
144 | 				dependencies: [],
145 | 				priority: 'medium',
146 | 				testStrategy: 'Unit test the updated functionality',
147 | 				subtasks: []
148 | 			},
149 | 			{
150 | 				id: 3,
151 | 				title: 'Updated Task 3',
152 | 				status: 'pending',
153 | 				details: 'New details 3 based on direction',
154 | 				description: 'Updated description',
155 | 				dependencies: [],
156 | 				priority: 'medium',
157 | 				testStrategy: 'Integration test the updated features',
158 | 				subtasks: []
159 | 			}
160 | 		];
161 | 
162 | 		const mockApiResponse = {
163 | 			mainResult: {
164 | 				tasks: mockUpdatedTasks // generateObject returns structured data
165 | 			},
166 | 			telemetryData: {}
167 | 		};
168 | 
169 | 		// Configure mocks - readJSON should return the resolved view with tasks at top level
170 | 		readJSON.mockReturnValue({
171 | 			...mockInitialTasks.master,
172 | 			tag: 'master',
173 | 			_rawTaggedData: mockInitialTasks
174 | 		});
175 | 		generateObjectService.mockResolvedValue(mockApiResponse);
176 | 
177 | 		// Act
178 | 		const result = await updateTasks(
179 | 			mockTasksPath,
180 | 			mockFromId,
181 | 			mockPrompt,
182 | 			false, // research
183 | 			{ projectRoot: '/mock/path', tag: 'master' }, // context
184 | 			'json' // output format
185 | 		);
186 | 
187 | 		// Assert
188 | 		// 1. Read JSON called
189 | 		expect(readJSON).toHaveBeenCalledWith(
190 | 			mockTasksPath,
191 | 			'/mock/path',
192 | 			'master'
193 | 		);
194 | 
195 | 		// 2. AI Service called with correct args
196 | 		expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
197 | 
198 | 		// 3. Write JSON called with correctly merged tasks
199 | 		expect(writeJSON).toHaveBeenCalledWith(
200 | 			mockTasksPath,
201 | 			expect.objectContaining({
202 | 				_rawTaggedData: expect.objectContaining({
203 | 					master: expect.objectContaining({
204 | 						tasks: expect.arrayContaining([
205 | 							expect.objectContaining({ id: 1 }),
206 | 							expect.objectContaining({ id: 2, title: 'Updated Task 2' }),
207 | 							expect.objectContaining({ id: 3, title: 'Updated Task 3' })
208 | 						])
209 | 					})
210 | 				})
211 | 			}),
212 | 			'/mock/path',
213 | 			'master'
214 | 		);
215 | 
216 | 		// 4. Check return value
217 | 		expect(result).toEqual(
218 | 			expect.objectContaining({
219 | 				success: true,
220 | 				updatedTasks: mockUpdatedTasks,
221 | 				telemetryData: {}
222 | 			})
223 | 		);
224 | 	});
225 | 
226 | 	test('should handle no tasks to update', async () => {
227 | 		// Arrange
228 | 		const mockTasksPath = '/mock/path/tasks.json';
229 | 		const mockFromId = 99; // Non-existent ID
230 | 		const mockPrompt = 'Update non-existent tasks';
231 | 		const mockInitialTasks = {
232 | 			master: {
233 | 				tasks: [
234 | 					{ id: 1, status: 'done' },
235 | 					{ id: 2, status: 'done' }
236 | 				]
237 | 			}
238 | 		};
239 | 
240 | 		// Configure mocks - readJSON should return the resolved view with tasks at top level
241 | 		readJSON.mockReturnValue({
242 | 			...mockInitialTasks.master,
243 | 			tag: 'master',
244 | 			_rawTaggedData: mockInitialTasks
245 | 		});
246 | 
247 | 		// Act
248 | 		const result = await updateTasks(
249 | 			mockTasksPath,
250 | 			mockFromId,
251 | 			mockPrompt,
252 | 			false,
253 | 			{ projectRoot: '/mock/path', tag: 'master' },
254 | 			'json'
255 | 		);
256 | 
257 | 		// Assert
258 | 		expect(readJSON).toHaveBeenCalledWith(
259 | 			mockTasksPath,
260 | 			'/mock/path',
261 | 			'master'
262 | 		);
263 | 		expect(generateObjectService).not.toHaveBeenCalled();
264 | 		expect(writeJSON).not.toHaveBeenCalled();
265 | 		expect(log).toHaveBeenCalledWith(
266 | 			'info',
267 | 			expect.stringContaining('No tasks to update')
268 | 		);
269 | 
270 | 		// Should return early with no updates
271 | 		expect(result).toBeUndefined();
272 | 	});
273 | 
274 | 	test('should preserve all tags when updating tasks in tagged context', async () => {
275 | 		// Arrange - Simple 2-tag structure to test tag corruption fix
276 | 		const mockTasksPath = '/mock/path/tasks.json';
277 | 		const mockFromId = 1;
278 | 		const mockPrompt = 'Update master tag tasks';
279 | 
280 | 		const mockTaggedData = {
281 | 			master: {
282 | 				tasks: [
283 | 					{
284 | 						id: 1,
285 | 						title: 'Master Task',
286 | 						status: 'pending',
287 | 						details: 'Old details'
288 | 					},
289 | 					{
290 | 						id: 2,
291 | 						title: 'Master Task 2',
292 | 						status: 'done',
293 | 						details: 'Done task'
294 | 					}
295 | 				],
296 | 				metadata: {
297 | 					created: '2024-01-01T00:00:00.000Z',
298 | 					description: 'Master tag tasks'
299 | 				}
300 | 			},
301 | 			'feature-branch': {
302 | 				tasks: [
303 | 					{
304 | 						id: 1,
305 | 						title: 'Feature Task',
306 | 						status: 'pending',
307 | 						details: 'Feature work'
308 | 					}
309 | 				],
310 | 				metadata: {
311 | 					created: '2024-01-02T00:00:00.000Z',
312 | 					description: 'Feature branch tasks'
313 | 				}
314 | 			}
315 | 		};
316 | 
317 | 		const mockUpdatedTasks = [
318 | 			{
319 | 				id: 1,
320 | 				title: 'Updated Master Task',
321 | 				status: 'pending',
322 | 				details: 'Updated details',
323 | 				description: 'Updated description',
324 | 				dependencies: [],
325 | 				priority: 'medium',
326 | 				testStrategy: 'Test the updated functionality',
327 | 				subtasks: []
328 | 			}
329 | 		];
330 | 
331 | 		// Configure mocks - readJSON returns resolved view for master tag
332 | 		readJSON.mockReturnValue({
333 | 			...mockTaggedData.master,
334 | 			tag: 'master',
335 | 			_rawTaggedData: mockTaggedData
336 | 		});
337 | 
338 | 		generateObjectService.mockResolvedValue({
339 | 			mainResult: {
340 | 				tasks: mockUpdatedTasks
341 | 			},
342 | 			telemetryData: { commandName: 'update-tasks', totalCost: 0.05 }
343 | 		});
344 | 
345 | 		// Act
346 | 		const result = await updateTasks(
347 | 			mockTasksPath,
348 | 			mockFromId,
349 | 			mockPrompt,
350 | 			false, // research
351 | 			{ projectRoot: '/mock/project/root', tag: 'master' },
352 | 			'json'
353 | 		);
354 | 
355 | 		// Assert - CRITICAL: Both tags must be preserved (this would fail before the fix)
356 | 		expect(writeJSON).toHaveBeenCalledWith(
357 | 			mockTasksPath,
358 | 			expect.objectContaining({
359 | 				_rawTaggedData: expect.objectContaining({
360 | 					master: expect.objectContaining({
361 | 						tasks: expect.arrayContaining([
362 | 							expect.objectContaining({ id: 1, title: 'Updated Master Task' }),
363 | 							expect.objectContaining({ id: 2, title: 'Master Task 2' }) // Unchanged done task
364 | 						])
365 | 					}),
366 | 					// CRITICAL: This tag would be missing/corrupted if the bug existed
367 | 					'feature-branch': expect.objectContaining({
368 | 						tasks: expect.arrayContaining([
369 | 							expect.objectContaining({ id: 1, title: 'Feature Task' })
370 | 						]),
371 | 						metadata: expect.objectContaining({
372 | 							description: 'Feature branch tasks'
373 | 						})
374 | 					})
375 | 				})
376 | 			}),
377 | 			'/mock/project/root',
378 | 			'master'
379 | 		);
380 | 
381 | 		expect(result.success).toBe(true);
382 | 		expect(result.updatedTasks).toEqual(mockUpdatedTasks);
383 | 	});
384 | });
385 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/services/commit-message-generator.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { beforeEach, describe, expect, it } from 'vitest';
  2 | import { CommitMessageGenerator } from './commit-message-generator.js';
  3 | 
  4 | describe('CommitMessageGenerator', () => {
  5 | 	let generator: CommitMessageGenerator;
  6 | 
  7 | 	beforeEach(() => {
  8 | 		generator = new CommitMessageGenerator();
  9 | 	});
 10 | 
 11 | 	describe('generateMessage', () => {
 12 | 		it('should generate basic conventional commit message', () => {
 13 | 			const message = generator.generateMessage({
 14 | 				type: 'feat',
 15 | 				description: 'add user authentication',
 16 | 				changedFiles: ['packages/tm-core/src/auth/auth-manager.ts']
 17 | 			});
 18 | 
 19 | 			expect(message).toContain('feat(core): add user authentication');
 20 | 		});
 21 | 
 22 | 		it('should include scope from changed files', () => {
 23 | 			const message = generator.generateMessage({
 24 | 				type: 'fix',
 25 | 				description: 'resolve CLI argument parsing',
 26 | 				changedFiles: ['packages/cli/src/commands/start.ts']
 27 | 			});
 28 | 
 29 | 			expect(message).toContain('fix(cli): resolve CLI argument parsing');
 30 | 		});
 31 | 
 32 | 		it('should include task metadata in commit body', () => {
 33 | 			const message = generator.generateMessage({
 34 | 				type: 'feat',
 35 | 				description: 'implement feature',
 36 | 				changedFiles: ['packages/tm-core/src/index.ts'],
 37 | 				taskId: '5.3',
 38 | 				phase: 'GREEN'
 39 | 			});
 40 | 
 41 | 			expect(message).toContain('Task: 5.3');
 42 | 			expect(message).toContain('Phase: GREEN');
 43 | 		});
 44 | 
 45 | 		it('should include test results metadata', () => {
 46 | 			const message = generator.generateMessage({
 47 | 				type: 'test',
 48 | 				description: 'add unit tests',
 49 | 				changedFiles: ['packages/tm-core/src/auth/auth.test.ts'],
 50 | 				testsPassing: 42,
 51 | 				testsFailing: 0
 52 | 			});
 53 | 
 54 | 			expect(message).toContain('Tests: 42 passing');
 55 | 		});
 56 | 
 57 | 		it('should include failing test count when present', () => {
 58 | 			const message = generator.generateMessage({
 59 | 				type: 'fix',
 60 | 				description: 'fix test failures',
 61 | 				changedFiles: ['packages/tm-core/src/index.ts'],
 62 | 				testsPassing: 40,
 63 | 				testsFailing: 2
 64 | 			});
 65 | 
 66 | 			expect(message).toContain('Tests: 40 passing, 2 failing');
 67 | 		});
 68 | 
 69 | 		it('should include custom body text', () => {
 70 | 			const message = generator.generateMessage({
 71 | 				type: 'feat',
 72 | 				description: 'add new feature',
 73 | 				changedFiles: ['packages/tm-core/src/index.ts'],
 74 | 				body: 'This is a detailed explanation\nof the changes made.'
 75 | 			});
 76 | 
 77 | 			expect(message).toContain('This is a detailed explanation');
 78 | 			expect(message).toContain('of the changes made.');
 79 | 		});
 80 | 
 81 | 		it('should handle multiple changed files with different scopes', () => {
 82 | 			const message = generator.generateMessage({
 83 | 				type: 'refactor',
 84 | 				description: 'reorganize code structure',
 85 | 				changedFiles: [
 86 | 					'packages/cli/src/index.ts',
 87 | 					'packages/tm-core/src/index.ts'
 88 | 				]
 89 | 			});
 90 | 
 91 | 			// Should use CLI scope (higher priority due to count or priority)
 92 | 			expect(message).toMatch(/refactor\((cli|core)\):/);
 93 | 		});
 94 | 
 95 | 		it('should handle test files and detect test scope', () => {
 96 | 			const message = generator.generateMessage({
 97 | 				type: 'test',
 98 | 				description: 'add integration tests',
 99 | 				changedFiles: ['packages/tm-core/src/workflow/workflow.test.ts']
100 | 			});
101 | 
102 | 			expect(message).toContain('test(test):');
103 | 		});
104 | 
105 | 		it('should handle docs changes', () => {
106 | 			const message = generator.generateMessage({
107 | 				type: 'docs',
108 | 				description: 'update README',
109 | 				changedFiles: ['README.md', 'docs/guide.md']
110 | 			});
111 | 
112 | 			expect(message).toContain('docs(docs):');
113 | 		});
114 | 
115 | 		it('should omit scope if not detected', () => {
116 | 			const message = generator.generateMessage({
117 | 				type: 'chore',
118 | 				description: 'update dependencies',
119 | 				changedFiles: []
120 | 			});
121 | 
122 | 			expect(message).toContain('chore(repo): update dependencies');
123 | 		});
124 | 
125 | 		it('should support manual scope override', () => {
126 | 			const message = generator.generateMessage({
127 | 				type: 'feat',
128 | 				description: 'add feature',
129 | 				changedFiles: ['packages/tm-core/src/index.ts'],
130 | 				scope: 'api'
131 | 			});
132 | 
133 | 			expect(message).toContain('feat(api): add feature');
134 | 		});
135 | 
136 | 		it('should handle breaking changes indicator', () => {
137 | 			const message = generator.generateMessage({
138 | 				type: 'feat',
139 | 				description: 'change API structure',
140 | 				changedFiles: ['packages/tm-core/src/index.ts'],
141 | 				breaking: true
142 | 			});
143 | 
144 | 			expect(message).toContain('feat(core)!: change API structure');
145 | 		});
146 | 
147 | 		it('should format complete message with all metadata', () => {
148 | 			const message = generator.generateMessage({
149 | 				type: 'feat',
150 | 				description: 'implement TDD workflow',
151 | 				changedFiles: ['packages/tm-core/src/workflow/orchestrator.ts'],
152 | 				body: 'Implemented complete RED-GREEN-COMMIT cycle with state persistence.',
153 | 				taskId: '4.1',
154 | 				phase: 'GREEN',
155 | 				testsPassing: 74,
156 | 				testsFailing: 0
157 | 			});
158 | 
159 | 			expect(message).toContain('feat(core): implement TDD workflow');
160 | 			expect(message).toContain('Implemented complete RED-GREEN-COMMIT cycle');
161 | 			expect(message).toContain('Task: 4.1');
162 | 			expect(message).toContain('Phase: GREEN');
163 | 			expect(message).toContain('Tests: 74 passing');
164 | 		});
165 | 	});
166 | 
167 | 	describe('validateConventionalCommit', () => {
168 | 		it('should validate correct conventional commit format', () => {
169 | 			const message = 'feat(core): add feature\n\nDetails here.';
170 | 			const result = generator.validateConventionalCommit(message);
171 | 
172 | 			expect(result.isValid).toBe(true);
173 | 			expect(result.errors).toEqual([]);
174 | 		});
175 | 
176 | 		it('should detect missing type', () => {
177 | 			const message = 'add feature';
178 | 			const result = generator.validateConventionalCommit(message);
179 | 
180 | 			expect(result.isValid).toBe(false);
181 | 			expect(result.errors.length).toBeGreaterThan(0);
182 | 			expect(result.errors[0]).toContain('Invalid conventional commit format');
183 | 		});
184 | 
185 | 		it('should detect invalid type', () => {
186 | 			const message = 'invalid(core): add feature';
187 | 			const result = generator.validateConventionalCommit(message);
188 | 
189 | 			expect(result.isValid).toBe(false);
190 | 			expect(result.errors.length).toBeGreaterThan(0);
191 | 		});
192 | 
193 | 		it('should detect missing description', () => {
194 | 			const message = 'feat(core):';
195 | 			const result = generator.validateConventionalCommit(message);
196 | 
197 | 			expect(result.isValid).toBe(false);
198 | 			expect(result.errors.length).toBeGreaterThan(0);
199 | 			expect(result.errors[0]).toContain('Invalid conventional commit format');
200 | 		});
201 | 
202 | 		it('should accept valid types', () => {
203 | 			const validTypes = [
204 | 				'feat',
205 | 				'fix',
206 | 				'docs',
207 | 				'style',
208 | 				'refactor',
209 | 				'test',
210 | 				'chore'
211 | 			];
212 | 
213 | 			for (const type of validTypes) {
214 | 				const message = `${type}(core): do something`;
215 | 				const result = generator.validateConventionalCommit(message);
216 | 
217 | 				expect(result.isValid).toBe(true);
218 | 			}
219 | 		});
220 | 
221 | 		it('should accept breaking change indicator', () => {
222 | 			const message = 'feat(core)!: breaking change';
223 | 			const result = generator.validateConventionalCommit(message);
224 | 
225 | 			expect(result.isValid).toBe(true);
226 | 		});
227 | 
228 | 		it('should accept message without scope', () => {
229 | 			const message = 'fix: resolve issue';
230 | 			const result = generator.validateConventionalCommit(message);
231 | 
232 | 			expect(result.isValid).toBe(true);
233 | 		});
234 | 	});
235 | 
236 | 	describe('parseCommitMessage', () => {
237 | 		it('should parse conventional commit message', () => {
238 | 			const message = 'feat(core): add feature\n\nDetailed explanation.';
239 | 			const parsed = generator.parseCommitMessage(message);
240 | 
241 | 			expect(parsed.type).toBe('feat');
242 | 			expect(parsed.scope).toBe('core');
243 | 			expect(parsed.description).toBe('add feature');
244 | 			expect(parsed.body).toContain('Detailed explanation.');
245 | 			expect(parsed.breaking).toBe(false);
246 | 		});
247 | 
248 | 		it('should parse breaking change indicator', () => {
249 | 			const message = 'feat(core)!: breaking change';
250 | 			const parsed = generator.parseCommitMessage(message);
251 | 
252 | 			expect(parsed.type).toBe('feat');
253 | 			expect(parsed.breaking).toBe(true);
254 | 		});
255 | 
256 | 		it('should parse message without scope', () => {
257 | 			const message = 'fix: resolve issue';
258 | 			const parsed = generator.parseCommitMessage(message);
259 | 
260 | 			expect(parsed.type).toBe('fix');
261 | 			expect(parsed.scope).toBeUndefined();
262 | 			expect(parsed.description).toBe('resolve issue');
263 | 		});
264 | 
265 | 		it('should handle multiline body', () => {
266 | 			const message = 'feat: add feature\n\nLine 1\nLine 2\nLine 3';
267 | 			const parsed = generator.parseCommitMessage(message);
268 | 
269 | 			expect(parsed.body).toContain('Line 1');
270 | 			expect(parsed.body).toContain('Line 2');
271 | 			expect(parsed.body).toContain('Line 3');
272 | 		});
273 | 	});
274 | 
275 | 	describe('edge cases', () => {
276 | 		it('should handle empty changed files list', () => {
277 | 			const message = generator.generateMessage({
278 | 				type: 'chore',
279 | 				description: 'general maintenance',
280 | 				changedFiles: []
281 | 			});
282 | 
283 | 			expect(message).toContain('chore(repo):');
284 | 		});
285 | 
286 | 		it('should handle very long description', () => {
287 | 			const longDesc = 'a'.repeat(200);
288 | 			const message = generator.generateMessage({
289 | 				type: 'feat',
290 | 				description: longDesc,
291 | 				changedFiles: ['packages/tm-core/src/index.ts']
292 | 			});
293 | 
294 | 			expect(message).toContain(longDesc);
295 | 		});
296 | 
297 | 		it('should handle special characters in description', () => {
298 | 			const message = generator.generateMessage({
299 | 				type: 'fix',
300 | 				description: 'resolve issue with $special @characters #123',
301 | 				changedFiles: ['packages/tm-core/src/index.ts']
302 | 			});
303 | 
304 | 			expect(message).toContain('$special @characters #123');
305 | 		});
306 | 
307 | 		it('should handle zero passing tests', () => {
308 | 			const message = generator.generateMessage({
309 | 				type: 'test',
310 | 				description: 'add failing test',
311 | 				changedFiles: ['test.ts'],
312 | 				testsPassing: 0,
313 | 				testsFailing: 1
314 | 			});
315 | 
316 | 			expect(message).toContain('Tests: 0 passing, 1 failing');
317 | 		});
318 | 	});
319 | });
320 | 
```

--------------------------------------------------------------------------------
/tests/manual/progress/parse-prd-analysis.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * parse-prd-analysis.js
  5 |  *
  6 |  * Detailed timing and accuracy analysis for parse-prd progress reporting.
  7 |  * Tests different task generation complexities using the sample PRD from fixtures.
  8 |  * Validates real-time characteristics and focuses on progress behavior and performance metrics.
  9 |  * Uses tests/fixtures/sample-prd.txt for consistent testing across all scenarios.
 10 |  */
 11 | 
 12 | import fs from 'fs';
 13 | import path from 'path';
 14 | import chalk from 'chalk';
 15 | import { fileURLToPath } from 'url';
 16 | 
 17 | const __filename = fileURLToPath(import.meta.url);
 18 | const __dirname = path.dirname(__filename);
 19 | 
 20 | import parsePRD from '../../../scripts/modules/task-manager/parse-prd/index.js';
 21 | 
 22 | // Use the same project root as the main test file
 23 | const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
 24 | 
 25 | /**
 26 |  * Get the path to the sample PRD file
 27 |  */
 28 | function getSamplePRDPath() {
 29 | 	return path.resolve(PROJECT_ROOT, 'tests', 'fixtures', 'sample-prd.txt');
 30 | }
 31 | 
 32 | /**
 33 |  * Detailed Progress Reporter for timing analysis
 34 |  */
 35 | class DetailedProgressReporter {
 36 | 	constructor() {
 37 | 		this.progressHistory = [];
 38 | 		this.startTime = Date.now();
 39 | 		this.lastProgress = 0;
 40 | 	}
 41 | 
 42 | 	async reportProgress(data) {
 43 | 		const timestamp = Date.now() - this.startTime;
 44 | 		const timeSinceLastProgress =
 45 | 			this.progressHistory.length > 0
 46 | 				? timestamp -
 47 | 					this.progressHistory[this.progressHistory.length - 1].timestamp
 48 | 				: timestamp;
 49 | 
 50 | 		const entry = {
 51 | 			timestamp,
 52 | 			timeSinceLastProgress,
 53 | 			...data
 54 | 		};
 55 | 
 56 | 		this.progressHistory.push(entry);
 57 | 
 58 | 		const percentage = data.total
 59 | 			? Math.round((data.progress / data.total) * 100)
 60 | 			: 0;
 61 | 		console.log(
 62 | 			chalk.blue(`[${timestamp}ms] (+${timeSinceLastProgress}ms)`),
 63 | 			chalk.green(`${percentage}%`),
 64 | 			`(${data.progress}/${data.total})`,
 65 | 			chalk.yellow(data.message)
 66 | 		);
 67 | 	}
 68 | 
 69 | 	getAnalysis() {
 70 | 		if (this.progressHistory.length === 0) return null;
 71 | 
 72 | 		const totalDuration =
 73 | 			this.progressHistory[this.progressHistory.length - 1].timestamp;
 74 | 		const intervals = this.progressHistory
 75 | 			.slice(1)
 76 | 			.map((entry) => entry.timeSinceLastProgress);
 77 | 		const avgInterval =
 78 | 			intervals.length > 0
 79 | 				? intervals.reduce((a, b) => a + b, 0) / intervals.length
 80 | 				: 0;
 81 | 		const minInterval = intervals.length > 0 ? Math.min(...intervals) : 0;
 82 | 		const maxInterval = intervals.length > 0 ? Math.max(...intervals) : 0;
 83 | 
 84 | 		return {
 85 | 			totalReports: this.progressHistory.length,
 86 | 			totalDuration,
 87 | 			avgInterval: Math.round(avgInterval),
 88 | 			minInterval,
 89 | 			maxInterval,
 90 | 			intervals
 91 | 		};
 92 | 	}
 93 | 
 94 | 	printDetailedAnalysis() {
 95 | 		const analysis = this.getAnalysis();
 96 | 		if (!analysis) {
 97 | 			console.log(chalk.red('No progress data to analyze'));
 98 | 			return;
 99 | 		}
100 | 
101 | 		console.log(chalk.cyan('\n=== Detailed Progress Analysis ==='));
102 | 		console.log(`Total Progress Reports: ${analysis.totalReports}`);
103 | 		console.log(`Total Duration: ${analysis.totalDuration}ms`);
104 | 		console.log(`Average Interval: ${analysis.avgInterval}ms`);
105 | 		console.log(`Min Interval: ${analysis.minInterval}ms`);
106 | 		console.log(`Max Interval: ${analysis.maxInterval}ms`);
107 | 
108 | 		console.log(chalk.cyan('\n=== Progress Timeline ==='));
109 | 		this.progressHistory.forEach((entry, index) => {
110 | 			const percentage = entry.total
111 | 				? Math.round((entry.progress / entry.total) * 100)
112 | 				: 0;
113 | 			const intervalText =
114 | 				index > 0 ? ` (+${entry.timeSinceLastProgress}ms)` : '';
115 | 			console.log(
116 | 				`${index + 1}. [${entry.timestamp}ms]${intervalText} ${percentage}% - ${entry.message}`
117 | 			);
118 | 		});
119 | 
120 | 		// Check for real-time characteristics
121 | 		console.log(chalk.cyan('\n=== Real-time Characteristics ==='));
122 | 		const hasRealTimeUpdates = analysis.intervals.some(
123 | 			(interval) => interval < 10000
124 | 		); // Less than 10s
125 | 		const hasConsistentUpdates = analysis.intervals.length > 3;
126 | 		const hasProgressiveUpdates = this.progressHistory.every(
127 | 			(entry, index) =>
128 | 				index === 0 ||
129 | 				entry.progress >= this.progressHistory[index - 1].progress
130 | 		);
131 | 
132 | 		console.log(`✅ Real-time updates: ${hasRealTimeUpdates ? 'YES' : 'NO'}`);
133 | 		console.log(
134 | 			`✅ Consistent updates: ${hasConsistentUpdates ? 'YES' : 'NO'}`
135 | 		);
136 | 		console.log(
137 | 			`✅ Progressive updates: ${hasProgressiveUpdates ? 'YES' : 'NO'}`
138 | 		);
139 | 	}
140 | }
141 | 
142 | /**
143 |  * Get PRD path for complexity testing
144 |  * For complexity testing, we'll use the same sample PRD but request different numbers of tasks
145 |  * This provides more realistic testing since the AI will generate different complexity based on task count
146 |  */
147 | function getPRDPathForComplexity(complexity = 'medium') {
148 | 	// Always use the same sample PRD file - complexity will be controlled by task count
149 | 	return getSamplePRDPath();
150 | }
151 | 
152 | /**
153 |  * Test streaming with different task generation complexities
154 |  * Uses the same sample PRD but requests different numbers of tasks to test complexity scaling
155 |  */
156 | async function testStreamingComplexity() {
157 | 	console.log(
158 | 		chalk.cyan(
159 | 			'🧪 Testing Streaming with Different Task Generation Complexities\n'
160 | 		)
161 | 	);
162 | 
163 | 	const complexities = ['simple', 'medium', 'complex'];
164 | 	const results = [];
165 | 
166 | 	for (const complexity of complexities) {
167 | 		console.log(
168 | 			chalk.yellow(`\n--- Testing ${complexity.toUpperCase()} Complexity ---`)
169 | 		);
170 | 
171 | 		const testPRDPath = getPRDPathForComplexity(complexity);
172 | 		const testTasksPath = path.join(__dirname, `test-tasks-${complexity}.json`);
173 | 
174 | 		// Clean up existing file
175 | 		if (fs.existsSync(testTasksPath)) {
176 | 			fs.unlinkSync(testTasksPath);
177 | 		}
178 | 
179 | 		const progressReporter = new DetailedProgressReporter();
180 | 		const expectedTasks =
181 | 			complexity === 'simple' ? 3 : complexity === 'medium' ? 6 : 10;
182 | 
183 | 		try {
184 | 			const startTime = Date.now();
185 | 
186 | 			await parsePRD(testPRDPath, testTasksPath, expectedTasks, {
187 | 				force: true,
188 | 				append: false,
189 | 				research: false,
190 | 				reportProgress: progressReporter.reportProgress.bind(progressReporter),
191 | 				projectRoot: PROJECT_ROOT
192 | 			});
193 | 
194 | 			const endTime = Date.now();
195 | 			const duration = endTime - startTime;
196 | 
197 | 			console.log(
198 | 				chalk.green(`✅ ${complexity} complexity completed in ${duration}ms`)
199 | 			);
200 | 
201 | 			progressReporter.printDetailedAnalysis();
202 | 
203 | 			results.push({
204 | 				complexity,
205 | 				duration,
206 | 				analysis: progressReporter.getAnalysis()
207 | 			});
208 | 		} catch (error) {
209 | 			console.error(
210 | 				chalk.red(`❌ ${complexity} complexity failed: ${error.message}`)
211 | 			);
212 | 			results.push({
213 | 				complexity,
214 | 				error: error.message
215 | 			});
216 | 		} finally {
217 | 			// Clean up (only the tasks file, not the PRD since we're using the fixture)
218 | 			if (fs.existsSync(testTasksPath)) fs.unlinkSync(testTasksPath);
219 | 		}
220 | 	}
221 | 
222 | 	// Summary
223 | 	console.log(chalk.cyan('\n=== Complexity Test Summary ==='));
224 | 	results.forEach((result) => {
225 | 		if (result.error) {
226 | 			console.log(`${result.complexity}: ❌ FAILED - ${result.error}`);
227 | 		} else {
228 | 			console.log(
229 | 				`${result.complexity}: ✅ ${result.duration}ms (${result.analysis.totalReports} reports)`
230 | 			);
231 | 		}
232 | 	});
233 | 
234 | 	return results;
235 | }
236 | 
237 | /**
238 |  * Test progress accuracy
239 |  */
240 | async function testProgressAccuracy() {
241 | 	console.log(chalk.cyan('🧪 Testing Progress Accuracy\n'));
242 | 
243 | 	const testPRDPath = getSamplePRDPath();
244 | 	const testTasksPath = path.join(__dirname, 'test-accuracy-tasks.json');
245 | 
246 | 	// Clean up existing file
247 | 	if (fs.existsSync(testTasksPath)) {
248 | 		fs.unlinkSync(testTasksPath);
249 | 	}
250 | 
251 | 	const progressReporter = new DetailedProgressReporter();
252 | 
253 | 	try {
254 | 		await parsePRD(testPRDPath, testTasksPath, 8, {
255 | 			force: true,
256 | 			append: false,
257 | 			research: false,
258 | 			reportProgress: progressReporter.reportProgress.bind(progressReporter),
259 | 			projectRoot: PROJECT_ROOT
260 | 		});
261 | 
262 | 		console.log(chalk.green('✅ Progress accuracy test completed'));
263 | 		progressReporter.printDetailedAnalysis();
264 | 
265 | 		// Additional accuracy checks
266 | 		const analysis = progressReporter.getAnalysis();
267 | 		console.log(chalk.cyan('\n=== Accuracy Metrics ==='));
268 | 		console.log(
269 | 			`Progress consistency: ${analysis.intervals.every((i) => i > 0) ? 'PASS' : 'FAIL'}`
270 | 		);
271 | 		console.log(
272 | 			`Reasonable intervals: ${analysis.intervals.every((i) => i < 30000) ? 'PASS' : 'FAIL'}`
273 | 		);
274 | 		console.log(
275 | 			`Expected report count: ${analysis.totalReports >= 8 ? 'PASS' : 'FAIL'}`
276 | 		);
277 | 	} catch (error) {
278 | 		console.error(
279 | 			chalk.red(`❌ Progress accuracy test failed: ${error.message}`)
280 | 		);
281 | 	} finally {
282 | 		// Clean up (only the tasks file, not the PRD since we're using the fixture)
283 | 		if (fs.existsSync(testTasksPath)) fs.unlinkSync(testTasksPath);
284 | 	}
285 | }
286 | 
287 | /**
288 |  * Main test runner
289 |  */
290 | async function main() {
291 | 	const args = process.argv.slice(2);
292 | 	const testType = args[0] || 'accuracy';
293 | 
294 | 	console.log(chalk.bold.cyan('🚀 Task Master Detailed Progress Tests\n'));
295 | 	console.log(chalk.blue(`Test type: ${testType}\n`));
296 | 
297 | 	try {
298 | 		switch (testType.toLowerCase()) {
299 | 			case 'accuracy':
300 | 				await testProgressAccuracy();
301 | 				break;
302 | 
303 | 			case 'complexity':
304 | 				await testStreamingComplexity();
305 | 				break;
306 | 
307 | 			case 'all':
308 | 				console.log(chalk.yellow('Running all detailed tests...\n'));
309 | 				await testProgressAccuracy();
310 | 				console.log('\n' + '='.repeat(60) + '\n');
311 | 				await testStreamingComplexity();
312 | 				break;
313 | 
314 | 			default:
315 | 				console.log(chalk.red(`Unknown test type: ${testType}`));
316 | 				console.log(
317 | 					chalk.yellow('Available options: accuracy, complexity, all')
318 | 				);
319 | 				process.exit(1);
320 | 		}
321 | 
322 | 		console.log(chalk.green('\n🎉 Detailed tests completed successfully!'));
323 | 	} catch (error) {
324 | 		console.error(chalk.red(`\n❌ Test failed: ${error.message}`));
325 | 		console.error(chalk.red(error.stack));
326 | 		process.exit(1);
327 | 	}
328 | }
329 | 
330 | // Run if called directly
331 | if (import.meta.url === `file://${process.argv[1]}`) {
332 | 	// Top-level await is available in ESM; keep compatibility with Node ≥14
333 | 	await main();
334 | }
335 | 
```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/utils/path-normalizer.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it } from 'vitest';
  2 | import {
  3 | 	denormalizeProjectPath,
  4 | 	isValidNormalizedPath,
  5 | 	normalizeProjectPath
  6 | } from './path-normalizer.js';
  7 | 
  8 | describe('Path Normalizer (base64url encoding)', () => {
  9 | 	describe('normalizeProjectPath', () => {
 10 | 		it('should encode Unix paths to base64url', () => {
 11 | 			const input = '/Users/test/projects/myapp';
 12 | 			const normalized = normalizeProjectPath(input);
 13 | 
 14 | 			// Should be valid base64url (only A-Z, a-z, 0-9, -, _)
 15 | 			expect(/^[A-Za-z0-9_-]+$/.test(normalized)).toBe(true);
 16 | 			// Should not contain slashes
 17 | 			expect(normalized).not.toContain('/');
 18 | 			expect(normalized).not.toContain('\\');
 19 | 		});
 20 | 
 21 | 		it('should encode Windows paths to base64url', () => {
 22 | 			const input = 'C:\\Users\\test\\projects\\myapp';
 23 | 			const normalized = normalizeProjectPath(input);
 24 | 
 25 | 			// Should be valid base64url
 26 | 			expect(/^[A-Za-z0-9_-]+$/.test(normalized)).toBe(true);
 27 | 			expect(normalized).not.toContain('/');
 28 | 			expect(normalized).not.toContain('\\');
 29 | 		});
 30 | 
 31 | 		it('should encode paths with hyphens (preserving them for round-trip)', () => {
 32 | 			const input = '/projects/my-app';
 33 | 			const normalized = normalizeProjectPath(input);
 34 | 
 35 | 			// Should be valid base64url
 36 | 			expect(/^[A-Za-z0-9_-]+$/.test(normalized)).toBe(true);
 37 | 			// Hyphens in base64url are from encoding, not original path
 38 | 			expect(isValidNormalizedPath(normalized)).toBe(true);
 39 | 		});
 40 | 
 41 | 		it('should encode paths with special characters', () => {
 42 | 			const input = '/projects/myapp (v2)';
 43 | 			const normalized = normalizeProjectPath(input);
 44 | 
 45 | 			// Should be valid base64url
 46 | 			expect(/^[A-Za-z0-9_-]+$/.test(normalized)).toBe(true);
 47 | 		});
 48 | 
 49 | 		it('should encode relative paths', () => {
 50 | 			const input = './projects/app';
 51 | 			const normalized = normalizeProjectPath(input);
 52 | 
 53 | 			// Should be valid base64url
 54 | 			expect(/^[A-Za-z0-9_-]+$/.test(normalized)).toBe(true);
 55 | 		});
 56 | 
 57 | 		it('should handle empty string', () => {
 58 | 			const input = '';
 59 | 			const expected = '';
 60 | 			expect(normalizeProjectPath(input)).toBe(expected);
 61 | 		});
 62 | 
 63 | 		it('should encode single directory', () => {
 64 | 			const input = 'project';
 65 | 			const normalized = normalizeProjectPath(input);
 66 | 
 67 | 			// Should be valid base64url
 68 | 			expect(/^[A-Za-z0-9_-]+$/.test(normalized)).toBe(true);
 69 | 		});
 70 | 
 71 | 		it('should encode paths with multiple consecutive slashes', () => {
 72 | 			const input = '/Users//test///project';
 73 | 			const normalized = normalizeProjectPath(input);
 74 | 
 75 | 			// Should be valid base64url
 76 | 			expect(/^[A-Za-z0-9_-]+$/.test(normalized)).toBe(true);
 77 | 		});
 78 | 	});
 79 | 
 80 | 	describe('denormalizeProjectPath', () => {
 81 | 		it('should decode base64url back to original path', () => {
 82 | 			const original = '/Users/test/projects/myapp';
 83 | 			const normalized = normalizeProjectPath(original);
 84 | 			const denormalized = denormalizeProjectPath(normalized);
 85 | 
 86 | 			expect(denormalized).toBe(original);
 87 | 		});
 88 | 
 89 | 		it('should decode base64url for Windows paths', () => {
 90 | 			const original = 'C:\\Users\\test\\project';
 91 | 			const normalized = normalizeProjectPath(original);
 92 | 			const denormalized = denormalizeProjectPath(normalized);
 93 | 
 94 | 			expect(denormalized).toBe(original);
 95 | 		});
 96 | 
 97 | 		it('should handle empty string', () => {
 98 | 			const input = '';
 99 | 			const expected = '';
100 | 			expect(denormalizeProjectPath(input)).toBe(expected);
101 | 		});
102 | 
103 | 		it('should preserve hyphens in directory names (no longer a limitation!)', () => {
104 | 			const original = '/projects/my-app';
105 | 			const normalized = normalizeProjectPath(original);
106 | 			const denormalized = denormalizeProjectPath(normalized);
107 | 
108 | 			// With base64url, hyphens are preserved correctly
109 | 			expect(denormalized).toBe(original);
110 | 		});
111 | 
112 | 		it('should handle invalid base64url gracefully', () => {
113 | 			// Invalid base64url - should return the input as fallback
114 | 			const invalid = 'not@valid#base64url';
115 | 			const result = denormalizeProjectPath(invalid);
116 | 
117 | 			// Should return input unchanged for backward compatibility
118 | 			expect(result).toBe(invalid);
119 | 		});
120 | 	});
121 | 
122 | 	describe('isValidNormalizedPath', () => {
123 | 		it('should return true for valid base64url strings', () => {
124 | 			// Valid base64url characters: A-Z, a-z, 0-9, -, _
125 | 			expect(isValidNormalizedPath('VXNlcnMtdGVzdC1wcm9qZWN0')).toBe(true);
126 | 			expect(isValidNormalizedPath('abc123_-ABC')).toBe(true);
127 | 		});
128 | 
129 | 		it('should return true for base64url with hyphens and underscores', () => {
130 | 			expect(isValidNormalizedPath('test-path_encoded')).toBe(true);
131 | 		});
132 | 
133 | 		it('should return false for paths with slashes', () => {
134 | 			expect(isValidNormalizedPath('Users/test/project')).toBe(false);
135 | 		});
136 | 
137 | 		it('should return false for paths with backslashes', () => {
138 | 			expect(isValidNormalizedPath('Users\\test\\project')).toBe(false);
139 | 		});
140 | 
141 | 		it('should return true for empty string', () => {
142 | 			expect(isValidNormalizedPath('')).toBe(true);
143 | 		});
144 | 
145 | 		it('should return false for strings with special characters not in base64url', () => {
146 | 			// Base64url only allows: A-Z, a-z, 0-9, -, _
147 | 			expect(isValidNormalizedPath('my-app (v2)')).toBe(false); // parentheses and spaces not allowed
148 | 			expect(isValidNormalizedPath('test@example')).toBe(false); // @ not allowed
149 | 			expect(isValidNormalizedPath('test+value')).toBe(false); // + not allowed
150 | 		});
151 | 
152 | 		it('should validate normalized paths correctly', () => {
153 | 			const path = '/Users/test/my-app';
154 | 			const normalized = normalizeProjectPath(path);
155 | 			expect(isValidNormalizedPath(normalized)).toBe(true);
156 | 		});
157 | 	});
158 | 
159 | 	describe('Round-trip conversion', () => {
160 | 		it('should perfectly preserve ALL Unix paths (including those with hyphens)', () => {
161 | 			const originalPaths = [
162 | 				'/Users/test/projects/myapp',
163 | 				'/root/deep/nested/path',
164 | 				'./relative/path',
165 | 				'/projects/my-app', // Now works correctly!
166 | 				'/path/with-multiple-hyphens/in-names'
167 | 			];
168 | 
169 | 			for (const original of originalPaths) {
170 | 				const normalized = normalizeProjectPath(original);
171 | 				const denormalized = denormalizeProjectPath(normalized);
172 | 
173 | 				// Perfect round-trip with base64url encoding
174 | 				expect(denormalized).toBe(original);
175 | 			}
176 | 		});
177 | 
178 | 		it('should perfectly preserve Windows paths (including drive letters)', () => {
179 | 			const originalPaths = [
180 | 				'C:\\Users\\test\\project',
181 | 				'D:\\Projects\\my-app',
182 | 				'E:\\path\\with-hyphens\\test'
183 | 			];
184 | 
185 | 			for (const original of originalPaths) {
186 | 				const normalized = normalizeProjectPath(original);
187 | 				const denormalized = denormalizeProjectPath(normalized);
188 | 
189 | 				// Perfect round-trip - drive letters and colons preserved
190 | 				expect(denormalized).toBe(original);
191 | 			}
192 | 		});
193 | 
194 | 		it('should preserve paths with special characters', () => {
195 | 			const originalPaths = [
196 | 				'/projects/my app (v2)',
197 | 				'/path/with spaces/test',
198 | 				'/path/with-dashes-and_underscores',
199 | 				'/path/with.dots.and-dashes'
200 | 			];
201 | 
202 | 			for (const original of originalPaths) {
203 | 				const normalized = normalizeProjectPath(original);
204 | 				const denormalized = denormalizeProjectPath(normalized);
205 | 
206 | 				// Perfect round-trip for all special characters
207 | 				expect(denormalized).toBe(original);
208 | 			}
209 | 		});
210 | 
211 | 		it('should handle mixed slashes and preserve exact path structure', () => {
212 | 			const original = '/Users/test\\mixed/path';
213 | 			const normalized = normalizeProjectPath(original);
214 | 			const denormalized = denormalizeProjectPath(normalized);
215 | 
216 | 			// Exact preservation of mixed slashes
217 | 			expect(denormalized).toBe(original);
218 | 		});
219 | 
220 | 		it('should preserve multiple consecutive slashes', () => {
221 | 			const original = '/Users//test///project';
222 | 			const normalized = normalizeProjectPath(original);
223 | 			const denormalized = denormalizeProjectPath(normalized);
224 | 
225 | 			// Exact preservation of all slashes
226 | 			expect(denormalized).toBe(original);
227 | 		});
228 | 	});
229 | 
230 | 	describe('Cross-platform consistency', () => {
231 | 		it('should produce filesystem-safe normalized output for all platforms', () => {
232 | 			const unixPath = '/Users/test/project';
233 | 			const windowsPath = 'C:\\Users\\test\\project';
234 | 
235 | 			const normalizedUnix = normalizeProjectPath(unixPath);
236 | 			const normalizedWindows = normalizeProjectPath(windowsPath);
237 | 
238 | 			// Both should be valid base64url (no slashes or backslashes)
239 | 			expect(normalizedUnix).not.toContain('/');
240 | 			expect(normalizedUnix).not.toContain('\\');
241 | 			expect(normalizedWindows).not.toContain('/');
242 | 			expect(normalizedWindows).not.toContain('\\');
243 | 
244 | 			// Both should be valid base64url format
245 | 			expect(isValidNormalizedPath(normalizedUnix)).toBe(true);
246 | 			expect(isValidNormalizedPath(normalizedWindows)).toBe(true);
247 | 		});
248 | 
249 | 		it('should produce different normalized outputs for different paths', () => {
250 | 			// Unix and Windows paths are different, so should produce different encoded values
251 | 			const unixPath = '/Users/test/project';
252 | 			const windowsPath = 'C:\\Users\\test\\project';
253 | 
254 | 			const normalizedUnix = normalizeProjectPath(unixPath);
255 | 			const normalizedWindows = normalizeProjectPath(windowsPath);
256 | 
257 | 			// Different inputs should produce different outputs
258 | 			expect(normalizedUnix).not.toBe(normalizedWindows);
259 | 
260 | 			// But both should denormalize back to their originals
261 | 			expect(denormalizeProjectPath(normalizedUnix)).toBe(unixPath);
262 | 			expect(denormalizeProjectPath(normalizedWindows)).toBe(windowsPath);
263 | 		});
264 | 
265 | 		it('should handle Unicode characters in paths', () => {
266 | 			const unicodePaths = [
267 | 				'/Users/测试/project',
268 | 				'/Users/test/プロジェクト',
269 | 				'/Users/тест/project'
270 | 			];
271 | 
272 | 			for (const original of unicodePaths) {
273 | 				const normalized = normalizeProjectPath(original);
274 | 				const denormalized = denormalizeProjectPath(normalized);
275 | 
276 | 				// Perfect round-trip for Unicode
277 | 				expect(denormalized).toBe(original);
278 | 				expect(isValidNormalizedPath(normalized)).toBe(true);
279 | 			}
280 | 		});
281 | 	});
282 | });
283 | 
```
Page 28/69FirstPrevNextLast