claude-task-master/scripts/modules/dependency-manager.js

1234 lines
38 KiB
JavaScript
Raw Normal View History

Refactor: Modularize Task Master CLI into Modules Directory Simplified the Task Master CLI by organizing code into modules within the directory. **Why:** - **Better Organization:** Code is now grouped by function (AI, commands, dependencies, tasks, UI, utilities). - **Easier to Maintain:** Smaller modules are simpler to update and fix. - **Scalable:** New features can be added more easily in a structured way. **What Changed:** - Moved code from single _____ _ __ __ _ |_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __ | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__| | | (_| \__ \ < | | | | (_| \__ \ || __/ | |_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_| by https://x.com/eyaltoledano ╭────────────────────────────────────────────╮ │ │ │ Version: 0.9.16 Project: Task Master │ │ │ ╰────────────────────────────────────────────╯ ╭─────────────────────╮ │ │ │ Task Master CLI │ │ │ ╰─────────────────────╯ ╭───────────────────╮ │ Task Generation │ ╰───────────────────╯ parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document generate Create individual task files from tasks… ╭───────────────────╮ │ Task Management │ ╰───────────────────╯ list [--status=<status>] [--with-subtas… List all tasks with their status set-status --id=<id> --status=<status> Update task status (done, pending, etc.) update --from=<id> --prompt="<context>" Update tasks based on new requirements add-task --prompt="<text>" [--dependencies=… Add a new task using AI add-dependency --id=<id> --depends-on=<id> Add a dependency to a task remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task ╭──────────────────────────╮ │ Task Analysis & Detail │ ╰──────────────────────────╯ analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re… complexity-report [--file=<path>] Display the complexity analysis report expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks expand --all [--force] [--research] Expand all pending tasks with subtasks clear-subtasks --id=<id> Remove subtasks from specified tasks ╭─────────────────────────────╮ │ Task Navigation & Viewing │ ╰─────────────────────────────╯ next Show the next task to work on based on … show <id> Display detailed information about a sp… ╭─────────────────────────╮ │ Dependency Management │ ╰─────────────────────────╯ validate-dependenci… Identify invalid dependencies without f… fix-dependencies Fix invalid dependencies automatically ╭─────────────────────────╮ │ Environment Variables │ ╰─────────────────────────╯ ANTHROPIC_API_KEY Your Anthropic API key Required MODEL Claude model to use Default: claude-3-7-sonn… MAX_TOKENS Maximum tokens for responses Default: 4000 TEMPERATURE Temperature for model responses Default: 0.7 PERPLEXITY_API_KEY Perplexity API key for research Optional PERPLEXITY_MODEL Perplexity model to use Default: sonar-small-onl… DEBUG Enable debug logging Default: false LOG_LEVEL Console output level (debug,info,warn,error) Default: info DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3 DEFAULT_PRIORITY Default task priority Default: medium PROJECT_NAME Project name displayed in UI Default: Task Master file into these new modules: - : AI interactions (Claude, Perplexity) - : CLI command definitions (Commander.js) - : Task dependency handling - : Core task operations (create, list, update, etc.) - : User interface elements (display, formatting) - : Utility functions and configuration - : Exports all modules - Replaced direct use of _____ _ __ __ _ |_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __ | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__| | | (_| \__ \ < | | | | (_| \__ \ || __/ | |_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_| by https://x.com/eyaltoledano ╭────────────────────────────────────────────╮ │ │ │ Version: 0.9.16 Project: Task Master │ │ │ ╰────────────────────────────────────────────╯ ╭─────────────────────╮ │ │ │ Task Master CLI │ │ │ ╰─────────────────────╯ ╭───────────────────╮ │ Task Generation │ ╰───────────────────╯ parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document generate Create individual task files from tasks… ╭───────────────────╮ │ Task Management │ ╰───────────────────╯ list [--status=<status>] [--with-subtas… List all tasks with their status set-status --id=<id> --status=<status> Update task status (done, pending, etc.) update --from=<id> --prompt="<context>" Update tasks based on new requirements add-task --prompt="<text>" [--dependencies=… Add a new task using AI add-dependency --id=<id> --depends-on=<id> Add a dependency to a task remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task ╭──────────────────────────╮ │ Task Analysis & Detail │ ╰──────────────────────────╯ analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re… complexity-report [--file=<path>] Display the complexity analysis report expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks expand --all [--force] [--research] Expand all pending tasks with subtasks clear-subtasks --id=<id> Remove subtasks from specified tasks ╭─────────────────────────────╮ │ Task Navigation & Viewing │ ╰─────────────────────────────╯ next Show the next task to work on based on … show <id> Display detailed information about a sp… ╭─────────────────────────╮ │ Dependency Management │ ╰─────────────────────────╯ validate-dependenci… Identify invalid dependencies without f… fix-dependencies Fix invalid dependencies automatically ╭─────────────────────────╮ │ Environment Variables │ ╰─────────────────────────╯ ANTHROPIC_API_KEY Your Anthropic API key Required MODEL Claude model to use Default: claude-3-7-sonn… MAX_TOKENS Maximum tokens for responses Default: 4000 TEMPERATURE Temperature for model responses Default: 0.7 PERPLEXITY_API_KEY Perplexity API key for research Optional PERPLEXITY_MODEL Perplexity model to use Default: sonar-small-onl… DEBUG Enable debug logging Default: false LOG_LEVEL Console output level (debug,info,warn,error) Default: info DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3 DEFAULT_PRIORITY Default task priority Default: medium PROJECT_NAME Project name displayed in UI Default: Task Master with the global command (see ). - Updated documentation () to reflect the new command. **Benefits:** Code is now cleaner, easier to work with, and ready for future growth. Use the command (or ) to run the CLI. See for command details.
2025-03-23 23:19:37 -04:00
/**
* dependency-manager.js
* Manages task dependencies and relationships
*/
import path from "path";
import chalk from "chalk";
import boxen from "boxen";
Refactor: Modularize Task Master CLI into Modules Directory Simplified the Task Master CLI by organizing code into modules within the directory. **Why:** - **Better Organization:** Code is now grouped by function (AI, commands, dependencies, tasks, UI, utilities). - **Easier to Maintain:** Smaller modules are simpler to update and fix. - **Scalable:** New features can be added more easily in a structured way. **What Changed:** - Moved code from single _____ _ __ __ _ |_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __ | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__| | | (_| \__ \ < | | | | (_| \__ \ || __/ | |_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_| by https://x.com/eyaltoledano ╭────────────────────────────────────────────╮ │ │ │ Version: 0.9.16 Project: Task Master │ │ │ ╰────────────────────────────────────────────╯ ╭─────────────────────╮ │ │ │ Task Master CLI │ │ │ ╰─────────────────────╯ ╭───────────────────╮ │ Task Generation │ ╰───────────────────╯ parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document generate Create individual task files from tasks… ╭───────────────────╮ │ Task Management │ ╰───────────────────╯ list [--status=<status>] [--with-subtas… List all tasks with their status set-status --id=<id> --status=<status> Update task status (done, pending, etc.) update --from=<id> --prompt="<context>" Update tasks based on new requirements add-task --prompt="<text>" [--dependencies=… Add a new task using AI add-dependency --id=<id> --depends-on=<id> Add a dependency to a task remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task ╭──────────────────────────╮ │ Task Analysis & Detail │ ╰──────────────────────────╯ analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re… complexity-report [--file=<path>] Display the complexity analysis report expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks expand --all [--force] [--research] Expand all pending tasks with subtasks clear-subtasks --id=<id> Remove subtasks from specified tasks ╭─────────────────────────────╮ │ Task Navigation & Viewing │ ╰─────────────────────────────╯ next Show the next task to work on based on … show <id> Display detailed information about a sp… ╭─────────────────────────╮ │ Dependency Management │ ╰─────────────────────────╯ validate-dependenci… Identify invalid dependencies without f… fix-dependencies Fix invalid dependencies automatically ╭─────────────────────────╮ │ Environment Variables │ ╰─────────────────────────╯ ANTHROPIC_API_KEY Your Anthropic API key Required MODEL Claude model to use Default: claude-3-7-sonn… MAX_TOKENS Maximum tokens for responses Default: 4000 TEMPERATURE Temperature for model responses Default: 0.7 PERPLEXITY_API_KEY Perplexity API key for research Optional PERPLEXITY_MODEL Perplexity model to use Default: sonar-small-onl… DEBUG Enable debug logging Default: false LOG_LEVEL Console output level (debug,info,warn,error) Default: info DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3 DEFAULT_PRIORITY Default task priority Default: medium PROJECT_NAME Project name displayed in UI Default: Task Master file into these new modules: - : AI interactions (Claude, Perplexity) - : CLI command definitions (Commander.js) - : Task dependency handling - : Core task operations (create, list, update, etc.) - : User interface elements (display, formatting) - : Utility functions and configuration - : Exports all modules - Replaced direct use of _____ _ __ __ _ |_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __ | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__| | | (_| \__ \ < | | | | (_| \__ \ || __/ | |_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_| by https://x.com/eyaltoledano ╭────────────────────────────────────────────╮ │ │ │ Version: 0.9.16 Project: Task Master │ │ │ ╰────────────────────────────────────────────╯ ╭─────────────────────╮ │ │ │ Task Master CLI │ │ │ ╰─────────────────────╯ ╭───────────────────╮ │ Task Generation │ ╰───────────────────╯ parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document generate Create individual task files from tasks… ╭───────────────────╮ │ Task Management │ ╰───────────────────╯ list [--status=<status>] [--with-subtas… List all tasks with their status set-status --id=<id> --status=<status> Update task status (done, pending, etc.) update --from=<id> --prompt="<context>" Update tasks based on new requirements add-task --prompt="<text>" [--dependencies=… Add a new task using AI add-dependency --id=<id> --depends-on=<id> Add a dependency to a task remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task ╭──────────────────────────╮ │ Task Analysis & Detail │ ╰──────────────────────────╯ analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re… complexity-report [--file=<path>] Display the complexity analysis report expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks expand --all [--force] [--research] Expand all pending tasks with subtasks clear-subtasks --id=<id> Remove subtasks from specified tasks ╭─────────────────────────────╮ │ Task Navigation & Viewing │ ╰─────────────────────────────╯ next Show the next task to work on based on … show <id> Display detailed information about a sp… ╭─────────────────────────╮ │ Dependency Management │ ╰─────────────────────────╯ validate-dependenci… Identify invalid dependencies without f… fix-dependencies Fix invalid dependencies automatically ╭─────────────────────────╮ │ Environment Variables │ ╰─────────────────────────╯ ANTHROPIC_API_KEY Your Anthropic API key Required MODEL Claude model to use Default: claude-3-7-sonn… MAX_TOKENS Maximum tokens for responses Default: 4000 TEMPERATURE Temperature for model responses Default: 0.7 PERPLEXITY_API_KEY Perplexity API key for research Optional PERPLEXITY_MODEL Perplexity model to use Default: sonar-small-onl… DEBUG Enable debug logging Default: false LOG_LEVEL Console output level (debug,info,warn,error) Default: info DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3 DEFAULT_PRIORITY Default task priority Default: medium PROJECT_NAME Project name displayed in UI Default: Task Master with the global command (see ). - Updated documentation () to reflect the new command. **Benefits:** Code is now cleaner, easier to work with, and ready for future growth. Use the command (or ) to run the CLI. See for command details.
2025-03-23 23:19:37 -04:00
2025-04-09 00:25:27 +02:00
import {
log,
readJSON,
writeJSON,
taskExists,
formatTaskId,
findCycles,
isSilentMode,
} from "./utils.js";
2025-04-09 00:25:27 +02:00
import { displayBanner } from "./ui.js";
Refactor: Modularize Task Master CLI into Modules Directory Simplified the Task Master CLI by organizing code into modules within the directory. **Why:** - **Better Organization:** Code is now grouped by function (AI, commands, dependencies, tasks, UI, utilities). - **Easier to Maintain:** Smaller modules are simpler to update and fix. - **Scalable:** New features can be added more easily in a structured way. **What Changed:** - Moved code from single _____ _ __ __ _ |_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __ | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__| | | (_| \__ \ < | | | | (_| \__ \ || __/ | |_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_| by https://x.com/eyaltoledano ╭────────────────────────────────────────────╮ │ │ │ Version: 0.9.16 Project: Task Master │ │ │ ╰────────────────────────────────────────────╯ ╭─────────────────────╮ │ │ │ Task Master CLI │ │ │ ╰─────────────────────╯ ╭───────────────────╮ │ Task Generation │ ╰───────────────────╯ parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document generate Create individual task files from tasks… ╭───────────────────╮ │ Task Management │ ╰───────────────────╯ list [--status=<status>] [--with-subtas… List all tasks with their status set-status --id=<id> --status=<status> Update task status (done, pending, etc.) update --from=<id> --prompt="<context>" Update tasks based on new requirements add-task --prompt="<text>" [--dependencies=… Add a new task using AI add-dependency --id=<id> --depends-on=<id> Add a dependency to a task remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task ╭──────────────────────────╮ │ Task Analysis & Detail │ ╰──────────────────────────╯ analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re… complexity-report [--file=<path>] Display the complexity analysis report expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks expand --all [--force] [--research] Expand all pending tasks with subtasks clear-subtasks --id=<id> Remove subtasks from specified tasks ╭─────────────────────────────╮ │ Task Navigation & Viewing │ ╰─────────────────────────────╯ next Show the next task to work on based on … show <id> Display detailed information about a sp… ╭─────────────────────────╮ │ Dependency Management │ ╰─────────────────────────╯ validate-dependenci… Identify invalid dependencies without f… fix-dependencies Fix invalid dependencies automatically ╭─────────────────────────╮ │ Environment Variables │ ╰─────────────────────────╯ ANTHROPIC_API_KEY Your Anthropic API key Required MODEL Claude model to use Default: claude-3-7-sonn… MAX_TOKENS Maximum tokens for responses Default: 4000 TEMPERATURE Temperature for model responses Default: 0.7 PERPLEXITY_API_KEY Perplexity API key for research Optional PERPLEXITY_MODEL Perplexity model to use Default: sonar-small-onl… DEBUG Enable debug logging Default: false LOG_LEVEL Console output level (debug,info,warn,error) Default: info DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3 DEFAULT_PRIORITY Default task priority Default: medium PROJECT_NAME Project name displayed in UI Default: Task Master file into these new modules: - : AI interactions (Claude, Perplexity) - : CLI command definitions (Commander.js) - : Task dependency handling - : Core task operations (create, list, update, etc.) - : User interface elements (display, formatting) - : Utility functions and configuration - : Exports all modules - Replaced direct use of _____ _ __ __ _ |_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __ | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__| | | (_| \__ \ < | | | | (_| \__ \ || __/ | |_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_| by https://x.com/eyaltoledano ╭────────────────────────────────────────────╮ │ │ │ Version: 0.9.16 Project: Task Master │ │ │ ╰────────────────────────────────────────────╯ ╭─────────────────────╮ │ │ │ Task Master CLI │ │ │ ╰─────────────────────╯ ╭───────────────────╮ │ Task Generation │ ╰───────────────────╯ parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document generate Create individual task files from tasks… ╭───────────────────╮ │ Task Management │ ╰───────────────────╯ list [--status=<status>] [--with-subtas… List all tasks with their status set-status --id=<id> --status=<status> Update task status (done, pending, etc.) update --from=<id> --prompt="<context>" Update tasks based on new requirements add-task --prompt="<text>" [--dependencies=… Add a new task using AI add-dependency --id=<id> --depends-on=<id> Add a dependency to a task remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task ╭──────────────────────────╮ │ Task Analysis & Detail │ ╰──────────────────────────╯ analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re… complexity-report [--file=<path>] Display the complexity analysis report expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks expand --all [--force] [--research] Expand all pending tasks with subtasks clear-subtasks --id=<id> Remove subtasks from specified tasks ╭─────────────────────────────╮ │ Task Navigation & Viewing │ ╰─────────────────────────────╯ next Show the next task to work on based on … show <id> Display detailed information about a sp… ╭─────────────────────────╮ │ Dependency Management │ ╰─────────────────────────╯ validate-dependenci… Identify invalid dependencies without f… fix-dependencies Fix invalid dependencies automatically ╭─────────────────────────╮ │ Environment Variables │ ╰─────────────────────────╯ ANTHROPIC_API_KEY Your Anthropic API key Required MODEL Claude model to use Default: claude-3-7-sonn… MAX_TOKENS Maximum tokens for responses Default: 4000 TEMPERATURE Temperature for model responses Default: 0.7 PERPLEXITY_API_KEY Perplexity API key for research Optional PERPLEXITY_MODEL Perplexity model to use Default: sonar-small-onl… DEBUG Enable debug logging Default: false LOG_LEVEL Console output level (debug,info,warn,error) Default: info DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3 DEFAULT_PRIORITY Default task priority Default: medium PROJECT_NAME Project name displayed in UI Default: Task Master with the global command (see ). - Updated documentation () to reflect the new command. **Benefits:** Code is now cleaner, easier to work with, and ready for future growth. Use the command (or ) to run the CLI. See for command details.
2025-03-23 23:19:37 -04:00
import { generateTaskFiles } from "./task-manager.js";
2025-04-09 00:25:27 +02:00
Refactor: Modularize Task Master CLI into Modules Directory Simplified the Task Master CLI by organizing code into modules within the directory. **Why:** - **Better Organization:** Code is now grouped by function (AI, commands, dependencies, tasks, UI, utilities). - **Easier to Maintain:** Smaller modules are simpler to update and fix. - **Scalable:** New features can be added more easily in a structured way. **What Changed:** - Moved code from single _____ _ __ __ _ |_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __ | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__| | | (_| \__ \ < | | | | (_| \__ \ || __/ | |_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_| by https://x.com/eyaltoledano ╭────────────────────────────────────────────╮ │ │ │ Version: 0.9.16 Project: Task Master │ │ │ ╰────────────────────────────────────────────╯ ╭─────────────────────╮ │ │ │ Task Master CLI │ │ │ ╰─────────────────────╯ ╭───────────────────╮ │ Task Generation │ ╰───────────────────╯ parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document generate Create individual task files from tasks… ╭───────────────────╮ │ Task Management │ ╰───────────────────╯ list [--status=<status>] [--with-subtas… List all tasks with their status set-status --id=<id> --status=<status> Update task status (done, pending, etc.) update --from=<id> --prompt="<context>" Update tasks based on new requirements add-task --prompt="<text>" [--dependencies=… Add a new task using AI add-dependency --id=<id> --depends-on=<id> Add a dependency to a task remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task ╭──────────────────────────╮ │ Task Analysis & Detail │ ╰──────────────────────────╯ analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re… complexity-report [--file=<path>] Display the complexity analysis report expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks expand --all [--force] [--research] Expand all pending tasks with subtasks clear-subtasks --id=<id> Remove subtasks from specified tasks ╭─────────────────────────────╮ │ Task Navigation & Viewing │ ╰─────────────────────────────╯ next Show the next task to work on based on … show <id> Display detailed information about a sp… ╭─────────────────────────╮ │ Dependency Management │ ╰─────────────────────────╯ validate-dependenci… Identify invalid dependencies without f… fix-dependencies Fix invalid dependencies automatically ╭─────────────────────────╮ │ Environment Variables │ ╰─────────────────────────╯ ANTHROPIC_API_KEY Your Anthropic API key Required MODEL Claude model to use Default: claude-3-7-sonn… MAX_TOKENS Maximum tokens for responses Default: 4000 TEMPERATURE Temperature for model responses Default: 0.7 PERPLEXITY_API_KEY Perplexity API key for research Optional PERPLEXITY_MODEL Perplexity model to use Default: sonar-small-onl… DEBUG Enable debug logging Default: false LOG_LEVEL Console output level (debug,info,warn,error) Default: info DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3 DEFAULT_PRIORITY Default task priority Default: medium PROJECT_NAME Project name displayed in UI Default: Task Master file into these new modules: - : AI interactions (Claude, Perplexity) - : CLI command definitions (Commander.js) - : Task dependency handling - : Core task operations (create, list, update, etc.) - : User interface elements (display, formatting) - : Utility functions and configuration - : Exports all modules - Replaced direct use of _____ _ __ __ _ |_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __ | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__| | | (_| \__ \ < | | | | (_| \__ \ || __/ | |_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_| by https://x.com/eyaltoledano ╭────────────────────────────────────────────╮ │ │ │ Version: 0.9.16 Project: Task Master │ │ │ ╰────────────────────────────────────────────╯ ╭─────────────────────╮ │ │ │ Task Master CLI │ │ │ ╰─────────────────────╯ ╭───────────────────╮ │ Task Generation │ ╰───────────────────╯ parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document generate Create individual task files from tasks… ╭───────────────────╮ │ Task Management │ ╰───────────────────╯ list [--status=<status>] [--with-subtas… List all tasks with their status set-status --id=<id> --status=<status> Update task status (done, pending, etc.) update --from=<id> --prompt="<context>" Update tasks based on new requirements add-task --prompt="<text>" [--dependencies=… Add a new task using AI add-dependency --id=<id> --depends-on=<id> Add a dependency to a task remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task ╭──────────────────────────╮ │ Task Analysis & Detail │ ╰──────────────────────────╯ analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re… complexity-report [--file=<path>] Display the complexity analysis report expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks expand --all [--force] [--research] Expand all pending tasks with subtasks clear-subtasks --id=<id> Remove subtasks from specified tasks ╭─────────────────────────────╮ │ Task Navigation & Viewing │ ╰─────────────────────────────╯ next Show the next task to work on based on … show <id> Display detailed information about a sp… ╭─────────────────────────╮ │ Dependency Management │ ╰─────────────────────────╯ validate-dependenci… Identify invalid dependencies without f… fix-dependencies Fix invalid dependencies automatically ╭─────────────────────────╮ │ Environment Variables │ ╰─────────────────────────╯ ANTHROPIC_API_KEY Your Anthropic API key Required MODEL Claude model to use Default: claude-3-7-sonn… MAX_TOKENS Maximum tokens for responses Default: 4000 TEMPERATURE Temperature for model responses Default: 0.7 PERPLEXITY_API_KEY Perplexity API key for research Optional PERPLEXITY_MODEL Perplexity model to use Default: sonar-small-onl… DEBUG Enable debug logging Default: false LOG_LEVEL Console output level (debug,info,warn,error) Default: info DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3 DEFAULT_PRIORITY Default task priority Default: medium PROJECT_NAME Project name displayed in UI Default: Task Master with the global command (see ). - Updated documentation () to reflect the new command. **Benefits:** Code is now cleaner, easier to work with, and ready for future growth. Use the command (or ) to run the CLI. See for command details.
2025-03-23 23:19:37 -04:00
/**
* Add a dependency to a task
* @param {string} tasksPath - Path to the tasks.json file
* @param {number|string} taskId - ID of the task to add dependency to
* @param {number|string} dependencyId - ID of the task to add as dependency
*/
async function addDependency(tasksPath, taskId, dependencyId) {
log("info", `Adding dependency ${dependencyId} to task ${taskId}...`);
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
log("error", "No valid tasks found in tasks.json");
process.exit(1);
}
// Format the task and dependency IDs correctly
const formattedTaskId =
typeof taskId === "string" && taskId.includes(".")
? taskId
: parseInt(taskId, 10);
const formattedDependencyId = formatTaskId(dependencyId);
// Check if the dependency task or subtask actually exists
if (!taskExists(data.tasks, formattedDependencyId)) {
log(
"error",
`Dependency target ${formattedDependencyId} does not exist in tasks.json`
);
process.exit(1);
}
// Find the task to update
let targetTask = null;
let isSubtask = false;
if (typeof formattedTaskId === "string" && formattedTaskId.includes(".")) {
// Handle dot notation for subtasks (e.g., "1.2")
const [parentId, subtaskId] = formattedTaskId
.split(".")
.map((id) => parseInt(id, 10));
const parentTask = data.tasks.find((t) => t.id === parentId);
if (!parentTask) {
log("error", `Parent task ${parentId} not found.`);
process.exit(1);
}
if (!parentTask.subtasks) {
log("error", `Parent task ${parentId} has no subtasks.`);
process.exit(1);
}
targetTask = parentTask.subtasks.find((s) => s.id === subtaskId);
isSubtask = true;
if (!targetTask) {
log("error", `Subtask ${formattedTaskId} not found.`);
process.exit(1);
}
} else {
// Regular task (not a subtask)
targetTask = data.tasks.find((t) => t.id === formattedTaskId);
if (!targetTask) {
log("error", `Task ${formattedTaskId} not found.`);
process.exit(1);
}
}
// Initialize dependencies array if it doesn't exist
if (!targetTask.dependencies) {
targetTask.dependencies = [];
}
// Check if dependency already exists
if (
targetTask.dependencies.some((d) => {
// Convert both to strings for comparison to handle both numeric and string IDs
return String(d) === String(formattedDependencyId);
})
) {
log(
"warn",
`Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.`
);
return;
}
// Check if the task is trying to depend on itself - compare full IDs (including subtask parts)
if (String(formattedTaskId) === String(formattedDependencyId)) {
log("error", `Task ${formattedTaskId} cannot depend on itself.`);
process.exit(1);
}
// For subtasks of the same parent, we need to make sure we're not treating it as a self-dependency
// Check if we're dealing with subtasks with the same parent task
let isSelfDependency = false;
if (
typeof formattedTaskId === "string" &&
typeof formattedDependencyId === "string" &&
formattedTaskId.includes(".") &&
formattedDependencyId.includes(".")
) {
const [taskParentId] = formattedTaskId.split(".");
const [depParentId] = formattedDependencyId.split(".");
// Only treat it as a self-dependency if both the parent ID and subtask ID are identical
isSelfDependency = formattedTaskId === formattedDependencyId;
// Log for debugging
log(
"debug",
`Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}`
);
log(
"debug",
`Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}`
);
}
if (isSelfDependency) {
log("error", `Subtask ${formattedTaskId} cannot depend on itself.`);
process.exit(1);
}
// Check for circular dependencies
let dependencyChain = [formattedTaskId];
if (
!isCircularDependency(data.tasks, formattedDependencyId, dependencyChain)
) {
// Add the dependency
targetTask.dependencies.push(formattedDependencyId);
// Sort dependencies numerically or by parent task ID first, then subtask ID
targetTask.dependencies.sort((a, b) => {
if (typeof a === "number" && typeof b === "number") {
return a - b;
} else if (typeof a === "string" && typeof b === "string") {
const [aParent, aChild] = a.split(".").map(Number);
const [bParent, bChild] = b.split(".").map(Number);
return aParent !== bParent ? aParent - bParent : aChild - bChild;
} else if (typeof a === "number") {
return -1; // Numbers come before strings
} else {
return 1; // Strings come after numbers
}
});
// Save changes
writeJSON(tasksPath, data);
log(
"success",
`Added dependency ${formattedDependencyId} to task ${formattedTaskId}`
);
// Display a more visually appealing success message
if (!isSilentMode()) {
console.log(
boxen(
chalk.green(`Successfully added dependency:\n\n`) +
`Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`,
{
padding: 1,
borderColor: "green",
borderStyle: "round",
margin: { top: 1 },
}
)
);
}
// Generate updated task files
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
log("info", "Task files regenerated with updated dependencies.");
} else {
log(
"error",
`Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.`
);
process.exit(1);
}
2025-04-09 00:25:27 +02:00
}
/**
* Remove a dependency from a task
* @param {string} tasksPath - Path to the tasks.json file
* @param {number|string} taskId - ID of the task to remove dependency from
* @param {number|string} dependencyId - ID of the task to remove as dependency
*/
async function removeDependency(tasksPath, taskId, dependencyId) {
log("info", `Removing dependency ${dependencyId} from task ${taskId}...`);
// Read tasks file
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
log("error", "No valid tasks found.");
process.exit(1);
}
// Format the task and dependency IDs correctly
const formattedTaskId =
typeof taskId === "string" && taskId.includes(".")
? taskId
: parseInt(taskId, 10);
const formattedDependencyId = formatTaskId(dependencyId);
// Find the task to update
let targetTask = null;
let isSubtask = false;
if (typeof formattedTaskId === "string" && formattedTaskId.includes(".")) {
// Handle dot notation for subtasks (e.g., "1.2")
const [parentId, subtaskId] = formattedTaskId
.split(".")
.map((id) => parseInt(id, 10));
const parentTask = data.tasks.find((t) => t.id === parentId);
if (!parentTask) {
log("error", `Parent task ${parentId} not found.`);
process.exit(1);
}
if (!parentTask.subtasks) {
log("error", `Parent task ${parentId} has no subtasks.`);
process.exit(1);
}
targetTask = parentTask.subtasks.find((s) => s.id === subtaskId);
isSubtask = true;
if (!targetTask) {
log("error", `Subtask ${formattedTaskId} not found.`);
process.exit(1);
}
} else {
// Regular task (not a subtask)
targetTask = data.tasks.find((t) => t.id === formattedTaskId);
if (!targetTask) {
log("error", `Task ${formattedTaskId} not found.`);
process.exit(1);
}
}
// Check if the task has any dependencies
if (!targetTask.dependencies || targetTask.dependencies.length === 0) {
log(
"info",
`Task ${formattedTaskId} has no dependencies, nothing to remove.`
);
return;
}
// Normalize the dependency ID for comparison to handle different formats
const normalizedDependencyId = String(formattedDependencyId);
// 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}`;
}
return depStr === normalizedDependencyId;
});
if (dependencyIndex === -1) {
log(
"info",
`Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.`
);
return;
}
// Remove the dependency
targetTask.dependencies.splice(dependencyIndex, 1);
// Save the updated tasks
writeJSON(tasksPath, data);
// Success message
log(
"success",
`Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}`
);
if (!isSilentMode()) {
// Display a more visually appealing success message
console.log(
boxen(
chalk.green(`Successfully removed dependency:\n\n`) +
`Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`,
{
padding: 1,
borderColor: "green",
borderStyle: "round",
margin: { top: 1 },
}
)
);
}
// Regenerate task files
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
2025-04-09 00:25:27 +02:00
}
/**
* Check if adding a dependency would create a circular dependency
* @param {Array} tasks - Array of all tasks
* @param {number|string} taskId - ID of task to check
* @param {Array} chain - Chain of dependencies to check
* @returns {boolean} True if circular dependency would be created
*/
function isCircularDependency(tasks, taskId, chain = []) {
// Convert taskId to string for comparison
const taskIdStr = String(taskId);
// If we've seen this task before in the chain, we have a circular dependency
if (chain.some((id) => String(id) === taskIdStr)) {
return true;
}
// Find the task or subtask
let task = null;
let parentIdForSubtask = null;
// Check if this is a subtask reference (e.g., "1.2")
if (taskIdStr.includes(".")) {
const [parentId, subtaskId] = taskIdStr.split(".").map(Number);
const parentTask = tasks.find((t) => t.id === parentId);
parentIdForSubtask = parentId; // Store parent ID if it's a subtask
if (parentTask && parentTask.subtasks) {
task = parentTask.subtasks.find((st) => st.id === subtaskId);
}
} else {
// Regular task
task = tasks.find((t) => String(t.id) === taskIdStr);
}
if (!task) {
return false; // Task doesn't exist, can't create circular dependency
}
// No dependencies, can't create circular dependency
if (!task.dependencies || task.dependencies.length === 0) {
return false;
}
// Check each dependency recursively
const newChain = [...chain, taskIdStr]; // Use taskIdStr for consistency
return task.dependencies.some((depId) => {
let normalizedDepId = String(depId);
// Normalize relative subtask dependencies
if (typeof depId === "number" && parentIdForSubtask !== null) {
// If the current task is a subtask AND the dependency is a number,
// assume it refers to a sibling subtask.
normalizedDepId = `${parentIdForSubtask}.${depId}`;
}
// Pass the normalized ID to the recursive call
return isCircularDependency(tasks, normalizedDepId, newChain);
});
2025-04-09 00:25:27 +02:00
}
/**
* Validate task dependencies
* @param {Array} tasks - Array of all tasks
* @returns {Object} Validation result with valid flag and issues array
*/
function validateTaskDependencies(tasks) {
const issues = [];
// Check each task's dependencies
tasks.forEach((task) => {
if (!task.dependencies) {
return; // No dependencies to validate
}
task.dependencies.forEach((depId) => {
// Check for self-dependencies
if (String(depId) === String(task.id)) {
issues.push({
type: "self",
taskId: task.id,
message: `Task ${task.id} depends on itself`,
});
return;
}
// Check if dependency exists
if (!taskExists(tasks, depId)) {
issues.push({
type: "missing",
taskId: task.id,
dependencyId: depId,
message: `Task ${task.id} depends on non-existent task ${depId}`,
});
}
});
// Check for circular dependencies
if (isCircularDependency(tasks, task.id)) {
issues.push({
type: "circular",
taskId: task.id,
message: `Task ${task.id} is part of a circular dependency chain`,
});
}
// Check subtask dependencies if they exist
if (task.subtasks && task.subtasks.length > 0) {
task.subtasks.forEach((subtask) => {
if (!subtask.dependencies) {
return; // No dependencies to validate
}
// Create a full subtask ID for reference
const fullSubtaskId = `${task.id}.${subtask.id}`;
subtask.dependencies.forEach((depId) => {
// Check for self-dependencies in subtasks
if (
String(depId) === String(fullSubtaskId) ||
(typeof depId === "number" && depId === subtask.id)
) {
issues.push({
type: "self",
taskId: fullSubtaskId,
message: `Subtask ${fullSubtaskId} depends on itself`,
});
return;
}
// Check if dependency exists
if (!taskExists(tasks, depId)) {
issues.push({
type: "missing",
taskId: fullSubtaskId,
dependencyId: depId,
message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}`,
});
}
});
// Check for circular dependencies in subtasks
if (isCircularDependency(tasks, fullSubtaskId)) {
issues.push({
type: "circular",
taskId: fullSubtaskId,
message: `Subtask ${fullSubtaskId} is part of a circular dependency chain`,
});
}
});
}
});
return {
valid: issues.length === 0,
issues,
};
2025-04-09 00:25:27 +02:00
}
/**
* Remove duplicate dependencies from tasks
* @param {Object} tasksData - Tasks data object with tasks array
* @returns {Object} Updated tasks data with duplicates removed
*/
function removeDuplicateDependencies(tasksData) {
const tasks = tasksData.tasks.map((task) => {
if (!task.dependencies) {
return task;
}
// Convert to Set and back to array to remove duplicates
const uniqueDeps = [...new Set(task.dependencies)];
return {
...task,
dependencies: uniqueDeps,
};
});
return {
...tasksData,
tasks,
};
2025-04-09 00:25:27 +02:00
}
/**
* Clean up invalid subtask dependencies
* @param {Object} tasksData - Tasks data object with tasks array
* @returns {Object} Updated tasks data with invalid subtask dependencies removed
*/
function cleanupSubtaskDependencies(tasksData) {
const tasks = tasksData.tasks.map((task) => {
// Handle task's own dependencies
if (task.dependencies) {
task.dependencies = task.dependencies.filter((depId) => {
// Keep only dependencies that exist
return taskExists(tasksData.tasks, depId);
});
}
// Handle subtask dependencies
if (task.subtasks) {
task.subtasks = task.subtasks.map((subtask) => {
if (!subtask.dependencies) {
return subtask;
}
// Filter out dependencies to non-existent subtasks
subtask.dependencies = subtask.dependencies.filter((depId) => {
return taskExists(tasksData.tasks, depId);
});
return subtask;
});
}
return task;
});
return {
...tasksData,
tasks,
};
2025-04-09 00:25:27 +02:00
}
/**
* Validate dependencies in task files
* @param {string} tasksPath - Path to tasks.json
*/
async function validateDependenciesCommand(tasksPath, options = {}) {
log("info", "Checking for invalid dependencies in task files...");
// Read tasks data
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
log("error", "No valid tasks found in tasks.json");
process.exit(1);
}
// Count of tasks and subtasks for reporting
const taskCount = data.tasks.length;
let subtaskCount = 0;
data.tasks.forEach((task) => {
if (task.subtasks && Array.isArray(task.subtasks)) {
subtaskCount += task.subtasks.length;
}
});
log(
"info",
`Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...`
);
try {
// Directly call the validation function
const validationResult = validateTaskDependencies(data.tasks);
if (!validationResult.valid) {
log(
"error",
`Dependency validation failed. Found ${validationResult.issues.length} issue(s):`
);
validationResult.issues.forEach((issue) => {
let errorMsg = ` [${issue.type.toUpperCase()}] Task ${issue.taskId}: ${issue.message}`;
if (issue.dependencyId) {
errorMsg += ` (Dependency: ${issue.dependencyId})`;
}
log("error", errorMsg); // Log each issue as an error
});
// Optionally exit if validation fails, depending on desired behavior
// process.exit(1); // Uncomment if validation failure should stop the process
// Display summary box even on failure, showing issues found
if (!isSilentMode()) {
console.log(
boxen(
chalk.red(`Dependency Validation FAILED\n\n`) +
`${chalk.cyan("Tasks checked:")} ${taskCount}\n` +
`${chalk.cyan("Subtasks checked:")} ${subtaskCount}\n` +
`${chalk.red("Issues found:")} ${validationResult.issues.length}`, // Display count from result
{
padding: 1,
borderColor: "red",
borderStyle: "round",
margin: { top: 1, bottom: 1 },
}
)
);
}
} else {
log(
"success",
"No invalid dependencies found - all dependencies are valid"
);
// Show validation summary - only if not in silent mode
if (!isSilentMode()) {
console.log(
boxen(
chalk.green(`All Dependencies Are Valid\n\n`) +
`${chalk.cyan("Tasks checked:")} ${taskCount}\n` +
`${chalk.cyan("Subtasks checked:")} ${subtaskCount}\n` +
`${chalk.cyan("Total dependencies verified:")} ${countAllDependencies(data.tasks)}`,
{
padding: 1,
borderColor: "green",
borderStyle: "round",
margin: { top: 1, bottom: 1 },
}
)
);
}
}
} catch (error) {
log("error", "Error validating dependencies:", error);
process.exit(1);
}
2025-04-09 00:25:27 +02:00
}
/**
* Helper function to count all dependencies across tasks and subtasks
* @param {Array} tasks - All tasks
* @returns {number} - Total number of dependencies
*/
function countAllDependencies(tasks) {
let count = 0;
tasks.forEach((task) => {
// Count main task dependencies
if (task.dependencies && Array.isArray(task.dependencies)) {
count += task.dependencies.length;
}
// Count subtask dependencies
if (task.subtasks && Array.isArray(task.subtasks)) {
task.subtasks.forEach((subtask) => {
if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
count += subtask.dependencies.length;
}
});
}
});
return count;
2025-04-09 00:25:27 +02:00
}
/**
* Fixes invalid dependencies in tasks.json
* @param {string} tasksPath - Path to tasks.json
* @param {Object} options - Options object
2025-04-09 00:25:27 +02:00
*/
async function fixDependenciesCommand(tasksPath, options = {}) {
log("info", "Checking for and fixing invalid dependencies in tasks.json...");
try {
// Read tasks data
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
log("error", "No valid tasks found in tasks.json");
process.exit(1);
}
// Create a deep copy of the original data for comparison
const originalData = JSON.parse(JSON.stringify(data));
// Track fixes for reporting
const stats = {
nonExistentDependenciesRemoved: 0,
selfDependenciesRemoved: 0,
duplicateDependenciesRemoved: 0,
circularDependenciesFixed: 0,
tasksFixed: 0,
subtasksFixed: 0,
};
// First phase: Remove duplicate dependencies in tasks
data.tasks.forEach((task) => {
if (task.dependencies && Array.isArray(task.dependencies)) {
const uniqueDeps = new Set();
const originalLength = task.dependencies.length;
task.dependencies = task.dependencies.filter((depId) => {
const depIdStr = String(depId);
if (uniqueDeps.has(depIdStr)) {
log(
"info",
`Removing duplicate dependency from task ${task.id}: ${depId}`
);
stats.duplicateDependenciesRemoved++;
return false;
}
uniqueDeps.add(depIdStr);
return true;
});
if (task.dependencies.length < originalLength) {
stats.tasksFixed++;
}
}
// Check for duplicates in subtasks
if (task.subtasks && Array.isArray(task.subtasks)) {
task.subtasks.forEach((subtask) => {
if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
const uniqueDeps = new Set();
const originalLength = subtask.dependencies.length;
subtask.dependencies = subtask.dependencies.filter((depId) => {
let depIdStr = String(depId);
if (typeof depId === "number" && depId < 100) {
depIdStr = `${task.id}.${depId}`;
}
if (uniqueDeps.has(depIdStr)) {
log(
"info",
`Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}`
);
stats.duplicateDependenciesRemoved++;
return false;
}
uniqueDeps.add(depIdStr);
return true;
});
if (subtask.dependencies.length < originalLength) {
stats.subtasksFixed++;
}
}
});
}
});
// Create validity maps for tasks and subtasks
const validTaskIds = new Set(data.tasks.map((t) => t.id));
const validSubtaskIds = new Set();
data.tasks.forEach((task) => {
if (task.subtasks && Array.isArray(task.subtasks)) {
task.subtasks.forEach((subtask) => {
validSubtaskIds.add(`${task.id}.${subtask.id}`);
});
}
});
// Second phase: Remove invalid task dependencies (non-existent tasks)
data.tasks.forEach((task) => {
if (task.dependencies && Array.isArray(task.dependencies)) {
const originalLength = task.dependencies.length;
task.dependencies = task.dependencies.filter((depId) => {
const isSubtask = typeof depId === "string" && depId.includes(".");
if (isSubtask) {
// Check if the subtask exists
if (!validSubtaskIds.has(depId)) {
log(
"info",
`Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)`
);
stats.nonExistentDependenciesRemoved++;
return false;
}
return true;
} else {
// Check if the task exists
const numericId =
typeof depId === "string" ? parseInt(depId, 10) : depId;
if (!validTaskIds.has(numericId)) {
log(
"info",
`Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)`
);
stats.nonExistentDependenciesRemoved++;
return false;
}
return true;
}
});
if (task.dependencies.length < originalLength) {
stats.tasksFixed++;
}
}
// Check subtask dependencies for invalid references
if (task.subtasks && Array.isArray(task.subtasks)) {
task.subtasks.forEach((subtask) => {
if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
const originalLength = subtask.dependencies.length;
const subtaskId = `${task.id}.${subtask.id}`;
// First check for self-dependencies
const hasSelfDependency = subtask.dependencies.some((depId) => {
if (typeof depId === "string" && depId.includes(".")) {
return depId === subtaskId;
} else if (typeof depId === "number" && depId < 100) {
return depId === subtask.id;
}
return false;
});
if (hasSelfDependency) {
subtask.dependencies = subtask.dependencies.filter((depId) => {
const normalizedDepId =
typeof depId === "number" && depId < 100
? `${task.id}.${depId}`
: String(depId);
if (normalizedDepId === subtaskId) {
log(
"info",
`Removing self-dependency from subtask ${subtaskId}`
);
stats.selfDependenciesRemoved++;
return false;
}
return true;
});
}
// Then check for non-existent dependencies
subtask.dependencies = subtask.dependencies.filter((depId) => {
if (typeof depId === "string" && depId.includes(".")) {
if (!validSubtaskIds.has(depId)) {
log(
"info",
`Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)`
);
stats.nonExistentDependenciesRemoved++;
return false;
}
return true;
}
// Handle numeric dependencies
const numericId =
typeof depId === "number" ? depId : parseInt(depId, 10);
// Small numbers likely refer to subtasks in the same task
if (numericId < 100) {
const fullSubtaskId = `${task.id}.${numericId}`;
if (!validSubtaskIds.has(fullSubtaskId)) {
log(
"info",
`Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}`
);
stats.nonExistentDependenciesRemoved++;
return false;
}
return true;
}
// Otherwise it's a task reference
if (!validTaskIds.has(numericId)) {
log(
"info",
`Removing invalid task dependency from subtask ${subtaskId}: ${numericId}`
);
stats.nonExistentDependenciesRemoved++;
return false;
}
return true;
});
if (subtask.dependencies.length < originalLength) {
stats.subtasksFixed++;
}
}
});
}
});
// Third phase: Check for circular dependencies
log("info", "Checking for circular dependencies...");
// Build the dependency map for subtasks
const subtaskDependencyMap = new Map();
data.tasks.forEach((task) => {
if (task.subtasks && Array.isArray(task.subtasks)) {
task.subtasks.forEach((subtask) => {
const subtaskId = `${task.id}.${subtask.id}`;
if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
const normalizedDeps = subtask.dependencies.map((depId) => {
if (typeof depId === "string" && depId.includes(".")) {
return depId;
} else if (typeof depId === "number" && depId < 100) {
return `${task.id}.${depId}`;
}
return String(depId);
});
subtaskDependencyMap.set(subtaskId, normalizedDeps);
} else {
subtaskDependencyMap.set(subtaskId, []);
}
});
}
});
// Check for and fix circular dependencies
for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) {
const visited = new Set();
const recursionStack = new Set();
// Detect cycles
const cycleEdges = findCycles(
subtaskId,
subtaskDependencyMap,
visited,
recursionStack
);
if (cycleEdges.length > 0) {
const [taskId, subtaskNum] = subtaskId
.split(".")
.map((part) => Number(part));
const task = data.tasks.find((t) => t.id === taskId);
if (task && task.subtasks) {
const subtask = task.subtasks.find((st) => st.id === subtaskNum);
if (subtask && subtask.dependencies) {
const originalLength = subtask.dependencies.length;
const edgesToRemove = cycleEdges.map((edge) => {
if (edge.includes(".")) {
const [depTaskId, depSubtaskId] = edge
.split(".")
.map((part) => Number(part));
if (depTaskId === taskId) {
return depSubtaskId;
}
return edge;
}
return Number(edge);
});
subtask.dependencies = subtask.dependencies.filter((depId) => {
const normalizedDepId =
typeof depId === "number" && depId < 100
? `${taskId}.${depId}`
: String(depId);
if (
edgesToRemove.includes(depId) ||
edgesToRemove.includes(normalizedDepId)
) {
log(
"info",
`Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}`
);
stats.circularDependenciesFixed++;
return false;
}
return true;
});
if (subtask.dependencies.length < originalLength) {
stats.subtasksFixed++;
}
}
}
}
}
// Check if any changes were made by comparing with original data
const dataChanged = JSON.stringify(data) !== JSON.stringify(originalData);
if (dataChanged) {
// Save the changes
writeJSON(tasksPath, data);
log("success", "Fixed dependency issues in tasks.json");
// Regenerate task files
log("info", "Regenerating task files to reflect dependency changes...");
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
} else {
log("info", "No changes needed to fix dependencies");
}
// Show detailed statistics report
const totalFixedAll =
stats.nonExistentDependenciesRemoved +
stats.selfDependenciesRemoved +
stats.duplicateDependenciesRemoved +
stats.circularDependenciesFixed;
if (!isSilentMode()) {
if (totalFixedAll > 0) {
log("success", `Fixed ${totalFixedAll} dependency issues in total!`);
console.log(
boxen(
chalk.green(`Dependency Fixes Summary:\n\n`) +
`${chalk.cyan("Invalid dependencies removed:")} ${stats.nonExistentDependenciesRemoved}\n` +
`${chalk.cyan("Self-dependencies removed:")} ${stats.selfDependenciesRemoved}\n` +
`${chalk.cyan("Duplicate dependencies removed:")} ${stats.duplicateDependenciesRemoved}\n` +
`${chalk.cyan("Circular dependencies fixed:")} ${stats.circularDependenciesFixed}\n\n` +
`${chalk.cyan("Tasks fixed:")} ${stats.tasksFixed}\n` +
`${chalk.cyan("Subtasks fixed:")} ${stats.subtasksFixed}\n`,
{
padding: 1,
borderColor: "green",
borderStyle: "round",
margin: { top: 1, bottom: 1 },
}
)
);
} else {
log(
"success",
"No dependency issues found - all dependencies are valid"
);
console.log(
boxen(
chalk.green(`All Dependencies Are Valid\n\n`) +
`${chalk.cyan("Tasks checked:")} ${data.tasks.length}\n` +
`${chalk.cyan("Total dependencies verified:")} ${countAllDependencies(data.tasks)}`,
{
padding: 1,
borderColor: "green",
borderStyle: "round",
margin: { top: 1, bottom: 1 },
}
)
);
}
}
} catch (error) {
log("error", "Error in fix-dependencies command:", error);
process.exit(1);
}
2025-04-09 00:25:27 +02:00
}
/**
* Ensure at least one subtask in each task has no dependencies
* @param {Object} tasksData - The tasks data object with tasks array
* @returns {boolean} - True if any changes were made
*/
function ensureAtLeastOneIndependentSubtask(tasksData) {
if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
return false;
}
let changesDetected = false;
tasksData.tasks.forEach((task) => {
if (
!task.subtasks ||
!Array.isArray(task.subtasks) ||
task.subtasks.length === 0
) {
return;
}
// Check if any subtask has no dependencies
const hasIndependentSubtask = task.subtasks.some(
(st) =>
!st.dependencies ||
!Array.isArray(st.dependencies) ||
st.dependencies.length === 0
);
if (!hasIndependentSubtask) {
// Find the first subtask and clear its dependencies
if (task.subtasks.length > 0) {
const firstSubtask = task.subtasks[0];
log(
"debug",
`Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}`
);
firstSubtask.dependencies = [];
changesDetected = true;
}
}
});
return changesDetected;
2025-04-09 00:25:27 +02:00
}
/**
* Validate and fix dependencies across all tasks and subtasks
* This function is designed to be called after any task modification
* @param {Object} tasksData - The tasks data object with tasks array
* @param {string} tasksPath - Optional path to save the changes
* @returns {boolean} - True if any changes were made
*/
function validateAndFixDependencies(tasksData, tasksPath = null) {
if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
log("error", "Invalid tasks data");
return false;
}
log("debug", "Validating and fixing dependencies...");
// Create a deep copy for comparison
const originalData = JSON.parse(JSON.stringify(tasksData));
// 1. Remove duplicate dependencies from tasks and subtasks
tasksData.tasks = tasksData.tasks.map((task) => {
// Handle task dependencies
if (task.dependencies) {
const uniqueDeps = [...new Set(task.dependencies)];
task.dependencies = uniqueDeps;
}
// Handle subtask dependencies
if (task.subtasks) {
task.subtasks = task.subtasks.map((subtask) => {
if (subtask.dependencies) {
const uniqueDeps = [...new Set(subtask.dependencies)];
subtask.dependencies = uniqueDeps;
}
return subtask;
});
}
return task;
});
// 2. Remove invalid task dependencies (non-existent tasks)
tasksData.tasks.forEach((task) => {
// Clean up task dependencies
if (task.dependencies) {
task.dependencies = task.dependencies.filter((depId) => {
// Remove self-dependencies
if (String(depId) === String(task.id)) {
return false;
}
// Remove non-existent dependencies
return taskExists(tasksData.tasks, depId);
});
}
// Clean up subtask dependencies
if (task.subtasks) {
task.subtasks.forEach((subtask) => {
if (subtask.dependencies) {
subtask.dependencies = subtask.dependencies.filter((depId) => {
// Handle numeric subtask references
if (typeof depId === "number" && depId < 100) {
const fullSubtaskId = `${task.id}.${depId}`;
return taskExists(tasksData.tasks, fullSubtaskId);
}
// Handle full task/subtask references
return taskExists(tasksData.tasks, depId);
});
}
});
}
});
// 3. Ensure at least one subtask has no dependencies in each task
tasksData.tasks.forEach((task) => {
if (task.subtasks && task.subtasks.length > 0) {
const hasIndependentSubtask = task.subtasks.some(
(st) =>
!st.dependencies ||
!Array.isArray(st.dependencies) ||
st.dependencies.length === 0
);
if (!hasIndependentSubtask) {
task.subtasks[0].dependencies = [];
}
}
});
// Check if any changes were made by comparing with original data
const changesDetected =
JSON.stringify(tasksData) !== JSON.stringify(originalData);
// Save changes if needed
if (tasksPath && changesDetected) {
try {
writeJSON(tasksPath, tasksData);
log("debug", "Saved dependency fixes to tasks.json");
} catch (error) {
log("error", "Failed to save dependency fixes to tasks.json", error);
}
}
return changesDetected;
2025-04-09 00:25:27 +02:00
}
export {
addDependency,
removeDependency,
isCircularDependency,
validateTaskDependencies,
validateDependenciesCommand,
fixDependenciesCommand,
removeDuplicateDependencies,
cleanupSubtaskDependencies,
ensureAtLeastOneIndependentSubtask,
validateAndFixDependencies,
2025-04-09 00:25:27 +02:00
};