workflows/default/systems/mcp/tools/task-mark-done/script.ps1
|
# Import modules Import-Module (Join-Path $global:DotbotProjectRoot ".bot\systems\mcp\modules\SessionTracking.psm1") -Force Import-Module (Join-Path $global:DotbotProjectRoot ".bot\systems\mcp\modules\PathSanitizer.psm1") -Force Import-Module (Join-Path $global:DotbotProjectRoot ".bot\systems\mcp\modules\TaskStore.psm1") -Force # Helper: append a diagnostic entry to the shared activity log so the operator # can see task_mark_done failures in the dashboard activity stream. function Write-TaskMarkDoneFailure { param( [string]$TaskId, [string]$Message, [array]$VerificationResults = @() ) try { $controlDir = Join-Path $global:DotbotProjectRoot ".bot\.control" $activityFile = Join-Path $controlDir "activity.jsonl" if (-not (Test-Path $controlDir)) { return } $failedScripts = @($VerificationResults | Where-Object { $_.success -eq $false -and -not $_.skipped }) if ($failedScripts.Count -gt 0) { $detail = ($failedScripts | ForEach-Object { $failLines = if ($_.failures) { ($_.failures | ForEach-Object { $_.issue }) -join '; ' } else { $_.message } "$($_.script): $failLines" }) -join ' | ' $Message = "$Message — $detail" } $entry = [ordered]@{ type = "text" timestamp = (Get-Date).ToUniversalTime().ToString("o") message = $Message task_id = $TaskId phase = "execution" process_id = $env:DOTBOT_PROCESS_ID } ($entry | ConvertTo-Json -Compress) | Add-Content -Path $activityFile -Encoding UTF8 } catch { # Non-fatal } } # Helper function to extract execution-phase activity logs function Get-ExecutionActivityLog { param( [string]$TaskId, [string]$ProjectRoot ) $controlDir = Join-Path $global:DotbotProjectRoot ".bot\.control" $activityFile = Join-Path $controlDir "activity.jsonl" if (-not (Test-Path $activityFile)) { return @() } $taskActivities = @() Get-Content $activityFile | ForEach-Object { try { $entry = $_ | ConvertFrom-Json if ($entry.task_id -eq $TaskId -and (-not $entry.phase -or $entry.phase -eq 'execution')) { $sanitizedMessage = Remove-AbsolutePaths -Text $entry.message -ProjectRoot $ProjectRoot $sanitizedEntry = $entry | Select-Object -Property type, timestamp $sanitizedEntry | Add-Member -NotePropertyName 'message' -NotePropertyValue $sanitizedMessage -Force $taskActivities += $sanitizedEntry } } catch { Write-BotLog -Level Debug -Message "Cleanup: failed to remove item" -Exception $_ } } return $taskActivities } function Invoke-VerificationScripts { param( [string]$TaskId, [string]$Category, [string]$ProjectRoot ) $scriptsDir = Join-Path $global:DotbotProjectRoot ".bot\hooks\verify" $configPath = Join-Path $scriptsDir "config.json" if (-not (Test-Path $configPath)) { return @{ AllPassed = $true; Scripts = @() } } $config = Get-Content $configPath -Raw | ConvertFrom-Json $results = @() foreach ($scriptConfig in $config.scripts) { $scriptPath = Join-Path $scriptsDir $scriptConfig.name if (-not (Test-Path $scriptPath)) { $results += @{ success = $false; script = $scriptConfig.name; message = "Script file not found" } continue } if ($scriptConfig.skip_if_category -and $scriptConfig.skip_if_category -contains $Category) { $results += @{ success = $true; script = $scriptConfig.name; message = "Skipped (category: $Category)"; skipped = $true } continue } if ($scriptConfig.run_if_category -and $scriptConfig.run_if_category -notcontains $Category) { $results += @{ success = $true; script = $scriptConfig.name; message = "Skipped (not applicable for category: $Category)"; skipped = $true } continue } try { if (-not $ProjectRoot) { throw "Project root parameter is required" } if (-not (Test-Path $ProjectRoot)) { throw "Project root directory does not exist: $ProjectRoot" } if (-not (Test-Path (Join-Path $ProjectRoot ".git"))) { throw "Project root does not contain .git folder: $ProjectRoot" } Push-Location $ProjectRoot try { $output = & $scriptPath -TaskId $TaskId -Category $Category 2>&1 $result = $output | ConvertFrom-Json -ErrorAction Stop $results += $result } finally { Pop-Location } if ($scriptConfig.required -and -not $result.success) { break } } catch { $results += @{ success = $false script = $scriptConfig.name message = "Script execution failed: $($_.Exception.Message)" details = @{ error = $_.Exception.Message } } if ($scriptConfig.required) { break } } } $failedScripts = $results | Where-Object { $_.success -eq $false -and -not $_.skipped } return @{ AllPassed = ($failedScripts.Count -eq 0); Scripts = $results } } function Invoke-TaskMarkDone { param( [hashtable]$Arguments ) $taskId = $Arguments['task_id'] if (-not $taskId) { throw "Task ID is required" } $projectRoot = $global:DotbotProjectRoot if (-not $projectRoot) { throw "Project root not available. MCP server may not have initialized correctly." } # Pre-read the task to run verification before the transition $found = Find-TaskFileById -TaskId $taskId -SearchStatuses @('todo', 'analysing', 'analysed', 'in-progress', 'done') if (-not $found) { Write-TaskMarkDoneFailure -TaskId $taskId -Message "task_mark_done failed: task '$taskId' not found in todo/, analysing/, analysed/, in-progress/, or done/" throw "Task with ID '$taskId' not found" } # Already done — idempotent if ($found.Status -eq 'done') { return @{ success = $true; message = "Task is already marked as done"; task_id = $taskId; status = 'done' } } $taskContent = $found.Content # Run verification scripts BEFORE transition $verificationResults = Invoke-VerificationScripts -TaskId $taskId -Category $taskContent.category -ProjectRoot $projectRoot if (-not $verificationResults.AllPassed) { Write-TaskMarkDoneFailure -TaskId $taskId -Message "task_mark_done blocked: verification failed for '$($taskContent.name)'" -VerificationResults $verificationResults.Scripts return @{ success = $false message = "Task verification failed - task stays in '$($found.Status)'" task_id = $taskId current_status = $found.Status verification_passed = $false verification_results = $verificationResults.Scripts } } # Extract commit information $commitUpdates = @{} try { $modulePath = Join-Path $global:DotbotProjectRoot ".bot\systems\mcp\modules\Extract-CommitInfo.ps1" if (Test-Path $modulePath) { . $modulePath $commits = Get-TaskCommitInfo -TaskId $taskId -ProjectRoot $projectRoot if ($commits -and $commits.Count -gt 0) { $mostRecent = $commits[0] $commitUpdates['commit_sha'] = $mostRecent.commit_sha $commitUpdates['commit_subject'] = $mostRecent.commit_subject $commitUpdates['files_created'] = $mostRecent.files_created $commitUpdates['files_deleted'] = $mostRecent.files_deleted $commitUpdates['files_modified'] = $mostRecent.files_modified $commitUpdates['commits'] = $commits } } } catch { Write-BotLog -Level Warn -Message "Failed to extract commit info" -Exception $_ } # Capture execution-phase activity log $executionActivities = Get-ExecutionActivityLog -TaskId $taskId -ProjectRoot $projectRoot # Build updates $updates = @{ completed_at = if (-not $taskContent.completed_at) { (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'") } else { $taskContent.completed_at } } foreach ($key in $commitUpdates.Keys) { $updates[$key] = $commitUpdates[$key] } if ($executionActivities.Count -gt 0) { $updates['execution_activity_log'] = $executionActivities } $result = Move-TaskState -TaskId $taskId ` -FromStates @('todo', 'analysing', 'analysed', 'in-progress', 'done') ` -ToState 'done' ` -Updates $updates # Close current Claude session (execution complete) $claudeSessionId = $env:CLAUDE_SESSION_ID if ($claudeSessionId) { Close-SessionOnTask -TaskContent $result.task_content -SessionId $claudeSessionId -Phase 'execution' $result.task_content | ConvertTo-Json -Depth 20 | Set-Content -Path $result.file_path -Encoding UTF8 } return @{ success = $true message = "Task marked as done" task_id = $taskId old_status = $result.old_status new_status = 'done' old_path = $found.File.FullName new_path = $result.file_path verification_passed = $true verification_results = $verificationResults.Scripts } } |