Compare commits

..

29 Commits

Author SHA1 Message Date
Ralph Khreish
d31ef7a39c Merge pull request #1015 from eyaltoledano/changeset-release/main
Version Packages
2025-07-20 00:57:39 +03:00
github-actions[bot]
66555099ca Version Packages 2025-07-19 21:56:03 +00:00
Ralph Khreish
1e565eab53 Release 0.21 (#1009)
* fix: prevent CLAUDE.md overwrite by using imports (#949)

* fix: prevent CLAUDE.md overwrite by using imports

- Copy Task Master instructions to .taskmaster/CLAUDE.md
- Add import section to user's CLAUDE.md instead of overwriting
- Preserve existing user content
- Clean removal of Task Master content on uninstall

Closes #929

* chore: add changeset for Claude import fix

* fix: task master (tm) custom slash commands w/ proper syntax (#968)

* feat: add task master (tm) custom slash commands

Add comprehensive task management system integration via custom slash commands.
Includes commands for:
- Project initialization and setup
- Task parsing from PRD documents
- Task creation, update, and removal
- Subtask management
- Dependency tracking and validation
- Complexity analysis and task expansion
- Project status and reporting
- Workflow automation

This provides a complete task management workflow directly within Claude Code.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: add changeset

---------

Co-authored-by: neno-is-ooo <204701868+neno-is-ooo@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>

* chore: create extension scaffolding (#989)

* chore: create extension scaffolding

* chore: fix workspace for changeset

* chore: fix package-lock

* feat(profiles): Add MCP configuration to Claude Code rules (#980)

* add .mcp.json with claude profile

* add changeset

* update changeset

* update test

* fix: show command no longer requires complexity report to exist (#979)

Co-authored-by: Ben Vargas <ben@example.com>

* feat: complete Groq provider integration and add Kimi K2 model (#978)

* feat: complete Groq provider integration and add Kimi K2 model

- Add missing getRequiredApiKeyName() method to GroqProvider class
- Register GroqProvider in ai-services-unified.js PROVIDERS object
- Add Groq API key handling to config-manager.js (isApiKeySet and getMcpApiKeyStatus)
- Add GROQ_API_KEY to env.example with format hint
- Add moonshotai/kimi-k2-instruct model to Groq provider ($1/$3 per 1M tokens, 16k max)
- Fix import sorting for linting compliance
- Add GroqProvider mock to ai-services-unified tests

Fixes missing implementation pieces that prevented Groq provider from working.

* chore: improve changeset

---------

Co-authored-by: Ben Vargas <ben@example.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>

* docs: Auto-update and format models.md

* feat: Add Amp rule profile with AGENT.md and MCP config (#973)

* Amp profile + tests

* generatlize to Agent instead of Claude Code to support any agent

* add changeset

* unnecessary tab formatting

* fix exports

* fix formatting

* feat: Add Zed editor rule profile with agent rules and MCP config (#974)

* zed profile

* add changeset

* update changeset

* fix: Add missing API keys to .env.example and README.md (#972)

* add OLLAMA_API_KEY

* add missing API keys

* add changeset

* update keys and fix OpenAI comment

* chore: create extension scaffolding (#989)

* chore: create extension scaffolding

* chore: fix workspace for changeset

* chore: fix package-lock

* feat(profiles): Add MCP configuration to Claude Code rules (#980)

* add .mcp.json with claude profile

* add changeset

* update changeset

* update test

* fix: show command no longer requires complexity report to exist (#979)

Co-authored-by: Ben Vargas <ben@example.com>

* feat: complete Groq provider integration and add Kimi K2 model (#978)

* feat: complete Groq provider integration and add Kimi K2 model

- Add missing getRequiredApiKeyName() method to GroqProvider class
- Register GroqProvider in ai-services-unified.js PROVIDERS object
- Add Groq API key handling to config-manager.js (isApiKeySet and getMcpApiKeyStatus)
- Add GROQ_API_KEY to env.example with format hint
- Add moonshotai/kimi-k2-instruct model to Groq provider ($1/$3 per 1M tokens, 16k max)
- Fix import sorting for linting compliance
- Add GroqProvider mock to ai-services-unified tests

Fixes missing implementation pieces that prevented Groq provider from working.

* chore: improve changeset

---------

Co-authored-by: Ben Vargas <ben@example.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>

* docs: Auto-update and format models.md

* feat: Add Amp rule profile with AGENT.md and MCP config (#973)

* Amp profile + tests

* generatlize to Agent instead of Claude Code to support any agent

* add changeset

* unnecessary tab formatting

* fix exports

* fix formatting

* feat: Add Zed editor rule profile with agent rules and MCP config (#974)

* zed profile

* add changeset

* update changeset

---------

Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: Ben Vargas <ben@vargas.com>
Co-authored-by: Ben Vargas <ben@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* feat: Add OpenCode rule profile with AGENTS.md and MCP config (#970)

* add opencode to profile lists

* add opencode profile / modify mcp config after add

* add changeset

* not necessary; main config being updated

* add issue link

* add/fix tests

* fix url and docsUrl

* update test for new urls

* fix formatting

* update/fix tests

* chore: add coderabbit configuration (#992)

* chore: add coderabbit configuration

* chore: fix coderabbit config

* chore: improve coderabbit config

* chore: more coderabbit reviews

* chore: remove all defaults

* docs: Update MCP server name for consistency and use 'Add to Cursor' button (#995)

* update MCP server name to task-master-ai for consistency

* add changeset

* update cursor link & switch to https

* switch back to Add to Cursor button (https link)

* update changeset

* update changeset

* update changeset

* update changeset

* use GitHub markdown format

* fix(ai-validation): comprehensive fixes for AI response validation issues (#1000)

* fix(ai-validation): comprehensive fixes for AI response validation issues

  - Fix update command validation when AI omits subtasks/status/dependencies
  - Fix add-task command when AI returns non-string details field
  - Fix update-task command when AI subtasks miss required fields
  - Add preprocessing to ensure proper field types before validation
  - Prevent split() errors on non-string fields
  - Set proper defaults for missing required fields

* chore: run format

* chore: implement coderabbit suggestions

* feat: add kiro profile (#1001)

* feat: add kiro profile

* chore: fix format

* chore: implement requested changes

* chore: fix CI

* refactor: remove unused resource and resource template initialization (#1002)

* refactor: remove unused resource and resource template initialization

* chore: implement requested changes

* fix(core): Implement Boundary-First Tag Resolution (#943)

* refactor(context): Standardize tag and projectRoot handling across all task tools

This commit unifies context management by adopting a boundary-first resolution strategy. All task-scoped tools now resolve `tag` and `projectRoot` at their entry point and forward these values to the underlying direct functions.

This approach centralizes context logic, ensuring consistent behavior and enhanced flexibility in multi-tag environments.

* fix(tag): Clean up tag handling in task functions and sync process

This commit refines the handling of the `tag` parameter across multiple functions, ensuring consistent context management. The `tag` is now passed more efficiently in `listTasksDirect`, `setTaskStatusDirect`, and `syncTasksToReadme`, improving clarity and reducing redundancy. Additionally, a TODO comment has been added in `sync-readme.js` to address future tag support enhancements.

* feat(tag): Implement Boundary-First Tag Resolution for consistent tag handling

This commit introduces Boundary-First Tag Resolution in the task manager, ensuring consistent and deterministic tag handling across CLI and MCP. This change resolves potential race conditions and improves the reliability of tag-specific operations.

Additionally, the `expandTask` function has been updated to use the resolved tag when writing JSON, enhancing data integrity during task updates.

* chore(biome): formatting

* fix(expand-task): Update writeJSON call to use tag instead of resolvedTag

* fix(commands): Enhance complexity report path resolution and task initialization
`resolveComplexityReportPath` function to streamline output path generation based on tag context and user-defined output.
- Improved clarity and maintainability of command handling by centralizing path resolution logic.

* Fix: unknown currentTag

* fix(task-manager): Update generateTaskFiles calls to include tag and projectRoot parameters

This commit modifies the `moveTask` and `updateSubtaskById` functions to pass the `tag` and `projectRoot` parameters to the `generateTaskFiles` function. This ensures that task files are generated with the correct context when requested, enhancing consistency in task management operations.

* fix(commands): Refactor tag handling and complexity report path resolution
This commit updates the `registerCommands` function to utilize `taskMaster.getCurrentTag()` for consistent tag retrieval across command actions. It also enhances the initialization of `TaskMaster` by passing the tag directly, improving clarity and maintainability. The complexity report path resolution is streamlined to ensure correct file naming based on the current tag context.

* fix(task-master): Update complexity report path expectations in tests
This commit modifies the `initTaskMaster` test to expect a valid string for the complexity report path, ensuring it matches the expected file naming convention. This change enhances test reliability by verifying the correct output format when the path is generated.

* fix(set-task-status): Enhance logging and tag resolution in task status updates
This commit improves the logging output in the `registerSetTaskStatusTool` function to include the tag context when setting task statuses. It also updates the tag handling by resolving the tag using the `resolveTag` utility, ensuring that the correct tag is used when updating task statuses. Additionally, the `setTaskStatus` function is modified to remove the tag parameter from the `readJSON` and `writeJSON` calls, streamlining the data handling process.

* fix(commands, expand-task, task-manager): Add complexity report option and enhance path handling
This commit introduces a new `--complexity-report` option in the `registerCommands` function, allowing users to specify a custom path for the complexity report. The `expandTask` function is updated to accept the `complexityReportPath` from the context, ensuring it is utilized correctly during task expansion. Additionally, the `setTaskStatus` function now includes the `tag` parameter in the `readJSON` and `writeJSON` calls, improving task status updates with proper context. The `initTaskMaster` function is also modified to create parent directories for output paths, enhancing file handling robustness.

* fix(expand-task): Add complexityReportPath to context for task expansion tests

This commit updates the test for the `expandTask` function by adding the `complexityReportPath` to the context object. This change ensures that the complexity report path is correctly utilized in the test, aligning with recent enhancements to complexity report handling in the task manager.

* chore: implement suggested changes

* fix(parse-prd): Clarify tag parameter description for task organization
Updated the documentation for the `tag` parameter in the `parse-prd.js` file to provide a clearer context on its purpose for organizing tasks into separate task lists.

* Fix Inconsistent tag resolution pattern.

* fix: Enhance complexity report path handling with tag support

This commit updates various functions to incorporate the `tag` parameter when resolving complexity report paths. The `expandTaskDirect`, `resolveComplexityReportPath`, and related tools now utilize the current tag context, improving consistency in task management. Additionally, the complexity report path is now correctly passed through the context in the `expand-task` and `set-task-status` tools, ensuring accurate report retrieval based on the active tag.

* Updated the JSDoc for the `tag` parameter in the `show-task.js` file.

* Remove redundant comment on tag parameter in readJSON call

* Remove unused import for getTagAwareFilePath

* Add missed complexityReportPath to args for task expansion

* fix(tests): Enhance research tests with tag-aware functionality

This commit updates the `research.test.js` file to improve the testing of the `performResearch` function by incorporating tag-aware functionality. Key changes include mocking the `findProjectRoot` to return a valid path, enhancing the `ContextGatherer` and `FuzzyTaskSearch` mocks, and adding comprehensive tests for tag parameter handling in various scenarios. The tests now cover passing different tag values, ensuring correct behavior when tags are provided, undefined, or null, and validating the integration of tags in task discovery and context gathering processes.

* Remove unused import for

* fix: Refactor complexity report path handling and improve argument destructuring

This commit enhances the `expandTaskDirect` function by improving the destructuring of arguments for better readability. It also updates the `analyze.js` and `analyze-task-complexity.js` files to utilize the new `resolveComplexityReportOutputPath` function, ensuring tag-aware resolution of output paths. Additionally, logging has been added to provide clarity on the report path being used.

* test: Add complexity report tag isolation tests and improve path handling

This commit introduces a new test file for complexity report tag isolation, ensuring that different tags maintain separate complexity reports. It enhances the existing tests in `analyze-task-complexity.test.js` by updating expectations to use `expect.stringContaining` for file paths, improving robustness against path changes. The new tests cover various scenarios, including path resolution and report generation for both master and feature tags, ensuring no cross-tag contamination occurs.

* Update scripts/modules/task-manager/list-tasks.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update scripts/modules/task-manager/list-tasks.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* test(complexity-report): Fix tag slugification in filename expectations

- Update mocks to use slugifyTagForFilePath for cross-platform compatibility
- Replace raw tag values with slugified versions in expected filenames
- Fix test expecting 'feature/user-auth-v2' to expect 'feature-user-auth-v2'
- Align test with actual filename generation logic that sanitizes special chars

---------

Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: Update VS Code profile with MCP config transformation (#971)

* remove dash in server name

* add OLLAMA_API_KEY to VS Code MCP instructions

* transform vscode mcp to correct format

* add changeset

* switch back to task-master-ai

* use task-master-ai

* Batch fixes before release (#1011)

* fix: improve projectRoot

* fix: improve task-master lang command

* feat: add documentation to the readme so more people can access it

* fix: expand command subtask dependency validation

* fix: update command more reliable with perplexity and other models

* chore: fix CI

* chore: implement requested changes

* chore: fix CI

* chore: fix changeset release for extension package (#1012)

* chore: fix changeset release for extension package

* chore: fix CI

* chore: rc version bump

* chore: adjust kimi k2 max tokens (#1014)

* docs: Auto-update and format models.md

---------

Co-authored-by: Ben Vargas <ben@vargas.com>
Co-authored-by: neno-is-ooo <204701868+neno-is-ooo@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joe Danziger <joe@ticc.net>
Co-authored-by: Ben Vargas <ben@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Parthy <52548018+mm-parthy@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-07-20 00:55:29 +03:00
github-actions[bot]
d87a7f1076 docs: Auto-update and format models.md 2025-07-20 00:51:41 +03:00
Ralph Khreish
5b3dd3f29b chore: adjust kimi k2 max tokens (#1014) 2025-07-20 00:51:41 +03:00
github-actions[bot]
b7804302a1 chore: rc version bump 2025-07-20 00:51:41 +03:00
Ralph Khreish
b2841c261f chore: fix changeset release for extension package (#1012)
* chore: fix changeset release for extension package

* chore: fix CI
2025-07-20 00:51:41 +03:00
Ralph Khreish
444aa5ae19 Batch fixes before release (#1011)
* fix: improve projectRoot

* fix: improve task-master lang command

* feat: add documentation to the readme so more people can access it

* fix: expand command subtask dependency validation

* fix: update command more reliable with perplexity and other models

* chore: fix CI

* chore: implement requested changes

* chore: fix CI
2025-07-20 00:51:41 +03:00
Joe Danziger
858d4a1c54 fix: Update VS Code profile with MCP config transformation (#971)
* remove dash in server name

* add OLLAMA_API_KEY to VS Code MCP instructions

* transform vscode mcp to correct format

* add changeset

* switch back to task-master-ai

* use task-master-ai
2025-07-20 00:51:41 +03:00
Parthy
fd005c4c54 fix(core): Implement Boundary-First Tag Resolution (#943)
* refactor(context): Standardize tag and projectRoot handling across all task tools

This commit unifies context management by adopting a boundary-first resolution strategy. All task-scoped tools now resolve `tag` and `projectRoot` at their entry point and forward these values to the underlying direct functions.

This approach centralizes context logic, ensuring consistent behavior and enhanced flexibility in multi-tag environments.

* fix(tag): Clean up tag handling in task functions and sync process

This commit refines the handling of the `tag` parameter across multiple functions, ensuring consistent context management. The `tag` is now passed more efficiently in `listTasksDirect`, `setTaskStatusDirect`, and `syncTasksToReadme`, improving clarity and reducing redundancy. Additionally, a TODO comment has been added in `sync-readme.js` to address future tag support enhancements.

* feat(tag): Implement Boundary-First Tag Resolution for consistent tag handling

This commit introduces Boundary-First Tag Resolution in the task manager, ensuring consistent and deterministic tag handling across CLI and MCP. This change resolves potential race conditions and improves the reliability of tag-specific operations.

Additionally, the `expandTask` function has been updated to use the resolved tag when writing JSON, enhancing data integrity during task updates.

* chore(biome): formatting

* fix(expand-task): Update writeJSON call to use tag instead of resolvedTag

* fix(commands): Enhance complexity report path resolution and task initialization
`resolveComplexityReportPath` function to streamline output path generation based on tag context and user-defined output.
- Improved clarity and maintainability of command handling by centralizing path resolution logic.

* Fix: unknown currentTag

* fix(task-manager): Update generateTaskFiles calls to include tag and projectRoot parameters

This commit modifies the `moveTask` and `updateSubtaskById` functions to pass the `tag` and `projectRoot` parameters to the `generateTaskFiles` function. This ensures that task files are generated with the correct context when requested, enhancing consistency in task management operations.

* fix(commands): Refactor tag handling and complexity report path resolution
This commit updates the `registerCommands` function to utilize `taskMaster.getCurrentTag()` for consistent tag retrieval across command actions. It also enhances the initialization of `TaskMaster` by passing the tag directly, improving clarity and maintainability. The complexity report path resolution is streamlined to ensure correct file naming based on the current tag context.

* fix(task-master): Update complexity report path expectations in tests
This commit modifies the `initTaskMaster` test to expect a valid string for the complexity report path, ensuring it matches the expected file naming convention. This change enhances test reliability by verifying the correct output format when the path is generated.

* fix(set-task-status): Enhance logging and tag resolution in task status updates
This commit improves the logging output in the `registerSetTaskStatusTool` function to include the tag context when setting task statuses. It also updates the tag handling by resolving the tag using the `resolveTag` utility, ensuring that the correct tag is used when updating task statuses. Additionally, the `setTaskStatus` function is modified to remove the tag parameter from the `readJSON` and `writeJSON` calls, streamlining the data handling process.

* fix(commands, expand-task, task-manager): Add complexity report option and enhance path handling
This commit introduces a new `--complexity-report` option in the `registerCommands` function, allowing users to specify a custom path for the complexity report. The `expandTask` function is updated to accept the `complexityReportPath` from the context, ensuring it is utilized correctly during task expansion. Additionally, the `setTaskStatus` function now includes the `tag` parameter in the `readJSON` and `writeJSON` calls, improving task status updates with proper context. The `initTaskMaster` function is also modified to create parent directories for output paths, enhancing file handling robustness.

* fix(expand-task): Add complexityReportPath to context for task expansion tests

This commit updates the test for the `expandTask` function by adding the `complexityReportPath` to the context object. This change ensures that the complexity report path is correctly utilized in the test, aligning with recent enhancements to complexity report handling in the task manager.

* chore: implement suggested changes

* fix(parse-prd): Clarify tag parameter description for task organization
Updated the documentation for the `tag` parameter in the `parse-prd.js` file to provide a clearer context on its purpose for organizing tasks into separate task lists.

* Fix Inconsistent tag resolution pattern.

* fix: Enhance complexity report path handling with tag support

This commit updates various functions to incorporate the `tag` parameter when resolving complexity report paths. The `expandTaskDirect`, `resolveComplexityReportPath`, and related tools now utilize the current tag context, improving consistency in task management. Additionally, the complexity report path is now correctly passed through the context in the `expand-task` and `set-task-status` tools, ensuring accurate report retrieval based on the active tag.

* Updated the JSDoc for the `tag` parameter in the `show-task.js` file.

* Remove redundant comment on tag parameter in readJSON call

* Remove unused import for getTagAwareFilePath

* Add missed complexityReportPath to args for task expansion

* fix(tests): Enhance research tests with tag-aware functionality

This commit updates the `research.test.js` file to improve the testing of the `performResearch` function by incorporating tag-aware functionality. Key changes include mocking the `findProjectRoot` to return a valid path, enhancing the `ContextGatherer` and `FuzzyTaskSearch` mocks, and adding comprehensive tests for tag parameter handling in various scenarios. The tests now cover passing different tag values, ensuring correct behavior when tags are provided, undefined, or null, and validating the integration of tags in task discovery and context gathering processes.

* Remove unused import for

* fix: Refactor complexity report path handling and improve argument destructuring

This commit enhances the `expandTaskDirect` function by improving the destructuring of arguments for better readability. It also updates the `analyze.js` and `analyze-task-complexity.js` files to utilize the new `resolveComplexityReportOutputPath` function, ensuring tag-aware resolution of output paths. Additionally, logging has been added to provide clarity on the report path being used.

* test: Add complexity report tag isolation tests and improve path handling

This commit introduces a new test file for complexity report tag isolation, ensuring that different tags maintain separate complexity reports. It enhances the existing tests in `analyze-task-complexity.test.js` by updating expectations to use `expect.stringContaining` for file paths, improving robustness against path changes. The new tests cover various scenarios, including path resolution and report generation for both master and feature tags, ensuring no cross-tag contamination occurs.

* Update scripts/modules/task-manager/list-tasks.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update scripts/modules/task-manager/list-tasks.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* test(complexity-report): Fix tag slugification in filename expectations

- Update mocks to use slugifyTagForFilePath for cross-platform compatibility
- Replace raw tag values with slugified versions in expected filenames
- Fix test expecting 'feature/user-auth-v2' to expect 'feature-user-auth-v2'
- Align test with actual filename generation logic that sanitizes special chars

---------

Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-07-20 00:51:41 +03:00
Ralph Khreish
0451ebcc32 refactor: remove unused resource and resource template initialization (#1002)
* refactor: remove unused resource and resource template initialization

* chore: implement requested changes
2025-07-20 00:51:41 +03:00
Ralph Khreish
9c58a92243 feat: add kiro profile (#1001)
* feat: add kiro profile

* chore: fix format

* chore: implement requested changes

* chore: fix CI
2025-07-20 00:51:41 +03:00
Ralph Khreish
f772a96d00 fix(ai-validation): comprehensive fixes for AI response validation issues (#1000)
* fix(ai-validation): comprehensive fixes for AI response validation issues

  - Fix update command validation when AI omits subtasks/status/dependencies
  - Fix add-task command when AI returns non-string details field
  - Fix update-task command when AI subtasks miss required fields
  - Add preprocessing to ensure proper field types before validation
  - Prevent split() errors on non-string fields
  - Set proper defaults for missing required fields

* chore: run format

* chore: implement coderabbit suggestions
2025-07-20 00:51:41 +03:00
Joe Danziger
0886c83d0c docs: Update MCP server name for consistency and use 'Add to Cursor' button (#995)
* update MCP server name to task-master-ai for consistency

* add changeset

* update cursor link & switch to https

* switch back to Add to Cursor button (https link)

* update changeset

* update changeset

* update changeset

* update changeset

* use GitHub markdown format
2025-07-20 00:51:41 +03:00
Ralph Khreish
806ec99939 chore: add coderabbit configuration (#992)
* chore: add coderabbit configuration

* chore: fix coderabbit config

* chore: improve coderabbit config

* chore: more coderabbit reviews

* chore: remove all defaults
2025-07-20 00:51:41 +03:00
Joe Danziger
36c4a7a869 feat: Add OpenCode rule profile with AGENTS.md and MCP config (#970)
* add opencode to profile lists

* add opencode profile / modify mcp config after add

* add changeset

* not necessary; main config being updated

* add issue link

* add/fix tests

* fix url and docsUrl

* update test for new urls

* fix formatting

* update/fix tests
2025-07-20 00:51:41 +03:00
Joe Danziger
88c434a939 fix: Add missing API keys to .env.example and README.md (#972)
* add OLLAMA_API_KEY

* add missing API keys

* add changeset

* update keys and fix OpenAI comment

* chore: create extension scaffolding (#989)

* chore: create extension scaffolding

* chore: fix workspace for changeset

* chore: fix package-lock

* feat(profiles): Add MCP configuration to Claude Code rules (#980)

* add .mcp.json with claude profile

* add changeset

* update changeset

* update test

* fix: show command no longer requires complexity report to exist (#979)

Co-authored-by: Ben Vargas <ben@example.com>

* feat: complete Groq provider integration and add Kimi K2 model (#978)

* feat: complete Groq provider integration and add Kimi K2 model

- Add missing getRequiredApiKeyName() method to GroqProvider class
- Register GroqProvider in ai-services-unified.js PROVIDERS object
- Add Groq API key handling to config-manager.js (isApiKeySet and getMcpApiKeyStatus)
- Add GROQ_API_KEY to env.example with format hint
- Add moonshotai/kimi-k2-instruct model to Groq provider ($1/$3 per 1M tokens, 16k max)
- Fix import sorting for linting compliance
- Add GroqProvider mock to ai-services-unified tests

Fixes missing implementation pieces that prevented Groq provider from working.

* chore: improve changeset

---------

Co-authored-by: Ben Vargas <ben@example.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>

* docs: Auto-update and format models.md

* feat: Add Amp rule profile with AGENT.md and MCP config (#973)

* Amp profile + tests

* generatlize to Agent instead of Claude Code to support any agent

* add changeset

* unnecessary tab formatting

* fix exports

* fix formatting

* feat: Add Zed editor rule profile with agent rules and MCP config (#974)

* zed profile

* add changeset

* update changeset

---------

Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: Ben Vargas <ben@vargas.com>
Co-authored-by: Ben Vargas <ben@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-20 00:51:41 +03:00
Joe Danziger
b0e09c76ed feat: Add Zed editor rule profile with agent rules and MCP config (#974)
* zed profile

* add changeset

* update changeset
2025-07-20 00:51:41 +03:00
Joe Danziger
6c5e0f97f8 feat: Add Amp rule profile with AGENT.md and MCP config (#973)
* Amp profile + tests

* generatlize to Agent instead of Claude Code to support any agent

* add changeset

* unnecessary tab formatting

* fix exports

* fix formatting
2025-07-20 00:51:41 +03:00
github-actions[bot]
8774e7d5ae docs: Auto-update and format models.md 2025-07-20 00:51:41 +03:00
Ben Vargas
58a301c380 feat: complete Groq provider integration and add Kimi K2 model (#978)
* feat: complete Groq provider integration and add Kimi K2 model

- Add missing getRequiredApiKeyName() method to GroqProvider class
- Register GroqProvider in ai-services-unified.js PROVIDERS object
- Add Groq API key handling to config-manager.js (isApiKeySet and getMcpApiKeyStatus)
- Add GROQ_API_KEY to env.example with format hint
- Add moonshotai/kimi-k2-instruct model to Groq provider ($1/$3 per 1M tokens, 16k max)
- Fix import sorting for linting compliance
- Add GroqProvider mock to ai-services-unified tests

Fixes missing implementation pieces that prevented Groq provider from working.

* chore: improve changeset

---------

Co-authored-by: Ben Vargas <ben@example.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
2025-07-20 00:51:41 +03:00
Ben Vargas
624922ca59 fix: show command no longer requires complexity report to exist (#979)
Co-authored-by: Ben Vargas <ben@example.com>
2025-07-20 00:51:41 +03:00
Joe Danziger
0a70ab6179 feat(profiles): Add MCP configuration to Claude Code rules (#980)
* add .mcp.json with claude profile

* add changeset

* update changeset

* update test
2025-07-20 00:51:41 +03:00
Ralph Khreish
901eec1058 chore: create extension scaffolding (#989)
* chore: create extension scaffolding

* chore: fix workspace for changeset

* chore: fix package-lock
2025-07-20 00:51:41 +03:00
Ralph Khreish
4629128943 fix: task master (tm) custom slash commands w/ proper syntax (#968)
* feat: add task master (tm) custom slash commands

Add comprehensive task management system integration via custom slash commands.
Includes commands for:
- Project initialization and setup
- Task parsing from PRD documents
- Task creation, update, and removal
- Subtask management
- Dependency tracking and validation
- Complexity analysis and task expansion
- Project status and reporting
- Workflow automation

This provides a complete task management workflow directly within Claude Code.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: add changeset

---------

Co-authored-by: neno-is-ooo <204701868+neno-is-ooo@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-20 00:51:41 +03:00
Ben Vargas
6d69d02fe0 fix: prevent CLAUDE.md overwrite by using imports (#949)
* fix: prevent CLAUDE.md overwrite by using imports

- Copy Task Master instructions to .taskmaster/CLAUDE.md
- Add import section to user's CLAUDE.md instead of overwriting
- Preserve existing user content
- Clean removal of Task Master content on uninstall

Closes #929

* chore: add changeset for Claude import fix
2025-07-20 00:51:41 +03:00
Ralph Khreish
458496e3b6 Merge pull request #961 from eyaltoledano/changeset-release/main 2025-07-12 22:49:56 +03:00
github-actions[bot]
fb92693d81 Version Packages 2025-07-12 19:31:08 +00:00
Ralph Khreish
f6ba4a36ee Merge pull request #958 from eyaltoledano/next 2025-07-12 22:30:36 +03:00
123 changed files with 4745 additions and 753 deletions

View File

@@ -1,12 +0,0 @@
---
"task-master-ai": patch
---
Prevent CLAUDE.md overwrite by using Claude Code's import feature
- Task Master now creates its instructions in `.taskmaster/CLAUDE.md` instead of overwriting the user's `CLAUDE.md`
- Adds an import section to the user's CLAUDE.md that references the Task Master instructions
- Preserves existing user content in CLAUDE.md files
- Provides clean uninstall that only removes Task Master's additions
**Breaking Change**: Task Master instructions for Claude Code are now stored in `.taskmaster/CLAUDE.md` and imported into the main CLAUDE.md file. Users who previously had Task Master content directly in their CLAUDE.md will need to run `task-master rules remove claude` followed by `task-master rules add claude` to migrate to the new structure.

View File

@@ -1,7 +0,0 @@
---
"task-master-ai": patch
---
Fix: show command no longer requires complexity report file to exist
The `tm show` command was incorrectly requiring the complexity report file to exist even when not needed. Now it only validates the complexity report path when a custom report file is explicitly provided via the -r/--report option.

View File

@@ -1,10 +0,0 @@
---
"task-master-ai": minor
---
Complete Groq provider integration and add MoonshotAI Kimi K2 model support
- Fixed Groq provider registration
- Added Groq API key validation
- Added GROQ_API_KEY to .env.example
- Added moonshotai/kimi-k2-instruct model with $1/$3 per 1M token pricing and 16k max output

View File

@@ -1,7 +0,0 @@
---
"task-master-ai": minor
---
feat: Add Zed editor rule profile with agent rules and MCP config
- Resolves #637

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": minor
---
Add Amp rule profile with AGENT.md and MCP config

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Add MCP configuration support to Claude Code rules

View File

@@ -1,7 +0,0 @@
---
"task-master-ai": patch
---
Fixed the comprehensive taskmaster system integration via custom slash commands with proper syntax
- Provide claude clode with a complete set of of commands that can trigger task master events directly within Claude Code

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Correct MCP server name and use 'Add to Cursor' button with updated placeholder keys.

View File

@@ -1,7 +0,0 @@
---
"task-master-ai": minor
---
Add OpenCode profile with AGENTS.md and MCP config
- Resolves #965

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Add missing API keys to .env.example and README.md

View File

@@ -1,5 +1,139 @@
# task-master-ai # task-master-ai
## 0.21.0
### Minor Changes
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`9c58a92`](https://github.com/eyaltoledano/claude-task-master/commit/9c58a922436c0c5e7ff1b20ed2edbc269990c772) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add Kiro editor rule profile support
- Add support for Kiro IDE with custom rule files and MCP configuration
- Generate rule files in `.kiro/steering/` directory with markdown format
- Include MCP server configuration with enhanced file inclusion patterns
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`444aa5a`](https://github.com/eyaltoledano/claude-task-master/commit/444aa5ae1943ba72d012b3f01b1cc9362a328248) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Created a comprehensive documentation site for Task Master AI. Visit https://docs.task-master.dev to explore guides, API references, and examples.
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`58a301c`](https://github.com/eyaltoledano/claude-task-master/commit/58a301c380d18a9d9509137f3e989d24200a5faa) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Complete Groq provider integration and add MoonshotAI Kimi K2 model support
- Fixed Groq provider registration
- Added Groq API key validation
- Added GROQ_API_KEY to .env.example
- Added moonshotai/kimi-k2-instruct model with $1/$3 per 1M token pricing and 16k max output
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`b0e09c7`](https://github.com/eyaltoledano/claude-task-master/commit/b0e09c76ed73b00434ac95606679f570f1015a3d) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - feat: Add Zed editor rule profile with agent rules and MCP config
- Resolves #637
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`6c5e0f9`](https://github.com/eyaltoledano/claude-task-master/commit/6c5e0f97f8403c4da85c1abba31cb8b1789511a7) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add Amp rule profile with AGENT.md and MCP config
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`444aa5a`](https://github.com/eyaltoledano/claude-task-master/commit/444aa5ae1943ba72d012b3f01b1cc9362a328248) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve project root detection
- No longer creates an infinite loop when unable to detect your code workspace
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`36c4a7a`](https://github.com/eyaltoledano/claude-task-master/commit/36c4a7a86924c927ad7f86a4f891f66ad55eb4d2) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add OpenCode profile with AGENTS.md and MCP config
- Resolves #965
### Patch Changes
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`444aa5a`](https://github.com/eyaltoledano/claude-task-master/commit/444aa5ae1943ba72d012b3f01b1cc9362a328248) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Make `task-master update` more reliable with AI responses
The `update` command now handles AI responses more robustly. If the AI forgets to include certain task fields, the command will automatically fill in the missing data from your original tasks instead of failing. This means smoother bulk task updates without losing important information like IDs, dependencies, or completed subtasks.
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`444aa5a`](https://github.com/eyaltoledano/claude-task-master/commit/444aa5ae1943ba72d012b3f01b1cc9362a328248) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix subtask dependency validation when expanding tasks
When using `task-master expand` to break down tasks into subtasks, dependencies between subtasks are now properly validated. Previously, subtasks with dependencies would fail validation. Now subtasks can correctly depend on their siblings within the same parent task.
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`6d69d02`](https://github.com/eyaltoledano/claude-task-master/commit/6d69d02fe03edcc785380415995d5cfcdd97acbb) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Prevent CLAUDE.md overwrite by using Claude Code's import feature
- Task Master now creates its instructions in `.taskmaster/CLAUDE.md` instead of overwriting the user's `CLAUDE.md`
- Adds an import section to the user's CLAUDE.md that references the Task Master instructions
- Preserves existing user content in CLAUDE.md files
- Provides clean uninstall that only removes Task Master's additions
**Breaking Change**: Task Master instructions for Claude Code are now stored in `.taskmaster/CLAUDE.md` and imported into the main CLAUDE.md file. Users who previously had Task Master content directly in their CLAUDE.md will need to run `task-master rules remove claude` followed by `task-master rules add claude` to migrate to the new structure.
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`fd005c4`](https://github.com/eyaltoledano/claude-task-master/commit/fd005c4c5481ffac58b11f01a448fa5b29056b8d) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Implement Boundary-First Tag Resolution to ensure consistent and deterministic tag handling across CLI and MCP, resolving potential race conditions.
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`444aa5a`](https://github.com/eyaltoledano/claude-task-master/commit/444aa5ae1943ba72d012b3f01b1cc9362a328248) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix `task-master lang --setup` breaking when no language is defined, now defaults to English
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`624922c`](https://github.com/eyaltoledano/claude-task-master/commit/624922ca598c4ce8afe9a5646ebb375d4616db63) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix: show command no longer requires complexity report file to exist
The `tm show` command was incorrectly requiring the complexity report file to exist even when not needed. Now it only validates the complexity report path when a custom report file is explicitly provided via the -r/--report option.
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`858d4a1`](https://github.com/eyaltoledano/claude-task-master/commit/858d4a1c5486d20e7e3a8e37e3329d7fb8200310) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Update VS Code profile with MCP config transformation
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`0451ebc`](https://github.com/eyaltoledano/claude-task-master/commit/0451ebcc32cd7e9d395b015aaa8602c4734157e1) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP server error when retrieving tools and resources
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`0a70ab6`](https://github.com/eyaltoledano/claude-task-master/commit/0a70ab6179cb2b5b4b2d9dc256a7a3b69a0e5dd6) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add MCP configuration support to Claude Code rules
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`4629128`](https://github.com/eyaltoledano/claude-task-master/commit/4629128943f6283385f4762c09cf2752f855cc33) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fixed the comprehensive taskmaster system integration via custom slash commands with proper syntax
- Provide claude clode with a complete set of of commands that can trigger task master events directly within Claude Code
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`0886c83`](https://github.com/eyaltoledano/claude-task-master/commit/0886c83d0c678417c0313256a6dd96f7ee2c9ac6) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Correct MCP server name and use 'Add to Cursor' button with updated placeholder keys.
- [#1009](https://github.com/eyaltoledano/claude-task-master/pull/1009) [`88c434a`](https://github.com/eyaltoledano/claude-task-master/commit/88c434a9393e429d9277f59b3e20f1005076bbe0) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add missing API keys to .env.example and README.md
## 0.21.0-rc.0
### Minor Changes
- [#1001](https://github.com/eyaltoledano/claude-task-master/pull/1001) [`75a36ea`](https://github.com/eyaltoledano/claude-task-master/commit/75a36ea99a1c738a555bdd4fe7c763d0c5925e37) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add Kiro editor rule profile support
- Add support for Kiro IDE with custom rule files and MCP configuration
- Generate rule files in `.kiro/steering/` directory with markdown format
- Include MCP server configuration with enhanced file inclusion patterns
- [#1011](https://github.com/eyaltoledano/claude-task-master/pull/1011) [`3eb050a`](https://github.com/eyaltoledano/claude-task-master/commit/3eb050aaddb90fca1a04517e2ee24f73934323be) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Created a comprehensive documentation site for Task Master AI. Visit https://docs.task-master.dev to explore guides, API references, and examples.
- [#978](https://github.com/eyaltoledano/claude-task-master/pull/978) [`fedfd6a`](https://github.com/eyaltoledano/claude-task-master/commit/fedfd6a0f41a78094f7ee7f69be689b699475a79) Thanks [@ben-vargas](https://github.com/ben-vargas)! - Complete Groq provider integration and add MoonshotAI Kimi K2 model support
- Fixed Groq provider registration
- Added Groq API key validation
- Added GROQ_API_KEY to .env.example
- Added moonshotai/kimi-k2-instruct model with $1/$3 per 1M token pricing and 16k max output
- [#974](https://github.com/eyaltoledano/claude-task-master/pull/974) [`5b0eda0`](https://github.com/eyaltoledano/claude-task-master/commit/5b0eda07f20a365aa2ec1736eed102bca81763a9) Thanks [@joedanz](https://github.com/joedanz)! - feat: Add Zed editor rule profile with agent rules and MCP config
- Resolves #637
- [#973](https://github.com/eyaltoledano/claude-task-master/pull/973) [`6d05e86`](https://github.com/eyaltoledano/claude-task-master/commit/6d05e8622c1d761acef10414940ff9a766b3b57d) Thanks [@joedanz](https://github.com/joedanz)! - Add Amp rule profile with AGENT.md and MCP config
- [#1011](https://github.com/eyaltoledano/claude-task-master/pull/1011) [`3eb050a`](https://github.com/eyaltoledano/claude-task-master/commit/3eb050aaddb90fca1a04517e2ee24f73934323be) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve project root detection
- No longer creates an infinite loop when unable to detect your code workspace
- [#970](https://github.com/eyaltoledano/claude-task-master/pull/970) [`b87499b`](https://github.com/eyaltoledano/claude-task-master/commit/b87499b56e626001371a87ed56ffc72675d829f3) Thanks [@joedanz](https://github.com/joedanz)! - Add OpenCode profile with AGENTS.md and MCP config
- Resolves #965
### Patch Changes
- [#1011](https://github.com/eyaltoledano/claude-task-master/pull/1011) [`3eb050a`](https://github.com/eyaltoledano/claude-task-master/commit/3eb050aaddb90fca1a04517e2ee24f73934323be) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Make `task-master update` more reliable with AI responses
The `update` command now handles AI responses more robustly. If the AI forgets to include certain task fields, the command will automatically fill in the missing data from your original tasks instead of failing. This means smoother bulk task updates without losing important information like IDs, dependencies, or completed subtasks.
- [#1011](https://github.com/eyaltoledano/claude-task-master/pull/1011) [`3eb050a`](https://github.com/eyaltoledano/claude-task-master/commit/3eb050aaddb90fca1a04517e2ee24f73934323be) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix subtask dependency validation when expanding tasks
When using `task-master expand` to break down tasks into subtasks, dependencies between subtasks are now properly validated. Previously, subtasks with dependencies would fail validation. Now subtasks can correctly depend on their siblings within the same parent task.
- [#949](https://github.com/eyaltoledano/claude-task-master/pull/949) [`f662654`](https://github.com/eyaltoledano/claude-task-master/commit/f662654afb8e7a230448655265d6f41adf6df62c) Thanks [@ben-vargas](https://github.com/ben-vargas)! - Prevent CLAUDE.md overwrite by using Claude Code's import feature
- Task Master now creates its instructions in `.taskmaster/CLAUDE.md` instead of overwriting the user's `CLAUDE.md`
- Adds an import section to the user's CLAUDE.md that references the Task Master instructions
- Preserves existing user content in CLAUDE.md files
- Provides clean uninstall that only removes Task Master's additions
**Breaking Change**: Task Master instructions for Claude Code are now stored in `.taskmaster/CLAUDE.md` and imported into the main CLAUDE.md file. Users who previously had Task Master content directly in their CLAUDE.md will need to run `task-master rules remove claude` followed by `task-master rules add claude` to migrate to the new structure.
- [#943](https://github.com/eyaltoledano/claude-task-master/pull/943) [`f98df5c`](https://github.com/eyaltoledano/claude-task-master/commit/f98df5c0fdb253b2b55d4278c11d626529c4dba4) Thanks [@mm-parthy](https://github.com/mm-parthy)! - Implement Boundary-First Tag Resolution to ensure consistent and deterministic tag handling across CLI and MCP, resolving potential race conditions.
- [#1011](https://github.com/eyaltoledano/claude-task-master/pull/1011) [`3eb050a`](https://github.com/eyaltoledano/claude-task-master/commit/3eb050aaddb90fca1a04517e2ee24f73934323be) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix `task-master lang --setup` breaking when no language is defined, now defaults to English
- [#979](https://github.com/eyaltoledano/claude-task-master/pull/979) [`ab2e946`](https://github.com/eyaltoledano/claude-task-master/commit/ab2e94608749a2f148118daa0443bd32bca6e7a1) Thanks [@ben-vargas](https://github.com/ben-vargas)! - Fix: show command no longer requires complexity report file to exist
The `tm show` command was incorrectly requiring the complexity report file to exist even when not needed. Now it only validates the complexity report path when a custom report file is explicitly provided via the -r/--report option.
- [#971](https://github.com/eyaltoledano/claude-task-master/pull/971) [`5544222`](https://github.com/eyaltoledano/claude-task-master/commit/55442226d0aa4870470d2a9897f5538d6a0e329e) Thanks [@joedanz](https://github.com/joedanz)! - Update VS Code profile with MCP config transformation
- [#1002](https://github.com/eyaltoledano/claude-task-master/pull/1002) [`6d0654c`](https://github.com/eyaltoledano/claude-task-master/commit/6d0654cb4191cee794e1c8cbf2b92dc33d4fb410) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP server error when retrieving tools and resources
- [#980](https://github.com/eyaltoledano/claude-task-master/pull/980) [`cc4fe20`](https://github.com/eyaltoledano/claude-task-master/commit/cc4fe205fb468e7144c650acc92486df30731560) Thanks [@joedanz](https://github.com/joedanz)! - Add MCP configuration support to Claude Code rules
- [#968](https://github.com/eyaltoledano/claude-task-master/pull/968) [`7b4803a`](https://github.com/eyaltoledano/claude-task-master/commit/7b4803a479105691c7ed032fd878fe3d48d82724) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fixed the comprehensive taskmaster system integration via custom slash commands with proper syntax
- Provide claude clode with a complete set of of commands that can trigger task master events directly within Claude Code
- [#995](https://github.com/eyaltoledano/claude-task-master/pull/995) [`b78de8d`](https://github.com/eyaltoledano/claude-task-master/commit/b78de8dbb4d6dc93b48e2f81c32960ef069736ed) Thanks [@joedanz](https://github.com/joedanz)! - Correct MCP server name and use 'Add to Cursor' button with updated placeholder keys.
- [#972](https://github.com/eyaltoledano/claude-task-master/pull/972) [`1c7badf`](https://github.com/eyaltoledano/claude-task-master/commit/1c7badff2f5c548bfa90a3b2634e63087a382a84) Thanks [@joedanz](https://github.com/joedanz)! - Add missing API keys to .env.example and README.md
## 0.20.0 ## 0.20.0
### Minor Changes ### Minor Changes

View File

@@ -14,7 +14,13 @@ A task management system for AI-driven development with Claude, designed to work
## Documentation ## Documentation
For more detailed information, check out the documentation in the `docs` directory: 📚 **[View Full Documentation](https://docs.task-master.dev)**
For detailed guides, API references, and comprehensive examples, visit our documentation site.
### Quick Reference
The following documentation is also available in the `docs` directory:
- [Configuration Guide](docs/configuration.md) - Set up environment variables and customize Task Master - [Configuration Guide](docs/configuration.md) - Set up environment variables and customize Task Master
- [Tutorial](docs/tutorial.md) - Step-by-step guide to getting started with Task Master - [Tutorial](docs/tutorial.md) - Step-by-step guide to getting started with Task Master

View File

@@ -1,5 +1,6 @@
{ {
"name": "extension", "name": "extension",
"private": true,
"version": "0.20.0", "version": "0.20.0",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@@ -1,4 +1,4 @@
# Available Models as of July 16, 2025 # Available Models as of July 19, 2025
## Main Models ## Main Models

View File

@@ -16,12 +16,14 @@ import {
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string|number} args.id - Task ID to add dependency to * @param {string|number} args.id - Task ID to add dependency to
* @param {string|number} args.dependsOn - Task ID that will become a dependency * @param {string|number} args.dependsOn - Task ID that will become a dependency
* @param {string} args.tag - Tag for the task (optional)
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<Object>} - Result object with success status and data/error information * @returns {Promise<Object>} - Result object with success status and data/error information
*/ */
export async function addDependencyDirect(args, log) { export async function addDependencyDirect(args, log) {
// Destructure expected args // Destructure expected args
const { tasksJsonPath, id, dependsOn } = args; const { tasksJsonPath, id, dependsOn, tag, projectRoot } = args;
try { try {
log.info(`Adding dependency with args: ${JSON.stringify(args)}`); log.info(`Adding dependency with args: ${JSON.stringify(args)}`);
@@ -76,8 +78,11 @@ export async function addDependencyDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response // Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode(); enableSilentMode();
// Create context object
const context = { projectRoot, tag };
// Call the core function using the provided path // Call the core function using the provided path
await addDependency(tasksPath, taskId, dependencyId); await addDependency(tasksPath, taskId, dependencyId, context);
// Restore normal logging // Restore normal logging
disableSilentMode(); disableSilentMode();

View File

@@ -24,6 +24,7 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool) * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
* @param {boolean} [args.research=false] - Whether to use research capabilities for task creation * @param {boolean} [args.research=false] - Whether to use research capabilities for task creation
* @param {string} [args.projectRoot] - Project root path * @param {string} [args.projectRoot] - Project root path
* @param {string} [args.tag] - Tag for the task (optional)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @param {Object} context - Additional context (session) * @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } } * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
@@ -36,7 +37,8 @@ export async function addTaskDirect(args, log, context = {}) {
dependencies, dependencies,
priority, priority,
research, research,
projectRoot projectRoot,
tag
} = args; } = args;
const { session } = context; // Destructure session from context const { session } = context; // Destructure session from context
@@ -121,7 +123,8 @@ export async function addTaskDirect(args, log, context = {}) {
mcpLog, mcpLog,
projectRoot, projectRoot,
commandName: 'add-task', commandName: 'add-task',
outputType: 'mcp' outputType: 'mcp',
tag
}, },
'json', // outputFormat 'json', // outputFormat
manualTaskData, // Pass the manual task data manualTaskData, // Pass the manual task data
@@ -147,7 +150,8 @@ export async function addTaskDirect(args, log, context = {}) {
mcpLog, mcpLog,
projectRoot, projectRoot,
commandName: 'add-task', commandName: 'add-task',
outputType: 'mcp' outputType: 'mcp',
tag
}, },
'json', // outputFormat 'json', // outputFormat
null, // manualTaskData is null for AI creation null, // manualTaskData is null for AI creation

View File

@@ -22,6 +22,7 @@ import { createLogWrapper } from '../../tools/utils.js'; // Import the new utili
* @param {number} [args.from] - Starting task ID in a range to analyze * @param {number} [args.from] - Starting task ID in a range to analyze
* @param {number} [args.to] - Ending task ID in a range to analyze * @param {number} [args.to] - Ending task ID in a range to analyze
* @param {string} [args.projectRoot] - Project root path. * @param {string} [args.projectRoot] - Project root path.
* @param {string} [args.tag] - Tag for the task (optional)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @param {Object} [context={}] - Context object containing session data * @param {Object} [context={}] - Context object containing session data
* @param {Object} [context.session] - MCP session object * @param {Object} [context.session] - MCP session object
@@ -37,7 +38,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
projectRoot, projectRoot,
ids, ids,
from, from,
to to,
tag
} = args; } = args;
const logWrapper = createLogWrapper(log); const logWrapper = createLogWrapper(log);
@@ -91,7 +93,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
projectRoot: projectRoot, // Pass projectRoot here projectRoot: projectRoot, // Pass projectRoot here
id: ids, // Pass the ids parameter to the core function as 'id' id: ids, // Pass the ids parameter to the core function as 'id'
from: from, // Pass from parameter from: from, // Pass from parameter
to: to // Pass to parameter to: to, // Pass to parameter
tag // forward tag
}; };
// --- End Initial Checks --- // --- End Initial Checks ---
@@ -112,7 +115,9 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
session, session,
mcpLog: logWrapper, mcpLog: logWrapper,
commandName: 'analyze-complexity', commandName: 'analyze-complexity',
outputType: 'mcp' outputType: 'mcp',
projectRoot,
tag
}); });
report = coreResult.report; report = coreResult.report;
} catch (error) { } catch (error) {

View File

@@ -18,6 +18,7 @@ import path from 'path';
* @param {string} [args.id] - Task IDs (comma-separated) to clear subtasks from * @param {string} [args.id] - Task IDs (comma-separated) to clear subtasks from
* @param {boolean} [args.all] - Clear subtasks from all tasks * @param {boolean} [args.all] - Clear subtasks from all tasks
* @param {string} [args.tag] - Tag context to operate on (defaults to current active tag) * @param {string} [args.tag] - Tag context to operate on (defaults to current active tag)
* @param {string} [args.projectRoot] - Project root path (for MCP/env fallback)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>} * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/ */
@@ -80,7 +81,7 @@ export async function clearSubtasksDirect(args, log) {
}; };
} }
const currentTag = data.tag || 'master'; const currentTag = data.tag || tag;
const tasks = data.tasks; const tasks = data.tasks;
// If all is specified, get all task IDs // If all is specified, get all task IDs

View File

@@ -18,6 +18,7 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {string} [args.prompt] - Additional context to guide subtask generation * @param {string} [args.prompt] - Additional context to guide subtask generation
* @param {boolean} [args.force] - Force regeneration of subtasks for tasks that already have them * @param {boolean} [args.force] - Force regeneration of subtasks for tasks that already have them
* @param {string} [args.projectRoot] - Project root path. * @param {string} [args.projectRoot] - Project root path.
* @param {string} [args.tag] - Tag for the task (optional)
* @param {Object} log - Logger object from FastMCP * @param {Object} log - Logger object from FastMCP
* @param {Object} context - Context object containing session * @param {Object} context - Context object containing session
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>} * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
@@ -25,7 +26,8 @@ import { createLogWrapper } from '../../tools/utils.js';
export async function expandAllTasksDirect(args, log, context = {}) { export async function expandAllTasksDirect(args, log, context = {}) {
const { session } = context; // Extract session const { session } = context; // Extract session
// Destructure expected args, including projectRoot // Destructure expected args, including projectRoot
const { tasksJsonPath, num, research, prompt, force, projectRoot } = args; const { tasksJsonPath, num, research, prompt, force, projectRoot, tag } =
args;
// Create logger wrapper using the utility // Create logger wrapper using the utility
const mcpLog = createLogWrapper(log); const mcpLog = createLogWrapper(log);
@@ -44,7 +46,7 @@ export async function expandAllTasksDirect(args, log, context = {}) {
enableSilentMode(); // Enable silent mode for the core function call enableSilentMode(); // Enable silent mode for the core function call
try { try {
log.info( log.info(
`Calling core expandAllTasks with args: ${JSON.stringify({ num, research, prompt, force, projectRoot })}` `Calling core expandAllTasks with args: ${JSON.stringify({ num, research, prompt, force, projectRoot, tag })}`
); );
// Parse parameters (ensure correct types) // Parse parameters (ensure correct types)
@@ -60,7 +62,7 @@ export async function expandAllTasksDirect(args, log, context = {}) {
useResearch, useResearch,
additionalContext, additionalContext,
forceFlag, forceFlag,
{ session, mcpLog, projectRoot }, { session, mcpLog, projectRoot, tag },
'json' 'json'
); );

View File

@@ -35,8 +35,17 @@ import { createLogWrapper } from '../../tools/utils.js';
export async function expandTaskDirect(args, log, context = {}) { export async function expandTaskDirect(args, log, context = {}) {
const { session } = context; // Extract session const { session } = context; // Extract session
// Destructure expected args, including projectRoot // Destructure expected args, including projectRoot
const { tasksJsonPath, id, num, research, prompt, force, projectRoot, tag } = const {
args; tasksJsonPath,
id,
num,
research,
prompt,
force,
projectRoot,
tag,
complexityReportPath
} = args;
// Log session root data for debugging // Log session root data for debugging
log.info( log.info(
@@ -192,6 +201,7 @@ export async function expandTaskDirect(args, log, context = {}) {
useResearch, useResearch,
additionalContext, additionalContext,
{ {
complexityReportPath,
mcpLog, mcpLog,
session, session,
projectRoot, projectRoot,

View File

@@ -53,10 +53,9 @@ export async function fixDependenciesDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response // Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode(); enableSilentMode();
const options = { projectRoot, tag };
// Call the original command function using the provided path and proper context // Call the original command function using the provided path and proper context
await fixDependenciesCommand(tasksPath, { await fixDependenciesCommand(tasksPath, options);
context: { projectRoot, tag }
});
// Restore normal logging // Restore normal logging
disableSilentMode(); disableSilentMode();

View File

@@ -13,12 +13,16 @@ import {
* Direct function wrapper for generateTaskFiles with error handling. * Direct function wrapper for generateTaskFiles with error handling.
* *
* @param {Object} args - Command arguments containing tasksJsonPath and outputDir. * @param {Object} args - Command arguments containing tasksJsonPath and outputDir.
* @param {string} args.tasksJsonPath - Path to the tasks.json file.
* @param {string} args.outputDir - Path to the output directory.
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @returns {Promise<Object>} - Result object with success status and data/error information. * @returns {Promise<Object>} - Result object with success status and data/error information.
*/ */
export async function generateTaskFilesDirect(args, log) { export async function generateTaskFilesDirect(args, log) {
// Destructure expected args // Destructure expected args
const { tasksJsonPath, outputDir } = args; const { tasksJsonPath, outputDir, projectRoot, tag } = args;
try { try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`); log.info(`Generating task files with args: ${JSON.stringify(args)}`);
@@ -51,8 +55,12 @@ export async function generateTaskFilesDirect(args, log) {
// Enable silent mode to prevent logs from being written to stdout // Enable silent mode to prevent logs from being written to stdout
enableSilentMode(); enableSilentMode();
// The function is synchronous despite being awaited elsewhere // Pass projectRoot and tag so the core respects context
generateTaskFiles(tasksPath, resolvedOutputDir); generateTaskFiles(tasksPath, resolvedOutputDir, {
projectRoot,
tag,
mcpLog: log
});
// Restore normal logging after task generation // Restore normal logging after task generation
disableSilentMode(); disableSilentMode();

View File

@@ -13,12 +13,19 @@ import {
* Direct function wrapper for listTasks with error handling and caching. * Direct function wrapper for listTasks with error handling and caching.
* *
* @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly). * @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly).
* @param {string} args.tasksJsonPath - Path to the tasks.json file.
* @param {string} args.reportPath - Path to the report file.
* @param {string} args.status - Status of the task.
* @param {boolean} args.withSubtasks - Whether to include subtasks.
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }. * @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }.
*/ */
export async function listTasksDirect(args, log, context = {}) { export async function listTasksDirect(args, log, context = {}) {
// Destructure the explicit tasksJsonPath from args // Destructure the explicit tasksJsonPath from args
const { tasksJsonPath, reportPath, status, withSubtasks, projectRoot } = args; const { tasksJsonPath, reportPath, status, withSubtasks, projectRoot, tag } =
args;
const { session } = context; const { session } = context;
if (!tasksJsonPath) { if (!tasksJsonPath) {
@@ -52,8 +59,7 @@ export async function listTasksDirect(args, log, context = {}) {
reportPath, reportPath,
withSubtasksFilter, withSubtasksFilter,
'json', 'json',
null, // tag { projectRoot, session, tag }
{ projectRoot, session } // context
); );
if (!resultData || !resultData.tasks) { if (!resultData || !resultData.tasks) {

View File

@@ -17,12 +17,14 @@ import {
* @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3' or '7,8,9') * @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3' or '7,8,9')
* @param {string} args.file - Alternative path to the tasks.json file * @param {string} args.file - Alternative path to the tasks.json file
* @param {string} args.projectRoot - Project root directory * @param {string} args.projectRoot - Project root directory
* @param {string} args.tag - Tag for the task (optional)
* @param {boolean} args.generateFiles - Whether to regenerate task files after moving (default: true) * @param {boolean} args.generateFiles - Whether to regenerate task files after moving (default: true)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: Object}>} * @returns {Promise<{success: boolean, data?: Object, error?: Object}>}
*/ */
export async function moveTaskDirect(args, log, context = {}) { export async function moveTaskDirect(args, log, context = {}) {
const { session } = context; const { session } = context;
const { projectRoot, tag } = args;
// Validate required parameters // Validate required parameters
if (!args.sourceId) { if (!args.sourceId) {
@@ -73,8 +75,8 @@ export async function moveTaskDirect(args, log, context = {}) {
args.destinationId, args.destinationId,
generateFiles, generateFiles,
{ {
projectRoot: args.projectRoot, projectRoot,
tag: args.tag tag
} }
); );

View File

@@ -18,12 +18,15 @@ import {
* *
* @param {Object} args - Command arguments * @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.reportPath - Path to the report file.
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } } * @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } }
*/ */
export async function nextTaskDirect(args, log, context = {}) { export async function nextTaskDirect(args, log, context = {}) {
// Destructure expected args // Destructure expected args
const { tasksJsonPath, reportPath, projectRoot } = args; const { tasksJsonPath, reportPath, projectRoot, tag } = args;
const { session } = context; const { session } = context;
if (!tasksJsonPath) { if (!tasksJsonPath) {
@@ -46,7 +49,7 @@ export async function nextTaskDirect(args, log, context = {}) {
log.info(`Finding next task from ${tasksJsonPath}`); log.info(`Finding next task from ${tasksJsonPath}`);
// Read tasks data using the provided path // Read tasks data using the provided path
const data = readJSON(tasksJsonPath, projectRoot); const data = readJSON(tasksJsonPath, projectRoot, tag);
if (!data || !data.tasks) { if (!data || !data.tasks) {
disableSilentMode(); // Disable before return disableSilentMode(); // Disable before return
return { return {

View File

@@ -20,6 +20,13 @@ import { TASKMASTER_TASKS_FILE } from '../../../../src/constants/paths.js';
* Direct function wrapper for parsing PRD documents and generating tasks. * Direct function wrapper for parsing PRD documents and generating tasks.
* *
* @param {Object} args - Command arguments containing projectRoot, input, output, numTasks options. * @param {Object} args - Command arguments containing projectRoot, input, output, numTasks options.
* @param {string} args.input - Path to the input PRD file.
* @param {string} args.output - Path to the output directory.
* @param {string} args.numTasks - Number of tasks to generate.
* @param {boolean} args.force - Whether to force parsing.
* @param {boolean} args.append - Whether to append to the output file.
* @param {boolean} args.research - Whether to use research mode.
* @param {string} args.tag - Tag context for organizing tasks into separate task lists.
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data. * @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information. * @returns {Promise<Object>} - Result object with success status and data/error information.
@@ -34,7 +41,8 @@ export async function parsePRDDirect(args, log, context = {}) {
force, force,
append, append,
research, research,
projectRoot projectRoot,
tag
} = args; } = args;
// Create the standard logger wrapper // Create the standard logger wrapper
@@ -152,6 +160,7 @@ export async function parsePRDDirect(args, log, context = {}) {
session, session,
mcpLog: logWrapper, mcpLog: logWrapper,
projectRoot, projectRoot,
tag,
force, force,
append, append,
research, research,

View File

@@ -14,12 +14,14 @@ import {
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string|number} args.id - Task ID to remove dependency from * @param {string|number} args.id - Task ID to remove dependency from
* @param {string|number} args.dependsOn - Task ID to remove as a dependency * @param {string|number} args.dependsOn - Task ID to remove as a dependency
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>} * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/ */
export async function removeDependencyDirect(args, log) { export async function removeDependencyDirect(args, log) {
// Destructure expected args // Destructure expected args
const { tasksJsonPath, id, dependsOn } = args; const { tasksJsonPath, id, dependsOn, projectRoot, tag } = args;
try { try {
log.info(`Removing dependency with args: ${JSON.stringify(args)}`); log.info(`Removing dependency with args: ${JSON.stringify(args)}`);
@@ -75,7 +77,10 @@ export async function removeDependencyDirect(args, log) {
enableSilentMode(); enableSilentMode();
// Call the core function using the provided tasksPath // Call the core function using the provided tasksPath
await removeDependency(tasksPath, taskId, dependencyId); await removeDependency(tasksPath, taskId, dependencyId, {
projectRoot,
tag
});
// Restore normal logging // Restore normal logging
disableSilentMode(); disableSilentMode();

View File

@@ -15,12 +15,14 @@ import {
* @param {string} args.id - Subtask ID in format "parentId.subtaskId" (required) * @param {string} args.id - Subtask ID in format "parentId.subtaskId" (required)
* @param {boolean} [args.convert] - Whether to convert the subtask to a standalone task * @param {boolean} [args.convert] - Whether to convert the subtask to a standalone task
* @param {boolean} [args.skipGenerate] - Skip regenerating task files * @param {boolean} [args.skipGenerate] - Skip regenerating task files
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>} * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/ */
export async function removeSubtaskDirect(args, log) { export async function removeSubtaskDirect(args, log) {
// Destructure expected args // Destructure expected args
const { tasksJsonPath, id, convert, skipGenerate } = args; const { tasksJsonPath, id, convert, skipGenerate, projectRoot, tag } = args;
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response // Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode(); enableSilentMode();
@@ -82,7 +84,11 @@ export async function removeSubtaskDirect(args, log) {
tasksPath, tasksPath,
id, id,
convertToTask, convertToTask,
generateFiles generateFiles,
{
projectRoot,
tag
}
); );
// Restore normal logging // Restore normal logging

View File

@@ -20,7 +20,8 @@ import {
* @param {Object} args - Command arguments * @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple). * @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple).
* @param {string} [args.tag] - Tag context to operate on (defaults to current active tag). * @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } } * @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } }
*/ */
@@ -117,7 +118,7 @@ export async function removeTaskDirect(args, log, context = {}) {
removedTasks: result.removedTasks, removedTasks: result.removedTasks,
message: result.message, message: result.message,
tasksPath: tasksJsonPath, tasksPath: tasksJsonPath,
tag: data.tag || tag || 'master' tag
} }
}; };
} finally { } finally {

View File

@@ -24,6 +24,7 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {string} [args.saveTo] - Automatically save to task/subtask ID (e.g., "15" or "15.2") * @param {string} [args.saveTo] - Automatically save to task/subtask ID (e.g., "15" or "15.2")
* @param {boolean} [args.saveToFile=false] - Save research results to .taskmaster/docs/research/ directory * @param {boolean} [args.saveToFile=false] - Save research results to .taskmaster/docs/research/ directory
* @param {string} [args.projectRoot] - Project root path * @param {string} [args.projectRoot] - Project root path
* @param {string} [args.tag] - Tag for the task (optional)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @param {Object} context - Additional context (session) * @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } } * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
@@ -39,7 +40,8 @@ export async function researchDirect(args, log, context = {}) {
detailLevel = 'medium', detailLevel = 'medium',
saveTo, saveTo,
saveToFile = false, saveToFile = false,
projectRoot projectRoot,
tag
} = args; } = args;
const { session } = context; // Destructure session from context const { session } = context; // Destructure session from context
@@ -111,6 +113,7 @@ export async function researchDirect(args, log, context = {}) {
includeProjectTree, includeProjectTree,
detailLevel, detailLevel,
projectRoot, projectRoot,
tag,
saveToFile saveToFile
}; };
@@ -169,7 +172,8 @@ ${result.result}`;
mcpLog, mcpLog,
commandName: 'research-save', commandName: 'research-save',
outputType: 'mcp', outputType: 'mcp',
projectRoot projectRoot,
tag
}, },
'json' 'json'
); );
@@ -200,7 +204,8 @@ ${result.result}`;
mcpLog, mcpLog,
commandName: 'research-save', commandName: 'research-save',
outputType: 'mcp', outputType: 'mcp',
projectRoot projectRoot,
tag
}, },
'json', 'json',
true // appendMode = true true // appendMode = true

View File

@@ -14,6 +14,11 @@ import { nextTaskDirect } from './next-task.js';
* Direct function wrapper for setTaskStatus with error handling. * Direct function wrapper for setTaskStatus with error handling.
* *
* @param {Object} args - Command arguments containing id, status, tasksJsonPath, and projectRoot. * @param {Object} args - Command arguments containing id, status, tasksJsonPath, and projectRoot.
* @param {string} args.id - The ID of the task to update.
* @param {string} args.status - The new status to set for the task.
* @param {string} args.tasksJsonPath - Path to the tasks.json file.
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @param {Object} context - Additional context (session) * @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object with success status and data/error information. * @returns {Promise<Object>} - Result object with success status and data/error information.
@@ -70,17 +75,12 @@ export async function setTaskStatusDirect(args, log, context = {}) {
enableSilentMode(); // Enable silent mode before calling core function enableSilentMode(); // Enable silent mode before calling core function
try { try {
// Call the core function // Call the core function
await setTaskStatus( await setTaskStatus(tasksPath, taskId, newStatus, {
tasksPath,
taskId,
newStatus,
{
mcpLog: log, mcpLog: log,
projectRoot, projectRoot,
session session,
},
tag tag
); });
log.info(`Successfully set task ${taskId} status to ${newStatus}`); log.info(`Successfully set task ${taskId} status to ${newStatus}`);
@@ -103,7 +103,8 @@ export async function setTaskStatusDirect(args, log, context = {}) {
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
reportPath: complexityReportPath, reportPath: complexityReportPath,
projectRoot: projectRoot projectRoot: projectRoot,
tag
}, },
log, log,
{ session } { session }

View File

@@ -19,6 +19,7 @@ import { findTasksPath } from '../utils/path-utils.js';
* @param {string} args.reportPath - Explicit path to the complexity report file. * @param {string} args.reportPath - Explicit path to the complexity report file.
* @param {string} [args.status] - Optional status to filter subtasks by. * @param {string} [args.status] - Optional status to filter subtasks by.
* @param {string} args.projectRoot - Absolute path to the project root directory (already normalized by tool). * @param {string} args.projectRoot - Absolute path to the project root directory (already normalized by tool).
* @param {string} [args.tag] - Tag for the task
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data. * @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information. * @returns {Promise<Object>} - Result object with success status and data/error information.
@@ -26,7 +27,7 @@ import { findTasksPath } from '../utils/path-utils.js';
export async function showTaskDirect(args, log) { export async function showTaskDirect(args, log) {
// This function doesn't need session context since it only reads data // This function doesn't need session context since it only reads data
// Destructure projectRoot and other args. projectRoot is assumed normalized. // Destructure projectRoot and other args. projectRoot is assumed normalized.
const { id, file, reportPath, status, projectRoot } = args; const { id, file, reportPath, status, projectRoot, tag } = args;
log.info( log.info(
`Showing task direct function. ID: ${id}, File: ${file}, Status Filter: ${status}, ProjectRoot: ${projectRoot}` `Showing task direct function. ID: ${id}, File: ${file}, Status Filter: ${status}, ProjectRoot: ${projectRoot}`
@@ -55,7 +56,7 @@ export async function showTaskDirect(args, log) {
// --- Rest of the function remains the same, using tasksJsonPath --- // --- Rest of the function remains the same, using tasksJsonPath ---
try { try {
const tasksData = readJSON(tasksJsonPath, projectRoot); const tasksData = readJSON(tasksJsonPath, projectRoot, tag);
if (!tasksData || !tasksData.tasks) { if (!tasksData || !tasksData.tasks) {
return { return {
success: false, success: false,

View File

@@ -20,6 +20,7 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {string} args.prompt - Information to append to the subtask. * @param {string} args.prompt - Information to append to the subtask.
* @param {boolean} [args.research] - Whether to use research role. * @param {boolean} [args.research] - Whether to use research role.
* @param {string} [args.projectRoot] - Project root path. * @param {string} [args.projectRoot] - Project root path.
* @param {string} [args.tag] - Tag for the task (optional)
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data. * @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information. * @returns {Promise<Object>} - Result object with success status and data/error information.
@@ -27,7 +28,7 @@ import { createLogWrapper } from '../../tools/utils.js';
export async function updateSubtaskByIdDirect(args, log, context = {}) { export async function updateSubtaskByIdDirect(args, log, context = {}) {
const { session } = context; const { session } = context;
// Destructure expected args, including projectRoot // Destructure expected args, including projectRoot
const { tasksJsonPath, id, prompt, research, projectRoot } = args; const { tasksJsonPath, id, prompt, research, projectRoot, tag } = args;
const logWrapper = createLogWrapper(log); const logWrapper = createLogWrapper(log);
@@ -112,6 +113,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
mcpLog: logWrapper, mcpLog: logWrapper,
session, session,
projectRoot, projectRoot,
tag,
commandName: 'update-subtask', commandName: 'update-subtask',
outputType: 'mcp' outputType: 'mcp'
}, },

View File

@@ -21,6 +21,7 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {boolean} [args.research] - Whether to use research role. * @param {boolean} [args.research] - Whether to use research role.
* @param {boolean} [args.append] - Whether to append timestamped information instead of full update. * @param {boolean} [args.append] - Whether to append timestamped information instead of full update.
* @param {string} [args.projectRoot] - Project root path. * @param {string} [args.projectRoot] - Project root path.
* @param {string} [args.tag] - Tag for the task (optional)
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data. * @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information. * @returns {Promise<Object>} - Result object with success status and data/error information.
@@ -28,7 +29,8 @@ import { createLogWrapper } from '../../tools/utils.js';
export async function updateTaskByIdDirect(args, log, context = {}) { export async function updateTaskByIdDirect(args, log, context = {}) {
const { session } = context; const { session } = context;
// Destructure expected args, including projectRoot // Destructure expected args, including projectRoot
const { tasksJsonPath, id, prompt, research, append, projectRoot } = args; const { tasksJsonPath, id, prompt, research, append, projectRoot, tag } =
args;
const logWrapper = createLogWrapper(log); const logWrapper = createLogWrapper(log);
@@ -116,6 +118,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
mcpLog: logWrapper, mcpLog: logWrapper,
session, session,
projectRoot, projectRoot,
tag,
commandName: 'update-task', commandName: 'update-task',
outputType: 'mcp' outputType: 'mcp'
}, },

View File

@@ -15,6 +15,12 @@ import {
* Direct function wrapper for updating tasks based on new context. * Direct function wrapper for updating tasks based on new context.
* *
* @param {Object} args - Command arguments containing projectRoot, from, prompt, research options. * @param {Object} args - Command arguments containing projectRoot, from, prompt, research options.
* @param {string} args.from - The ID of the task to update.
* @param {string} args.prompt - The prompt to update the task with.
* @param {boolean} args.research - Whether to use research mode.
* @param {string} args.tasksJsonPath - Path to the tasks.json file.
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data. * @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information. * @returns {Promise<Object>} - Result object with success status and data/error information.

View File

@@ -13,12 +13,14 @@ import fs from 'fs';
* Validate dependencies in tasks.json * Validate dependencies in tasks.json
* @param {Object} args - Function arguments * @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>} * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/ */
export async function validateDependenciesDirect(args, log) { export async function validateDependenciesDirect(args, log) {
// Destructure the explicit tasksJsonPath // Destructure the explicit tasksJsonPath
const { tasksJsonPath } = args; const { tasksJsonPath, projectRoot, tag } = args;
if (!tasksJsonPath) { if (!tasksJsonPath) {
log.error('validateDependenciesDirect called without tasksJsonPath'); log.error('validateDependenciesDirect called without tasksJsonPath');
@@ -51,8 +53,9 @@ export async function validateDependenciesDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response // Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode(); enableSilentMode();
const options = { projectRoot, tag };
// Call the original command function using the provided tasksPath // Call the original command function using the provided tasksPath
await validateDependenciesCommand(tasksPath); await validateDependenciesCommand(tasksPath, options);
// Restore normal logging // Restore normal logging
disableSilentMode(); disableSilentMode();

View File

@@ -121,6 +121,7 @@ export function resolveComplexityReportPath(args, log = silentLogger) {
// Get explicit path from args.complexityReport if provided // Get explicit path from args.complexityReport if provided
const explicitPath = args?.complexityReport; const explicitPath = args?.complexityReport;
const rawProjectRoot = args?.projectRoot; const rawProjectRoot = args?.projectRoot;
const tag = args?.tag;
// If explicit path is provided and absolute, use it directly // If explicit path is provided and absolute, use it directly
if (explicitPath && path.isAbsolute(explicitPath)) { if (explicitPath && path.isAbsolute(explicitPath)) {
@@ -139,7 +140,11 @@ export function resolveComplexityReportPath(args, log = silentLogger) {
// Use core findComplexityReportPath with explicit path and normalized projectRoot context // Use core findComplexityReportPath with explicit path and normalized projectRoot context
if (projectRoot) { if (projectRoot) {
return coreFindComplexityReportPath(explicitPath, { projectRoot }, log); return coreFindComplexityReportPath(
explicitPath,
{ projectRoot, tag },
log
);
} }
// Fallback to core function without projectRoot context // Fallback to core function without projectRoot context

View File

@@ -32,10 +32,6 @@ class TaskMasterMCPServer {
this.server = new FastMCP(this.options); this.server = new FastMCP(this.options);
this.initialized = false; this.initialized = false;
this.server.addResource({});
this.server.addResourceTemplate({});
// Bind methods // Bind methods
this.init = this.init.bind(this); this.init = this.init.bind(this);
this.start = this.start.bind(this); this.start = this.start.bind(this);

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { addDependencyDirect } from '../core/task-master-core.js'; import { addDependencyDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the addDependency tool with the MCP server * Register the addDependency tool with the MCP server
@@ -33,14 +34,18 @@ export function registerAddDependencyTool(server) {
), ),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
log.info( log.info(
`Adding dependency for task ${args.id} to depend on ${args.dependsOn}` `Adding dependency for task ${args.id} to depend on ${args.dependsOn}`
); );
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
let tasksJsonPath; let tasksJsonPath;
try { try {
tasksJsonPath = findTasksPath( tasksJsonPath = findTasksPath(
@@ -61,7 +66,9 @@ export function registerAddDependencyTool(server) {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
// Pass other relevant args // Pass other relevant args
id: args.id, id: args.id,
dependsOn: args.dependsOn dependsOn: args.dependsOn,
projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log log
// Remove context object // Remove context object

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { addSubtaskDirect } from '../core/task-master-core.js'; import { addSubtaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the addSubtask tool with the MCP server * Register the addSubtask tool with the MCP server
@@ -52,17 +53,21 @@ export function registerAddSubtaskTool(server) {
.describe( .describe(
'Absolute path to the tasks file (default: tasks/tasks.json)' 'Absolute path to the tasks file (default: tasks/tasks.json)'
), ),
tag: z.string().optional().describe('Tag context to operate on'),
skipGenerate: z skipGenerate: z
.boolean() .boolean()
.optional() .optional()
.describe('Skip regenerating task files'), .describe('Skip regenerating task files'),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(`Adding subtask with args: ${JSON.stringify(args)}`); log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
@@ -91,7 +96,7 @@ export function registerAddSubtaskTool(server) {
dependencies: args.dependencies, dependencies: args.dependencies,
skipGenerate: args.skipGenerate, skipGenerate: args.skipGenerate,
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
tag: args.tag tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { addTaskDirect } from '../core/task-master-core.js'; import { addTaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the addTask tool with the MCP server * Register the addTask tool with the MCP server
@@ -58,6 +59,7 @@ export function registerAddTaskTool(server) {
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.'), .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on'),
research: z research: z
.boolean() .boolean()
.optional() .optional()
@@ -67,6 +69,11 @@ export function registerAddTaskTool(server) {
try { try {
log.info(`Starting add-task with args: ${JSON.stringify(args)}`); log.info(`Starting add-task with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -93,7 +100,8 @@ export function registerAddTaskTool(server) {
dependencies: args.dependencies, dependencies: args.dependencies,
priority: args.priority, priority: args.priority,
research: args.research, research: args.research,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -13,7 +13,9 @@ import {
} from './utils.js'; } from './utils.js';
import { analyzeTaskComplexityDirect } from '../core/task-master-core.js'; // Assuming core functions are exported via task-master-core.js import { analyzeTaskComplexityDirect } from '../core/task-master-core.js'; // Assuming core functions are exported via task-master-core.js
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js'; import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js';
import { resolveComplexityReportOutputPath } from '../../../src/utils/path-utils.js';
/** /**
* Register the analyze_project_complexity tool * Register the analyze_project_complexity tool
@@ -70,15 +72,22 @@ export function registerAnalyzeProjectComplexityTool(server) {
.describe('Ending task ID in a range to analyze.'), .describe('Ending task ID in a range to analyze.'),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
const toolName = 'analyze_project_complexity'; // Define tool name for logging const toolName = 'analyze_project_complexity'; // Define tool name for logging
try { try {
log.info( log.info(
`Executing ${toolName} tool with args: ${JSON.stringify(args)}` `Executing ${toolName} tool with args: ${JSON.stringify(args)}`
); );
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
let tasksJsonPath; let tasksJsonPath;
try { try {
tasksJsonPath = findTasksPath( tasksJsonPath = findTasksPath(
@@ -93,9 +102,14 @@ export function registerAnalyzeProjectComplexityTool(server) {
); );
} }
const outputPath = args.output const outputPath = resolveComplexityReportOutputPath(
? path.resolve(args.projectRoot, args.output) args.output,
: path.resolve(args.projectRoot, COMPLEXITY_REPORT_FILE); {
projectRoot: args.projectRoot,
tag: resolvedTag
},
log
);
log.info(`${toolName}: Report output path: ${outputPath}`); log.info(`${toolName}: Report output path: ${outputPath}`);
@@ -123,6 +137,7 @@ export function registerAnalyzeProjectComplexityTool(server) {
threshold: args.threshold, threshold: args.threshold,
research: args.research, research: args.research,
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
tag: resolvedTag,
ids: args.ids, ids: args.ids,
from: args.from, from: args.from,
to: args.to to: args.to

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { clearSubtasksDirect } from '../core/task-master-core.js'; import { clearSubtasksDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the clearSubtasks tool with the MCP server * Register the clearSubtasks tool with the MCP server
@@ -46,6 +47,11 @@ export function registerClearSubtasksTool(server) {
try { try {
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`); log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -65,8 +71,9 @@ export function registerClearSubtasksTool(server) {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
id: args.id, id: args.id,
all: args.all, all: args.all,
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
tag: args.tag || 'master' tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -12,6 +12,7 @@ import {
import { complexityReportDirect } from '../core/task-master-core.js'; import { complexityReportDirect } from '../core/task-master-core.js';
import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js'; import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js';
import { findComplexityReportPath } from '../core/utils/path-utils.js'; import { findComplexityReportPath } from '../core/utils/path-utils.js';
import { getCurrentTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the complexityReport tool with the MCP server * Register the complexityReport tool with the MCP server
@@ -38,12 +39,16 @@ export function registerComplexityReportTool(server) {
`Getting complexity report with args: ${JSON.stringify(args)}` `Getting complexity report with args: ${JSON.stringify(args)}`
); );
const resolvedTag = getCurrentTag(args.projectRoot);
const pathArgs = { const pathArgs = {
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
complexityReport: args.file complexityReport: args.file,
tag: resolvedTag
}; };
const reportPath = findComplexityReportPath(pathArgs, log); const reportPath = findComplexityReportPath(pathArgs, log);
log.info('Reading complexity report from path: ', reportPath);
if (!reportPath) { if (!reportPath) {
return createErrorResponse( return createErrorResponse(

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { expandAllTasksDirect } from '../core/task-master-core.js'; import { expandAllTasksDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the expandAll tool with the MCP server * Register the expandAll tool with the MCP server
@@ -57,7 +58,8 @@ export function registerExpandAllTool(server) {
.optional() .optional()
.describe( .describe(
'Absolute path to the project root directory (derived from session if possible)' 'Absolute path to the project root directory (derived from session if possible)'
) ),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
@@ -65,6 +67,10 @@ export function registerExpandAllTool(server) {
`Tool expand_all execution started with args: ${JSON.stringify(args)}` `Tool expand_all execution started with args: ${JSON.stringify(args)}`
); );
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
let tasksJsonPath; let tasksJsonPath;
try { try {
tasksJsonPath = findTasksPath( tasksJsonPath = findTasksPath(
@@ -86,7 +92,8 @@ export function registerExpandAllTool(server) {
research: args.research, research: args.research,
prompt: args.prompt, prompt: args.prompt,
force: args.force, force: args.force,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -10,7 +10,11 @@ import {
withNormalizedProjectRoot withNormalizedProjectRoot
} from './utils.js'; } from './utils.js';
import { expandTaskDirect } from '../core/task-master-core.js'; import { expandTaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import {
findTasksPath,
findComplexityReportPath
} from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the expand-task tool with the MCP server * Register the expand-task tool with the MCP server
@@ -51,7 +55,10 @@ export function registerExpandTaskTool(server) {
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
log.info(`Starting expand-task with args: ${JSON.stringify(args)}`); log.info(`Starting expand-task with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -66,6 +73,11 @@ export function registerExpandTaskTool(server) {
); );
} }
const complexityReportPath = findComplexityReportPath(
{ ...args, tag: resolvedTag },
log
);
const result = await expandTaskDirect( const result = await expandTaskDirect(
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
@@ -74,8 +86,9 @@ export function registerExpandTaskTool(server) {
research: args.research, research: args.research,
prompt: args.prompt, prompt: args.prompt,
force: args.force, force: args.force,
complexityReportPath,
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
tag: args.tag || 'master' tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -11,7 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { fixDependenciesDirect } from '../core/task-master-core.js'; import { fixDependenciesDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the fixDependencies tool with the MCP server * Register the fixDependencies tool with the MCP server
* @param {Object} server - FastMCP server instance * @param {Object} server - FastMCP server instance
@@ -31,6 +31,11 @@ export function registerFixDependenciesTool(server) {
try { try {
log.info(`Fixing dependencies with args: ${JSON.stringify(args)}`); log.info(`Fixing dependencies with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -49,7 +54,7 @@ export function registerFixDependenciesTool(server) {
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
tag: args.tag tag: resolvedTag
}, },
log log
); );

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { generateTaskFilesDirect } from '../core/task-master-core.js'; import { generateTaskFilesDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
import path from 'path'; import path from 'path';
/** /**
@@ -30,12 +31,17 @@ export function registerGenerateTool(server) {
.describe('Output directory (default: same directory as tasks file)'), .describe('Output directory (default: same directory as tasks file)'),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`); log.info(`Generating task files with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -58,7 +64,8 @@ export function registerGenerateTool(server) {
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
outputDir: outputDir, outputDir: outputDir,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -14,6 +14,7 @@ import {
findTasksPath, findTasksPath,
findComplexityReportPath findComplexityReportPath
} from '../core/utils/path-utils.js'; } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Custom processor function that removes allTasks from the response * Custom processor function that removes allTasks from the response
@@ -67,7 +68,8 @@ export function registerShowTaskTool(server) {
.string() .string()
.describe( .describe(
'Absolute path to the project root directory (Optional, usually from session)' 'Absolute path to the project root directory (Optional, usually from session)'
) ),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
const { id, file, status, projectRoot } = args; const { id, file, status, projectRoot } = args;
@@ -76,6 +78,10 @@ export function registerShowTaskTool(server) {
log.info( log.info(
`Getting task details for ID: ${id}${status ? ` (filtering subtasks by status: ${status})` : ''} in root: ${projectRoot}` `Getting task details for ID: ${id}${status ? ` (filtering subtasks by status: ${status})` : ''} in root: ${projectRoot}`
); );
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Resolve the path to tasks.json using the NORMALIZED projectRoot from args // Resolve the path to tasks.json using the NORMALIZED projectRoot from args
let tasksJsonPath; let tasksJsonPath;
@@ -99,7 +105,8 @@ export function registerShowTaskTool(server) {
complexityReportPath = findComplexityReportPath( complexityReportPath = findComplexityReportPath(
{ {
projectRoot: projectRoot, projectRoot: projectRoot,
complexityReport: args.complexityReport complexityReport: args.complexityReport,
tag: resolvedTag
}, },
log log
); );
@@ -113,7 +120,8 @@ export function registerShowTaskTool(server) {
// Pass other relevant args // Pass other relevant args
id: id, id: id,
status: status, status: status,
projectRoot: projectRoot projectRoot: projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -15,6 +15,8 @@ import {
resolveComplexityReportPath resolveComplexityReportPath
} from '../core/utils/path-utils.js'; } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the getTasks tool with the MCP server * Register the getTasks tool with the MCP server
* @param {Object} server - FastMCP server instance * @param {Object} server - FastMCP server instance
@@ -51,12 +53,17 @@ export function registerListTasksTool(server) {
), ),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
log.info(`Getting tasks with filters: ${JSON.stringify(args)}`); log.info(`Getting tasks with filters: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Resolve the path to tasks.json using new path utilities // Resolve the path to tasks.json using new path utilities
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -71,7 +78,10 @@ export function registerListTasksTool(server) {
// Resolve the path to complexity report // Resolve the path to complexity report
let complexityReportPath; let complexityReportPath;
try { try {
complexityReportPath = resolveComplexityReportPath(args, session); complexityReportPath = resolveComplexityReportPath(
{ ...args, tag: resolvedTag },
session
);
} catch (error) { } catch (error) {
log.error(`Error finding complexity report: ${error.message}`); log.error(`Error finding complexity report: ${error.message}`);
// This is optional, so we don't fail the operation // This is optional, so we don't fail the operation
@@ -84,7 +94,8 @@ export function registerListTasksTool(server) {
status: args.status, status: args.status,
withSubtasks: args.withSubtasks, withSubtasks: args.withSubtasks,
reportPath: complexityReportPath, reportPath: complexityReportPath,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { moveTaskDirect } from '../core/task-master-core.js'; import { moveTaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the moveTask tool with the MCP server * Register the moveTask tool with the MCP server
@@ -36,10 +37,15 @@ export function registerMoveTaskTool(server) {
.string() .string()
.describe( .describe(
'Root directory of the project (typically derived from session)' 'Root directory of the project (typically derived from session)'
) ),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Find tasks.json path if not provided // Find tasks.json path if not provided
let tasksJsonPath = args.file; let tasksJsonPath = args.file;
@@ -79,7 +85,8 @@ export function registerMoveTaskTool(server) {
sourceId: fromId, sourceId: fromId,
destinationId: toId, destinationId: toId,
tasksJsonPath, tasksJsonPath,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }
@@ -115,7 +122,8 @@ export function registerMoveTaskTool(server) {
sourceId: args.from, sourceId: args.from,
destinationId: args.to, destinationId: args.to,
tasksJsonPath, tasksJsonPath,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -14,6 +14,7 @@ import {
resolveTasksPath, resolveTasksPath,
resolveComplexityReportPath resolveComplexityReportPath
} from '../core/utils/path-utils.js'; } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the nextTask tool with the MCP server * Register the nextTask tool with the MCP server
@@ -34,11 +35,16 @@ export function registerNextTaskTool(server) {
), ),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
log.info(`Finding next task with args: ${JSON.stringify(args)}`); log.info(`Finding next task with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Resolve the path to tasks.json using new path utilities // Resolve the path to tasks.json using new path utilities
let tasksJsonPath; let tasksJsonPath;
@@ -54,7 +60,10 @@ export function registerNextTaskTool(server) {
// Resolve the path to complexity report (optional) // Resolve the path to complexity report (optional)
let complexityReportPath; let complexityReportPath;
try { try {
complexityReportPath = resolveComplexityReportPath(args, session); complexityReportPath = resolveComplexityReportPath(
{ ...args, tag: resolvedTag },
session
);
} catch (error) { } catch (error) {
log.error(`Error finding complexity report: ${error.message}`); log.error(`Error finding complexity report: ${error.message}`);
// This is optional, so we don't fail the operation // This is optional, so we don't fail the operation
@@ -65,7 +74,8 @@ export function registerNextTaskTool(server) {
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
reportPath: complexityReportPath, reportPath: complexityReportPath,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -15,6 +15,7 @@ import {
TASKMASTER_DOCS_DIR, TASKMASTER_DOCS_DIR,
TASKMASTER_TASKS_FILE TASKMASTER_TASKS_FILE
} from '../../../src/constants/paths.js'; } from '../../../src/constants/paths.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the parse_prd tool * Register the parse_prd tool
@@ -24,6 +25,7 @@ export function registerParsePRDTool(server) {
server.addTool({ server.addTool({
name: 'parse_prd', name: 'parse_prd',
description: `Parse a Product Requirements Document (PRD) text file to automatically generate initial tasks. Reinitializing the project is not necessary to run this tool. It is recommended to run parse-prd after initializing the project and creating/importing a prd.txt file in the project root's ${TASKMASTER_DOCS_DIR} directory.`, description: `Parse a Product Requirements Document (PRD) text file to automatically generate initial tasks. Reinitializing the project is not necessary to run this tool. It is recommended to run parse-prd after initializing the project and creating/importing a prd.txt file in the project root's ${TASKMASTER_DOCS_DIR} directory.`,
parameters: z.object({ parameters: z.object({
input: z input: z
.string() .string()
@@ -33,6 +35,7 @@ export function registerParsePRDTool(server) {
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.'), .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on'),
output: z output: z
.string() .string()
.optional() .optional()
@@ -63,7 +66,18 @@ export function registerParsePRDTool(server) {
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
const result = await parsePRDDirect(args, log, { session }); const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
const result = await parsePRDDirect(
{
...args,
tag: resolvedTag
},
log,
{ session }
);
return handleApiResult( return handleApiResult(
result, result,
log, log,

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { removeDependencyDirect } from '../core/task-master-core.js'; import { removeDependencyDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the removeDependency tool with the MCP server * Register the removeDependency tool with the MCP server
@@ -31,10 +32,15 @@ export function registerRemoveDependencyTool(server) {
), ),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info( log.info(
`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}` `Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`
); );
@@ -57,7 +63,9 @@ export function registerRemoveDependencyTool(server) {
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
id: args.id, id: args.id,
dependsOn: args.dependsOn dependsOn: args.dependsOn,
projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log log
); );

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { removeSubtaskDirect } from '../core/task-master-core.js'; import { removeSubtaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the removeSubtask tool with the MCP server * Register the removeSubtask tool with the MCP server
@@ -44,10 +45,15 @@ export function registerRemoveSubtaskTool(server) {
.describe('Skip regenerating task files'), .describe('Skip regenerating task files'),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(`Removing subtask with args: ${JSON.stringify(args)}`); log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
@@ -70,7 +76,8 @@ export function registerRemoveSubtaskTool(server) {
id: args.id, id: args.id,
convert: args.convert, convert: args.convert,
skipGenerate: args.skipGenerate, skipGenerate: args.skipGenerate,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { removeTaskDirect } from '../core/task-master-core.js'; import { removeTaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the remove-task tool with the MCP server * Register the remove-task tool with the MCP server
@@ -45,6 +46,11 @@ export function registerRemoveTaskTool(server) {
try { try {
log.info(`Removing task(s) with ID(s): ${args.id}`); log.info(`Removing task(s) with ID(s): ${args.id}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -66,7 +72,7 @@ export function registerRemoveTaskTool(server) {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
id: args.id, id: args.id,
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
tag: args.tag tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -10,6 +10,7 @@ import {
withNormalizedProjectRoot withNormalizedProjectRoot
} from './utils.js'; } from './utils.js';
import { researchDirect } from '../core/task-master-core.js'; import { researchDirect } from '../core/task-master-core.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the research tool with the MCP server * Register the research tool with the MCP server
@@ -19,6 +20,7 @@ export function registerResearchTool(server) {
server.addTool({ server.addTool({
name: 'research', name: 'research',
description: 'Perform AI-powered research queries with project context', description: 'Perform AI-powered research queries with project context',
parameters: z.object({ parameters: z.object({
query: z.string().describe('Research query/prompt (required)'), query: z.string().describe('Research query/prompt (required)'),
taskIds: z taskIds: z
@@ -61,10 +63,15 @@ export function registerResearchTool(server) {
), ),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info( log.info(
`Starting research with query: "${args.query.substring(0, 100)}${args.query.length > 100 ? '...' : ''}"` `Starting research with query: "${args.query.substring(0, 100)}${args.query.length > 100 ? '...' : ''}"`
); );
@@ -80,7 +87,8 @@ export function registerResearchTool(server) {
detailLevel: args.detailLevel || 'medium', detailLevel: args.detailLevel || 'medium',
saveTo: args.saveTo, saveTo: args.saveTo,
saveToFile: args.saveToFile || false, saveToFile: args.saveToFile || false,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -18,6 +18,7 @@ import {
findComplexityReportPath findComplexityReportPath
} from '../core/utils/path-utils.js'; } from '../core/utils/path-utils.js';
import { TASK_STATUS_OPTIONS } from '../../../src/constants/task-status.js'; import { TASK_STATUS_OPTIONS } from '../../../src/constants/task-status.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the setTaskStatus tool with the MCP server * Register the setTaskStatus tool with the MCP server
@@ -52,8 +53,15 @@ export function registerSetTaskStatusTool(server) {
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`); log.info(
`Setting status of task(s) ${args.id} to: ${args.status} ${
args.tag ? `in tag: ${args.tag}` : 'in current tag'
}`
);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -73,7 +81,8 @@ export function registerSetTaskStatusTool(server) {
complexityReportPath = findComplexityReportPath( complexityReportPath = findComplexityReportPath(
{ {
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
complexityReport: args.complexityReport complexityReport: args.complexityReport,
tag: resolvedTag
}, },
log log
); );
@@ -88,7 +97,7 @@ export function registerSetTaskStatusTool(server) {
status: args.status, status: args.status,
complexityReportPath, complexityReportPath,
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
tag: args.tag tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { updateSubtaskByIdDirect } from '../core/task-master-core.js'; import { updateSubtaskByIdDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the update-subtask tool with the MCP server * Register the update-subtask tool with the MCP server
@@ -35,11 +36,17 @@ export function registerUpdateSubtaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'), file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
const toolName = 'update_subtask'; const toolName = 'update_subtask';
try { try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(`Updating subtask with args: ${JSON.stringify(args)}`); log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
let tasksJsonPath; let tasksJsonPath;
@@ -61,7 +68,8 @@ export function registerUpdateSubtaskTool(server) {
id: args.id, id: args.id,
prompt: args.prompt, prompt: args.prompt,
research: args.research, research: args.research,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { updateTaskByIdDirect } from '../core/task-master-core.js'; import { updateTaskByIdDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the update-task tool with the MCP server * Register the update-task tool with the MCP server
@@ -43,11 +44,16 @@ export function registerUpdateTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'), file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
const toolName = 'update_task'; const toolName = 'update_task';
try { try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info( log.info(
`Executing ${toolName} tool with args: ${JSON.stringify(args)}` `Executing ${toolName} tool with args: ${JSON.stringify(args)}`
); );
@@ -74,7 +80,8 @@ export function registerUpdateTaskTool(server) {
prompt: args.prompt, prompt: args.prompt,
research: args.research, research: args.research,
append: args.append, append: args.append,
projectRoot: args.projectRoot projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { updateTasksDirect } from '../core/task-master-core.js'; import { updateTasksDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the update tool with the MCP server * Register the update tool with the MCP server
@@ -50,6 +51,11 @@ export function registerUpdateTool(server) {
const toolName = 'update'; const toolName = 'update';
const { from, prompt, research, file, projectRoot, tag } = args; const { from, prompt, research, file, projectRoot, tag } = args;
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
try { try {
log.info( log.info(
`Executing ${toolName} tool with normalized root: ${projectRoot}` `Executing ${toolName} tool with normalized root: ${projectRoot}`
@@ -73,7 +79,7 @@ export function registerUpdateTool(server) {
prompt: prompt, prompt: prompt,
research: research, research: research,
projectRoot: projectRoot, projectRoot: projectRoot,
tag: tag tag: resolvedTag
}, },
log, log,
{ session } { session }

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { validateDependenciesDirect } from '../core/task-master-core.js'; import { validateDependenciesDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js'; import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/** /**
* Register the validateDependencies tool with the MCP server * Register the validateDependencies tool with the MCP server
@@ -25,10 +26,15 @@ export function registerValidateDependenciesTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'), file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}), }),
execute: withNormalizedProjectRoot(async (args, { log, session }) => { execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try { try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(`Validating dependencies with args: ${JSON.stringify(args)}`); log.info(`Validating dependencies with args: ${JSON.stringify(args)}`);
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
@@ -47,7 +53,9 @@ export function registerValidateDependenciesTool(server) {
const result = await validateDependenciesDirect( const result = await validateDependenciesDirect(
{ {
tasksJsonPath: tasksJsonPath tasksJsonPath: tasksJsonPath,
projectRoot: args.projectRoot,
tag: resolvedTag
}, },
log log
); );

View File

@@ -1,6 +1,6 @@
{ {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.20.0", "version": "0.21.0",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.", "description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@@ -9,7 +9,10 @@
"task-master-mcp": "mcp-server/server.js", "task-master-mcp": "mcp-server/server.js",
"task-master-ai": "mcp-server/server.js" "task-master-ai": "mcp-server/server.js"
}, },
"workspaces": ["apps/*", "."], "workspaces": [
"apps/*",
"."
],
"scripts": { "scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest", "test": "node --experimental-vm-modules node_modules/.bin/jest",
"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures", "test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",

View File

@@ -826,7 +826,8 @@ function registerCommands(programInstance) {
let taskMaster; let taskMaster;
try { try {
const initOptions = { const initOptions = {
prdPath: file || options.input || true prdPath: file || options.input || true,
tag: options.tag
}; };
// Only include tasksPath if output is explicitly specified // Only include tasksPath if output is explicitly specified
if (options.output) { if (options.output) {
@@ -852,8 +853,7 @@ function registerCommands(programInstance) {
const useAppend = append; const useAppend = append;
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -966,7 +966,8 @@ function registerCommands(programInstance) {
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const fromId = parseInt(options.from, 10); // Validation happens here const fromId = parseInt(options.from, 10); // Validation happens here
@@ -976,8 +977,7 @@ function registerCommands(programInstance) {
const tasksPath = taskMaster.getTasksPath(); const tasksPath = taskMaster.getTasksPath();
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -1066,13 +1066,13 @@ function registerCommands(programInstance) {
try { try {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const tasksPath = taskMaster.getTasksPath(); const tasksPath = taskMaster.getTasksPath();
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -1238,13 +1238,13 @@ function registerCommands(programInstance) {
try { try {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const tasksPath = taskMaster.getTasksPath(); const tasksPath = taskMaster.getTasksPath();
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -1404,11 +1404,12 @@ function registerCommands(programInstance) {
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const outputDir = options.output; const outputDir = options.output;
const tag = options.tag; const tag = taskMaster.getCurrentTag();
console.log( console.log(
chalk.blue(`Generating task files from: ${taskMaster.getTasksPath()}`) chalk.blue(`Generating task files from: ${taskMaster.getTasksPath()}`)
@@ -1444,12 +1445,12 @@ function registerCommands(programInstance) {
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const taskId = options.id; const taskId = options.id;
const status = options.status; const status = options.status;
const tag = options.tag;
if (!taskId || !status) { if (!taskId || !status) {
console.error(chalk.red('Error: Both --id and --status are required')); console.error(chalk.red('Error: Both --id and --status are required'));
@@ -1465,11 +1466,9 @@ function registerCommands(programInstance) {
process.exit(1); process.exit(1);
} }
const tag = taskMaster.getCurrentTag();
// Resolve tag using standard pattern and show current tag context displayCurrentTagIndicator(tag);
const resolvedTag =
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
displayCurrentTagIndicator(resolvedTag);
console.log( console.log(
chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`) chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`)
@@ -1501,7 +1500,8 @@ function registerCommands(programInstance) {
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const initOptions = { const initOptions = {
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}; };
// Only pass complexityReportPath if user provided a custom path // Only pass complexityReportPath if user provided a custom path
@@ -1513,9 +1513,7 @@ function registerCommands(programInstance) {
const statusFilter = options.status; const statusFilter = options.status;
const withSubtasks = options.withSubtasks || false; const withSubtasks = options.withSubtasks || false;
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -1535,8 +1533,7 @@ function registerCommands(programInstance) {
taskMaster.getComplexityReportPath(), taskMaster.getComplexityReportPath(),
withSubtasks, withSubtasks,
'text', 'text',
tag, { projectRoot: taskMaster.getProjectRoot(), tag }
{ projectRoot: taskMaster.getProjectRoot() }
); );
}); });
@@ -1565,18 +1562,29 @@ function registerCommands(programInstance) {
'Path to the tasks file (relative to project root)', 'Path to the tasks file (relative to project root)',
TASKMASTER_TASKS_FILE // Allow file override TASKMASTER_TASKS_FILE // Allow file override
) // Allow file override ) // Allow file override
.option(
'-cr, --complexity-report <file>',
'Path to the report file',
COMPLEXITY_REPORT_FILE
)
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const initOptions = {
tasksPath: options.file || true tasksPath: options.file || true,
}); tag: options.tag
const tag = options.tag; };
if (options.complexityReport) {
initOptions.complexityReportPath = options.complexityReport;
}
const taskMaster = initTaskMaster(initOptions);
const tag = taskMaster.getCurrentTag();
// Show current tag context // Show current tag context
displayCurrentTagIndicator( displayCurrentTagIndicator(tag);
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
);
if (options.all) { if (options.all) {
// --- Handle expand --all --- // --- Handle expand --all ---
@@ -1589,7 +1597,11 @@ function registerCommands(programInstance) {
options.research, // Pass research flag options.research, // Pass research flag
options.prompt, // Pass additional context options.prompt, // Pass additional context
options.force, // Pass force flag options.force, // Pass force flag
{ projectRoot: taskMaster.getProjectRoot(), tag } // Pass context with projectRoot and tag {
projectRoot: taskMaster.getProjectRoot(),
tag,
complexityReportPath: taskMaster.getComplexityReportPath()
} // Pass context with projectRoot and tag
// outputFormat defaults to 'text' in expandAllTasks for CLI // outputFormat defaults to 'text' in expandAllTasks for CLI
); );
} catch (error) { } catch (error) {
@@ -1616,7 +1628,11 @@ function registerCommands(programInstance) {
options.num, options.num,
options.research, options.research,
options.prompt, options.prompt,
{ projectRoot: taskMaster.getProjectRoot(), tag }, // Pass context with projectRoot and tag {
projectRoot: taskMaster.getProjectRoot(),
tag,
complexityReportPath: taskMaster.getComplexityReportPath()
}, // Pass context with projectRoot and tag
options.force // Pass the force flag down options.force // Pass the force flag down
); );
// expandTask logs its own success/failure for single task // expandTask logs its own success/failure for single task
@@ -1669,34 +1685,28 @@ function registerCommands(programInstance) {
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const initOptions = { const initOptions = {
tasksPath: options.file || true // Tasks file is required to analyze tasksPath: options.file || true, // Tasks file is required to analyze
tag: options.tag
}; };
// Only include complexityReportPath if output is explicitly specified // Only include complexityReportPath if output is explicitly specified
if (options.output) { if (options.output) {
initOptions.complexityReportPath = options.output; initOptions.complexityReportPath = options.output;
} }
const taskMaster = initTaskMaster(initOptions); const taskMaster = initTaskMaster(initOptions);
const tag = options.tag;
const modelOverride = options.model; const modelOverride = options.model;
const thresholdScore = parseFloat(options.threshold); const thresholdScore = parseFloat(options.threshold);
const useResearch = options.research || false; const useResearch = options.research || false;
// Use the provided tag, or the current active tag, or default to 'master' // Use the provided tag, or the current active tag, or default to 'master'
const targetTag = const targetTag = taskMaster.getCurrentTag();
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(targetTag); displayCurrentTagIndicator(targetTag);
// Tag-aware output file naming: master -> task-complexity-report.json, other tags -> task-complexity-report_tagname.json // Use user's explicit output path if provided, otherwise use tag-aware default
const baseOutputPath = const outputPath = taskMaster.getComplexityReportPath();
taskMaster.getComplexityReportPath() ||
path.join(taskMaster.getProjectRoot(), COMPLEXITY_REPORT_FILE);
const outputPath =
options.output === COMPLEXITY_REPORT_FILE && targetTag !== 'master'
? baseOutputPath.replace('.json', `_${targetTag}.json`)
: options.output || baseOutputPath;
console.log( console.log(
chalk.blue( chalk.blue(
@@ -1777,9 +1787,12 @@ function registerCommands(programInstance) {
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (prompt, options) => { .action(async (prompt, options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const initOptions = {
tasksPath: options.file || true tasksPath: options.file || true,
}); tag: options.tag
};
const taskMaster = initTaskMaster(initOptions);
// Parameter validation // Parameter validation
if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) { if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
@@ -1879,8 +1892,7 @@ function registerCommands(programInstance) {
} }
} }
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -2113,17 +2125,17 @@ ${result.result}
.action(async (options) => { .action(async (options) => {
const taskIds = options.id; const taskIds = options.id;
const all = options.all; const all = options.all;
const tag = options.tag;
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const tag = taskMaster.getCurrentTag();
// Show current tag context // Show current tag context
displayCurrentTagIndicator( displayCurrentTagIndicator(tag);
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
);
if (!taskIds && !all) { if (!taskIds && !all) {
console.error( console.error(
@@ -2219,15 +2231,16 @@ ${result.result}
// Correctly determine projectRoot // Correctly determine projectRoot
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const projectRoot = taskMaster.getProjectRoot(); const projectRoot = taskMaster.getProjectRoot();
const tag = taskMaster.getCurrentTag();
// Show current tag context // Show current tag context
displayCurrentTagIndicator( displayCurrentTagIndicator(tag);
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
);
let manualTaskData = null; let manualTaskData = null;
if (isManualCreation) { if (isManualCreation) {
@@ -2263,7 +2276,7 @@ ${result.result}
const context = { const context = {
projectRoot, projectRoot,
tag: options.tag, tag,
commandName: 'add-task', commandName: 'add-task',
outputType: 'cli' outputType: 'cli'
}; };
@@ -2309,22 +2322,36 @@ ${result.result}
) )
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => { .action(async (options) => {
const tag = options.tag; const initOptions = {
tasksPath: options.file || true,
tag: options.tag
};
if (options.report && options.report !== COMPLEXITY_REPORT_FILE) {
initOptions.complexityReportPath = options.report;
}
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag,
complexityReportPath: options.report || false
}); });
const tag = taskMaster.getCurrentTag();
const context = {
projectRoot: taskMaster.getProjectRoot(),
tag
};
// Show current tag context // Show current tag context
displayCurrentTagIndicator( displayCurrentTagIndicator(tag);
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
);
await displayNextTask( await displayNextTask(
taskMaster.getTasksPath(), taskMaster.getTasksPath(),
taskMaster.getComplexityReportPath(), taskMaster.getComplexityReportPath(),
{ projectRoot: taskMaster.getProjectRoot(), tag } context
); );
}); });
@@ -2364,12 +2391,10 @@ ${result.result}
const idArg = taskId || options.id; const idArg = taskId || options.id;
const statusFilter = options.status; const statusFilter = options.status;
const tag = options.tag; const tag = taskMaster.getCurrentTag();
// Show current tag context // Show current tag context
displayCurrentTagIndicator( displayCurrentTagIndicator(tag);
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
);
if (!idArg) { if (!idArg) {
console.error(chalk.red('Error: Please provide a task ID')); console.error(chalk.red('Error: Please provide a task ID'));
@@ -2398,8 +2423,7 @@ ${result.result}
taskIds[0], taskIds[0],
taskMaster.getComplexityReportPath(), taskMaster.getComplexityReportPath(),
statusFilter, statusFilter,
tag, { projectRoot: taskMaster.getProjectRoot(), tag }
{ projectRoot: taskMaster.getProjectRoot() }
); );
} }
}); });
@@ -2417,17 +2441,19 @@ ${result.result}
) )
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => { .action(async (options) => {
const initOptions = {
tasksPath: options.file || true,
tag: options.tag
};
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster(initOptions);
tasksPath: options.file || true
});
const taskId = options.id; const taskId = options.id;
const dependencyId = options.dependsOn; const dependencyId = options.dependsOn;
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -2472,17 +2498,19 @@ ${result.result}
) )
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => { .action(async (options) => {
const initOptions = {
tasksPath: options.file || true,
tag: options.tag
};
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster(initOptions);
tasksPath: options.file || true
});
const taskId = options.id; const taskId = options.id;
const dependencyId = options.dependsOn; const dependencyId = options.dependsOn;
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -2527,14 +2555,16 @@ ${result.result}
) )
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => { .action(async (options) => {
const initOptions = {
tasksPath: options.file || true,
tag: options.tag
};
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster(initOptions);
tasksPath: options.file || true
});
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -2555,14 +2585,16 @@ ${result.result}
) )
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => { .action(async (options) => {
const initOptions = {
tasksPath: options.file || true,
tag: options.tag
};
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster(initOptions);
tasksPath: options.file || true
});
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -2583,26 +2615,21 @@ ${result.result}
) )
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster const initOptions = {
const taskMaster = initTaskMaster({ tag: options.tag
complexityReportPath: options.file || true };
});
// Use the provided tag, or the current active tag, or default to 'master' if (options.file && options.file !== COMPLEXITY_REPORT_FILE) {
const targetTag = initOptions.complexityReportPath = options.file;
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'; }
// Initialize TaskMaster
const taskMaster = initTaskMaster(initOptions);
// Show current tag context // Show current tag context
displayCurrentTagIndicator(targetTag); displayCurrentTagIndicator(taskMaster.getCurrentTag());
// Tag-aware report file naming: master -> task-complexity-report.json, other tags -> task-complexity-report_tagname.json await displayComplexityReport(taskMaster.getComplexityReportPath());
const baseReportPath = taskMaster.getComplexityReportPath();
const reportPath =
options.file === COMPLEXITY_REPORT_FILE && targetTag !== 'master'
? baseReportPath.replace('.json', `_${targetTag}.json`)
: baseReportPath;
await displayComplexityReport(reportPath);
}); });
// add-subtask command // add-subtask command
@@ -2632,7 +2659,8 @@ ${result.result}
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const parentId = options.parent; const parentId = options.parent;
@@ -2640,8 +2668,7 @@ ${result.result}
const generateFiles = !options.skipGenerate; const generateFiles = !options.skipGenerate;
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -2816,13 +2843,14 @@ ${result.result}
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const subtaskIds = options.id; const subtaskIds = options.id;
const convertToTask = options.convert || false; const convertToTask = options.convert || false;
const generateFiles = !options.skipGenerate; const generateFiles = !options.skipGenerate;
const tag = options.tag; const tag = taskMaster.getCurrentTag();
if (!subtaskIds) { if (!subtaskIds) {
console.error( console.error(
@@ -3117,14 +3145,14 @@ ${result.result}
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const taskIdsString = options.id; const taskIdsString = options.id;
// Resolve tag using standard pattern // Resolve tag using standard pattern
const tag = const tag = taskMaster.getCurrentTag();
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
// Show current tag context // Show current tag context
displayCurrentTagIndicator(tag); displayCurrentTagIndicator(tag);
@@ -3699,10 +3727,7 @@ Examples:
const taskMaster = initTaskMaster({}); const taskMaster = initTaskMaster({});
const projectRoot = taskMaster.getProjectRoot(); // Find project root for context const projectRoot = taskMaster.getProjectRoot(); // Find project root for context
const { response, setup } = options; const { response, setup } = options;
console.log( let responseLanguage = response !== undefined ? response : 'English';
chalk.blue('Response language set to:', JSON.stringify(options))
);
let responseLanguage = response || 'English';
if (setup) { if (setup) {
console.log( console.log(
chalk.blue('Starting interactive response language setup...') chalk.blue('Starting interactive response language setup...')
@@ -3744,6 +3769,7 @@ Examples:
`❌ Error setting response language: ${result.error.message}` `❌ Error setting response language: ${result.error.message}`
) )
); );
process.exit(1);
} }
}); });
@@ -3768,12 +3794,13 @@ Examples:
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const sourceId = options.from; const sourceId = options.from;
const destinationId = options.to; const destinationId = options.to;
const tag = options.tag; const tag = taskMaster.getCurrentTag();
if (!sourceId || !destinationId) { if (!sourceId || !destinationId) {
console.error( console.error(
@@ -4201,15 +4228,19 @@ Examples:
'-s, --status <status>', '-s, --status <status>',
'Show only tasks matching this status (e.g., pending, done)' 'Show only tasks matching this status (e.g., pending, done)'
) )
.option('-t, --tag <tag>', 'Tag to use for the task list (default: master)')
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || true tasksPath: options.file || true,
tag: options.tag
}); });
const withSubtasks = options.withSubtasks || false; const withSubtasks = options.withSubtasks || false;
const status = options.status || null; const status = options.status || null;
const tag = taskMaster.getCurrentTag();
console.log( console.log(
chalk.blue( chalk.blue(
`📝 Syncing tasks to README.md${withSubtasks ? ' (with subtasks)' : ''}${status ? ` (status: ${status})` : ''}...` `📝 Syncing tasks to README.md${withSubtasks ? ' (with subtasks)' : ''}${status ? ` (status: ${status})` : ''}...`
@@ -4219,7 +4250,8 @@ Examples:
const success = await syncTasksToReadme(taskMaster.getProjectRoot(), { const success = await syncTasksToReadme(taskMaster.getProjectRoot(), {
withSubtasks, withSubtasks,
status, status,
tasksPath: taskMaster.getTasksPath() tasksPath: taskMaster.getTasksPath(),
tag
}); });
if (!success) { if (!success) {
@@ -4941,6 +4973,33 @@ async function runCLI(argv = process.argv) {
} }
} }
/**
* Resolve the final complexity-report path.
* Rules:
* 1. If caller passes --output, always respect it.
* 2. If no explicit output AND tag === 'master' → default report file
* 3. If no explicit output AND tag !== 'master' → append _<tag>.json
*
* @param {string|undefined} outputOpt --output value from CLI (may be undefined)
* @param {string} targetTag resolved tag (defaults to 'master')
* @param {string} projectRoot absolute project root
* @returns {string} absolute path for the report
*/
export function resolveComplexityReportPath({
projectRoot,
tag = 'master',
output // may be undefined
}) {
// 1. user knows best
if (output) {
return path.isAbsolute(output) ? output : path.join(projectRoot, output);
}
// 2. default naming
const base = path.join(projectRoot, COMPLEXITY_REPORT_FILE);
return tag !== 'master' ? base.replace('.json', `_${tag}.json`) : base;
}
export { export {
registerCommands, registerCommands,
setupCLI, setupCLI,

View File

@@ -27,6 +27,8 @@ import { generateTaskFiles } from './task-manager.js';
* @param {number|string} taskId - ID of the task to add dependency to * @param {number|string} taskId - ID of the task to add dependency to
* @param {number|string} dependencyId - ID of the task to add as dependency * @param {number|string} dependencyId - ID of the task to add as dependency
* @param {Object} context - Context object containing projectRoot and tag information * @param {Object} context - Context object containing projectRoot and tag information
* @param {string} [context.projectRoot] - Project root path
* @param {string} [context.tag] - Tag for the task
*/ */
async function addDependency(tasksPath, taskId, dependencyId, context = {}) { async function addDependency(tasksPath, taskId, dependencyId, context = {}) {
log('info', `Adding dependency ${dependencyId} to task ${taskId}...`); log('info', `Adding dependency ${dependencyId} to task ${taskId}...`);
@@ -214,6 +216,8 @@ async function addDependency(tasksPath, taskId, dependencyId, context = {}) {
* @param {number|string} taskId - ID of the task to remove dependency from * @param {number|string} taskId - ID of the task to remove dependency from
* @param {number|string} dependencyId - ID of the task to remove as dependency * @param {number|string} dependencyId - ID of the task to remove as dependency
* @param {Object} context - Context object containing projectRoot and tag information * @param {Object} context - Context object containing projectRoot and tag information
* @param {string} [context.projectRoot] - Project root path
* @param {string} [context.tag] - Tag for the task
*/ */
async function removeDependency(tasksPath, taskId, dependencyId, context = {}) { async function removeDependency(tasksPath, taskId, dependencyId, context = {}) {
log('info', `Removing dependency ${dependencyId} from task ${taskId}...`); log('info', `Removing dependency ${dependencyId} from task ${taskId}...`);

View File

@@ -303,7 +303,7 @@
"output": 3.0 "output": 3.0
}, },
"allowed_roles": ["main", "fallback"], "allowed_roles": ["main", "fallback"],
"max_tokens": 16384 "max_tokens": 131072
}, },
{ {
"id": "llama-3.3-70b-versatile", "id": "llama-3.3-70b-versatile",

View File

@@ -91,11 +91,12 @@ function createEndMarker() {
* @param {string} options.status - Filter by status (e.g., 'pending', 'done') * @param {string} options.status - Filter by status (e.g., 'pending', 'done')
* @param {string} options.tasksPath - Custom path to tasks.json * @param {string} options.tasksPath - Custom path to tasks.json
* @returns {boolean} - True if sync was successful, false otherwise * @returns {boolean} - True if sync was successful, false otherwise
* TODO: Add tag support - this is not currently supported how we want to handle this - Parthy
*/ */
export async function syncTasksToReadme(projectRoot = null, options = {}) { export async function syncTasksToReadme(projectRoot = null, options = {}) {
try { try {
const actualProjectRoot = projectRoot || findProjectRoot() || '.'; const actualProjectRoot = projectRoot || findProjectRoot() || '.';
const { withSubtasks = false, status, tasksPath } = options; const { withSubtasks = false, status, tasksPath, tag } = options;
// Get current tasks using the list-tasks functionality with markdown-readme format // Get current tasks using the list-tasks functionality with markdown-readme format
const tasksOutput = await listTasks( const tasksOutput = await listTasks(
@@ -104,7 +105,8 @@ export async function syncTasksToReadme(projectRoot = null, options = {}) {
status, status,
null, null,
withSubtasks, withSubtasks,
'markdown-readme' 'markdown-readme',
{ projectRoot, tag }
); );
if (!tasksOutput) { if (!tasksOutput) {

View File

@@ -12,6 +12,8 @@ import generateTaskFiles from './generate-task-files.js';
* @param {Object} newSubtaskData - Data for creating a new subtask (used if existingTaskId is null) * @param {Object} newSubtaskData - Data for creating a new subtask (used if existingTaskId is null)
* @param {boolean} generateFiles - Whether to regenerate task files after adding the subtask * @param {boolean} generateFiles - Whether to regenerate task files after adding the subtask
* @param {Object} context - Context object containing projectRoot and tag information * @param {Object} context - Context object containing projectRoot and tag information
* @param {string} context.projectRoot - Project root path
* @param {string} context.tag - Tag for the task
* @returns {Object} The newly created or converted subtask * @returns {Object} The newly created or converted subtask
*/ */
async function addSubtask( async function addSubtask(
@@ -22,13 +24,12 @@ async function addSubtask(
generateFiles = true, generateFiles = true,
context = {} context = {}
) { ) {
const { projectRoot, tag } = context;
try { try {
log('info', `Adding subtask to parent task ${parentId}...`); log('info', `Adding subtask to parent task ${parentId}...`);
const currentTag =
context.tag || getCurrentTag(context.projectRoot) || 'master';
// Read the existing tasks with proper context // Read the existing tasks with proper context
const data = readJSON(tasksPath, context.projectRoot, currentTag); const data = readJSON(tasksPath, projectRoot, tag);
if (!data || !data.tasks) { if (!data || !data.tasks) {
throw new Error(`Invalid or missing tasks file at ${tasksPath}`); throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
} }
@@ -139,7 +140,7 @@ async function addSubtask(
} }
// Write the updated tasks back to the file with proper context // Write the updated tasks back to the file with proper context
writeJSON(tasksPath, data, context.projectRoot, currentTag); writeJSON(tasksPath, data, projectRoot, tag);
// Generate task files if requested // Generate task files if requested
if (generateFiles) { if (generateFiles) {

View File

@@ -22,8 +22,7 @@ import {
truncate, truncate,
ensureTagMetadata, ensureTagMetadata,
performCompleteTagMigration, performCompleteTagMigration,
markMigrationForNotice, markMigrationForNotice
getCurrentTag
} from '../utils.js'; } from '../utils.js';
import { generateObjectService } from '../ai-services-unified.js'; import { generateObjectService } from '../ai-services-unified.js';
import { getDefaultPriority } from '../config-manager.js'; import { getDefaultPriority } from '../config-manager.js';
@@ -93,7 +92,7 @@ function getAllTasks(rawData) {
* @param {string} [context.projectRoot] - Project root path (for MCP/env fallback) * @param {string} [context.projectRoot] - Project root path (for MCP/env fallback)
* @param {string} [context.commandName] - The name of the command being executed (for telemetry) * @param {string} [context.commandName] - The name of the command being executed (for telemetry)
* @param {string} [context.outputType] - The output type ('cli' or 'mcp', for telemetry) * @param {string} [context.outputType] - The output type ('cli' or 'mcp', for telemetry)
* @param {string} [tag] - Tag for the task (optional) * @param {string} [context.tag] - Tag for the task (optional)
* @returns {Promise<object>} An object containing newTaskId and telemetryData * @returns {Promise<object>} An object containing newTaskId and telemetryData
*/ */
async function addTask( async function addTask(
@@ -104,10 +103,10 @@ async function addTask(
context = {}, context = {},
outputFormat = 'text', // Default to text for CLI outputFormat = 'text', // Default to text for CLI
manualTaskData = null, manualTaskData = null,
useResearch = false, useResearch = false
tag = null
) { ) {
const { session, mcpLog, projectRoot, commandName, outputType } = context; const { session, mcpLog, projectRoot, commandName, outputType, tag } =
context;
const isMCP = !!mcpLog; const isMCP = !!mcpLog;
// Create a consistent logFn object regardless of context // Create a consistent logFn object regardless of context
@@ -224,7 +223,7 @@ async function addTask(
try { try {
// Read the existing tasks - IMPORTANT: Read the raw data without tag resolution // Read the existing tasks - IMPORTANT: Read the raw data without tag resolution
let rawData = readJSON(tasksPath, projectRoot); // No tag parameter let rawData = readJSON(tasksPath, projectRoot, tag); // No tag parameter
// Handle the case where readJSON returns resolved data with _rawTaggedData // Handle the case where readJSON returns resolved data with _rawTaggedData
if (rawData && rawData._rawTaggedData) { if (rawData && rawData._rawTaggedData) {
@@ -279,8 +278,7 @@ async function addTask(
} }
// Use the provided tag, or the current active tag, or default to 'master' // Use the provided tag, or the current active tag, or default to 'master'
const targetTag = const targetTag = tag;
tag || context.tag || getCurrentTag(projectRoot) || 'master';
// Ensure the target tag exists // Ensure the target tag exists
if (!rawData[targetTag]) { if (!rawData[targetTag]) {
@@ -389,7 +387,7 @@ async function addTask(
report(`Generating task data with AI with prompt:\n${prompt}`, 'info'); report(`Generating task data with AI with prompt:\n${prompt}`, 'info');
// --- Use the new ContextGatherer --- // --- Use the new ContextGatherer ---
const contextGatherer = new ContextGatherer(projectRoot); const contextGatherer = new ContextGatherer(projectRoot, tag);
const gatherResult = await contextGatherer.gather({ const gatherResult = await contextGatherer.gather({
semanticQuery: prompt, semanticQuery: prompt,
dependencyTasks: numericDependencies, dependencyTasks: numericDependencies,

View File

@@ -19,6 +19,7 @@ import {
COMPLEXITY_REPORT_FILE, COMPLEXITY_REPORT_FILE,
LEGACY_TASKS_FILE LEGACY_TASKS_FILE
} from '../../../src/constants/paths.js'; } from '../../../src/constants/paths.js';
import { resolveComplexityReportOutputPath } from '../../../src/utils/path-utils.js';
import { ContextGatherer } from '../utils/contextGatherer.js'; import { ContextGatherer } from '../utils/contextGatherer.js';
import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js'; import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
import { flattenTasksWithSubtasks } from '../utils.js'; import { flattenTasksWithSubtasks } from '../utils.js';
@@ -71,6 +72,7 @@ Do not include any explanatory text, markdown formatting, or code block markers
* @param {string|number} [options.threshold] - Complexity threshold * @param {string|number} [options.threshold] - Complexity threshold
* @param {boolean} [options.research] - Use research role * @param {boolean} [options.research] - Use research role
* @param {string} [options.projectRoot] - Project root path (for MCP/env fallback). * @param {string} [options.projectRoot] - Project root path (for MCP/env fallback).
* @param {string} [options.tag] - Tag for the task
* @param {string} [options.id] - Comma-separated list of task IDs to analyze specifically * @param {string} [options.id] - Comma-separated list of task IDs to analyze specifically
* @param {number} [options.from] - Starting task ID in a range to analyze * @param {number} [options.from] - Starting task ID in a range to analyze
* @param {number} [options.to] - Ending task ID in a range to analyze * @param {number} [options.to] - Ending task ID in a range to analyze
@@ -84,7 +86,6 @@ Do not include any explanatory text, markdown formatting, or code block markers
async function analyzeTaskComplexity(options, context = {}) { async function analyzeTaskComplexity(options, context = {}) {
const { session, mcpLog } = context; const { session, mcpLog } = context;
const tasksPath = options.file || LEGACY_TASKS_FILE; const tasksPath = options.file || LEGACY_TASKS_FILE;
const outputPath = options.output || COMPLEXITY_REPORT_FILE;
const thresholdScore = parseFloat(options.threshold || '5'); const thresholdScore = parseFloat(options.threshold || '5');
const useResearch = options.research || false; const useResearch = options.research || false;
const projectRoot = options.projectRoot; const projectRoot = options.projectRoot;
@@ -109,6 +110,13 @@ async function analyzeTaskComplexity(options, context = {}) {
} }
}; };
// Resolve output path using tag-aware resolution
const outputPath = resolveComplexityReportOutputPath(
options.output,
{ projectRoot, tag },
reportLog
);
if (outputFormat === 'text') { if (outputFormat === 'text') {
console.log( console.log(
chalk.blue( chalk.blue(
@@ -220,7 +228,7 @@ async function analyzeTaskComplexity(options, context = {}) {
let gatheredContext = ''; let gatheredContext = '';
if (originalData && originalData.tasks.length > 0) { if (originalData && originalData.tasks.length > 0) {
try { try {
const contextGatherer = new ContextGatherer(projectRoot); const contextGatherer = new ContextGatherer(projectRoot, tag);
const allTasksFlat = flattenTasksWithSubtasks(originalData.tasks); const allTasksFlat = flattenTasksWithSubtasks(originalData.tasks);
const fuzzySearch = new FuzzyTaskSearch( const fuzzySearch = new FuzzyTaskSearch(
allTasksFlat, allTasksFlat,
@@ -535,7 +543,7 @@ async function analyzeTaskComplexity(options, context = {}) {
} }
} }
// Merge with existing report // Merge with existing report - only keep entries from the current tag
let finalComplexityAnalysis = []; let finalComplexityAnalysis = [];
if (existingReport && Array.isArray(existingReport.complexityAnalysis)) { if (existingReport && Array.isArray(existingReport.complexityAnalysis)) {
@@ -544,10 +552,14 @@ async function analyzeTaskComplexity(options, context = {}) {
complexityAnalysis.map((item) => item.taskId) complexityAnalysis.map((item) => item.taskId)
); );
// Keep existing entries that weren't in this analysis run // Keep existing entries that weren't in this analysis run AND belong to the current tag
// We determine tag membership by checking if the task ID exists in the current tag's tasks
const currentTagTaskIds = new Set(tasksData.tasks.map((t) => t.id));
const existingEntriesNotAnalyzed = const existingEntriesNotAnalyzed =
existingReport.complexityAnalysis.filter( existingReport.complexityAnalysis.filter(
(item) => !analyzedTaskIds.has(item.taskId) (item) =>
!analyzedTaskIds.has(item.taskId) &&
currentTagTaskIds.has(item.taskId) // Only keep entries for tasks in current tag
); );
// Combine with new analysis // Combine with new analysis
@@ -557,7 +569,7 @@ async function analyzeTaskComplexity(options, context = {}) {
]; ];
reportLog( reportLog(
`Merged ${complexityAnalysis.length} new analyses with ${existingEntriesNotAnalyzed.length} existing entries`, `Merged ${complexityAnalysis.length} new analyses with ${existingEntriesNotAnalyzed.length} existing entries from current tag`,
'info' 'info'
); );
} else { } else {

View File

@@ -11,6 +11,8 @@ import { displayBanner } from '../ui.js';
* @param {string} tasksPath - Path to the tasks.json file * @param {string} tasksPath - Path to the tasks.json file
* @param {string} taskIds - Task IDs to clear subtasks from * @param {string} taskIds - Task IDs to clear subtasks from
* @param {Object} context - Context object containing projectRoot and tag * @param {Object} context - Context object containing projectRoot and tag
* @param {string} [context.projectRoot] - Project root path
* @param {string} [context.tag] - Tag for the task
*/ */
function clearSubtasks(tasksPath, taskIds, context = {}) { function clearSubtasks(tasksPath, taskIds, context = {}) {
const { projectRoot, tag } = context; const { projectRoot, tag } = context;

View File

@@ -20,6 +20,8 @@ import boxen from 'boxen';
* @param {Object} context - Context object containing session and mcpLog. * @param {Object} context - Context object containing session and mcpLog.
* @param {Object} [context.session] - Session object from MCP. * @param {Object} [context.session] - Session object from MCP.
* @param {Object} [context.mcpLog] - MCP logger object. * @param {Object} [context.mcpLog] - MCP logger object.
* @param {string} [context.projectRoot] - Project root path
* @param {string} [context.tag] - Tag for the task
* @param {string} [outputFormat='text'] - Output format ('text' or 'json'). MCP calls should use 'json'. * @param {string} [outputFormat='text'] - Output format ('text' or 'json'). MCP calls should use 'json'.
* @returns {Promise<{success: boolean, expandedCount: number, failedCount: number, skippedCount: number, tasksToExpand: number, telemetryData: Array<Object>}>} - Result summary. * @returns {Promise<{success: boolean, expandedCount: number, failedCount: number, skippedCount: number, tasksToExpand: number, telemetryData: Array<Object>}>} - Result summary.
*/ */
@@ -32,12 +34,7 @@ async function expandAllTasks(
context = {}, context = {},
outputFormat = 'text' // Assume text default for CLI outputFormat = 'text' // Assume text default for CLI
) { ) {
const { const { session, mcpLog, projectRoot: providedProjectRoot, tag } = context;
session,
mcpLog,
projectRoot: providedProjectRoot,
tag: contextTag
} = context;
const isMCPCall = !!mcpLog; // Determine if called from MCP const isMCPCall = !!mcpLog; // Determine if called from MCP
const projectRoot = providedProjectRoot || findProjectRoot(); const projectRoot = providedProjectRoot || findProjectRoot();
@@ -79,7 +76,7 @@ async function expandAllTasks(
try { try {
logger.info(`Reading tasks from ${tasksPath}`); logger.info(`Reading tasks from ${tasksPath}`);
const data = readJSON(tasksPath, projectRoot, contextTag); const data = readJSON(tasksPath, projectRoot, tag);
if (!data || !data.tasks) { if (!data || !data.tasks) {
throw new Error(`Invalid tasks data in ${tasksPath}`); throw new Error(`Invalid tasks data in ${tasksPath}`);
} }
@@ -129,7 +126,7 @@ async function expandAllTasks(
numSubtasks, numSubtasks,
useResearch, useResearch,
additionalContext, additionalContext,
{ ...context, projectRoot, tag: data.tag || contextTag }, // Pass the whole context object with projectRoot and resolved tag { ...context, projectRoot, tag: data.tag || tag }, // Pass the whole context object with projectRoot and resolved tag
force force
); );
expandedCount++; expandedCount++;

View File

@@ -40,8 +40,10 @@ const subtaskSchema = z
.min(10) .min(10)
.describe('Detailed description of the subtask'), .describe('Detailed description of the subtask'),
dependencies: z dependencies: z
.array(z.number().int()) .array(z.string())
.describe('IDs of prerequisite subtasks within this expansion'), .describe(
'Array of subtask dependencies within the same parent task. Use format ["parentTaskId.1", "parentTaskId.2"]. Subtasks can only depend on siblings, not external tasks.'
),
details: z.string().min(20).describe('Implementation details and guidance'), details: z.string().min(20).describe('Implementation details and guidance'),
status: z status: z
.string() .string()
@@ -235,11 +237,9 @@ function parseSubtasksFromText(
...rawSubtask, ...rawSubtask,
id: currentId, id: currentId,
dependencies: Array.isArray(rawSubtask.dependencies) dependencies: Array.isArray(rawSubtask.dependencies)
? rawSubtask.dependencies ? rawSubtask.dependencies.filter(
.map((dep) => (typeof dep === 'string' ? parseInt(dep, 10) : dep)) (dep) =>
.filter( typeof dep === 'string' && dep.startsWith(`${parentTaskId}.`)
(depId) =>
!Number.isNaN(depId) && depId >= startId && depId < currentId
) )
: [], : [],
status: 'pending' status: 'pending'
@@ -290,6 +290,8 @@ function parseSubtasksFromText(
* @param {Object} context - Context object containing session and mcpLog. * @param {Object} context - Context object containing session and mcpLog.
* @param {Object} [context.session] - Session object from MCP. * @param {Object} [context.session] - Session object from MCP.
* @param {Object} [context.mcpLog] - MCP logger object. * @param {Object} [context.mcpLog] - MCP logger object.
* @param {string} [context.projectRoot] - Project root path
* @param {string} [context.tag] - Tag for the task
* @param {boolean} [force=false] - If true, replace existing subtasks; otherwise, append. * @param {boolean} [force=false] - If true, replace existing subtasks; otherwise, append.
* @returns {Promise<Object>} The updated parent task object with new subtasks. * @returns {Promise<Object>} The updated parent task object with new subtasks.
* @throws {Error} If task not found, AI service fails, or parsing fails. * @throws {Error} If task not found, AI service fails, or parsing fails.
@@ -303,7 +305,13 @@ async function expandTask(
context = {}, context = {},
force = false force = false
) { ) {
const { session, mcpLog, projectRoot: contextProjectRoot, tag } = context; const {
session,
mcpLog,
projectRoot: contextProjectRoot,
tag,
complexityReportPath
} = context;
const outputFormat = mcpLog ? 'json' : 'text'; const outputFormat = mcpLog ? 'json' : 'text';
// Determine projectRoot: Use from context if available, otherwise derive from tasksPath // Determine projectRoot: Use from context if available, otherwise derive from tasksPath
@@ -350,7 +358,7 @@ async function expandTask(
// --- Context Gathering --- // --- Context Gathering ---
let gatheredContext = ''; let gatheredContext = '';
try { try {
const contextGatherer = new ContextGatherer(projectRoot); const contextGatherer = new ContextGatherer(projectRoot, tag);
const allTasksFlat = flattenTasksWithSubtasks(data.tasks); const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'expand-task'); const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'expand-task');
const searchQuery = `${task.title} ${task.description}`; const searchQuery = `${task.title} ${task.description}`;
@@ -379,17 +387,10 @@ async function expandTask(
// --- Complexity Report Integration --- // --- Complexity Report Integration ---
let finalSubtaskCount; let finalSubtaskCount;
let complexityReasoningContext = ''; let complexityReasoningContext = '';
// Use tag-aware complexity report path
const complexityReportPath = getTagAwareFilePath(
COMPLEXITY_REPORT_FILE,
tag,
projectRoot
);
let taskAnalysis = null; let taskAnalysis = null;
logger.info( logger.info(
`Looking for complexity report at: ${complexityReportPath}${tag && tag !== 'master' ? ` (tag-specific for '${tag}')` : ''}` `Looking for complexity report at: ${complexityReportPath}${tag !== 'master' ? ` (tag-specific for '${tag}')` : ''}`
); );
try { try {

View File

@@ -12,16 +12,20 @@ import { getDebugFlag } from '../config-manager.js';
* @param {string} tasksPath - Path to the tasks.json file * @param {string} tasksPath - Path to the tasks.json file
* @param {string} outputDir - Output directory for task files * @param {string} outputDir - Output directory for task files
* @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot, tag) * @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot, tag)
* @param {string} [options.projectRoot] - Project root path
* @param {string} [options.tag] - Tag for the task
* @param {Object} [options.mcpLog] - MCP logger object
* @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode * @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
*/ */
function generateTaskFiles(tasksPath, outputDir, options = {}) { function generateTaskFiles(tasksPath, outputDir, options = {}) {
try { try {
const isMcpMode = !!options?.mcpLog; const isMcpMode = !!options?.mcpLog;
const { projectRoot, tag } = options;
// 1. Read the raw data structure, ensuring we have all tags. // 1. Read the raw data structure, ensuring we have all tags.
// We call readJSON without a specific tag to get the resolved default view, // We call readJSON without a specific tag to get the resolved default view,
// which correctly contains the full structure in `_rawTaggedData`. // which correctly contains the full structure in `_rawTaggedData`.
const resolvedData = readJSON(tasksPath, options.projectRoot); const resolvedData = readJSON(tasksPath, projectRoot, tag);
if (!resolvedData) { if (!resolvedData) {
throw new Error(`Could not read or parse tasks file: ${tasksPath}`); throw new Error(`Could not read or parse tasks file: ${tasksPath}`);
} }
@@ -29,13 +33,10 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
const rawData = resolvedData._rawTaggedData || resolvedData; const rawData = resolvedData._rawTaggedData || resolvedData;
// 2. Determine the target tag we need to generate files for. // 2. Determine the target tag we need to generate files for.
const targetTag = options.tag || resolvedData.tag || 'master'; const tagData = rawData[tag];
const tagData = rawData[targetTag];
if (!tagData || !tagData.tasks) { if (!tagData || !tagData.tasks) {
throw new Error( throw new Error(`Tag '${tag}' not found or has no tasks in the data.`);
`Tag '${targetTag}' not found or has no tasks in the data.`
);
} }
const tasksForGeneration = tagData.tasks; const tasksForGeneration = tagData.tasks;
@@ -46,15 +47,15 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
log( log(
'info', 'info',
`Preparing to regenerate ${tasksForGeneration.length} task files for tag '${targetTag}'` `Preparing to regenerate ${tasksForGeneration.length} task files for tag '${tag}'`
); );
// 3. Validate dependencies using the FULL, raw data structure to prevent data loss. // 3. Validate dependencies using the FULL, raw data structure to prevent data loss.
validateAndFixDependencies( validateAndFixDependencies(
rawData, // Pass the entire object with all tags rawData, // Pass the entire object with all tags
tasksPath, tasksPath,
options.projectRoot, projectRoot,
targetTag // Provide the current tag context for the operation tag // Provide the current tag context for the operation
); );
const allTasksInTag = tagData.tasks; const allTasksInTag = tagData.tasks;
@@ -66,14 +67,14 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
const files = fs.readdirSync(outputDir); const files = fs.readdirSync(outputDir);
// Tag-aware file patterns: master -> task_001.txt, other tags -> task_001_tagname.txt // Tag-aware file patterns: master -> task_001.txt, other tags -> task_001_tagname.txt
const masterFilePattern = /^task_(\d+)\.txt$/; const masterFilePattern = /^task_(\d+)\.txt$/;
const taggedFilePattern = new RegExp(`^task_(\\d+)_${targetTag}\\.txt$`); const taggedFilePattern = new RegExp(`^task_(\\d+)_${tag}\\.txt$`);
const orphanedFiles = files.filter((file) => { const orphanedFiles = files.filter((file) => {
let match = null; let match = null;
let fileTaskId = null; let fileTaskId = null;
// Check if file belongs to current tag // Check if file belongs to current tag
if (targetTag === 'master') { if (tag === 'master') {
match = file.match(masterFilePattern); match = file.match(masterFilePattern);
if (match) { if (match) {
fileTaskId = parseInt(match[1], 10); fileTaskId = parseInt(match[1], 10);
@@ -94,7 +95,7 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
if (orphanedFiles.length > 0) { if (orphanedFiles.length > 0) {
log( log(
'info', 'info',
`Found ${orphanedFiles.length} orphaned task files to remove for tag '${targetTag}'` `Found ${orphanedFiles.length} orphaned task files to remove for tag '${tag}'`
); );
orphanedFiles.forEach((file) => { orphanedFiles.forEach((file) => {
const filePath = path.join(outputDir, file); const filePath = path.join(outputDir, file);
@@ -108,13 +109,13 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
} }
// Generate task files for the target tag // Generate task files for the target tag
log('info', `Generating individual task files for tag '${targetTag}'...`); log('info', `Generating individual task files for tag '${tag}'...`);
tasksForGeneration.forEach((task) => { tasksForGeneration.forEach((task) => {
// Tag-aware file naming: master -> task_001.txt, other tags -> task_001_tagname.txt // Tag-aware file naming: master -> task_001.txt, other tags -> task_001_tagname.txt
const taskFileName = const taskFileName =
targetTag === 'master' tag === 'master'
? `task_${task.id.toString().padStart(3, '0')}.txt` ? `task_${task.id.toString().padStart(3, '0')}.txt`
: `task_${task.id.toString().padStart(3, '0')}_${targetTag}.txt`; : `task_${task.id.toString().padStart(3, '0')}_${tag}.txt`;
const taskPath = path.join(outputDir, taskFileName); const taskPath = path.join(outputDir, taskFileName);
@@ -174,7 +175,7 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
log( log(
'success', 'success',
`All ${tasksForGeneration.length} tasks for tag '${targetTag}' have been generated into '${outputDir}'.` `All ${tasksForGeneration.length} tasks for tag '${tag}' have been generated into '${outputDir}'.`
); );
if (isMcpMode) { if (isMcpMode) {

View File

@@ -26,8 +26,9 @@ import {
* @param {string} reportPath - Path to the complexity report * @param {string} reportPath - Path to the complexity report
* @param {boolean} withSubtasks - Whether to show subtasks * @param {boolean} withSubtasks - Whether to show subtasks
* @param {string} outputFormat - Output format (text or json) * @param {string} outputFormat - Output format (text or json)
* @param {string} tag - Optional tag to override current tag resolution * @param {Object} context - Context object (required)
* @param {Object} context - Optional context object containing projectRoot and other options * @param {string} context.projectRoot - Project root path
* @param {string} context.tag - Tag for the task
* @returns {Object} - Task list result for json format * @returns {Object} - Task list result for json format
*/ */
function listTasks( function listTasks(
@@ -36,18 +37,18 @@ function listTasks(
reportPath = null, reportPath = null,
withSubtasks = false, withSubtasks = false,
outputFormat = 'text', outputFormat = 'text',
tag = null,
context = {} context = {}
) { ) {
const { projectRoot, tag } = context;
try { try {
// Extract projectRoot from context if provided // Extract projectRoot from context if provided
const projectRoot = context.projectRoot || null;
const data = readJSON(tasksPath, projectRoot, tag); // Pass projectRoot to readJSON const data = readJSON(tasksPath, projectRoot, tag); // Pass projectRoot to readJSON
if (!data || !data.tasks) { if (!data || !data.tasks) {
throw new Error(`No valid tasks found in ${tasksPath}`); throw new Error(`No valid tasks found in ${tasksPath}`);
} }
// Add complexity scores to tasks if report exists // Add complexity scores to tasks if report exists
// `reportPath` is already tag-aware (resolved at the CLI boundary).
const complexityReport = readComplexityReport(reportPath); const complexityReport = readComplexityReport(reportPath);
// Apply complexity scores to tasks // Apply complexity scores to tasks
if (complexityReport && complexityReport.complexityAnalysis) { if (complexityReport && complexityReport.complexityAnalysis) {

View File

@@ -25,6 +25,10 @@ import { findConfigPath } from '../../../src/utils/path-utils.js';
import { log } from '../utils.js'; import { log } from '../utils.js';
import { CUSTOM_PROVIDERS } from '../../../src/constants/providers.js'; import { CUSTOM_PROVIDERS } from '../../../src/constants/providers.js';
// Constants
const CONFIG_MISSING_ERROR =
'The configuration file is missing. Run "task-master init" to create it.';
/** /**
* Fetches the list of models from OpenRouter API. * Fetches the list of models from OpenRouter API.
* @returns {Promise<Array|null>} A promise that resolves with the list of model IDs or null if fetch fails. * @returns {Promise<Array|null>} A promise that resolves with the list of model IDs or null if fetch fails.
@@ -168,9 +172,7 @@ async function getModelConfiguration(options = {}) {
); );
if (!configExists) { if (!configExists) {
throw new Error( throw new Error(CONFIG_MISSING_ERROR);
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
} }
try { try {
@@ -298,9 +300,7 @@ async function getAvailableModelsList(options = {}) {
); );
if (!configExists) { if (!configExists) {
throw new Error( throw new Error(CONFIG_MISSING_ERROR);
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
} }
try { try {
@@ -391,9 +391,7 @@ async function setModel(role, modelId, options = {}) {
); );
if (!configExists) { if (!configExists) {
throw new Error( throw new Error(CONFIG_MISSING_ERROR);
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
} }
// Validate role // Validate role

View File

@@ -1,11 +1,5 @@
import path from 'path'; import path from 'path';
import { import { log, readJSON, writeJSON, setTasksForTag } from '../utils.js';
log,
readJSON,
writeJSON,
getCurrentTag,
setTasksForTag
} from '../utils.js';
import { isTaskDependentOn } from '../task-manager.js'; import { isTaskDependentOn } from '../task-manager.js';
import generateTaskFiles from './generate-task-files.js'; import generateTaskFiles from './generate-task-files.js';
@@ -27,6 +21,7 @@ async function moveTask(
generateFiles = false, generateFiles = false,
options = {} options = {}
) { ) {
const { projectRoot, tag } = options;
// Check if we have comma-separated IDs (batch move) // Check if we have comma-separated IDs (batch move)
const sourceIds = sourceId.split(',').map((id) => id.trim()); const sourceIds = sourceId.split(',').map((id) => id.trim());
const destinationIds = destinationId.split(',').map((id) => id.trim()); const destinationIds = destinationId.split(',').map((id) => id.trim());
@@ -53,7 +48,10 @@ async function moveTask(
// Generate files once at the end if requested // Generate files once at the end if requested
if (generateFiles) { if (generateFiles) {
await generateTaskFiles(tasksPath, path.dirname(tasksPath)); await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
tag: tag,
projectRoot: projectRoot
});
} }
return { return {
@@ -64,7 +62,7 @@ async function moveTask(
// Single move logic // Single move logic
// Read the raw data without tag resolution to preserve tagged structure // Read the raw data without tag resolution to preserve tagged structure
let rawData = readJSON(tasksPath, options.projectRoot); // No tag parameter let rawData = readJSON(tasksPath, projectRoot, tag);
// Handle the case where readJSON returns resolved data with _rawTaggedData // Handle the case where readJSON returns resolved data with _rawTaggedData
if (rawData && rawData._rawTaggedData) { if (rawData && rawData._rawTaggedData) {
@@ -72,27 +70,19 @@ async function moveTask(
rawData = rawData._rawTaggedData; rawData = rawData._rawTaggedData;
} }
// Determine the current tag
const currentTag =
options.tag || getCurrentTag(options.projectRoot) || 'master';
// Ensure the tag exists in the raw data // Ensure the tag exists in the raw data
if ( if (!rawData || !rawData[tag] || !Array.isArray(rawData[tag].tasks)) {
!rawData ||
!rawData[currentTag] ||
!Array.isArray(rawData[currentTag].tasks)
) {
throw new Error( throw new Error(
`Invalid tasks file or tag "${currentTag}" not found at ${tasksPath}` `Invalid tasks file or tag "${tag}" not found at ${tasksPath}`
); );
} }
// Get the tasks for the current tag // Get the tasks for the current tag
const tasks = rawData[currentTag].tasks; const tasks = rawData[tag].tasks;
log( log(
'info', 'info',
`Moving task/subtask ${sourceId} to ${destinationId} (tag: ${currentTag})` `Moving task/subtask ${sourceId} to ${destinationId} (tag: ${tag})`
); );
// Parse source and destination IDs // Parse source and destination IDs
@@ -116,14 +106,17 @@ async function moveTask(
} }
// Update the data structure with the modified tasks // Update the data structure with the modified tasks
rawData[currentTag].tasks = tasks; rawData[tag].tasks = tasks;
// Always write the data object, never the _rawTaggedData directly // Always write the data object, never the _rawTaggedData directly
// The writeJSON function will filter out _rawTaggedData automatically // The writeJSON function will filter out _rawTaggedData automatically
writeJSON(tasksPath, rawData, options.projectRoot, currentTag); writeJSON(tasksPath, rawData, options.projectRoot, tag);
if (generateFiles) { if (generateFiles) {
await generateTaskFiles(tasksPath, path.dirname(tasksPath)); await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
tag: tag,
projectRoot: projectRoot
});
} }
return result; return result;

View File

@@ -19,7 +19,6 @@ import {
import { generateObjectService } from '../ai-services-unified.js'; import { generateObjectService } from '../ai-services-unified.js';
import { getDebugFlag } from '../config-manager.js'; import { getDebugFlag } from '../config-manager.js';
import { getPromptManager } from '../prompt-manager.js'; import { getPromptManager } from '../prompt-manager.js';
import generateTaskFiles from './generate-task-files.js';
import { displayAiUsageSummary } from '../ui.js'; import { displayAiUsageSummary } from '../ui.js';
// Define the Zod schema for a SINGLE task object // Define the Zod schema for a SINGLE task object
@@ -76,7 +75,7 @@ async function parsePRD(prdPath, tasksPath, numTasks, options = {}) {
const outputFormat = isMCP ? 'json' : 'text'; const outputFormat = isMCP ? 'json' : 'text';
// Use the provided tag, or the current active tag, or default to 'master' // Use the provided tag, or the current active tag, or default to 'master'
const targetTag = tag || getCurrentTag(projectRoot) || 'master'; const targetTag = tag;
const logFn = mcpLog const logFn = mcpLog
? mcpLog ? mcpLog

View File

@@ -9,6 +9,8 @@ import generateTaskFiles from './generate-task-files.js';
* @param {boolean} convertToTask - Whether to convert the subtask to a standalone task * @param {boolean} convertToTask - Whether to convert the subtask to a standalone task
* @param {boolean} generateFiles - Whether to regenerate task files after removing the subtask * @param {boolean} generateFiles - Whether to regenerate task files after removing the subtask
* @param {Object} context - Context object containing projectRoot and tag information * @param {Object} context - Context object containing projectRoot and tag information
* @param {string} [context.projectRoot] - Project root path
* @param {string} [context.tag] - Tag for the task
* @returns {Object|null} The removed subtask if convertToTask is true, otherwise null * @returns {Object|null} The removed subtask if convertToTask is true, otherwise null
*/ */
async function removeSubtask( async function removeSubtask(
@@ -18,11 +20,12 @@ async function removeSubtask(
generateFiles = true, generateFiles = true,
context = {} context = {}
) { ) {
const { projectRoot, tag } = context;
try { try {
log('info', `Removing subtask ${subtaskId}...`); log('info', `Removing subtask ${subtaskId}...`);
// Read the existing tasks with proper context // Read the existing tasks with proper context
const data = readJSON(tasksPath, context.projectRoot, context.tag); const data = readJSON(tasksPath, projectRoot, tag);
if (!data || !data.tasks) { if (!data || !data.tasks) {
throw new Error(`Invalid or missing tasks file at ${tasksPath}`); throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
} }
@@ -103,7 +106,7 @@ async function removeSubtask(
} }
// Write the updated tasks back to the file with proper context // Write the updated tasks back to the file with proper context
writeJSON(tasksPath, data, context.projectRoot, context.tag); writeJSON(tasksPath, data, projectRoot, tag);
// Generate task files if requested // Generate task files if requested
if (generateFiles) { if (generateFiles) {

View File

@@ -9,6 +9,8 @@ import taskExists from './task-exists.js';
* @param {string} tasksPath - Path to the tasks file * @param {string} tasksPath - Path to the tasks file
* @param {string} taskIds - Comma-separated string of task/subtask IDs to remove (e.g., '5,6.1,7') * @param {string} taskIds - Comma-separated string of task/subtask IDs to remove (e.g., '5,6.1,7')
* @param {Object} context - Context object containing projectRoot and tag information * @param {Object} context - Context object containing projectRoot and tag information
* @param {string} [context.projectRoot] - Project root path
* @param {string} [context.tag] - Tag for the task
* @returns {Object} Result object with success status, messages, and removed task info * @returns {Object} Result object with success status, messages, and removed task info
*/ */
async function removeTask(tasksPath, taskIds, context = {}) { async function removeTask(tasksPath, taskIds, context = {}) {
@@ -32,7 +34,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
try { try {
// Read the tasks file ONCE before the loop, preserving the full tagged structure // Read the tasks file ONCE before the loop, preserving the full tagged structure
const rawData = readJSON(tasksPath, projectRoot); // Read raw data const rawData = readJSON(tasksPath, projectRoot, tag); // Read raw data
if (!rawData) { if (!rawData) {
throw new Error(`Could not read tasks file at ${tasksPath}`); throw new Error(`Could not read tasks file at ${tasksPath}`);
} }
@@ -40,19 +42,18 @@ async function removeTask(tasksPath, taskIds, context = {}) {
// Use the full tagged data if available, otherwise use the data as is // Use the full tagged data if available, otherwise use the data as is
const fullTaggedData = rawData._rawTaggedData || rawData; const fullTaggedData = rawData._rawTaggedData || rawData;
const currentTag = tag || rawData.tag || 'master'; if (!fullTaggedData[tag] || !fullTaggedData[tag].tasks) {
if (!fullTaggedData[currentTag] || !fullTaggedData[currentTag].tasks) { throw new Error(`Tag '${tag}' not found or has no tasks.`);
throw new Error(`Tag '${currentTag}' not found or has no tasks.`);
} }
const tasks = fullTaggedData[currentTag].tasks; // Work with tasks from the correct tag const tasks = fullTaggedData[tag].tasks; // Work with tasks from the correct tag
const tasksToDeleteFiles = []; // Collect IDs of main tasks whose files should be deleted const tasksToDeleteFiles = []; // Collect IDs of main tasks whose files should be deleted
for (const taskId of taskIdsToRemove) { for (const taskId of taskIdsToRemove) {
// Check if the task ID exists *before* attempting removal // Check if the task ID exists *before* attempting removal
if (!taskExists(tasks, taskId)) { if (!taskExists(tasks, taskId)) {
const errorMsg = `Task with ID ${taskId} in tag '${currentTag}' not found or already removed.`; const errorMsg = `Task with ID ${taskId} in tag '${tag}' not found or already removed.`;
results.errors.push(errorMsg); results.errors.push(errorMsg);
results.success = false; // Mark overall success as false if any error occurs results.success = false; // Mark overall success as false if any error occurs
continue; // Skip to the next ID continue; // Skip to the next ID
@@ -94,7 +95,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
parentTask.subtasks.splice(subtaskIndex, 1); parentTask.subtasks.splice(subtaskIndex, 1);
results.messages.push( results.messages.push(
`Successfully removed subtask ${taskId} from tag '${currentTag}'` `Successfully removed subtask ${taskId} from tag '${tag}'`
); );
} }
// Handle main task removal // Handle main task removal
@@ -102,9 +103,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
const taskIdNum = parseInt(taskId, 10); const taskIdNum = parseInt(taskId, 10);
const taskIndex = tasks.findIndex((t) => t.id === taskIdNum); const taskIndex = tasks.findIndex((t) => t.id === taskIdNum);
if (taskIndex === -1) { if (taskIndex === -1) {
throw new Error( throw new Error(`Task with ID ${taskId} not found in tag '${tag}'`);
`Task with ID ${taskId} not found in tag '${currentTag}'`
);
} }
// Store the task info before removal // Store the task info before removal
@@ -116,7 +115,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
tasks.splice(taskIndex, 1); tasks.splice(taskIndex, 1);
results.messages.push( results.messages.push(
`Successfully removed task ${taskId} from tag '${currentTag}'` `Successfully removed task ${taskId} from tag '${tag}'`
); );
} }
} catch (innerError) { } catch (innerError) {
@@ -139,7 +138,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
); );
// Update the tasks in the current tag of the full data structure // Update the tasks in the current tag of the full data structure
fullTaggedData[currentTag].tasks = tasks; fullTaggedData[tag].tasks = tasks;
// Remove dependencies from all tags // Remove dependencies from all tags
for (const tagName in fullTaggedData) { for (const tagName in fullTaggedData) {
@@ -171,7 +170,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
} }
// Save the updated raw data structure // Save the updated raw data structure
writeJSON(tasksPath, fullTaggedData, projectRoot, currentTag); writeJSON(tasksPath, fullTaggedData, projectRoot, tag);
// Delete task files AFTER saving tasks.json // Delete task files AFTER saving tasks.json
for (const taskIdNum of tasksToDeleteFiles) { for (const taskIdNum of tasksToDeleteFiles) {
@@ -196,7 +195,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
try { try {
await generateTaskFiles(tasksPath, path.dirname(tasksPath), { await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
projectRoot, projectRoot,
tag: currentTag tag
}); });
results.messages.push('Task files regenerated successfully.'); results.messages.push('Task files regenerated successfully.');
} catch (genError) { } catch (genError) {

View File

@@ -35,6 +35,7 @@ import {
* @param {boolean} [options.includeProjectTree] - Include project file tree * @param {boolean} [options.includeProjectTree] - Include project file tree
* @param {string} [options.detailLevel] - Detail level: 'low', 'medium', 'high' * @param {string} [options.detailLevel] - Detail level: 'low', 'medium', 'high'
* @param {string} [options.projectRoot] - Project root directory * @param {string} [options.projectRoot] - Project root directory
* @param {string} [options.tag] - Tag for the task
* @param {boolean} [options.saveToFile] - Whether to save results to file (MCP mode) * @param {boolean} [options.saveToFile] - Whether to save results to file (MCP mode)
* @param {Object} [context] - Execution context * @param {Object} [context] - Execution context
* @param {Object} [context.session] - MCP session object * @param {Object} [context.session] - MCP session object
@@ -59,6 +60,7 @@ async function performResearch(
includeProjectTree = false, includeProjectTree = false,
detailLevel = 'medium', detailLevel = 'medium',
projectRoot: providedProjectRoot, projectRoot: providedProjectRoot,
tag,
saveToFile = false saveToFile = false
} = options; } = options;
@@ -101,7 +103,7 @@ async function performResearch(
try { try {
// Initialize context gatherer // Initialize context gatherer
const contextGatherer = new ContextGatherer(projectRoot); const contextGatherer = new ContextGatherer(projectRoot, tag);
// Auto-discover relevant tasks using fuzzy search to supplement provided tasks // Auto-discover relevant tasks using fuzzy search to supplement provided tasks
let finalTaskIds = [...taskIds]; // Start with explicitly provided tasks let finalTaskIds = [...taskIds]; // Start with explicitly provided tasks
@@ -114,7 +116,7 @@ async function performResearch(
'tasks', 'tasks',
'tasks.json' 'tasks.json'
); );
const tasksData = await readJSON(tasksPath, projectRoot); const tasksData = await readJSON(tasksPath, projectRoot, tag);
if (tasksData && tasksData.tasks && tasksData.tasks.length > 0) { if (tasksData && tasksData.tasks && tasksData.tasks.length > 0) {
// Flatten tasks to include subtasks for fuzzy search // Flatten tasks to include subtasks for fuzzy search
@@ -769,10 +771,7 @@ async function handleSaveToTask(
return; return;
} }
// Validate ID exists - use tag from context const data = readJSON(tasksPath, projectRoot, context.tag);
const { getCurrentTag } = await import('../utils.js');
const tag = context.tag || getCurrentTag(projectRoot) || 'master';
const data = readJSON(tasksPath, projectRoot, tag);
if (!data || !data.tasks) { if (!data || !data.tasks) {
console.log(chalk.red('❌ No valid tasks found.')); console.log(chalk.red('❌ No valid tasks found.'));
return; return;
@@ -806,7 +805,7 @@ async function handleSaveToTask(
trimmedTaskId, trimmedTaskId,
conversationThread, conversationThread,
false, // useResearch = false for simple append false, // useResearch = false for simple append
{ ...context, tag }, context,
'text' 'text'
); );
@@ -833,7 +832,7 @@ async function handleSaveToTask(
taskIdNum, taskIdNum,
conversationThread, conversationThread,
false, // useResearch = false for simple append false, // useResearch = false for simple append
{ ...context, tag }, context,
'text', 'text',
true // appendMode = true true // appendMode = true
); );

View File

@@ -34,7 +34,7 @@ function setResponseLanguage(lang, options = {}) {
error: { error: {
code: 'CONFIG_MISSING', code: 'CONFIG_MISSING',
message: message:
'The configuration file is missing. Run "task-master models --setup" to create it.' 'The configuration file is missing. Run "task-master init" to create it.'
} }
}; };
} }

View File

@@ -7,7 +7,6 @@ import {
readJSON, readJSON,
writeJSON, writeJSON,
findTaskById, findTaskById,
getCurrentTag,
ensureTagMetadata ensureTagMetadata
} from '../utils.js'; } from '../utils.js';
import { displayBanner } from '../ui.js'; import { displayBanner } from '../ui.js';
@@ -26,16 +25,13 @@ import {
* @param {string} taskIdInput - Task ID(s) to update * @param {string} taskIdInput - Task ID(s) to update
* @param {string} newStatus - New status * @param {string} newStatus - New status
* @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot for tag resolution) * @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot for tag resolution)
* @param {string} tag - Optional tag to override current tag resolution * @param {string} [options.projectRoot] - Project root path
* @param {string} [options.tag] - Optional tag to override current tag resolution
* @param {string} [options.mcpLog] - MCP logger object
* @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode * @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
*/ */
async function setTaskStatus( async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
tasksPath, const { projectRoot, tag } = options;
taskIdInput,
newStatus,
options = {},
tag = null
) {
try { try {
if (!isValidTaskStatus(newStatus)) { if (!isValidTaskStatus(newStatus)) {
throw new Error( throw new Error(
@@ -59,7 +55,7 @@ async function setTaskStatus(
log('info', `Reading tasks from ${tasksPath}...`); log('info', `Reading tasks from ${tasksPath}...`);
// Read the raw data without tag resolution to preserve tagged structure // Read the raw data without tag resolution to preserve tagged structure
let rawData = readJSON(tasksPath, options.projectRoot); // No tag parameter let rawData = readJSON(tasksPath, projectRoot, tag); // No tag parameter
// Handle the case where readJSON returns resolved data with _rawTaggedData // Handle the case where readJSON returns resolved data with _rawTaggedData
if (rawData && rawData._rawTaggedData) { if (rawData && rawData._rawTaggedData) {
@@ -67,24 +63,17 @@ async function setTaskStatus(
rawData = rawData._rawTaggedData; rawData = rawData._rawTaggedData;
} }
// Determine the current tag
const currentTag = tag || getCurrentTag(options.projectRoot) || 'master';
// Ensure the tag exists in the raw data // Ensure the tag exists in the raw data
if ( if (!rawData || !rawData[tag] || !Array.isArray(rawData[tag].tasks)) {
!rawData ||
!rawData[currentTag] ||
!Array.isArray(rawData[currentTag].tasks)
) {
throw new Error( throw new Error(
`Invalid tasks file or tag "${currentTag}" not found at ${tasksPath}` `Invalid tasks file or tag "${tag}" not found at ${tasksPath}`
); );
} }
// Get the tasks for the current tag // Get the tasks for the current tag
const data = { const data = {
tasks: rawData[currentTag].tasks, tasks: rawData[tag].tasks,
tag: currentTag, tag,
_rawTaggedData: rawData _rawTaggedData: rawData
}; };
@@ -123,16 +112,16 @@ async function setTaskStatus(
} }
// Update the raw data structure with the modified tasks // Update the raw data structure with the modified tasks
rawData[currentTag].tasks = data.tasks; rawData[tag].tasks = data.tasks;
// Ensure the tag has proper metadata // Ensure the tag has proper metadata
ensureTagMetadata(rawData[currentTag], { ensureTagMetadata(rawData[tag], {
description: `Tasks for ${currentTag} context` description: `Tasks for ${tag} context`
}); });
// Write the updated raw data back to the file // Write the updated raw data back to the file
// The writeJSON function will automatically filter out _rawTaggedData // The writeJSON function will automatically filter out _rawTaggedData
writeJSON(tasksPath, rawData, options.projectRoot, currentTag); writeJSON(tasksPath, rawData, projectRoot, tag);
// Validate dependencies after status update // Validate dependencies after status update
log('info', 'Validating dependencies after status update...'); log('info', 'Validating dependencies after status update...');

View File

@@ -17,8 +17,7 @@ import {
truncate, truncate,
isSilentMode, isSilentMode,
findProjectRoot, findProjectRoot,
flattenTasksWithSubtasks, flattenTasksWithSubtasks
getCurrentTag
} from '../utils.js'; } from '../utils.js';
import { generateTextService } from '../ai-services-unified.js'; import { generateTextService } from '../ai-services-unified.js';
import { getDebugFlag } from '../config-manager.js'; import { getDebugFlag } from '../config-manager.js';
@@ -37,6 +36,7 @@ import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
* @param {Object} [context.session] - Session object from MCP server. * @param {Object} [context.session] - Session object from MCP server.
* @param {Object} [context.mcpLog] - MCP logger object. * @param {Object} [context.mcpLog] - MCP logger object.
* @param {string} [context.projectRoot] - Project root path (needed for AI service key resolution). * @param {string} [context.projectRoot] - Project root path (needed for AI service key resolution).
* @param {string} [context.tag] - Tag for the task
* @param {string} [outputFormat='text'] - Output format ('text' or 'json'). Automatically 'json' if mcpLog is present. * @param {string} [outputFormat='text'] - Output format ('text' or 'json'). Automatically 'json' if mcpLog is present.
* @returns {Promise<Object|null>} - The updated subtask or null if update failed. * @returns {Promise<Object|null>} - The updated subtask or null if update failed.
*/ */
@@ -92,10 +92,7 @@ async function updateSubtaskById(
throw new Error('Could not determine project root directory'); throw new Error('Could not determine project root directory');
} }
// Determine the tag to use const data = readJSON(tasksPath, projectRoot, tag);
const currentTag = tag || getCurrentTag(projectRoot) || 'master';
const data = readJSON(tasksPath, projectRoot, currentTag);
if (!data || !data.tasks) { if (!data || !data.tasks) {
throw new Error( throw new Error(
`No valid tasks found in ${tasksPath}. The file may be corrupted or have an invalid format.` `No valid tasks found in ${tasksPath}. The file may be corrupted or have an invalid format.`
@@ -142,7 +139,7 @@ async function updateSubtaskById(
// --- Context Gathering --- // --- Context Gathering ---
let gatheredContext = ''; let gatheredContext = '';
try { try {
const contextGatherer = new ContextGatherer(projectRoot); const contextGatherer = new ContextGatherer(projectRoot, tag);
const allTasksFlat = flattenTasksWithSubtasks(data.tasks); const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update-subtask'); const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update-subtask');
const searchQuery = `${parentTask.title} ${subtask.title} ${prompt}`; const searchQuery = `${parentTask.title} ${subtask.title} ${prompt}`;
@@ -331,13 +328,17 @@ async function updateSubtaskById(
if (outputFormat === 'text' && getDebugFlag(session)) { if (outputFormat === 'text' && getDebugFlag(session)) {
console.log('>>> DEBUG: About to call writeJSON with updated data...'); console.log('>>> DEBUG: About to call writeJSON with updated data...');
} }
writeJSON(tasksPath, data, projectRoot, currentTag); writeJSON(tasksPath, data, projectRoot, tag);
if (outputFormat === 'text' && getDebugFlag(session)) { if (outputFormat === 'text' && getDebugFlag(session)) {
console.log('>>> DEBUG: writeJSON call completed.'); console.log('>>> DEBUG: writeJSON call completed.');
} }
report('success', `Successfully updated subtask ${subtaskId}`); report('success', `Successfully updated subtask ${subtaskId}`);
// await generateTaskFiles(tasksPath, path.dirname(tasksPath)); // Updated function call to make sure if uncommented it will generate the task files for the updated subtask based on the tag
// await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
// tag: tag,
// projectRoot: projectRoot
// });
if (outputFormat === 'text') { if (outputFormat === 'text') {
if (loadingIndicator) { if (loadingIndicator) {

View File

@@ -12,8 +12,7 @@ import {
truncate, truncate,
isSilentMode, isSilentMode,
flattenTasksWithSubtasks, flattenTasksWithSubtasks,
findProjectRoot, findProjectRoot
getCurrentTag
} from '../utils.js'; } from '../utils.js';
import { import {
@@ -262,6 +261,7 @@ function parseUpdatedTaskFromText(text, expectedTaskId, logFn, isMCP) {
* @param {Object} [context.session] - Session object from MCP server. * @param {Object} [context.session] - Session object from MCP server.
* @param {Object} [context.mcpLog] - MCP logger object. * @param {Object} [context.mcpLog] - MCP logger object.
* @param {string} [context.projectRoot] - Project root path. * @param {string} [context.projectRoot] - Project root path.
* @param {string} [context.tag] - Tag for the task
* @param {string} [outputFormat='text'] - Output format ('text' or 'json'). * @param {string} [outputFormat='text'] - Output format ('text' or 'json').
* @param {boolean} [appendMode=false] - If true, append to details instead of full update. * @param {boolean} [appendMode=false] - If true, append to details instead of full update.
* @returns {Promise<Object|null>} - The updated task or null if update failed. * @returns {Promise<Object|null>} - The updated task or null if update failed.
@@ -320,11 +320,8 @@ async function updateTaskById(
throw new Error('Could not determine project root directory'); throw new Error('Could not determine project root directory');
} }
// Determine the tag to use
const currentTag = tag || getCurrentTag(projectRoot) || 'master';
// --- Task Loading and Status Check (Keep existing) --- // --- Task Loading and Status Check (Keep existing) ---
const data = readJSON(tasksPath, projectRoot, currentTag); const data = readJSON(tasksPath, projectRoot, tag);
if (!data || !data.tasks) if (!data || !data.tasks)
throw new Error(`No valid tasks found in ${tasksPath}.`); throw new Error(`No valid tasks found in ${tasksPath}.`);
const taskIndex = data.tasks.findIndex((task) => task.id === taskId); const taskIndex = data.tasks.findIndex((task) => task.id === taskId);
@@ -364,7 +361,7 @@ async function updateTaskById(
// --- Context Gathering --- // --- Context Gathering ---
let gatheredContext = ''; let gatheredContext = '';
try { try {
const contextGatherer = new ContextGatherer(projectRoot); const contextGatherer = new ContextGatherer(projectRoot, tag);
const allTasksFlat = flattenTasksWithSubtasks(data.tasks); const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update-task'); const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update-task');
const searchQuery = `${taskToUpdate.title} ${taskToUpdate.description} ${prompt}`; const searchQuery = `${taskToUpdate.title} ${taskToUpdate.description} ${prompt}`;
@@ -559,7 +556,7 @@ async function updateTaskById(
// Write the updated task back to file // Write the updated task back to file
data.tasks[taskIndex] = taskToUpdate; data.tasks[taskIndex] = taskToUpdate;
writeJSON(tasksPath, data, projectRoot, currentTag); writeJSON(tasksPath, data, projectRoot, tag);
report('success', `Successfully appended to task ${taskId}`); report('success', `Successfully appended to task ${taskId}`);
// Display success message for CLI // Display success message for CLI
@@ -704,7 +701,7 @@ async function updateTaskById(
// --- End Update Task Data --- // --- End Update Task Data ---
// --- Write File and Generate (Unchanged) --- // --- Write File and Generate (Unchanged) ---
writeJSON(tasksPath, data, projectRoot, currentTag); writeJSON(tasksPath, data, projectRoot, tag);
report('success', `Successfully updated task ${taskId}`); report('success', `Successfully updated task ${taskId}`);
// await generateTaskFiles(tasksPath, path.dirname(tasksPath)); // await generateTaskFiles(tasksPath, path.dirname(tasksPath));
// --- End Write File --- // --- End Write File ---

View File

@@ -9,8 +9,7 @@ import {
readJSON, readJSON,
writeJSON, writeJSON,
truncate, truncate,
isSilentMode, isSilentMode
getCurrentTag
} from '../utils.js'; } from '../utils.js';
import { import {
@@ -43,7 +42,39 @@ const updatedTaskSchema = z
subtasks: z.array(z.any()).nullable() // Keep subtasks flexible for now subtasks: z.array(z.any()).nullable() // Keep subtasks flexible for now
}) })
.strip(); // Allow potential extra fields during parsing if needed, then validate structure .strip(); // Allow potential extra fields during parsing if needed, then validate structure
// Preprocessing schema that adds defaults before validation
const preprocessTaskSchema = z.preprocess((task) => {
// Ensure task is an object
if (typeof task !== 'object' || task === null) {
return {};
}
// Return task with defaults for missing fields
return {
...task,
// Add defaults for required fields if missing
id: task.id ?? 0,
title: task.title ?? 'Untitled Task',
description: task.description ?? '',
status: task.status ?? 'pending',
dependencies: Array.isArray(task.dependencies) ? task.dependencies : [],
// Optional fields - preserve undefined/null distinction
priority: task.hasOwnProperty('priority') ? task.priority : null,
details: task.hasOwnProperty('details') ? task.details : null,
testStrategy: task.hasOwnProperty('testStrategy')
? task.testStrategy
: null,
subtasks: Array.isArray(task.subtasks)
? task.subtasks
: task.subtasks === null
? null
: []
};
}, updatedTaskSchema);
const updatedTaskArraySchema = z.array(updatedTaskSchema); const updatedTaskArraySchema = z.array(updatedTaskSchema);
const preprocessedTaskArraySchema = z.array(preprocessTaskSchema);
/** /**
* Parses an array of task objects from AI's text response. * Parses an array of task objects from AI's text response.
@@ -196,32 +227,50 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
); );
} }
// Preprocess tasks to ensure required fields have proper defaults // Log missing fields for debugging before preprocessing
const preprocessedTasks = parsedTasks.map((task) => ({ let hasWarnings = false;
...task, parsedTasks.forEach((task, index) => {
// Ensure subtasks is always an array (not null or undefined) const missingFields = [];
subtasks: Array.isArray(task.subtasks) ? task.subtasks : [], if (!task.hasOwnProperty('id')) missingFields.push('id');
// Ensure status has a default value if missing if (!task.hasOwnProperty('status')) missingFields.push('status');
status: task.status || 'pending', if (!task.hasOwnProperty('dependencies'))
// Ensure dependencies is always an array missingFields.push('dependencies');
dependencies: Array.isArray(task.dependencies) ? task.dependencies : []
}));
const validationResult = updatedTaskArraySchema.safeParse(preprocessedTasks); if (missingFields.length > 0) {
if (!validationResult.success) { hasWarnings = true;
report('error', 'Parsed task array failed Zod validation.'); report(
validationResult.error.errors.forEach((err) => { 'warn',
report('error', ` - Path '${err.path.join('.')}': ${err.message}`); `Task ${index} is missing fields: ${missingFields.join(', ')} - will use defaults`
);
}
}); });
throw new Error(
`AI response failed task structure validation: ${validationResult.error.message}` if (hasWarnings) {
report(
'warn',
'Some tasks were missing required fields. Applying defaults...'
); );
} }
report('info', 'Successfully validated task structure.'); // Use the preprocessing schema to add defaults and validate
return validationResult.data.slice( const preprocessResult = preprocessedTaskArraySchema.safeParse(parsedTasks);
if (!preprocessResult.success) {
// This should rarely happen now since preprocessing adds defaults
report('error', 'Failed to validate task array even after preprocessing.');
preprocessResult.error.errors.forEach((err) => {
report('error', ` - Path '${err.path.join('.')}': ${err.message}`);
});
throw new Error(
`AI response failed validation: ${preprocessResult.error.message}`
);
}
report('info', 'Successfully validated and transformed task structure.');
return preprocessResult.data.slice(
0, 0,
expectedCount || validationResult.data.length expectedCount || preprocessResult.data.length
); );
} }
@@ -234,8 +283,8 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
* @param {Object} context - Context object containing session and mcpLog. * @param {Object} context - Context object containing session and mcpLog.
* @param {Object} [context.session] - Session object from MCP server. * @param {Object} [context.session] - Session object from MCP server.
* @param {Object} [context.mcpLog] - MCP logger object. * @param {Object} [context.mcpLog] - MCP logger object.
* @param {string} [context.tag] - Tag for the task
* @param {string} [outputFormat='text'] - Output format ('text' or 'json'). * @param {string} [outputFormat='text'] - Output format ('text' or 'json').
* @param {string} [tag=null] - Tag associated with the tasks.
*/ */
async function updateTasks( async function updateTasks(
tasksPath, tasksPath,
@@ -269,11 +318,8 @@ async function updateTasks(
throw new Error('Could not determine project root directory'); throw new Error('Could not determine project root directory');
} }
// Determine the current tag - prioritize explicit tag, then context.tag, then current tag
const currentTag = tag || getCurrentTag(projectRoot) || 'master';
// --- Task Loading/Filtering (Updated to pass projectRoot and tag) --- // --- Task Loading/Filtering (Updated to pass projectRoot and tag) ---
const data = readJSON(tasksPath, projectRoot, currentTag); const data = readJSON(tasksPath, projectRoot, tag);
if (!data || !data.tasks) if (!data || !data.tasks)
throw new Error(`No valid tasks found in ${tasksPath}`); throw new Error(`No valid tasks found in ${tasksPath}`);
const tasksToUpdate = data.tasks.filter( const tasksToUpdate = data.tasks.filter(
@@ -292,7 +338,7 @@ async function updateTasks(
// --- Context Gathering --- // --- Context Gathering ---
let gatheredContext = ''; let gatheredContext = '';
try { try {
const contextGatherer = new ContextGatherer(projectRoot); const contextGatherer = new ContextGatherer(projectRoot, tag);
const allTasksFlat = flattenTasksWithSubtasks(data.tasks); const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update'); const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update');
const searchResults = fuzzySearch.findRelevantTasks(prompt, { const searchResults = fuzzySearch.findRelevantTasks(prompt, {
@@ -478,7 +524,7 @@ async function updateTasks(
); );
// Fix: Pass projectRoot and currentTag to writeJSON // Fix: Pass projectRoot and currentTag to writeJSON
writeJSON(tasksPath, data, projectRoot, currentTag); writeJSON(tasksPath, data, projectRoot, tag);
if (isMCP) if (isMCP)
logFn.info( logFn.info(
`Successfully updated ${actualUpdateCount} tasks in ${tasksPath}` `Successfully updated ${actualUpdateCount} tasks in ${tasksPath}`

View File

@@ -1197,18 +1197,18 @@ async function displayNextTask(
* @param {string|number} taskId - The ID of the task to display * @param {string|number} taskId - The ID of the task to display
* @param {string} complexityReportPath - Path to the complexity report file * @param {string} complexityReportPath - Path to the complexity report file
* @param {string} [statusFilter] - Optional status to filter subtasks by * @param {string} [statusFilter] - Optional status to filter subtasks by
* @param {string} tag - Optional tag to override current tag resolution * @param {object} context - Context object containing projectRoot and tag
* @param {string} context.projectRoot - Project root path
* @param {string} context.tag - Tag for the task
*/ */
async function displayTaskById( async function displayTaskById(
tasksPath, tasksPath,
taskId, taskId,
complexityReportPath = null, complexityReportPath = null,
statusFilter = null, statusFilter = null,
tag = null,
context = {} context = {}
) { ) {
// Extract projectRoot from context const { projectRoot, tag } = context;
const projectRoot = context.projectRoot || null;
// Read the tasks file with proper projectRoot for tag resolution // Read the tasks file with proper projectRoot for tag resolution
const data = readJSON(tasksPath, projectRoot, tag); const data = readJSON(tasksPath, projectRoot, tag);
@@ -2251,7 +2251,9 @@ function displayAiUsageSummary(telemetryData, outputType = 'cli') {
* @param {Array<string>} taskIds - Array of task IDs to display * @param {Array<string>} taskIds - Array of task IDs to display
* @param {string} complexityReportPath - Path to complexity report * @param {string} complexityReportPath - Path to complexity report
* @param {string} statusFilter - Optional status filter for subtasks * @param {string} statusFilter - Optional status filter for subtasks
* @param {Object} context - Optional context object containing projectRoot and tag * @param {Object} context - Context object containing projectRoot and tag
* @param {string} [context.projectRoot] - Project root path
* @param {string} [context.tag] - Tag for the task
*/ */
async function displayMultipleTasksSummary( async function displayMultipleTasksSummary(
tasksPath, tasksPath,
@@ -2602,7 +2604,6 @@ async function displayMultipleTasksSummary(
choice.trim(), choice.trim(),
complexityReportPath, complexityReportPath,
statusFilter, statusFilter,
tag,
context context
); );
} }

View File

@@ -1190,6 +1190,7 @@ function aggregateTelemetry(telemetryArray, overallCommandName) {
} }
/** /**
* @deprecated Use TaskMaster.getCurrentTag() instead
* Gets the current tag from state.json or falls back to defaultTag from config * Gets the current tag from state.json or falls back to defaultTag from config
* @param {string} projectRoot - The project root directory (required) * @param {string} projectRoot - The project root directory (required)
* @returns {string} The current tag name * @returns {string} The current tag name

View File

@@ -21,7 +21,7 @@ const { encode } = pkg;
* Context Gatherer class for collecting and formatting context from various sources * Context Gatherer class for collecting and formatting context from various sources
*/ */
export class ContextGatherer { export class ContextGatherer {
constructor(projectRoot) { constructor(projectRoot, tag) {
this.projectRoot = projectRoot; this.projectRoot = projectRoot;
this.tasksPath = path.join( this.tasksPath = path.join(
projectRoot, projectRoot,
@@ -29,12 +29,13 @@ export class ContextGatherer {
'tasks', 'tasks',
'tasks.json' 'tasks.json'
); );
this.tag = tag;
this.allTasks = this._loadAllTasks(); this.allTasks = this._loadAllTasks();
} }
_loadAllTasks() { _loadAllTasks() {
try { try {
const data = readJSON(this.tasksPath, this.projectRoot); const data = readJSON(this.tasksPath, this.projectRoot, this.tag);
const tasks = data?.tasks || []; const tasks = data?.tasks || [];
return tasks; return tasks;
} catch (error) { } catch (error) {
@@ -958,10 +959,15 @@ export class ContextGatherer {
/** /**
* Factory function to create a context gatherer instance * Factory function to create a context gatherer instance
* @param {string} projectRoot - Project root directory * @param {string} projectRoot - Project root directory
* @param {string} tag - Tag for the task
* @returns {ContextGatherer} Context gatherer instance * @returns {ContextGatherer} Context gatherer instance
* @throws {Error} If tag is not provided
*/ */
export function createContextGatherer(projectRoot) { export function createContextGatherer(projectRoot, tag) {
return new ContextGatherer(projectRoot); if (!tag) {
throw new Error('Tag is required');
}
return new ContextGatherer(projectRoot, tag);
} }
export default ContextGatherer; export default ContextGatherer;

View File

@@ -1,5 +1,5 @@
/** /**
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'opencode' | 'roo' | 'trae' | 'windsurf' | 'vscode' | 'zed'} RulesProfile * @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'kiro' | 'opencode' | 'roo' | 'trae' | 'windsurf' | 'vscode' | 'zed'} RulesProfile
*/ */
/** /**
@@ -16,6 +16,7 @@
* - codex: Codex integration * - codex: Codex integration
* - cursor: Cursor IDE rules * - cursor: Cursor IDE rules
* - gemini: Gemini integration * - gemini: Gemini integration
* - kiro: Kiro IDE rules
* - opencode: OpenCode integration * - opencode: OpenCode integration
* - roo: Roo Code IDE rules * - roo: Roo Code IDE rules
* - trae: Trae IDE rules * - trae: Trae IDE rules
@@ -35,6 +36,7 @@ export const RULE_PROFILES = [
'codex', 'codex',
'cursor', 'cursor',
'gemini', 'gemini',
'kiro',
'opencode', 'opencode',
'roo', 'roo',
'trae', 'trae',

View File

@@ -5,6 +5,7 @@ export { clineProfile } from './cline.js';
export { codexProfile } from './codex.js'; export { codexProfile } from './codex.js';
export { cursorProfile } from './cursor.js'; export { cursorProfile } from './cursor.js';
export { geminiProfile } from './gemini.js'; export { geminiProfile } from './gemini.js';
export { kiroProfile } from './kiro.js';
export { opencodeProfile } from './opencode.js'; export { opencodeProfile } from './opencode.js';
export { rooProfile } from './roo.js'; export { rooProfile } from './roo.js';
export { traeProfile } from './trae.js'; export { traeProfile } from './trae.js';

42
src/profiles/kiro.js Normal file
View File

@@ -0,0 +1,42 @@
// Kiro profile for rule-transformer
import { createProfile } from './base-profile.js';
// Create and export kiro profile using the base factory
export const kiroProfile = createProfile({
name: 'kiro',
displayName: 'Kiro',
url: 'kiro.dev',
docsUrl: 'kiro.dev/docs',
profileDir: '.kiro',
rulesDir: '.kiro/steering', // Kiro rules location (full path)
mcpConfig: true,
mcpConfigName: 'settings/mcp.json', // Create directly in settings subdirectory
includeDefaultRules: true, // Include default rules to get all the standard files
targetExtension: '.md',
fileMap: {
// Override specific mappings - the base profile will create:
// 'rules/cursor_rules.mdc': 'kiro_rules.md'
// 'rules/dev_workflow.mdc': 'dev_workflow.md'
// 'rules/self_improve.mdc': 'self_improve.md'
// 'rules/taskmaster.mdc': 'taskmaster.md'
// We can add additional custom mappings here if needed
},
customReplacements: [
// Core Kiro directory structure changes
{ from: /\.cursor\/rules/g, to: '.kiro/steering' },
{ from: /\.cursor\/mcp\.json/g, to: '.kiro/settings/mcp.json' },
// Fix any remaining kiro/rules references that might be created during transformation
{ from: /\.kiro\/rules/g, to: '.kiro/steering' },
// Essential markdown link transformations for Kiro structure
{
from: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
to: '[$1](.kiro/steering/$2.md)'
},
// Kiro specific terminology
{ from: /rules directory/g, to: 'steering directory' },
{ from: /cursor rules/gi, to: 'Kiro steering files' }
]
});

View File

@@ -1,6 +1,162 @@
// VS Code conversion profile for rule-transformer // VS Code conversion profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { log } from '../../scripts/modules/utils.js';
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js'; import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
/**
* Transform standard MCP config format to VS Code format
* @param {Object} mcpConfig - Standard MCP configuration object
* @returns {Object} - Transformed VS Code configuration object
*/
function transformToVSCodeFormat(mcpConfig) {
const vscodeConfig = {};
// Transform mcpServers to servers
if (mcpConfig.mcpServers) {
vscodeConfig.servers = {};
for (const [serverName, serverConfig] of Object.entries(
mcpConfig.mcpServers
)) {
// Transform server configuration
const transformedServer = {
...serverConfig
};
// Add type: "stdio" after the env block
if (transformedServer.env) {
// Reorder properties: keep command, args, env, then add type
const reorderedServer = {};
if (transformedServer.command)
reorderedServer.command = transformedServer.command;
if (transformedServer.args)
reorderedServer.args = transformedServer.args;
if (transformedServer.env) reorderedServer.env = transformedServer.env;
reorderedServer.type = 'stdio';
// Add any other properties that might exist
Object.keys(transformedServer).forEach((key) => {
if (!['command', 'args', 'env', 'type'].includes(key)) {
reorderedServer[key] = transformedServer[key];
}
});
vscodeConfig.servers[serverName] = reorderedServer;
} else {
// If no env block, just add type at the end
transformedServer.type = 'stdio';
vscodeConfig.servers[serverName] = transformedServer;
}
}
}
return vscodeConfig;
}
/**
* Lifecycle function called after MCP config generation to transform to VS Code format
* @param {string} targetDir - Target project directory
* @param {string} assetsDir - Assets directory (unused for VS Code)
*/
function onPostConvertRulesProfile(targetDir, assetsDir) {
const vscodeConfigPath = path.join(targetDir, '.vscode', 'mcp.json');
if (!fs.existsSync(vscodeConfigPath)) {
log('debug', '[VS Code] No .vscode/mcp.json found to transform');
return;
}
try {
// Read the generated standard MCP config
const mcpConfigContent = fs.readFileSync(vscodeConfigPath, 'utf8');
const mcpConfig = JSON.parse(mcpConfigContent);
// Check if it's already in VS Code format (has servers instead of mcpServers)
if (mcpConfig.servers) {
log(
'info',
'[VS Code] mcp.json already in VS Code format, skipping transformation'
);
return;
}
// Transform to VS Code format
const vscodeConfig = transformToVSCodeFormat(mcpConfig);
// Write back the transformed config with proper formatting
fs.writeFileSync(
vscodeConfigPath,
JSON.stringify(vscodeConfig, null, 2) + '\n'
);
log('info', '[VS Code] Transformed mcp.json to VS Code format');
log('debug', `[VS Code] Renamed mcpServers->servers, added type: "stdio"`);
} catch (error) {
log('error', `[VS Code] Failed to transform mcp.json: ${error.message}`);
}
}
/**
* Lifecycle function called when removing VS Code profile
* @param {string} targetDir - Target project directory
*/
function onRemoveRulesProfile(targetDir) {
const vscodeConfigPath = path.join(targetDir, '.vscode', 'mcp.json');
if (!fs.existsSync(vscodeConfigPath)) {
log('debug', '[VS Code] No .vscode/mcp.json found to clean up');
return;
}
try {
// Read the current config
const configContent = fs.readFileSync(vscodeConfigPath, 'utf8');
const config = JSON.parse(configContent);
// Check if it has the servers section and task-master-ai server
if (config.servers && config.servers['task-master-ai']) {
// Remove task-master-ai server
delete config.servers['task-master-ai'];
// Check if there are other MCP servers
const remainingServers = Object.keys(config.servers);
if (remainingServers.length === 0) {
// No other servers, remove entire file
fs.rmSync(vscodeConfigPath, { force: true });
log('info', '[VS Code] Removed empty mcp.json file');
// Also remove .vscode directory if it's empty
const vscodeDir = path.dirname(vscodeConfigPath);
try {
const dirContents = fs.readdirSync(vscodeDir);
if (dirContents.length === 0) {
fs.rmSync(vscodeDir, { recursive: true, force: true });
log('debug', '[VS Code] Removed empty .vscode directory');
}
} catch (err) {
// Directory might not be empty or might not exist, that's fine
}
} else {
// Write back the modified config
fs.writeFileSync(
vscodeConfigPath,
JSON.stringify(config, null, 2) + '\n'
);
log(
'info',
'[VS Code] Removed TaskMaster from mcp.json, preserved other configurations'
);
}
} else {
log('debug', '[VS Code] TaskMaster not found in mcp.json');
}
} catch (error) {
log('error', `[VS Code] Failed to clean up mcp.json: ${error.message}`);
}
}
// Create and export vscode profile using the base factory // Create and export vscode profile using the base factory
export const vscodeProfile = createProfile({ export const vscodeProfile = createProfile({
name: 'vscode', name: 'vscode',
@@ -8,6 +164,8 @@ export const vscodeProfile = createProfile({
url: 'code.visualstudio.com', url: 'code.visualstudio.com',
docsUrl: 'code.visualstudio.com/docs', docsUrl: 'code.visualstudio.com/docs',
rulesDir: '.github/instructions', // VS Code instructions location rulesDir: '.github/instructions', // VS Code instructions location
profileDir: '.vscode', // VS Code configuration directory
mcpConfigName: 'mcp.json', // VS Code uses mcp.json in .vscode directory
customReplacements: [ customReplacements: [
// Core VS Code directory structure changes // Core VS Code directory structure changes
{ from: /\.cursor\/rules/g, to: '.github/instructions' }, { from: /\.cursor\/rules/g, to: '.github/instructions' },
@@ -28,5 +186,10 @@ export const vscodeProfile = createProfile({
// VS Code specific terminology // VS Code specific terminology
{ from: /rules directory/g, to: 'instructions directory' }, { from: /rules directory/g, to: 'instructions directory' },
{ from: /cursor rules/gi, to: 'VS Code instructions' } { from: /cursor rules/gi, to: 'VS Code instructions' }
] ],
onPostConvert: onPostConvertRulesProfile,
onRemove: onRemoveRulesProfile
}); });
// Export lifecycle functions separately to avoid naming conflicts
export { onPostConvertRulesProfile, onRemoveRulesProfile };

View File

@@ -56,17 +56,17 @@
"prompts": { "prompts": {
"complexity-report": { "complexity-report": {
"condition": "expansionPrompt", "condition": "expansionPrompt",
"system": "You are an AI assistant helping with task breakdown. Generate {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks based on the provided prompt and context.\nRespond ONLY with a valid JSON object containing a single key \"subtasks\" whose value is an array of the generated subtask objects.\nEach subtask object in the array must have keys: \"id\", \"title\", \"description\", \"dependencies\", \"details\", \"status\".\nEnsure the 'id' starts from {{nextSubtaskId}} and is sequential.\nEnsure 'dependencies' only reference valid prior subtask IDs generated in this response (starting from {{nextSubtaskId}}).\nEnsure 'status' is 'pending'.\nDo not include any other text or explanation.", "system": "You are an AI assistant helping with task breakdown. Generate {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks based on the provided prompt and context.\nRespond ONLY with a valid JSON object containing a single key \"subtasks\" whose value is an array of the generated subtask objects.\nEach subtask object in the array must have keys: \"id\", \"title\", \"description\", \"dependencies\", \"details\", \"status\".\nEnsure the 'id' starts from {{nextSubtaskId}} and is sequential.\nFor 'dependencies', use the full subtask ID format: \"{{task.id}}.1\", \"{{task.id}}.2\", etc. Only reference subtasks within this same task.\nEnsure 'status' is 'pending'.\nDo not include any other text or explanation.",
"user": "{{expansionPrompt}}{{#if additionalContext}}\n\n{{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\n\n{{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}" "user": "{{expansionPrompt}}{{#if additionalContext}}\n\n{{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\n\n{{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}"
}, },
"research": { "research": {
"condition": "useResearch === true && !expansionPrompt", "condition": "useResearch === true && !expansionPrompt",
"system": "You are an AI assistant that responds ONLY with valid JSON objects as requested. The object should contain a 'subtasks' array.", "system": "You are an AI assistant that responds ONLY with valid JSON objects as requested. The object should contain a 'subtasks' array.",
"user": "Analyze the following task and break it down into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks using your research capabilities. Assign sequential IDs starting from {{nextSubtaskId}}.\n\nParent Task:\nID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nConsider this context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nCRITICAL: Respond ONLY with a valid JSON object containing a single key \"subtasks\". The value must be an array of the generated subtasks, strictly matching this structure:\n\n{\n \"subtasks\": [\n {\n \"id\": <number>, // Sequential ID starting from {{nextSubtaskId}}\n \"title\": \"<string>\",\n \"description\": \"<string>\",\n \"dependencies\": [<number>], // e.g., [{{nextSubtaskId}} + 1]. If no dependencies, use an empty array [].\n \"details\": \"<string>\",\n \"testStrategy\": \"<string>\" // Optional\n },\n // ... (repeat for {{#if (gt subtaskCount 0)}}{{subtaskCount}}{{else}}appropriate number of{{/if}} subtasks)\n ]\n}\n\nImportant: For the 'dependencies' field, if a subtask has no dependencies, you MUST use an empty array, for example: \"dependencies\": []. Do not use null or omit the field.\n\nDo not include ANY explanatory text, markdown, or code block markers. Just the JSON object." "user": "Analyze the following task and break it down into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks using your research capabilities. Assign sequential IDs starting from {{nextSubtaskId}}.\n\nParent Task:\nID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nConsider this context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nCRITICAL: Respond ONLY with a valid JSON object containing a single key \"subtasks\". The value must be an array of the generated subtasks, strictly matching this structure:\n\n{\n \"subtasks\": [\n {\n \"id\": <number>, // Sequential ID starting from {{nextSubtaskId}}\n \"title\": \"<string>\",\n \"description\": \"<string>\",\n \"dependencies\": [\"<string>\"], // Use full subtask IDs like [\"{{task.id}}.1\", \"{{task.id}}.2\"]. If no dependencies, use an empty array [].\n \"details\": \"<string>\",\n \"testStrategy\": \"<string>\" // Optional\n },\n // ... (repeat for {{#if (gt subtaskCount 0)}}{{subtaskCount}}{{else}}appropriate number of{{/if}} subtasks)\n ]\n}\n\nImportant: For the 'dependencies' field, if a subtask has no dependencies, you MUST use an empty array, for example: \"dependencies\": []. Do not use null or omit the field.\n\nDo not include ANY explanatory text, markdown, or code block markers. Just the JSON object."
}, },
"default": { "default": {
"system": "You are an AI assistant helping with task breakdown for software development.\nYou need to break down a high-level task into {{#if (gt subtaskCount 0)}}{{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks that can be implemented one by one.\n\nSubtasks should:\n1. Be specific and actionable implementation steps\n2. Follow a logical sequence\n3. Each handle a distinct part of the parent task\n4. Include clear guidance on implementation approach\n5. Have appropriate dependency chains between subtasks (using the new sequential IDs)\n6. Collectively cover all aspects of the parent task\n\nFor each subtask, provide:\n- id: Sequential integer starting from the provided nextSubtaskId\n- title: Clear, specific title\n- description: Detailed description\n- dependencies: Array of prerequisite subtask IDs (use the new sequential IDs)\n- details: Implementation details, the output should be in string\n- testStrategy: Optional testing approach\n\nRespond ONLY with a valid JSON object containing a single key \"subtasks\" whose value is an array matching the structure described. Do not include any explanatory text, markdown formatting, or code block markers.", "system": "You are an AI assistant helping with task breakdown for software development.\nYou need to break down a high-level task into {{#if (gt subtaskCount 0)}}{{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks that can be implemented one by one.\n\nSubtasks should:\n1. Be specific and actionable implementation steps\n2. Follow a logical sequence\n3. Each handle a distinct part of the parent task\n4. Include clear guidance on implementation approach\n5. Have appropriate dependency chains between subtasks (using full subtask IDs)\n6. Collectively cover all aspects of the parent task\n\nFor each subtask, provide:\n- id: Sequential integer starting from the provided nextSubtaskId\n- title: Clear, specific title\n- description: Detailed description\n- dependencies: Array of prerequisite subtask IDs using full format like [\"{{task.id}}.1\", \"{{task.id}}.2\"]\n- details: Implementation details, the output should be in string\n- testStrategy: Optional testing approach\n\nRespond ONLY with a valid JSON object containing a single key \"subtasks\" whose value is an array matching the structure described. Do not include any explanatory text, markdown formatting, or code block markers.",
"user": "Break down this task into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks:\n\nTask ID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nAdditional context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nReturn ONLY the JSON object containing the \"subtasks\" array, matching this structure:\n\n{\n \"subtasks\": [\n {\n \"id\": {{nextSubtaskId}}, // First subtask ID\n \"title\": \"Specific subtask title\",\n \"description\": \"Detailed description\",\n \"dependencies\": [], // e.g., [{{nextSubtaskId}} + 1] if it depends on the next\n \"details\": \"Implementation guidance\",\n \"testStrategy\": \"Optional testing approach\"\n },\n // ... (repeat for {{#if (gt subtaskCount 0)}}a total of {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks with sequential IDs)\n ]\n}" "user": "Break down this task into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks:\n\nTask ID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nAdditional context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nReturn ONLY the JSON object containing the \"subtasks\" array, matching this structure:\n\n{\n \"subtasks\": [\n {\n \"id\": {{nextSubtaskId}}, // First subtask ID\n \"title\": \"Specific subtask title\",\n \"description\": \"Detailed description\",\n \"dependencies\": [], // e.g., [\"{{task.id}}.1\", \"{{task.id}}.2\"] for dependencies. Use empty array [] if no dependencies\n \"details\": \"Implementation guidance\",\n \"testStrategy\": \"Optional testing approach\"\n },\n // ... (repeat for {{#if (gt subtaskCount 0)}}a total of {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks with sequential IDs)\n ]\n}"
} }
} }
} }

View File

@@ -31,8 +31,8 @@
}, },
"prompts": { "prompts": {
"default": { "default": {
"system": "You are an AI assistant helping to update software development tasks based on new context.\nYou will be given a set of tasks and a prompt describing changes or new implementation details.\nYour job is to update the tasks to reflect these changes, while preserving their basic structure.\n\nGuidelines:\n1. Maintain the same IDs, statuses, and dependencies unless specifically mentioned in the prompt\n2. Update titles, descriptions, details, and test strategies to reflect the new information\n3. Do not change anything unnecessarily - just adapt what needs to change based on the prompt\n4. You should return ALL the tasks in order, not just the modified ones\n5. Return a complete valid JSON object with the updated tasks array\n6. VERY IMPORTANT: Preserve all subtasks marked as \"done\" or \"completed\" - do not modify their content\n7. For tasks with completed subtasks, build upon what has already been done rather than rewriting everything\n8. If an existing completed subtask needs to be changed/undone based on the new context, DO NOT modify it directly\n9. Instead, add a new subtask that clearly indicates what needs to be changed or replaced\n10. Use the existence of completed subtasks as an opportunity to make new subtasks more specific and targeted\n\nThe changes described in the prompt should be applied to ALL tasks in the list.", "system": "You are an AI assistant helping to update software development tasks based on new context.\nYou will be given a set of tasks and a prompt describing changes or new implementation details.\nYour job is to update the tasks to reflect these changes, while preserving their basic structure.\n\nCRITICAL RULES:\n1. Return ONLY a JSON array - no explanations, no markdown, no additional text before or after\n2. Each task MUST have ALL fields from the original (do not omit any fields)\n3. Maintain the same IDs, statuses, and dependencies unless specifically mentioned in the prompt\n4. Update titles, descriptions, details, and test strategies to reflect the new information\n5. Do not change anything unnecessarily - just adapt what needs to change based on the prompt\n6. You should return ALL the tasks in order, not just the modified ones\n7. Return a complete valid JSON array with all tasks\n8. VERY IMPORTANT: Preserve all subtasks marked as \"done\" or \"completed\" - do not modify their content\n9. For tasks with completed subtasks, build upon what has already been done rather than rewriting everything\n10. If an existing completed subtask needs to be changed/undone based on the new context, DO NOT modify it directly\n11. Instead, add a new subtask that clearly indicates what needs to be changed or replaced\n12. Use the existence of completed subtasks as an opportunity to make new subtasks more specific and targeted\n\nThe changes described in the prompt should be applied to ALL tasks in the list.",
"user": "Here are the tasks to update:\n{{{json tasks}}}\n\nPlease update these tasks based on the following new context:\n{{updatePrompt}}\n\nIMPORTANT: In the tasks JSON above, any subtasks with \"status\": \"done\" or \"status\": \"completed\" should be preserved exactly as is. Build your changes around these completed items.{{#if projectContext}}\n\n# Project Context\n\n{{projectContext}}{{/if}}\n\nReturn only the updated tasks as a valid JSON array." "user": "Here are the tasks to update:\n{{{json tasks}}}\n\nPlease update these tasks based on the following new context:\n{{updatePrompt}}\n\nIMPORTANT: In the tasks JSON above, any subtasks with \"status\": \"done\" or \"status\": \"completed\" should be preserved exactly as is. Build your changes around these completed items.{{#if projectContext}}\n\n# Project Context\n\n{{projectContext}}{{/if}}\n\nRequired JSON structure for EACH task (ALL fields MUST be present):\n{\n \"id\": <number>,\n \"title\": <string>,\n \"description\": <string>,\n \"status\": <string>,\n \"dependencies\": <array>,\n \"priority\": <string or null>,\n \"details\": <string or null>,\n \"testStrategy\": <string or null>,\n \"subtasks\": <array or null>\n}\n\nReturn a valid JSON array containing ALL the tasks with ALL their fields:\n- id (number) - preserve existing value\n- title (string)\n- description (string)\n- status (string) - preserve existing value unless explicitly changing\n- dependencies (array) - preserve existing value unless explicitly changing\n- priority (string or null)\n- details (string or null)\n- testStrategy (string or null)\n- subtasks (array or null)\n\nReturn ONLY the JSON array now:"
} }
} }
} }

View File

@@ -14,8 +14,10 @@ import {
TASKMASTER_DOCS_DIR, TASKMASTER_DOCS_DIR,
TASKMASTER_REPORTS_DIR, TASKMASTER_REPORTS_DIR,
TASKMASTER_CONFIG_FILE, TASKMASTER_CONFIG_FILE,
LEGACY_CONFIG_FILE LEGACY_CONFIG_FILE,
COMPLEXITY_REPORT_FILE
} from './constants/paths.js'; } from './constants/paths.js';
import { findProjectRoot } from './utils/path-utils.js';
/** /**
* TaskMaster class manages all the paths for the application. * TaskMaster class manages all the paths for the application.
@@ -23,13 +25,16 @@ import {
*/ */
export class TaskMaster { export class TaskMaster {
#paths; #paths;
#tag;
/** /**
* The constructor is intended to be used only by the initTaskMaster factory function. * The constructor is intended to be used only by the initTaskMaster factory function.
* @param {object} paths - A pre-resolved object of all application paths. * @param {object} paths - A pre-resolved object of all application paths.
* @param {string|undefined} tag - The current tag.
*/ */
constructor(paths) { constructor(paths, tag) {
this.#paths = Object.freeze({ ...paths }); this.#paths = Object.freeze({ ...paths });
this.#tag = tag;
} }
/** /**
@@ -64,9 +69,21 @@ export class TaskMaster {
* @returns {string|null} The absolute path to the complexity report. * @returns {string|null} The absolute path to the complexity report.
*/ */
getComplexityReportPath() { getComplexityReportPath() {
if (this.#paths.complexityReportPath) {
return this.#paths.complexityReportPath; return this.#paths.complexityReportPath;
} }
const complexityReportFile =
this.getCurrentTag() !== 'master'
? COMPLEXITY_REPORT_FILE.replace(
'.json',
`_${this.getCurrentTag()}.json`
)
: COMPLEXITY_REPORT_FILE;
return path.join(this.#paths.projectRoot, complexityReportFile);
}
/** /**
* @returns {string|null} The absolute path to the config.json file. * @returns {string|null} The absolute path to the config.json file.
*/ */
@@ -87,6 +104,45 @@ export class TaskMaster {
getAllPaths() { getAllPaths() {
return this.#paths; return this.#paths;
} }
/**
* Gets the current tag from state.json or falls back to defaultTag from config
* @returns {string} The current tag name
*/
getCurrentTag() {
if (this.#tag) {
return this.#tag;
}
try {
// Try to read current tag from state.json using fs directly
if (fs.existsSync(this.#paths.statePath)) {
const rawState = fs.readFileSync(this.#paths.statePath, 'utf8');
const stateData = JSON.parse(rawState);
if (stateData && stateData.currentTag) {
return stateData.currentTag;
}
}
} catch (error) {
// Ignore errors, fall back to default
}
// Fall back to defaultTag from config using fs directly
try {
if (fs.existsSync(this.#paths.configPath)) {
const rawConfig = fs.readFileSync(this.#paths.configPath, 'utf8');
const configData = JSON.parse(rawConfig);
if (configData && configData.global && configData.global.defaultTag) {
return configData.global.defaultTag;
}
}
} catch (error) {
// Ignore errors, use hardcoded default
}
// Final fallback
return 'master';
}
} }
/** /**
@@ -100,41 +156,42 @@ export class TaskMaster {
* @param {string} [overrides.complexityReportPath] * @param {string} [overrides.complexityReportPath]
* @param {string} [overrides.configPath] * @param {string} [overrides.configPath]
* @param {string} [overrides.statePath] * @param {string} [overrides.statePath]
* @param {string} [overrides.tag]
* @returns {TaskMaster} An initialized TaskMaster instance. * @returns {TaskMaster} An initialized TaskMaster instance.
*/ */
export function initTaskMaster(overrides = {}) { export function initTaskMaster(overrides = {}) {
const findProjectRoot = (startDir = process.cwd()) => {
const projectMarkers = [TASKMASTER_DIR, LEGACY_CONFIG_FILE];
let currentDir = path.resolve(startDir);
const rootDir = path.parse(currentDir).root;
while (currentDir !== rootDir) {
for (const marker of projectMarkers) {
const markerPath = path.join(currentDir, marker);
if (fs.existsSync(markerPath)) {
return currentDir;
}
}
currentDir = path.dirname(currentDir);
}
return null;
};
const resolvePath = ( const resolvePath = (
pathType, pathType,
override, override,
defaultPaths = [], defaultPaths = [],
basePath = null basePath = null,
createParentDirs = false
) => { ) => {
if (typeof override === 'string') { if (typeof override === 'string') {
const resolvedPath = path.isAbsolute(override) const resolvedPath = path.isAbsolute(override)
? override ? override
: path.resolve(basePath || process.cwd(), override); : path.resolve(basePath || process.cwd(), override);
if (createParentDirs) {
// For output paths, create parent directory if it doesn't exist
const parentDir = path.dirname(resolvedPath);
if (!fs.existsSync(parentDir)) {
try {
fs.mkdirSync(parentDir, { recursive: true });
} catch (error) {
throw new Error(
`Could not create directory for ${pathType}: ${parentDir}. Error: ${error.message}`
);
}
}
} else {
// Original validation logic
if (!fs.existsSync(resolvedPath)) { if (!fs.existsSync(resolvedPath)) {
throw new Error( throw new Error(
`${pathType} override path does not exist: ${resolvedPath}` `${pathType} override path does not exist: ${resolvedPath}`
); );
} }
}
return resolvedPath; return resolvedPath;
} }
@@ -192,13 +249,8 @@ export function initTaskMaster(overrides = {}) {
paths.projectRoot = resolvedOverride; paths.projectRoot = resolvedOverride;
} else { } else {
const foundRoot = findProjectRoot(); // findProjectRoot now always returns a value (fallback to cwd)
if (!foundRoot) { paths.projectRoot = findProjectRoot();
throw new Error(
'Unable to find project root. No project markers found. Run "init" command first.'
);
}
paths.projectRoot = foundRoot;
} }
// TaskMaster Directory // TaskMaster Directory
@@ -289,9 +341,10 @@ export function initTaskMaster(overrides = {}) {
'task-complexity-report.json', 'task-complexity-report.json',
'complexity-report.json' 'complexity-report.json'
], ],
paths.projectRoot paths.projectRoot,
true // Enable parent directory creation for output paths
); );
} }
return new TaskMaster(paths); return new TaskMaster(paths, overrides.tag);
} }

View File

@@ -66,8 +66,10 @@ export function findProjectRoot(startDir = process.cwd()) {
let currentDir = path.resolve(startDir); let currentDir = path.resolve(startDir);
const rootDir = path.parse(currentDir).root; const rootDir = path.parse(currentDir).root;
const maxDepth = 50; // Reasonable limit to prevent infinite loops
let depth = 0;
while (currentDir !== rootDir) { while (currentDir !== rootDir && depth < maxDepth) {
// Check if current directory contains any project markers // Check if current directory contains any project markers
for (const marker of projectMarkers) { for (const marker of projectMarkers) {
const markerPath = path.join(currentDir, marker); const markerPath = path.join(currentDir, marker);
@@ -76,9 +78,11 @@ export function findProjectRoot(startDir = process.cwd()) {
} }
} }
currentDir = path.dirname(currentDir); currentDir = path.dirname(currentDir);
depth++;
} }
return null; // Fallback to current working directory if no project root found
return process.cwd();
} }
/** /**
@@ -271,7 +275,12 @@ export function findComplexityReportPath(
'' // Project root '' // Project root
]; ];
const fileNames = ['task-complexity-report.json', 'complexity-report.json']; const fileNames = ['task-complexity', 'complexity-report'].map((fileName) => {
if (args?.tag && args?.tag !== 'master') {
return `${fileName}_${args.tag}.json`;
}
return `${fileName}.json`;
});
for (const location of locations) { for (const location of locations) {
for (const fileName of fileNames) { for (const fileName of fileNames) {
@@ -353,6 +362,7 @@ export function resolveComplexityReportOutputPath(
log = null log = null
) { ) {
const logger = getLoggerOrDefault(log); const logger = getLoggerOrDefault(log);
const tag = args?.tag;
// 1. If explicit path is provided, use it // 1. If explicit path is provided, use it
if (explicitPath) { if (explicitPath) {
@@ -369,13 +379,19 @@ export function resolveComplexityReportOutputPath(
// 2. Try to get project root from args (MCP) or find it // 2. Try to get project root from args (MCP) or find it
const rawProjectRoot = const rawProjectRoot =
args?.projectRoot || findProjectRoot() || process.cwd(); args?.projectRoot || findProjectRoot() || process.cwd();
// 3. Normalize project root to prevent double .taskmaster paths
const projectRoot = normalizeProjectRoot(rawProjectRoot); const projectRoot = normalizeProjectRoot(rawProjectRoot);
// 3. Use tag-aware filename
let filename = 'task-complexity-report.json';
if (tag && tag !== 'master') {
filename = `task-complexity-report_${tag}.json`;
}
// 4. Use new .taskmaster structure by default // 4. Use new .taskmaster structure by default
const defaultPath = path.join(projectRoot, COMPLEXITY_REPORT_FILE); const defaultPath = path.join(projectRoot, '.taskmaster/reports', filename);
logger.info?.(`Using default complexity report output path: ${defaultPath}`); logger.info?.(
`Using tag-aware complexity report output path: ${defaultPath}`
);
// Ensure the directory exists // Ensure the directory exists
const outputDir = path.dirname(defaultPath); const outputDir = path.dirname(defaultPath);

View File

@@ -368,7 +368,7 @@ log_step() {
log_success "Formatted complexity report saved to complexity_report_formatted.log" log_success "Formatted complexity report saved to complexity_report_formatted.log"
log_step "Expanding Task 1 (assuming it exists)" log_step "Expanding Task 1 (assuming it exists)"
cmd_output_expand1=$(task-master expand --id=1 2>&1) cmd_output_expand1=$(task-master expand --id=1 --cr complexity_results.json 2>&1)
exit_status_expand1=$? exit_status_expand1=$?
echo "$cmd_output_expand1" echo "$cmd_output_expand1"
extract_and_sum_cost "$cmd_output_expand1" extract_and_sum_cost "$cmd_output_expand1"

View File

@@ -0,0 +1,142 @@
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';
// Mock external modules
jest.mock('child_process', () => ({
execSync: jest.fn()
}));
// Mock console methods
jest.mock('console', () => ({
log: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
clear: jest.fn()
}));
describe('Kiro Integration', () => {
let tempDir;
beforeEach(() => {
jest.clearAllMocks();
// Create a temporary directory for testing
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
// Spy on fs methods
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
if (filePath.toString().includes('mcp.json')) {
return JSON.stringify({ mcpServers: {} }, null, 2);
}
return '{}';
});
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
});
afterEach(() => {
// Clean up the temporary directory
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch (err) {
console.error(`Error cleaning up: ${err.message}`);
}
});
// Test function that simulates the createProjectStructure behavior for Kiro files
function mockCreateKiroStructure() {
// This function simulates the actual kiro profile creation logic
// It explicitly calls the mocked fs methods to ensure consistency with the test environment
// Simulate directory creation calls - these will call the mocked mkdirSync
fs.mkdirSync(path.join(tempDir, '.kiro'), { recursive: true });
fs.mkdirSync(path.join(tempDir, '.kiro', 'steering'), { recursive: true });
fs.mkdirSync(path.join(tempDir, '.kiro', 'settings'), { recursive: true });
// Create MCP config file at .kiro/settings/mcp.json
// This will call the mocked writeFileSync
fs.writeFileSync(
path.join(tempDir, '.kiro', 'settings', 'mcp.json'),
JSON.stringify({ mcpServers: {} }, null, 2)
);
// Create kiro rule files in steering directory
// All these will call the mocked writeFileSync
fs.writeFileSync(
path.join(tempDir, '.kiro', 'steering', 'kiro_rules.md'),
'# Kiro Rules\n\nKiro-specific rules and instructions.'
);
fs.writeFileSync(
path.join(tempDir, '.kiro', 'steering', 'dev_workflow.md'),
'# Development Workflow\n\nDevelopment workflow instructions.'
);
fs.writeFileSync(
path.join(tempDir, '.kiro', 'steering', 'self_improve.md'),
'# Self Improvement\n\nSelf improvement guidelines.'
);
fs.writeFileSync(
path.join(tempDir, '.kiro', 'steering', 'taskmaster.md'),
'# Task Master\n\nTask Master integration instructions.'
);
}
test('creates all required .kiro directories', () => {
// Act
mockCreateKiroStructure();
// Assert
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.kiro'), {
recursive: true
});
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kiro', 'steering'),
{
recursive: true
}
);
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.kiro', 'settings'),
{
recursive: true
}
);
});
test('creates Kiro mcp.json with mcpServers format', () => {
// Act
mockCreateKiroStructure();
// Assert
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kiro', 'settings', 'mcp.json'),
JSON.stringify({ mcpServers: {} }, null, 2)
);
});
test('creates rule files in steering directory', () => {
// Act
mockCreateKiroStructure();
// Assert
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kiro', 'steering', 'kiro_rules.md'),
'# Kiro Rules\n\nKiro-specific rules and instructions.'
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kiro', 'steering', 'dev_workflow.md'),
'# Development Workflow\n\nDevelopment workflow instructions.'
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kiro', 'steering', 'self_improve.md'),
'# Self Improvement\n\nSelf improvement guidelines.'
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.kiro', 'steering', 'taskmaster.md'),
'# Task Master\n\nTask Master integration instructions.'
);
});
});

View File

@@ -41,6 +41,12 @@ describe('MCP Configuration Validation', () => {
expectedConfigName: 'settings.json', expectedConfigName: 'settings.json',
expectedPath: '.gemini/settings.json' expectedPath: '.gemini/settings.json'
}, },
kiro: {
shouldHaveMcp: true,
expectedDir: '.kiro',
expectedConfigName: 'settings/mcp.json',
expectedPath: '.kiro/settings/mcp.json'
},
opencode: { opencode: {
shouldHaveMcp: true, shouldHaveMcp: true,
expectedDir: '.', expectedDir: '.',
@@ -128,6 +134,7 @@ describe('MCP Configuration Validation', () => {
test('should ensure all MCP-enabled profiles use proper directory structure', () => { test('should ensure all MCP-enabled profiles use proper directory structure', () => {
const rootProfiles = ['opencode', 'claude', 'codex']; // Profiles that use root directory for config const rootProfiles = ['opencode', 'claude', 'codex']; // Profiles that use root directory for config
const nestedConfigProfiles = ['kiro']; // Profiles that use nested directories for config
RULE_PROFILES.forEach((profileName) => { RULE_PROFILES.forEach((profileName) => {
const profile = getRulesProfile(profileName); const profile = getRulesProfile(profileName);
@@ -140,6 +147,11 @@ describe('MCP Configuration Validation', () => {
// Other root profiles normalize to just the filename (no ./ prefix) // Other root profiles normalize to just the filename (no ./ prefix)
expect(profile.mcpConfigPath).toMatch(/^[\w_.]+$/); expect(profile.mcpConfigPath).toMatch(/^[\w_.]+$/);
} }
} else if (nestedConfigProfiles.includes(profileName)) {
// Profiles with nested config directories
expect(profile.mcpConfigPath).toMatch(
/^\.[\w-]+\/[\w-]+\/[\w_.]+$/
);
} else { } else {
// Other profiles should have config files in their specific directories // Other profiles should have config files in their specific directories
expect(profile.mcpConfigPath).toMatch(/^\.[\w-]+\/[\w_.]+$/); expect(profile.mcpConfigPath).toMatch(/^\.[\w-]+\/[\w_.]+$/);
@@ -347,6 +359,13 @@ describe('MCP Configuration Validation', () => {
// Other root profiles normalize to just the filename // Other root profiles normalize to just the filename
expect(profile.mcpConfigPath).toBe(profile.mcpConfigName); expect(profile.mcpConfigPath).toBe(profile.mcpConfigName);
} }
} else if (profileName === 'kiro') {
// Kiro has a nested config structure
const parts = profile.mcpConfigPath.split('/');
expect(parts).toHaveLength(3); // Should be profileDir/settings/mcp.json
expect(parts[0]).toBe(profile.profileDir);
expect(parts[1]).toBe('settings');
expect(parts[2]).toBe('mcp.json');
} else { } else {
// Non-root profiles should have profileDir/configName structure // Non-root profiles should have profileDir/configName structure
const parts = profile.mcpConfigPath.split('/'); const parts = profile.mcpConfigPath.split('/');

Some files were not shown because too many files have changed in this diff Show More