workflows/default/systems/mcp/tools/task-create/script.ps1
|
function Invoke-TaskCreate { param( [hashtable]$Arguments ) # Extract arguments $name = $Arguments['name'] $description = $Arguments['description'] $category = $Arguments['category'] $priority = $Arguments['priority'] $effort = $Arguments['effort'] $dependencies = $Arguments['dependencies'] $acceptanceCriteria = $Arguments['acceptance_criteria'] $steps = $Arguments['steps'] $applicableStandards = $Arguments['applicable_standards'] $applicableAgents = $Arguments['applicable_agents'] $applicableSkills = $Arguments['applicable_skills'] $applicableDecisions = $Arguments['applicable_decisions'] $needsInterview = $Arguments['needs_interview'] -eq $true $humanHours = $Arguments['human_hours'] $aiHours = $Arguments['ai_hours'] $workingDir = $Arguments['working_dir'] $taskType = if ($Arguments.ContainsKey('type') -and $Arguments['type']) { $Arguments['type'] } else { $null } $scriptPath = if ($Arguments.ContainsKey('script_path')) { $Arguments['script_path'] } else { $null } $mcpTool = if ($Arguments.ContainsKey('mcp_tool')) { $Arguments['mcp_tool'] } else { $null } $mcpArgs = if ($Arguments.ContainsKey('mcp_args')) { $Arguments['mcp_args'] } else { $null } $skipAnalysis = if ($Arguments.ContainsKey('skip_analysis')) { $Arguments['skip_analysis'] } else { $null } $skipWorktree = if ($Arguments.ContainsKey('skip_worktree')) { $Arguments['skip_worktree'] } else { $null } # Validate required fields if (-not $name) { throw "Task name is required" } if (-not $description) { throw "Task description is required" } # Validate category # Read categories from settings.default.json if available; fall back to defaults $defaultCategories = @('core', 'feature', 'enhancement', 'bugfix', 'infrastructure', 'ui-ux') $settingsPath = Join-Path $global:DotbotProjectRoot ".bot\settings\settings.default.json" if (Test-Path $settingsPath) { $settings = Get-Content $settingsPath -Raw | ConvertFrom-Json if ($settings.task_categories) { $validCategories = @($settings.task_categories) + $defaultCategories | Select-Object -Unique } else { $validCategories = $defaultCategories } } else { $validCategories = $defaultCategories } if ($category -and $category -notin $validCategories) { throw "Invalid category. Must be one of: $($validCategories -join ', ')" } # Validate effort $validEfforts = @('XS', 'S', 'M', 'L', 'XL') if ($effort -and $effort -notin $validEfforts) { throw "Invalid effort. Must be one of: $($validEfforts -join ', ')" } # Validate task type $validTypes = @('prompt', 'prompt_template', 'script', 'mcp', 'task_gen') if ($null -ne $taskType -and $taskType -notin $validTypes) { throw "Invalid type '$taskType'. Must be one of: $($validTypes -join ', ')" } if ($taskType -in @('script', 'task_gen') -and -not $scriptPath) { throw "script_path is required for type '$taskType'" } if ($taskType -eq 'mcp' -and -not $mcpTool) { throw "mcp_tool is required for type 'mcp'" } if ($taskType -eq 'prompt_template' -and -not $Arguments['prompt']) { throw "prompt is required for type 'prompt_template' (path to prompt template file)" } # Set defaults if (-not $category) { $category = 'feature' } if (-not $priority) { $priority = 50 } if (-not $effort) { $effort = 'M' } if (-not $dependencies) { $dependencies = @() } if (-not $acceptanceCriteria) { $acceptanceCriteria = @() } if (-not $steps) { $steps = @() } if (-not $applicableStandards) { $applicableStandards = @() } if (-not $applicableAgents) { $applicableAgents = @() } if (-not $applicableSkills) { $applicableSkills = @() } if (-not $applicableDecisions) { $applicableDecisions = @() } # needsInterview is already a boolean, no default needed # Validate dependencies exist if ($dependencies -and $dependencies.Count -gt 0) { # Import task index module $indexModule = Join-Path $global:DotbotProjectRoot ".bot\systems\mcp\modules\TaskIndexCache.psm1" if (-not (Get-Module TaskIndexCache)) { Import-Module $indexModule -Force } # Initialize index $tasksBaseDir = Join-Path $global:DotbotProjectRoot ".bot\workspace\tasks" Initialize-TaskIndex -TasksBaseDir $tasksBaseDir $index = Get-TaskIndex $invalidDeps = @() foreach ($dep in $dependencies) { $depLower = $dep.ToLower() $found = $false # Check all tasks (todo, in-progress, done) $allTasks = @($index.Todo.Values) + @($index.InProgress.Values) + @($index.Done.Values) foreach ($task in $allTasks) { # Check ID match if ($task.id -eq $dep) { $found = $true; break } # Check name match if ($task.name -eq $dep) { $found = $true; break } # Check slug match (generated from name) $taskSlug = ($task.name -replace '[^\w\s-]', '' -replace '\s+', '-').ToLower() if ($taskSlug -eq $depLower) { $found = $true; break } # Fuzzy match if ($taskSlug -like "*$depLower*" -or $depLower -like "*$taskSlug*") { $found = $true; break } } if (-not $found) { $invalidDeps += $dep } } if ($invalidDeps.Count -gt 0) { $depList = $invalidDeps -join "', '" throw "Invalid dependencies: '$depList'. These tasks do not exist. Create dependency tasks first or remove these dependencies." } } # Generate unique ID $id = [System.Guid]::NewGuid().ToString() # Defaults for type-related fields if (-not $taskType) { $taskType = 'prompt' } if ($taskType -ne 'prompt') { $skipAnalysis = if ($skipAnalysis -is [bool]) { $skipAnalysis } else { $true } $skipWorktree = if ($skipWorktree -is [bool]) { $skipWorktree } else { $true } } else { $skipAnalysis = if ($skipAnalysis -is [bool]) { $skipAnalysis } else { $false } $skipWorktree = if ($skipWorktree -is [bool]) { $skipWorktree } else { $false } } if (-not $mcpArgs) { $mcpArgs = @{} } # Create task object $task = @{ id = $id name = $name description = $description category = $category priority = [int]$priority effort = $effort status = 'todo' type = $taskType dependencies = $dependencies acceptance_criteria = $acceptanceCriteria steps = $steps applicable_standards = $applicableStandards applicable_agents = $applicableAgents applicable_skills = $applicableSkills applicable_decisions = $applicableDecisions needs_interview = $needsInterview human_hours = $humanHours ai_hours = $aiHours working_dir = $workingDir skip_analysis = [bool]$skipAnalysis skip_worktree = [bool]$skipWorktree created_at = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'") updated_at = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'") completed_at = $null } # Add type-specific fields if ($scriptPath) { $task['script_path'] = $scriptPath } if ($mcpTool) { $task['mcp_tool'] = $mcpTool } if ($mcpArgs.Count -gt 0) { $task['mcp_args'] = $mcpArgs } # Passthrough: preserve extra/custom fields from input (e.g., research_prompt, external_repo) $reservedFields = @('id', 'status', 'created_at', 'updated_at', 'completed_at') foreach ($key in $Arguments.Keys) { if (-not $task.ContainsKey($key) -and $key -notin $reservedFields) { $task[$key] = $Arguments[$key] } } # Define file path $tasksDir = Join-Path $global:DotbotProjectRoot ".bot\workspace\tasks\todo" # Ensure directory exists if (-not (Test-Path $tasksDir)) { New-Item -ItemType Directory -Force -Path $tasksDir | Out-Null } # Create filename from name (sanitized) $fileName = ($name -replace '[^\w\s-]', '' -replace '\s+', '-').ToLower() if ($fileName.Length -gt 50) { $fileName = $fileName.Substring(0, 50) } $fileName = "$fileName-$($id.Split('-')[0]).json" $filePath = Join-Path $tasksDir $fileName # Save task to file $task | ConvertTo-Json -Depth 10 | Set-Content -Path $filePath -Encoding UTF8 # Return result return @{ success = $true task_id = $id file_path = $filePath message = "Task '$name' created successfully with ID: $id" } } |