Private/Get-FixFromMarkdown.ps1

<#
.SYNOPSIS
    Extract fix/resolution steps from markdown content.
#>


function Get-FixFromMarkdown {
    <#
    .SYNOPSIS
        Extract fix, resolution, mitigation, or workaround sections from markdown.
    .DESCRIPTION
        Heuristically identifies sections containing fix steps by looking for:
        - Headings containing: fix, resolution, mitigation, workaround, steps, solution
        - PowerShell code blocks with commands
        - Numbered lists or bullet lists following such headings
    .PARAMETER MarkdownContent
        The markdown content to parse.
    .OUTPUTS
        PSCustomObject with FixSummary and FixSteps properties.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [AllowEmptyString()]
        [string]$MarkdownContent
    )

    $result = [PSCustomObject]@{
        FixSummary = ''
        FixSteps   = @()
    }

    if ([string]::IsNullOrWhiteSpace($MarkdownContent)) {
        return $result
    }

    # Look for sections with fix-related keywords
    $fixKeywords = @('fix', 'resolution', 'mitigation', 'workaround', 'solution', 'steps', 'remediation', 'repair', 'command')
    
    $lines = $MarkdownContent -split "`r?`n"
    $inFixSection = $false
    $inCodeBlock = $false
    $currentSection = @()
    $fixSectionContent = @()
    $codeBlockLines = @()
    $codeBlockMarker = '```'

    for ($i = 0; $i -lt $lines.Count; $i++) {
        $line = $lines[$i]
        
        # Check for code block markers
        if ($line.Trim() -match '^```') {
            if ($inCodeBlock) {
                # End of code block - save it if we're in a fix section
                if ($inFixSection -and @($codeBlockLines).Count -gt 0) {
                    $currentSection += $codeBlockMarker
                    $currentSection += $codeBlockLines
                    $currentSection += $codeBlockMarker
                }
                $codeBlockLines = @()
                $inCodeBlock = $false
            } else {
                # Start of code block
                $inCodeBlock = $true
                $codeBlockLines = @()
            }
            continue
        }

        # Collect code block content
        if ($inCodeBlock) {
            $codeBlockLines += $line
            continue
        }
        
        # Check if this is a heading with fix keywords
        if ($line -match '^#{1,6}\s+(.+)$') {
            $heading = $matches[1].ToLowerInvariant()
            
            # If we were in a fix section, save it
            if ($inFixSection -and @($currentSection).Count -gt 0) {
                $fixSectionContent += $currentSection
                $currentSection = @()
            }

            # Check if this heading indicates a fix section
            $inFixSection = $false
            foreach ($keyword in $fixKeywords) {
                if ($heading -like "*$keyword*") {
                    $inFixSection = $true
                    $currentSection += $line
                    break
                }
            }
        } elseif ($inFixSection) {
            # Collect content within fix section
            $currentSection += $line
        }
    }

    # Add final section if we were in one
    if ($inFixSection -and @($currentSection).Count -gt 0) {
        $fixSectionContent += $currentSection
    }

    # Extract steps from fix section content - prioritize code blocks as complete steps
    $steps = @()
    $contextText = @()  # Collect descriptive text that's not a step
    $inExtractCodeBlock = $false
    $currentCodeBlock = @()
    $stepCounter = 0
    
    foreach ($line in $fixSectionContent) {
        # Handle code blocks
        if ($line.Trim() -match '^```') {
            if ($inExtractCodeBlock) {
                # End of code block - process it
                if (@($currentCodeBlock).Count -gt 0) {
                    # Clean up code: remove comments, empty lines, join into logical commands
                    $cleanedCode = $currentCodeBlock | Where-Object { 
                        $_.Trim() -ne '' -and 
                        -not ($_.TrimStart() -match '^#[^!]') 
                    }
                    
                    if (@($cleanedCode).Count -gt 0) {
                        $stepCounter++
                        # Create a structured step with the full code block
                        $codeStep = [PSCustomObject]@{
                            Number  = $stepCounter
                            Type    = 'Code'
                            Content = $cleanedCode -join "`n"
                        }
                        $steps += $codeStep
                    }
                }
                $currentCodeBlock = @()
                $inExtractCodeBlock = $false
            } else {
                $inExtractCodeBlock = $true
            }
            continue
        }

        if ($inExtractCodeBlock) {
            $currentCodeBlock += $line
            continue
        }

        # Skip headings, empty lines, and blockquotes (notes/context)
        # Blockquotes (>) contain context/notes, not actionable steps
        if ([string]::IsNullOrWhiteSpace($line) -or $line -match '^#{1,6}\s' -or $line.TrimStart() -match '^>') {
            # If it's a blockquote, save stripped text as context
            if ($line -match '^\s*>\s*(.+)$') {
                $contextText += $matches[1].Trim()
            }
            continue
        }
        
        # Skip descriptive/contextual text that shouldn't be a step
        if ($line -imatch '(you will need|you must|you should|do the following|follow these|complete the|perform the|this (may|will|should)|check |below command|for more)') {
            $contextText += $line.Trim()
            continue
        }

        # Look for narrative instructions between code blocks
        # Lines starting with "Then" or "Next" etc - BUT skip filler phrases like "Then you will need"
        if ($line -match '^\s*(Then|Next|Now|After|Finally)\s+(.+)$' -and 
            $line -inotmatch '(you will need|you must|you should|do the following)') {
            $stepCounter++
            $textStep = [PSCustomObject]@{
                Number  = $stepCounter
                Type    = 'Text'
                Content = $matches[2].Trim()
            }
            $steps += $textStep
        }
        # Numbered list item (e.g., "1. Step one")
        elseif ($line -match '^\s*\d+\.\s+(.+)$') {
            $stepCounter++
            $textStep = [PSCustomObject]@{
                Number  = $stepCounter
                Type    = 'Text'
                Content = $matches[1].Trim()
            }
            $steps += $textStep
        }
        # Bullet list item (e.g., "- Step one" or "* Step one")
        elseif ($line -match '^\s*[-*]\s+(.+)$') {
            $stepCounter++
            $textStep = [PSCustomObject]@{
                Number  = $stepCounter
                Type    = 'Text'
                Content = $matches[1].Trim()
            }
            $steps += $textStep
        }
    }

    # Create fix summary - prioritize Overview/Symptoms sections for meaningful context
    $summary = ''
    
    # First, check the broader markdown for overview/symptoms sections
    $allLines = $MarkdownContent -split "`r?`n"
    $inOverview = $false
    foreach ($line in $allLines) {
        # Look for Overview, Symptoms, Issue, or Cause headings
        if ($line -match '^#{1,6}\s+(Overview|Symptoms?|Issue|Cause|Description)') {
            $inOverview = $true
            continue
        }
        # Stop at next heading
        if ($inOverview -and $line -match '^#{1,6}\s+') {
            break
        }
        # Collect first meaningful paragraph
        if ($inOverview -and -not [string]::IsNullOrWhiteSpace($line) -and 
            $line -notmatch '^#{1,6}\s' -and 
            $line.Trim() -notmatch '^```' -and
            $line.TrimStart() -notmatch '^>' -and
            $line.Length -gt 30) {
            $summary = $line.Trim()
            if ($summary.Length -gt 200) {
                $summary = $summary.Substring(0, 197) + '...'
            }
            break
        }
    }
    
    # Fallback: try to find a good descriptive paragraph from fix section
    if ([string]::IsNullOrWhiteSpace($summary)) {
        foreach ($line in $fixSectionContent) {
            if (-not [string]::IsNullOrWhiteSpace($line) -and 
                $line -notmatch '^\s*[-*\d]' -and 
                $line -notmatch '^#{1,6}\s' -and 
                $line.Trim() -notmatch '^```' -and
                $line.TrimStart() -notmatch '^>' -and
                $line -inotmatch '(we will need|you will need|you must|you should|do the following|follow these|complete the|perform the|then you|^check )' -and
                $line.Length -gt 30) {
                $summary = $line.Trim()
                if ($summary.Length -gt 200) {
                    $summary = $summary.Substring(0, 197) + '...'
                }
                break
            }
        }
    }

    $result.FixSummary = $summary
    $result.FixSteps = $steps

    return $result
}