workflows/default/systems/mcp/modules/Extract-CommitInfo.ps1

<#
.SYNOPSIS
    Extracts commit information for a given task ID.

.DESCRIPTION
    Searches recent commits for task ID references in the format [task:XXXXXXXX]
    and optionally parses workspace tags [bot:XXXXXXXX] from matching commits.
    and returns commit details including file changes.

.PARAMETER TaskId
    The task ID to search for. Can be full UUID or short 8-character ID.

.PARAMETER MaxCommits
    Maximum number of commits to search. Defaults to 50.

.PARAMETER ProjectRoot
    The root directory of the git repository. Defaults to current directory.

.EXAMPLE
    Get-TaskCommitInfo -TaskId "7b012fb8-d6fa-45e8-b89e-062b4bcb16ae"

.EXAMPLE
    Get-TaskCommitInfo -TaskId "7b012fb8" -MaxCommits 100
#>


function Get-TaskCommitInfo {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TaskId,

        [Parameter(Mandatory = $false)]
        [int]$MaxCommits = 50,

        [Parameter(Mandatory = $false)]
        [string]$ProjectRoot = $PWD
    )

    # Extract short task ID (first 8 characters)
    $shortTaskId = $TaskId.Substring(0, [Math]::Min(8, $TaskId.Length))

    # Pattern to search for in commit messages
    $taskPattern = "\[task:$shortTaskId\]"

    $results = @()

    try {
        Push-Location $ProjectRoot

        # Get list of commit SHAs first
        $commitShas = git log -n $MaxCommits --format="%H" 2>&1

        if ($LASTEXITCODE -ne 0) {
            Write-BotLog -Level Warn -Message "Failed to get git log: $commitShas"
            return $results
        }

        # Handle array or single string
        if ($commitShas -is [string]) {
            $commitShas = @($commitShas)
        }

        foreach ($sha in $commitShas) {
            $sha = $sha.Trim()
            if (-not $sha) { continue }

            # Get full commit message for this SHA
            $commitMessage = git log -1 --format="%B" $sha 2>&1
            if ($commitMessage -is [array]) {
                $commitMessage = $commitMessage -join "`n"
            }

            # Check if this commit contains our task ID
            if ($commitMessage -match $taskPattern) {
                # Get commit metadata
                $commitSubject = git log -1 --format="%s" $sha 2>&1
                $commitTimestamp = git log -1 --format="%aI" $sha 2>&1

                if ($commitSubject -is [array]) { $commitSubject = $commitSubject[0] }
                if ($commitTimestamp -is [array]) { $commitTimestamp = $commitTimestamp[0] }

                # Extract workspace short ID from [bot:XXXXXXXX] or [bot:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]
                $workspaceShortId = $null
                $botTagMatch = [regex]::Match($commitMessage, '\[bot:([0-9a-fA-F]{8})(?:-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})?\]')
                if ($botTagMatch.Success) {
                    $workspaceShortId = $botTagMatch.Groups[1].Value.ToLowerInvariant()
                }
                # Get file changes for this commit
                $fileChanges = Get-CommitFileChanges -CommitSha $sha

                $commitInfo = @{
                    commit_sha = $sha
                    commit_subject = $commitSubject.Trim()
                    commit_message = $commitMessage.Trim()
                    commit_timestamp = $commitTimestamp.Trim()
                    workspace_short_id = $workspaceShortId
                    files_created = $fileChanges.Created
                    files_deleted = $fileChanges.Deleted
                    files_modified = $fileChanges.Modified
                }

                $results += $commitInfo
            }
        }
    }
    catch {
        Write-BotLog -Level Warn -Message "Error extracting commit info" -Exception $_
    }
    finally {
        Pop-Location
    }

    return ,$results
}

function Get-CommitFileChanges {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$CommitSha
    )

    $created = @()
    $deleted = @()
    $modified = @()

    try {
        # Get file changes with status using diff-tree
        # --name-status shows: A (added), D (deleted), M (modified), R (renamed), C (copied)
        $diffOutput = git diff-tree --no-commit-id --name-status -r $CommitSha 2>&1

        if ($LASTEXITCODE -ne 0) {
            Write-BotLog -Level Warn -Message "Failed to get diff-tree for $CommitSha"
            return @{
                Created = $created
                Deleted = $deleted
                Modified = $modified
            }
        }

        # Handle array or single string
        if ($diffOutput -is [string]) {
            $lines = @($diffOutput)
        } else {
            $lines = $diffOutput
        }

        foreach ($line in $lines) {
            if (-not $line -or -not $line.Trim()) { continue }

            # Format: STATUS<tab>FILENAME (or STATUS<tab>OLDFILE<tab>NEWFILE for renames)
            $parts = $line -split "`t"
            if ($parts.Count -lt 2) { continue }

            $status = $parts[0].Trim()
            $filePath = $parts[1].Trim()

            switch -Regex ($status) {
                '^A' { $created += $filePath }
                '^D' { $deleted += $filePath }
                '^M' { $modified += $filePath }
                '^R' {
                    # Rename: old file deleted, new file created
                    $deleted += $filePath
                    if ($parts.Count -gt 2) {
                        $created += $parts[2].Trim()
                    }
                }
                '^C' {
                    # Copy: new file created
                    if ($parts.Count -gt 2) {
                        $created += $parts[2].Trim()
                    }
                }
            }
        }
    }
    catch {
        Write-BotLog -Level Warn -Message "Error getting file changes" -Exception $_
    }

    return @{
        Created = $created
        Deleted = $deleted
        Modified = $modified
    }
}

# Functions are exported automatically when dot-sourced