Compare commits
6 Commits
task-maste
...
claude/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2b2d543d4 | ||
|
|
e5a0d05b33 | ||
|
|
0d07733666 | ||
|
|
fc57480948 | ||
|
|
63ef0d36ca | ||
|
|
79b22a86e7 |
11
.changeset/brave-lions-sing.md
Normal file
11
.changeset/brave-lions-sing.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add Codex CLI provider with OAuth authentication
|
||||
|
||||
- Added codex-cli provider for GPT-5 and GPT-5-Codex models (272K input / 128K output)
|
||||
- OAuth-first authentication via `codex login` - no API key required
|
||||
- Optional OPENAI_CODEX_API_KEY support
|
||||
- Codebase analysis capabilities automatically enabled
|
||||
- Command-specific settings and approval/sandbox modes
|
||||
5
.changeset/chore-fix-docs.md
Normal file
5
.changeset/chore-fix-docs.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Improve `analyze-complexity` cli docs and `--research` flag documentation
|
||||
7
.changeset/cursor-slash-commands.md
Normal file
7
.changeset/cursor-slash-commands.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add Cursor IDE custom slash command support
|
||||
|
||||
Expose Task Master commands as Cursor slash commands by copying assets/claude/commands to .cursor/commands on profile add and cleaning up on remove.
|
||||
5
.changeset/curvy-weeks-flow.md
Normal file
5
.changeset/curvy-weeks-flow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Change parent task back to "pending" when all subtasks are in "pending" state
|
||||
5
.changeset/easy-spiders-wave.md
Normal file
5
.changeset/easy-spiders-wave.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Do a quick fix on build
|
||||
5
.changeset/fix-mcp-connection-errors.md
Normal file
5
.changeset/fix-mcp-connection-errors.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix MCP connection errors caused by deprecated generateTaskFiles calls. Resolves "Cannot read properties of null (reading 'toString')" errors when using MCP tools for task management operations.
|
||||
5
.changeset/fix-mcp-default-tasks-path.md
Normal file
5
.changeset/fix-mcp-default-tasks-path.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix MCP server error when file parameter not provided - now properly constructs default tasks.json path instead of failing with 'tasksJsonPath is required' error.
|
||||
5
.changeset/flat-cities-say.md
Normal file
5
.changeset/flat-cities-say.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Added api keys page on docs website: docs.task-master.dev/getting-started/api-keys
|
||||
10
.changeset/forty-tables-invite.md
Normal file
10
.changeset/forty-tables-invite.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Move to AI SDK v5:
|
||||
|
||||
- Works better with claude-code and gemini-cli as ai providers
|
||||
- Improved openai model family compatibility
|
||||
- Migrate ollama provider to v2
|
||||
- Closes #1223, #1013, #1161, #1174
|
||||
30
.changeset/gentle-cats-dance.md
Normal file
30
.changeset/gentle-cats-dance.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Migrate AI services to use generateObject for structured data generation
|
||||
|
||||
This update migrates all AI service calls from generateText to generateObject, ensuring more reliable and structured responses across all commands.
|
||||
|
||||
### Key Changes:
|
||||
|
||||
- **Unified AI Service**: Replaced separate generateText implementations with a single generateObjectService that handles structured data generation
|
||||
- **JSON Mode Support**: Added proper JSON mode configuration for providers that support it (OpenAI, Anthropic, Google, Groq)
|
||||
- **Schema Validation**: Integrated Zod schemas for all AI-generated content with automatic validation
|
||||
- **Provider Compatibility**: Maintained compatibility with all existing providers while leveraging their native structured output capabilities
|
||||
- **Improved Reliability**: Structured output generation reduces parsing errors and ensures consistent data formats
|
||||
|
||||
### Technical Improvements:
|
||||
|
||||
- Centralized provider configuration in `ai-providers-unified.js`
|
||||
- Added `generateObject` support detection for each provider
|
||||
- Implemented proper error handling for schema validation failures
|
||||
- Maintained backward compatibility with existing prompt structures
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
- Fixed subtask ID numbering issue where AI was generating inconsistent IDs (101-105, 601-603) instead of sequential numbering (1, 2, 3...)
|
||||
- Enhanced prompt instructions to enforce proper ID generation patterns
|
||||
- Ensured subtasks display correctly as X.1, X.2, X.3 format
|
||||
|
||||
This migration improves the reliability and consistency of AI-generated content throughout the Task Master application.
|
||||
13
.changeset/mcp-timeout-configuration.md
Normal file
13
.changeset/mcp-timeout-configuration.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Enhanced Roo Code profile with MCP timeout configuration for improved reliability during long-running AI operations. The Roo profile now automatically configures a 300-second timeout for MCP server operations, preventing timeouts during complex tasks like `parse-prd`, `expand-all`, `analyze-complexity`, and `research` operations. This change also replaces static MCP configuration files with programmatic generation for better maintainability.
|
||||
|
||||
**What's New:**
|
||||
- 300-second timeout for MCP operations (up from default 60 seconds)
|
||||
- Programmatic MCP configuration generation (replaces static asset files)
|
||||
- Enhanced reliability for AI-powered operations
|
||||
- Consistent with other AI coding assistant profiles
|
||||
|
||||
**Migration:** No user action required - existing Roo Code installations will automatically receive the enhanced MCP configuration on next initialization.
|
||||
5
.changeset/petite-ideas-grab.md
Normal file
5
.changeset/petite-ideas-grab.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix Claude Code settings validation for pathToClaudeCodeExecutable
|
||||
7
.changeset/plain-falcons-serve.md
Normal file
7
.changeset/plain-falcons-serve.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix cross-level task dependencies not being saved
|
||||
|
||||
Fixes an issue where adding dependencies between subtasks and top-level tasks (e.g., `task-master add-dependency --id=2.2 --depends-on=11`) would report success but fail to persist the changes. Dependencies can now be created in both directions between any task levels.
|
||||
26
.changeset/pre.json
Normal file
26
.changeset/pre.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"tag": "rc",
|
||||
"initialVersions": {
|
||||
"task-master-ai": "0.27.3",
|
||||
"docs": "0.0.4",
|
||||
"extension": "0.25.4"
|
||||
},
|
||||
"changesets": [
|
||||
"brave-lions-sing",
|
||||
"chore-fix-docs",
|
||||
"cursor-slash-commands",
|
||||
"curvy-weeks-flow",
|
||||
"easy-spiders-wave",
|
||||
"fix-mcp-connection-errors",
|
||||
"fix-mcp-default-tasks-path",
|
||||
"flat-cities-say",
|
||||
"forty-tables-invite",
|
||||
"gentle-cats-dance",
|
||||
"mcp-timeout-configuration",
|
||||
"petite-ideas-grab",
|
||||
"silly-pandas-find",
|
||||
"sweet-maps-rule",
|
||||
"whole-pigs-say"
|
||||
]
|
||||
}
|
||||
5
.changeset/silly-pandas-find.md
Normal file
5
.changeset/silly-pandas-find.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix sonar deep research model failing, should be called `sonar-deep-research`
|
||||
5
.changeset/sweet-maps-rule.md
Normal file
5
.changeset/sweet-maps-rule.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Upgrade grok-cli ai provider to ai sdk v5
|
||||
8
.changeset/whole-pigs-say.md
Normal file
8
.changeset/whole-pigs-say.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix complexity score not showing for `task-master show` and `task-master list`
|
||||
|
||||
- Added complexity score on "next task" when running `task-master list`
|
||||
- Added colors to complexity to reflect complexity (easy, medium, hard)
|
||||
79
CHANGELOG.md
79
CHANGELOG.md
@@ -1,84 +1,5 @@
|
||||
# task-master-ai
|
||||
|
||||
## 0.28.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1273](https://github.com/eyaltoledano/claude-task-master/pull/1273) [`b43b7ce`](https://github.com/eyaltoledano/claude-task-master/commit/b43b7ce201625eee956fb2f8cd332f238bb78c21) Thanks [@ben-vargas](https://github.com/ben-vargas)! - Add Codex CLI provider with OAuth authentication
|
||||
- Added codex-cli provider for GPT-5 and GPT-5-Codex models (272K input / 128K output)
|
||||
- OAuth-first authentication via `codex login` - no API key required
|
||||
- Optional OPENAI_CODEX_API_KEY support
|
||||
- Codebase analysis capabilities automatically enabled
|
||||
- Command-specific settings and approval/sandbox modes
|
||||
|
||||
- [#1215](https://github.com/eyaltoledano/claude-task-master/pull/1215) [`0079b7d`](https://github.com/eyaltoledano/claude-task-master/commit/0079b7defdad550811f704c470fdd01955d91d4d) Thanks [@joedanz](https://github.com/joedanz)! - Add Cursor IDE custom slash command support
|
||||
|
||||
Expose Task Master commands as Cursor slash commands by copying assets/claude/commands to .cursor/commands on profile add and cleaning up on remove.
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Added api keys page on docs website: docs.task-master.dev/getting-started/api-keys
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Move to AI SDK v5:
|
||||
- Works better with claude-code and gemini-cli as ai providers
|
||||
- Improved openai model family compatibility
|
||||
- Migrate ollama provider to v2
|
||||
- Closes #1223, #1013, #1161, #1174
|
||||
|
||||
- [#1262](https://github.com/eyaltoledano/claude-task-master/pull/1262) [`738ec51`](https://github.com/eyaltoledano/claude-task-master/commit/738ec51c049a295a12839b2dfddaf05e23b8fede) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Migrate AI services to use generateObject for structured data generation
|
||||
|
||||
This update migrates all AI service calls from generateText to generateObject, ensuring more reliable and structured responses across all commands.
|
||||
|
||||
### Key Changes:
|
||||
- **Unified AI Service**: Replaced separate generateText implementations with a single generateObjectService that handles structured data generation
|
||||
- **JSON Mode Support**: Added proper JSON mode configuration for providers that support it (OpenAI, Anthropic, Google, Groq)
|
||||
- **Schema Validation**: Integrated Zod schemas for all AI-generated content with automatic validation
|
||||
- **Provider Compatibility**: Maintained compatibility with all existing providers while leveraging their native structured output capabilities
|
||||
- **Improved Reliability**: Structured output generation reduces parsing errors and ensures consistent data formats
|
||||
|
||||
### Technical Improvements:
|
||||
- Centralized provider configuration in `ai-providers-unified.js`
|
||||
- Added `generateObject` support detection for each provider
|
||||
- Implemented proper error handling for schema validation failures
|
||||
- Maintained backward compatibility with existing prompt structures
|
||||
|
||||
### Bug Fixes:
|
||||
- Fixed subtask ID numbering issue where AI was generating inconsistent IDs (101-105, 601-603) instead of sequential numbering (1, 2, 3...)
|
||||
- Enhanced prompt instructions to enforce proper ID generation patterns
|
||||
- Ensured subtasks display correctly as X.1, X.2, X.3 format
|
||||
|
||||
This migration improves the reliability and consistency of AI-generated content throughout the Task Master application.
|
||||
|
||||
- [#1112](https://github.com/eyaltoledano/claude-task-master/pull/1112) [`d67b81d`](https://github.com/eyaltoledano/claude-task-master/commit/d67b81d25ddd927fabb6f5deb368e8993519c541) Thanks [@olssonsten](https://github.com/olssonsten)! - Enhanced Roo Code profile with MCP timeout configuration for improved reliability during long-running AI operations. The Roo profile now automatically configures a 300-second timeout for MCP server operations, preventing timeouts during complex tasks like `parse-prd`, `expand-all`, `analyze-complexity`, and `research` operations. This change also replaces static MCP configuration files with programmatic generation for better maintainability.
|
||||
|
||||
**What's New:**
|
||||
- 300-second timeout for MCP operations (up from default 60 seconds)
|
||||
- Programmatic MCP configuration generation (replaces static asset files)
|
||||
- Enhanced reliability for AI-powered operations
|
||||
- Consistent with other AI coding assistant profiles
|
||||
|
||||
**Migration:** No user action required - existing Roo Code installations will automatically receive the enhanced MCP configuration on next initialization.
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`986ac11`](https://github.com/eyaltoledano/claude-task-master/commit/986ac117aee00bcd3e6830a0f76e1ad6d10e0bca) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Upgrade grok-cli ai provider to ai sdk v5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1235](https://github.com/eyaltoledano/claude-task-master/pull/1235) [`aaacc3d`](https://github.com/eyaltoledano/claude-task-master/commit/aaacc3dae36247b4de72b2d2697f49e5df6d01e3) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve `analyze-complexity` cli docs and `--research` flag documentation
|
||||
|
||||
- [#1251](https://github.com/eyaltoledano/claude-task-master/pull/1251) [`0b2c696`](https://github.com/eyaltoledano/claude-task-master/commit/0b2c6967c4605c33a100cff16f6ce8ff09ad06f0) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Change parent task back to "pending" when all subtasks are in "pending" state
|
||||
|
||||
- [#1274](https://github.com/eyaltoledano/claude-task-master/pull/1274) [`4f984f8`](https://github.com/eyaltoledano/claude-task-master/commit/4f984f8a6965da9f9c7edd60ddfd6560ac022917) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Do a quick fix on build
|
||||
|
||||
- [#1277](https://github.com/eyaltoledano/claude-task-master/pull/1277) [`7b5a7c4`](https://github.com/eyaltoledano/claude-task-master/commit/7b5a7c4495a68b782f7407fc5d0e0d3ae81f42f5) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP connection errors caused by deprecated generateTaskFiles calls. Resolves "Cannot read properties of null (reading 'toString')" errors when using MCP tools for task management operations.
|
||||
|
||||
- [#1276](https://github.com/eyaltoledano/claude-task-master/pull/1276) [`caee040`](https://github.com/eyaltoledano/claude-task-master/commit/caee040907f856d31a660171c9e6d966f23c632e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP server error when file parameter not provided - now properly constructs default tasks.json path instead of failing with 'tasksJsonPath is required' error.
|
||||
|
||||
- [#1172](https://github.com/eyaltoledano/claude-task-master/pull/1172) [`b5fe723`](https://github.com/eyaltoledano/claude-task-master/commit/b5fe723f8ead928e9f2dbde13b833ee70ac3382d) Thanks [@jujax](https://github.com/jujax)! - Fix Claude Code settings validation for pathToClaudeCodeExecutable
|
||||
|
||||
- [#1192](https://github.com/eyaltoledano/claude-task-master/pull/1192) [`2b69936`](https://github.com/eyaltoledano/claude-task-master/commit/2b69936ee7b34346d6de5175af20e077359e2e2a) Thanks [@nukunga](https://github.com/nukunga)! - Fix sonar deep research model failing, should be called `sonar-deep-research`
|
||||
|
||||
- [#1270](https://github.com/eyaltoledano/claude-task-master/pull/1270) [`20004a3`](https://github.com/eyaltoledano/claude-task-master/commit/20004a39ea848f747e1ff48981bfe176554e4055) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix complexity score not showing for `task-master show` and `task-master list`
|
||||
- Added complexity score on "next task" when running `task-master list`
|
||||
- Added colors to complexity to reflect complexity (easy, medium, hard)
|
||||
|
||||
## 0.28.0-rc.2
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -4,13 +4,6 @@
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @tm/core@null
|
||||
|
||||
## null
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @tm/core@null
|
||||
|
||||
|
||||
@@ -48,6 +48,5 @@
|
||||
"*": {
|
||||
"*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# docs
|
||||
|
||||
## 0.0.5
|
||||
|
||||
## 0.0.4
|
||||
|
||||
## 0.0.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.4",
|
||||
"private": true,
|
||||
"description": "Task Master documentation powered by Mintlify",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## 0.25.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b43b7ce`](https://github.com/eyaltoledano/claude-task-master/commit/b43b7ce201625eee956fb2f8cd332f238bb78c21), [`aaacc3d`](https://github.com/eyaltoledano/claude-task-master/commit/aaacc3dae36247b4de72b2d2697f49e5df6d01e3), [`0079b7d`](https://github.com/eyaltoledano/claude-task-master/commit/0079b7defdad550811f704c470fdd01955d91d4d), [`0b2c696`](https://github.com/eyaltoledano/claude-task-master/commit/0b2c6967c4605c33a100cff16f6ce8ff09ad06f0), [`4f984f8`](https://github.com/eyaltoledano/claude-task-master/commit/4f984f8a6965da9f9c7edd60ddfd6560ac022917), [`7b5a7c4`](https://github.com/eyaltoledano/claude-task-master/commit/7b5a7c4495a68b782f7407fc5d0e0d3ae81f42f5), [`caee040`](https://github.com/eyaltoledano/claude-task-master/commit/caee040907f856d31a660171c9e6d966f23c632e), [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042), [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042), [`738ec51`](https://github.com/eyaltoledano/claude-task-master/commit/738ec51c049a295a12839b2dfddaf05e23b8fede), [`d67b81d`](https://github.com/eyaltoledano/claude-task-master/commit/d67b81d25ddd927fabb6f5deb368e8993519c541), [`b5fe723`](https://github.com/eyaltoledano/claude-task-master/commit/b5fe723f8ead928e9f2dbde13b833ee70ac3382d), [`2b69936`](https://github.com/eyaltoledano/claude-task-master/commit/2b69936ee7b34346d6de5175af20e077359e2e2a), [`986ac11`](https://github.com/eyaltoledano/claude-task-master/commit/986ac117aee00bcd3e6830a0f76e1ad6d10e0bca), [`20004a3`](https://github.com/eyaltoledano/claude-task-master/commit/20004a39ea848f747e1ff48981bfe176554e4055)]:
|
||||
- task-master-ai@0.28.0
|
||||
|
||||
## 0.25.5-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"displayName": "TaskMaster",
|
||||
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
||||
"version": "0.25.5",
|
||||
"version": "0.25.5-rc.0",
|
||||
"publisher": "Hamster",
|
||||
"icon": "assets/icon.png",
|
||||
"engines": {
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.28.0",
|
||||
"version": "0.28.0-rc.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "task-master-ai",
|
||||
"version": "0.28.0",
|
||||
"version": "0.28.0-rc.1",
|
||||
"license": "MIT WITH Commons-Clause",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
@@ -125,13 +125,13 @@
|
||||
}
|
||||
},
|
||||
"apps/docs": {
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.4",
|
||||
"devDependencies": {
|
||||
"mintlify": "^4.2.111"
|
||||
}
|
||||
},
|
||||
"apps/extension": {
|
||||
"version": "0.25.5",
|
||||
"version": "0.25.5-rc.0",
|
||||
"dependencies": {
|
||||
"task-master-ai": "*"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.28.0",
|
||||
"version": "0.28.0-rc.2",
|
||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# @tm/ai-sdk-provider-grok-cli
|
||||
|
||||
## null
|
||||
@@ -31,6 +31,5 @@
|
||||
"files": ["dist/**/*", "README.md"],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,4 @@
|
||||
|
||||
## null
|
||||
|
||||
## null
|
||||
|
||||
## 1.0.1
|
||||
|
||||
@@ -24,6 +24,5 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"tsup": "^8.5.0"
|
||||
},
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
## null
|
||||
|
||||
## null
|
||||
|
||||
## 0.26.1
|
||||
|
||||
All notable changes to the @task-master/tm-core package will be documented in this file.
|
||||
|
||||
@@ -42,6 +42,5 @@
|
||||
"files": ["src", "README.md", "CHANGELOG.md"],
|
||||
"keywords": ["task-management", "typescript", "ai", "prd", "parser"],
|
||||
"author": "Task Master AI",
|
||||
"license": "MIT",
|
||||
"version": ""
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -312,18 +312,23 @@ async function removeDependency(tasksPath, taskId, dependencyId, context = {}) {
|
||||
|
||||
// Check if the dependency exists by comparing string representations
|
||||
const dependencyIndex = targetTask.dependencies.findIndex((dep) => {
|
||||
// Convert both to strings for comparison
|
||||
let depStr = String(dep);
|
||||
|
||||
// Special handling for numeric IDs that might be subtask references
|
||||
if (typeof dep === 'number' && dep < 100 && isSubtask) {
|
||||
// It's likely a reference to another subtask in the same parent task
|
||||
// Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1)
|
||||
const [parentId] = formattedTaskId.split('.');
|
||||
depStr = `${parentId}.${dep}`;
|
||||
// Direct string comparison (handles both numeric IDs and dot notation)
|
||||
const depStr = String(dep);
|
||||
if (depStr === normalizedDependencyId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return depStr === normalizedDependencyId;
|
||||
// For subtasks: handle numeric dependencies that might be references to other subtasks
|
||||
// in the same parent (e.g., subtask 1.2 depending on subtask 1.1 stored as just "1")
|
||||
if (typeof dep === 'number' && dep < 100 && isSubtask) {
|
||||
const [parentId] = formattedTaskId.split('.');
|
||||
const fullSubtaskRef = `${parentId}.${dep}`;
|
||||
if (fullSubtaskRef === normalizedDependencyId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (dependencyIndex === -1) {
|
||||
@@ -396,8 +401,9 @@ function isCircularDependency(tasks, taskId, chain = []) {
|
||||
task = parentTask.subtasks.find((st) => st.id === subtaskId);
|
||||
}
|
||||
} else {
|
||||
// Regular task
|
||||
task = tasks.find((t) => String(t.id) === taskIdStr);
|
||||
// Regular task - handle both string and numeric task IDs
|
||||
const taskIdNum = parseInt(taskIdStr, 10);
|
||||
task = tasks.find((t) => t.id === taskIdNum || String(t.id) === taskIdStr);
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": [],
|
||||
"subtasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
35
tests/fixtures/sample-tasks.js
vendored
35
tests/fixtures/sample-tasks.js
vendored
@@ -88,3 +88,38 @@ export const emptySampleTasks = {
|
||||
},
|
||||
tasks: []
|
||||
};
|
||||
|
||||
export const crossLevelDependencyTasks = {
|
||||
tasks: [
|
||||
{
|
||||
id: 2,
|
||||
title: 'Task 2 with subtasks',
|
||||
description: 'Parent task',
|
||||
status: 'pending',
|
||||
dependencies: [],
|
||||
subtasks: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Subtask 2.1',
|
||||
description: 'First subtask',
|
||||
status: 'pending',
|
||||
dependencies: []
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Subtask 2.2',
|
||||
description: 'Second subtask that should depend on Task 11',
|
||||
status: 'pending',
|
||||
dependencies: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: 'Task 11',
|
||||
description: 'Top-level task that 2.2 should depend on',
|
||||
status: 'done',
|
||||
dependencies: []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -4,18 +4,47 @@
|
||||
|
||||
import { jest } from '@jest/globals';
|
||||
import {
|
||||
validateTaskDependencies,
|
||||
isCircularDependency,
|
||||
removeDuplicateDependencies,
|
||||
cleanupSubtaskDependencies,
|
||||
ensureAtLeastOneIndependentSubtask,
|
||||
validateAndFixDependencies,
|
||||
canMoveWithDependencies
|
||||
} from '../../scripts/modules/dependency-manager.js';
|
||||
import * as utils from '../../scripts/modules/utils.js';
|
||||
import { sampleTasks } from '../fixtures/sample-tasks.js';
|
||||
sampleTasks,
|
||||
crossLevelDependencyTasks
|
||||
} from '../fixtures/sample-tasks.js';
|
||||
|
||||
// Create mock functions that we can control in tests
|
||||
const mockTaskExists = jest.fn();
|
||||
const mockFormatTaskId = jest.fn();
|
||||
const mockFindCycles = jest.fn();
|
||||
const mockLog = jest.fn();
|
||||
const mockReadJSON = jest.fn();
|
||||
const mockWriteJSON = jest.fn();
|
||||
|
||||
// Mock the utils module using the same pattern as move-task-cross-tag.test.js
|
||||
jest.mock('../../scripts/modules/utils.js', () => ({
|
||||
log: mockLog,
|
||||
readJSON: mockReadJSON,
|
||||
writeJSON: mockWriteJSON,
|
||||
taskExists: mockTaskExists,
|
||||
formatTaskId: mockFormatTaskId,
|
||||
findCycles: mockFindCycles,
|
||||
traverseDependencies: jest.fn(() => []),
|
||||
isSilentMode: jest.fn(() => true),
|
||||
findProjectRoot: jest.fn(() => '/test'),
|
||||
resolveEnvVariable: jest.fn(() => undefined),
|
||||
isEmpty: jest.fn((v) =>
|
||||
v == null
|
||||
? true
|
||||
: Array.isArray(v)
|
||||
? v.length === 0
|
||||
: typeof v === 'object'
|
||||
? Object.keys(v).length === 0
|
||||
: false
|
||||
),
|
||||
// Common extras
|
||||
enableSilentMode: jest.fn(),
|
||||
disableSilentMode: jest.fn(),
|
||||
getTaskManager: jest.fn(async () => ({})),
|
||||
getTagAwareFilePath: jest.fn((basePath, _tag, projectRoot = '.') => basePath),
|
||||
readComplexityReport: jest.fn(() => null)
|
||||
}));
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('path');
|
||||
jest.mock('chalk', () => ({
|
||||
green: jest.fn((text) => `<green>${text}</green>`),
|
||||
@@ -27,22 +56,16 @@ jest.mock('chalk', () => ({
|
||||
|
||||
jest.mock('boxen', () => jest.fn((text) => `[boxed: ${text}]`));
|
||||
|
||||
// Mock utils module
|
||||
const mockTaskExists = jest.fn();
|
||||
const mockFormatTaskId = jest.fn();
|
||||
const mockFindCycles = jest.fn();
|
||||
const mockLog = jest.fn();
|
||||
const mockReadJSON = jest.fn();
|
||||
const mockWriteJSON = jest.fn();
|
||||
|
||||
jest.mock('../../scripts/modules/utils.js', () => ({
|
||||
log: mockLog,
|
||||
readJSON: mockReadJSON,
|
||||
writeJSON: mockWriteJSON,
|
||||
taskExists: mockTaskExists,
|
||||
formatTaskId: mockFormatTaskId,
|
||||
findCycles: mockFindCycles
|
||||
}));
|
||||
// Now import SUT after mocks are in place
|
||||
import {
|
||||
validateTaskDependencies,
|
||||
isCircularDependency,
|
||||
removeDuplicateDependencies,
|
||||
cleanupSubtaskDependencies,
|
||||
ensureAtLeastOneIndependentSubtask,
|
||||
validateAndFixDependencies,
|
||||
canMoveWithDependencies
|
||||
} from '../../scripts/modules/dependency-manager.js';
|
||||
|
||||
jest.mock('../../scripts/modules/ui.js', () => ({
|
||||
displayBanner: jest.fn()
|
||||
@@ -52,8 +75,8 @@ jest.mock('../../scripts/modules/task-manager.js', () => ({
|
||||
generateTaskFiles: jest.fn()
|
||||
}));
|
||||
|
||||
// Create a path for test files
|
||||
const TEST_TASKS_PATH = 'tests/fixture/test-tasks.json';
|
||||
// Use a temporary path for test files - Jest will clean up the temp directory
|
||||
const TEST_TASKS_PATH = '/tmp/jest-test-tasks.json';
|
||||
|
||||
describe('Dependency Manager Module', () => {
|
||||
beforeEach(() => {
|
||||
@@ -684,6 +707,8 @@ describe('Dependency Manager Module', () => {
|
||||
// IMPORTANT: Verify no calls to writeJSON with actual tasks.json
|
||||
expect(mockWriteJSON).not.toHaveBeenCalledWith(
|
||||
'tasks/tasks.json',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
@@ -737,6 +762,8 @@ describe('Dependency Manager Module', () => {
|
||||
// IMPORTANT: Verify no calls to writeJSON with actual tasks.json
|
||||
expect(mockWriteJSON).not.toHaveBeenCalledWith(
|
||||
'tasks/tasks.json',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
@@ -750,6 +777,8 @@ describe('Dependency Manager Module', () => {
|
||||
// IMPORTANT: Verify no calls to writeJSON with actual tasks.json
|
||||
expect(mockWriteJSON).not.toHaveBeenCalledWith(
|
||||
'tasks/tasks.json',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
@@ -803,6 +832,8 @@ describe('Dependency Manager Module', () => {
|
||||
// IMPORTANT: Verify no calls to writeJSON with actual tasks.json
|
||||
expect(mockWriteJSON).not.toHaveBeenCalledWith(
|
||||
'tasks/tasks.json',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
@@ -916,4 +947,297 @@ describe('Dependency Manager Module', () => {
|
||||
expect(result.conflicts).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cross-level dependency tests (Issue #542)', () => {
|
||||
let originalExit;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Ensure a fresh module instance so ESM mocks apply to dynamic imports
|
||||
jest.resetModules();
|
||||
originalExit = process.exit;
|
||||
process.exit = jest.fn();
|
||||
|
||||
// For ESM dynamic imports, use the same pattern
|
||||
await jest.unstable_mockModule('../../scripts/modules/utils.js', () => ({
|
||||
log: mockLog,
|
||||
readJSON: mockReadJSON,
|
||||
writeJSON: mockWriteJSON,
|
||||
taskExists: mockTaskExists,
|
||||
formatTaskId: mockFormatTaskId,
|
||||
findCycles: mockFindCycles,
|
||||
traverseDependencies: jest.fn(() => []),
|
||||
isSilentMode: jest.fn(() => true),
|
||||
findProjectRoot: jest.fn(() => '/test'),
|
||||
resolveEnvVariable: jest.fn(() => undefined),
|
||||
isEmpty: jest.fn((v) =>
|
||||
v == null
|
||||
? true
|
||||
: Array.isArray(v)
|
||||
? v.length === 0
|
||||
: typeof v === 'object'
|
||||
? Object.keys(v).length === 0
|
||||
: false
|
||||
),
|
||||
enableSilentMode: jest.fn(),
|
||||
disableSilentMode: jest.fn(),
|
||||
getTaskManager: jest.fn(async () => ({})),
|
||||
getTagAwareFilePath: jest.fn(
|
||||
(basePath, _tag, projectRoot = '.') => basePath
|
||||
),
|
||||
readComplexityReport: jest.fn(() => null)
|
||||
}));
|
||||
|
||||
// Also mock transitive imports to keep dependency surface minimal
|
||||
await jest.unstable_mockModule('../../scripts/modules/ui.js', () => ({
|
||||
displayBanner: jest.fn()
|
||||
}));
|
||||
await jest.unstable_mockModule(
|
||||
'../../scripts/modules/task-manager/generate-task-files.js',
|
||||
() => ({ default: jest.fn() })
|
||||
);
|
||||
// Set up test data that matches the issue report
|
||||
// Clone fixture data before each test to prevent mutation issues
|
||||
mockReadJSON.mockImplementation(() =>
|
||||
structuredClone(crossLevelDependencyTasks)
|
||||
);
|
||||
|
||||
// Configure mockTaskExists to properly validate cross-level dependencies
|
||||
mockTaskExists.mockImplementation((tasks, taskId) => {
|
||||
if (typeof taskId === 'string' && taskId.includes('.')) {
|
||||
const [parentId, subtaskId] = taskId.split('.').map(Number);
|
||||
const task = tasks.find((t) => t.id === parentId);
|
||||
return (
|
||||
task &&
|
||||
task.subtasks &&
|
||||
task.subtasks.some((st) => st.id === subtaskId)
|
||||
);
|
||||
}
|
||||
|
||||
const numericId =
|
||||
typeof taskId === 'string' ? parseInt(taskId, 10) : taskId;
|
||||
return tasks.some((task) => task.id === numericId);
|
||||
});
|
||||
|
||||
mockFormatTaskId.mockImplementation((id) => {
|
||||
if (typeof id === 'string' && id.includes('.')) return id; // keep dot notation
|
||||
return parseInt(id, 10); // normalize top-level task IDs to number
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.exit = originalExit;
|
||||
});
|
||||
|
||||
test('should allow subtask to depend on top-level task', async () => {
|
||||
const { addDependency } = await import(
|
||||
'../../scripts/modules/dependency-manager.js'
|
||||
);
|
||||
|
||||
// Test the specific scenario from Issue #542: subtask 2.2 depending on task 11
|
||||
await addDependency(TEST_TASKS_PATH, '2.2', 11, { projectRoot: '/test' });
|
||||
|
||||
// Verify we wrote to the test path (and not the real tasks.json)
|
||||
expect(mockWriteJSON).toHaveBeenCalledWith(
|
||||
TEST_TASKS_PATH,
|
||||
expect.anything(),
|
||||
'/test',
|
||||
undefined
|
||||
);
|
||||
expect(mockWriteJSON).not.toHaveBeenCalledWith(
|
||||
'tasks/tasks.json',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
// Get the specific write call for TEST_TASKS_PATH
|
||||
const writeCall = mockWriteJSON.mock.calls.find(
|
||||
([p]) => p === TEST_TASKS_PATH
|
||||
);
|
||||
expect(writeCall).toBeDefined();
|
||||
const savedData = writeCall[1];
|
||||
const parent2 = savedData.tasks.find((t) => t.id === 2);
|
||||
const subtask22 = parent2.subtasks.find((st) => st.id === 2);
|
||||
|
||||
// Verify the dependency was actually added to subtask 2.2
|
||||
expect(subtask22.dependencies).toContain(11);
|
||||
// Also verify a success log was emitted
|
||||
const successCall = mockLog.mock.calls.find(
|
||||
([level]) => level === 'success'
|
||||
);
|
||||
expect(successCall).toBeDefined();
|
||||
expect(successCall[1]).toContain('2.2');
|
||||
expect(successCall[1]).toContain('11');
|
||||
});
|
||||
|
||||
test('should allow top-level task to depend on subtask', async () => {
|
||||
const { addDependency } = await import(
|
||||
'../../scripts/modules/dependency-manager.js'
|
||||
);
|
||||
|
||||
// Test reverse scenario: task 11 depending on subtask 2.1
|
||||
await addDependency(TEST_TASKS_PATH, 11, '2.1', { projectRoot: '/test' });
|
||||
|
||||
// Stronger assertions for writeJSON call and locating the correct task
|
||||
expect(mockWriteJSON).toHaveBeenCalledWith(
|
||||
TEST_TASKS_PATH,
|
||||
expect.anything(),
|
||||
'/test',
|
||||
undefined
|
||||
);
|
||||
expect(mockWriteJSON).not.toHaveBeenCalledWith(
|
||||
'tasks/tasks.json',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
const writeCall = mockWriteJSON.mock.calls.find(
|
||||
([p]) => p === TEST_TASKS_PATH
|
||||
);
|
||||
expect(writeCall).toBeDefined();
|
||||
const savedData = writeCall[1];
|
||||
const task11 = savedData.tasks.find((t) => t.id === 11);
|
||||
|
||||
// Verify the dependency was actually added to task 11
|
||||
expect(task11.dependencies).toContain('2.1');
|
||||
// Verify a success log was emitted mentioning both task 11 and subtask 2.1
|
||||
const successCall = mockLog.mock.calls.find(
|
||||
([level]) => level === 'success'
|
||||
);
|
||||
expect(successCall).toBeDefined();
|
||||
expect(successCall[1]).toContain('11');
|
||||
expect(successCall[1]).toContain('2.1');
|
||||
});
|
||||
|
||||
test('should properly validate cross-level dependencies exist', async () => {
|
||||
// Test that validation correctly identifies when a cross-level dependency target doesn't exist
|
||||
mockTaskExists.mockImplementation((tasks, taskId) => {
|
||||
// Simulate task 99 not existing
|
||||
if (taskId === '99' || taskId === 99) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof taskId === 'string' && taskId.includes('.')) {
|
||||
const [parentId, subtaskId] = taskId.split('.').map(Number);
|
||||
const task = tasks.find((t) => t.id === parentId);
|
||||
return (
|
||||
task &&
|
||||
task.subtasks &&
|
||||
task.subtasks.some((st) => st.id === subtaskId)
|
||||
);
|
||||
}
|
||||
|
||||
const numericId =
|
||||
typeof taskId === 'string' ? parseInt(taskId, 10) : taskId;
|
||||
return tasks.some((task) => task.id === numericId);
|
||||
});
|
||||
|
||||
const { addDependency } = await import(
|
||||
'../../scripts/modules/dependency-manager.js'
|
||||
);
|
||||
|
||||
const exitError = new Error('process.exit invoked');
|
||||
process.exit.mockImplementation(() => {
|
||||
throw exitError;
|
||||
});
|
||||
|
||||
await expect(
|
||||
addDependency(TEST_TASKS_PATH, '2.2', 99, { projectRoot: '/test' })
|
||||
).rejects.toBe(exitError);
|
||||
|
||||
expect(process.exit).toHaveBeenCalledWith(1);
|
||||
expect(mockWriteJSON).not.toHaveBeenCalled();
|
||||
// Verify that an error was reported to the user
|
||||
expect(mockLog).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should remove top-level task dependency from a subtask', async () => {
|
||||
const { addDependency, removeDependency } = await import(
|
||||
'../../scripts/modules/dependency-manager.js'
|
||||
);
|
||||
|
||||
// Start with cloned data and add 11 to 2.2
|
||||
await addDependency(TEST_TASKS_PATH, '2.2', 11, { projectRoot: '/test' });
|
||||
|
||||
// Get the saved data from the add operation
|
||||
const addWriteCall = mockWriteJSON.mock.calls.find(
|
||||
([p]) => p === TEST_TASKS_PATH
|
||||
);
|
||||
expect(addWriteCall).toBeDefined();
|
||||
const dataWithDep = addWriteCall[1];
|
||||
|
||||
// Verify the dependency was added
|
||||
const subtask22AfterAdd = dataWithDep.tasks
|
||||
.find((t) => t.id === 2)
|
||||
.subtasks.find((st) => st.id === 2);
|
||||
expect(subtask22AfterAdd.dependencies).toContain(11);
|
||||
|
||||
// Clear mocks and re-setup mockReadJSON with the modified data
|
||||
jest.clearAllMocks();
|
||||
mockReadJSON.mockImplementation(() => structuredClone(dataWithDep));
|
||||
|
||||
await removeDependency(TEST_TASKS_PATH, '2.2', 11, {
|
||||
projectRoot: '/test'
|
||||
});
|
||||
|
||||
const writeCall = mockWriteJSON.mock.calls.find(
|
||||
([p]) => p === TEST_TASKS_PATH
|
||||
);
|
||||
expect(writeCall).toBeDefined();
|
||||
const saved = writeCall[1];
|
||||
const subtask22 = saved.tasks
|
||||
.find((t) => t.id === 2)
|
||||
.subtasks.find((st) => st.id === 2);
|
||||
expect(subtask22.dependencies).not.toContain(11);
|
||||
// Verify success log was emitted
|
||||
const successCall = mockLog.mock.calls.find(
|
||||
([level]) => level === 'success'
|
||||
);
|
||||
expect(successCall).toBeDefined();
|
||||
expect(successCall[1]).toContain('2.2');
|
||||
expect(successCall[1]).toContain('11');
|
||||
});
|
||||
|
||||
test('should remove subtask dependency from a top-level task', async () => {
|
||||
const { addDependency, removeDependency } = await import(
|
||||
'../../scripts/modules/dependency-manager.js'
|
||||
);
|
||||
|
||||
// Add subtask dependency to task 11
|
||||
await addDependency(TEST_TASKS_PATH, 11, '2.1', { projectRoot: '/test' });
|
||||
|
||||
// Get the saved data from the add operation
|
||||
const addWriteCall = mockWriteJSON.mock.calls.find(
|
||||
([p]) => p === TEST_TASKS_PATH
|
||||
);
|
||||
expect(addWriteCall).toBeDefined();
|
||||
const dataWithDep = addWriteCall[1];
|
||||
|
||||
// Verify the dependency was added
|
||||
const task11AfterAdd = dataWithDep.tasks.find((t) => t.id === 11);
|
||||
expect(task11AfterAdd.dependencies).toContain('2.1');
|
||||
|
||||
// Clear mocks and re-setup mockReadJSON with the modified data
|
||||
jest.clearAllMocks();
|
||||
mockReadJSON.mockImplementation(() => structuredClone(dataWithDep));
|
||||
|
||||
await removeDependency(TEST_TASKS_PATH, 11, '2.1', {
|
||||
projectRoot: '/test'
|
||||
});
|
||||
|
||||
const writeCall = mockWriteJSON.mock.calls.find(
|
||||
([p]) => p === TEST_TASKS_PATH
|
||||
);
|
||||
expect(writeCall).toBeDefined();
|
||||
const saved = writeCall[1];
|
||||
const task11 = saved.tasks.find((t) => t.id === 11);
|
||||
expect(task11.dependencies).not.toContain('2.1');
|
||||
// Verify success log was emitted
|
||||
const successCall = mockLog.mock.calls.find(
|
||||
([level]) => level === 'success'
|
||||
);
|
||||
expect(successCall).toBeDefined();
|
||||
expect(successCall[1]).toContain('11');
|
||||
expect(successCall[1]).toContain('2.1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user