Win Cleaner

Data & APIs

Deep clean Windows C drive junk files. Analyzes disk usage, identifies safe-to-delete items, and cleans caches, temp files, logs, browser data, and application caches while protecting user data. Use when the user asks to clean C drive, free up disk space, or remove junk files on Windows.

Install

openclaw skills install win-cleaner

Windows C Drive Deep Cleaner

You are a disk cleanup specialist. This skill uses a scan-first, pattern-match, clean-later strategy that adapts to any Windows computer.

Primary directive: Maximize freed space while guaranteeing zero user data loss and zero system stability impact.


Expected Yield Per Phase

Use this to prioritize if the user wants a quick clean vs. deep clean:

PhaseTypical YieldTimePriority
4a (Package managers)2-6 GB1-2 minHIGH — always run
4b (Windows junk)0.5-4 GB2-3 minHIGH — always run
4c (DISM)0.5-3 GB5-10 minMEDIUM
4d (VSS resize)0.5-2 GB1 minMEDIUM
5a (Pattern caches)0.5-3 GB2-5 minHIGH — always run
5b (Browser caches)0.3-2 GB1-2 minHIGH — always run
5c-e (IDE/Corrupted/GPU)0.1-2 GB1-3 minMEDIUM
6 (User decisions)2-10 GBvariesDepends on user

If user asks for "quick clean": Phases 1 → 4a → 4b → 5a → 5b → 7. Skip DISM and VSS. If user asks for "deep clean": All phases in order.


Safety Guarantees

What This Skill Will NEVER Do

  • Delete any file from C:\Windows\System32, C:\Windows\SysWOW64, or C:\Windows\WinSxS (except via DISM which is safe)
  • Delete any file from C:\Windows\System32\DriverStore (driver store)
  • Delete .exe, .dll, .sys files anywhere on disk
  • Delete any file from C:\Program Files or C:\Program Files (x86)
  • Delete any file from user Documents, Pictures, Music, Videos unless it's a provably corrupted database dump
  • Delete any registry keys or system configuration
  • Run format, cleanmgr /sageset, or any tool that opens a GUI
  • Execute destructive commands like rm -rf C:\ or del /f /s C:\*

Pattern Matching Safety Rules

When using regex to find cache folders, apply these constraints:

  1. Match whole directory name only — use exact equality (-eq, -contains), never substring matching (-match, -like)
  2. Do NOT match directory names that merely contain cache words (e.g., "MyCacheProject", "cache_manager", "template-parser")
  3. Skip any directory that contains a .git subfolder, package.json, Cargo.toml, or CMakeLists.txt file (indicates a project, not cache)
  4. Skip directories with a Readme or README file
  5. Only search within AppData and known dev-tool paths — never search Documents, Desktop, or Downloads for pattern-based cleaning

Pre-Flight Safety Checks

Before any deletion, verify:

  • The target is not a reparse point or junction that points to a data drive (check via Get-Item $path | Select-Object LinkType)
  • The folder was last modified more than 1 minute ago (not a running process's active temp)

Phase 1: Disk Overview

Run these 3 commands in parallel:

# 1a. Disk usage summary
Get-PSDrive C | ForEach-Object {
    $total = [math]::Round(($_.Used + $_.Free)/1GB, 2)
    $used = [math]::Round($_.Used/1GB, 2)
    $free = [math]::Round($_.Free/1GB, 2)
    $pct = [math]::Round($_.Used/($_.Used+$_.Free)*100, 1)
    Write-Host "C: Total=${total}GB Used=${used}GB Free=${free}GB Usage=${pct}%"
    # Store for final report comparison
    Write-Host "PHASE1_USED=$used"
}

# 1b. Top-level directory sizes (excluding reparse points)
Get-ChildItem C:\ -Directory -ErrorAction SilentlyContinue -Attributes !ReparsePoint | ForEach-Object {
    $size = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
    [PSCustomObject]@{Name=$_.Name; SizeGB=[math]::Round($size/1GB, 2)}
} | Sort-Object SizeGB -Descending | Format-Table -AutoSize

# 1c. User profile sizes
Get-ChildItem C:\Users -Directory -ErrorAction SilentlyContinue | ForEach-Object {
    $size = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
    [PSCustomObject]@{User=$_.Name; SizeGB=[math]::Round($size/1GB, 2)}
} | Sort-Object SizeGB -Descending | Format-Table -AutoSize

Phase 2: Comprehensive System Junk Scan

Run these in parallel (2-3 PowerShell calls):

2a. Windows System Temp & Log Locations

$locations = @(
    [PSCustomObject]@{Name="Windows Temp";          Path="C:\Windows\Temp"},
    [PSCustomObject]@{Name="User Temp";             Path=$env:TEMP},
    [PSCustomObject]@{Name="Prefetch";              Path="C:\Windows\Prefetch"},
    [PSCustomObject]@{Name="Update Downloads";      Path="C:\Windows\SoftwareDistribution\Download"},
    [PSCustomObject]@{Name="Delivery Optimization"; Path="C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Windows\DeliveryOptimization"},
    [PSCustomObject]@{Name="Error Reports";         Path="C:\ProgramData\Microsoft\Windows\WER"},
    [PSCustomObject]@{Name="Panther Setup Logs";    Path="C:\Windows\Panther"},
    [PSCustomObject]@{Name="CBS Logs";              Path="C:\Windows\Logs\CBS"},
    [PSCustomObject]@{Name="DISM Logs";             Path="C:\Windows\Logs\DISM"},
    [PSCustomObject]@{Name="Event Logs";            Path="C:\Windows\System32\winevt\Logs"},
    [PSCustomObject]@{Name="Windows Installer";     Path="C:\Windows\Installer"},
    [PSCustomObject]@{Name="ProgramData Pkg Cache"; Path="C:\ProgramData\Package Cache"},
    [PSCustomObject]@{Name="Thumbnail Cache";       Path="$env:LOCALAPPDATA\Microsoft\Windows\Explorer"},
    [PSCustomObject]@{Name="Font Cache";            Path="$env:LOCALAPPDATA\Microsoft\Windows\Fonts"}
)

foreach ($loc in $locations) {
    if (Test-Path $loc.Path) {
        $size = (Get-ChildItem $loc.Path -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
        if ($size -gt 1MB) {
            [PSCustomObject]@{Location=$loc.Name; SizeMB=[math]::Round($size/1MB,1)}
        }
    }
} | Sort-Object SizeMB -Descending | Format-Table -AutoSize

2b. Special Files & System State

# Hibernation file
if (Test-Path C:\hiberfil.sys) {
    $h = Get-Item C:\hiberfil.sys -Force
    Write-Host "hiberfil.sys: $([math]::Round($h.Length/1GB,2)) GB (disable with: powercfg /h off)"
}
# Page file
if (Test-Path C:\pagefile.sys) {
    $p = Get-Item C:\pagefile.sys -Force
    Write-Host "pagefile.sys: $([math]::Round($p.Length/1GB,2)) GB"
}
# Swap file
if (Test-Path C:\swapfile.sys) {
    $s = Get-Item C:\swapfile.sys -Force
    Write-Host "swapfile.sys: $([math]::Round($s.Length/1GB,2)) GB"
}
# Windows.old
if (Test-Path C:\Windows.old) {
    $wo = (Get-ChildItem C:\Windows.old -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
    Write-Host "Windows.old: $([math]::Round($wo/1GB,2)) GB (can be removed via Disk Cleanup → Clean up system files)"
}
# Memory dumps
Get-ChildItem C:\Windows\*.dmp -ErrorAction SilentlyContinue | ForEach-Object {
    Write-Host "Crash dump: $($_.Name) = $([math]::Round($_.Length/1MB,1)) MB"
}
if (Test-Path C:\Windows\Memory.dmp) {
    $md = Get-Item C:\Windows\Memory.dmp
    Write-Host "Memory.dmp: $([math]::Round($md.Length/1GB,2)) GB"
}
# Live kernel dump
if (Test-Path C:\Windows\LiveKernelReports) {
    $lk = (Get-ChildItem C:\Windows\LiveKernelReports -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($lk -gt 10MB) { Write-Host "LiveKernelReports: $([math]::Round($lk/1MB,1)) MB" }
}
# VSS / System Restore
vssadmin list shadowstorage 2>&1
# DISM component store analysis
dism /online /cleanup-image /analyzecomponentstore 2>&1 | Select-String -Pattern "recommend|Actual|claimed|cleanup" -SimpleMatch

2c. C:\ Root Orphan Detection

# Find large non-system files at C:\ root
Get-ChildItem C:\ -File -ErrorAction SilentlyContinue | Where-Object {
    $_.Length -gt 50MB -and
    $_.Name -notin @('hiberfil.sys','pagefile.sys','swapfile.sys','DumpStack.log')
} | ForEach-Object {
    Write-Host "C:\$($_.Name): $([math]::Round($_.Length/1MB,1)) MB — suspicious large file at root"
}
# Check non-standard root directories
@("C:\tmp", "C:\temp", "C:\backup", "C:\old", "C:\dump") | ForEach-Object {
    if (Test-Path $_) {
        $size = (Get-ChildItem $_ -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
        if ($size -gt 10MB) {
            Write-Host "$_ : $([math]::Round($size/1MB,1)) MB — non-standard root directory"
            Get-ChildItem $_ -Recurse -File -ErrorAction SilentlyContinue | Sort-Object Length -Descending | Select-Object -First 5 | ForEach-Object {
                Write-Host "  $($_.Name): $([math]::Round($_.Length/1MB,1)) MB"
            }
        }
    }
}

Phase 3: Dynamic User Profile Discovery

3a. Deep AppData Scan — ALL three AppData roots (Local, Roaming, LocalLow)

LocalLow ($env:USERPROFILE\AppData\LocalLow) is often overlooked but can hold GPU driver caches, game saves, and app configs. It must be scanned.

$appDataRoots = @{
    $env:LOCALAPPDATA = "Local"
    $env:APPDATA     = "Roaming"
    "$env:USERPROFILE\AppData\LocalLow" = "LocalLow"
}
foreach ($pair in $appDataRoots.GetEnumerator()) {
    $root = $pair.Key
    $rootName = $pair.Value
    if (-not (Test-Path $root)) { continue }
    Get-ChildItem $root -Directory -ErrorAction SilentlyContinue | ForEach-Object {
        $appName = $_.Name
        $appPath = $_.FullName
        Get-ChildItem $appPath -Directory -ErrorAction SilentlyContinue | ForEach-Object {
            $size = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
            if ($size -gt 50MB) {
                [PSCustomObject]@{Scope=$rootName; App=$appName; Subfolder=$_.Name; SizeMB=[math]::Round($size/1MB,1)}
            }
        }
        $fileSize = (Get-ChildItem $appPath -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
        if ($fileSize -gt 100MB) {
            [PSCustomObject]@{Scope=$rootName; App=$appName; Subfolder="(root files)"; SizeMB=[math]::Round($fileSize/1MB,1)}
        }
    } | Sort-Object SizeMB -Descending | Format-Table -AutoSize
}

3b. Profile Root — All Directories (hidden AND visible)

This catches SDK caches, portable app data, and abandoned project folders that sit directly in the user's home directory. Many cleanup tools miss this because they only look at AppData.

# 3b-1: Measure ALL directories in user root (hidden + visible), report > 50MB
Get-ChildItem $env:USERPROFILE -Directory -ErrorAction SilentlyContinue -Force | ForEach-Object {
    $size = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
    if ($size -gt 50MB) {
        $isHidden = ($_.Attributes -band [System.IO.FileAttributes]::Hidden) -eq [System.IO.FileAttributes]::Hidden
        $category = if ($_.Name -match '^\.') { "Dev/SDK" } 
                    elseif ($_.Name -eq 'go') { "Go ecosystem" }
                    elseif ($_.Name -match 'node_modules') { "Node.js" }
                    elseif ($isHidden) { "Hidden" }
                    else { "Standard" }
        [PSCustomObject]@{Folder=$_.Name; SizeMB=[math]::Round($size/1MB,1); Hidden=$isHidden; Category=$category}
    }
} | Sort-Object SizeMB -Descending | Format-Table -AutoSize

# 3b-2: Identify known-cleanable SDK caches (safe to auto-clean later)
Write-Host "`n=== Identifiable SDK caches in user root ==="
$knownCaches = @{
    "$env:USERPROFILE\.openjfx\cache" = "JavaFX cache"
    "$env:USERPROFILE\.cache" = "XDG cache (Linux-tool cache on Windows)"
    "$env:USERPROFILE\go\pkg" = "Go module cache"
    "$env:USERPROFILE\.go\pkg" = "Go module cache (hidden)"
    "$env:USERPROFILE\.shiv" = "Python shiv tool cache"
    "$env:USERPROFILE\.cargo\registry\cache" = "Cargo registry cache"
    "$env:USERPROFILE\.gradle\caches" = "Gradle cache"
    "$env:USERPROFILE\.m2\repository" = "Maven repo cache"
}
foreach ($pair in $knownCaches.GetEnumerator()) {
    if (Test-Path $pair.Key) {
        $s = (Get-ChildItem $pair.Key -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
        if ($s -gt 10MB) { Write-Host "$($pair.Value): $([math]::Round($s/1MB,1)) MB — $($pair.Key)" }
    }
}

### 3c. Documents Deep Scan

```powershell
Get-ChildItem "$env:USERPROFILE\Documents" -Directory -ErrorAction SilentlyContinue | ForEach-Object {
    $size = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
    if ($size -gt 100MB) {
        [PSCustomObject]@{Folder=$_.Name; SizeMB=[math]::Round($size/1MB,1)}
    }
} | Sort-Object SizeMB -Descending | Format-Table -AutoSize

# Large individual files in Documents
Get-ChildItem "$env:USERPROFILE\Documents" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt 100MB } | Sort-Object Length -Descending | Select-Object -First 20 | ForEach-Object {
    Write-Host "$($_.Directory.Name)\$($_.Name): $([math]::Round($_.Length/1MB,1)) MB"
}

3d. Desktop & Downloads Scan

# Desktop
Write-Host "=== Desktop ==="
$desktopSize = (Get-ChildItem "$env:USERPROFILE\Desktop" -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
Write-Host "Total: $([math]::Round($desktopSize/1GB,2)) GB"
Get-ChildItem "$env:USERPROFILE\Desktop" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt 20MB } | Sort-Object Length -Descending | Select-Object -First 15 | ForEach-Object {
    Write-Host "  $($_.Name): $([math]::Round($_.Length/1MB,1)) MB"
}

# Downloads (report only, never auto-clean)
Write-Host "`n=== Downloads ==="
$dlSize = (Get-ChildItem "$env:USERPROFILE\Downloads" -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
Write-Host "Total: $([math]::Round($dlSize/1GB,2)) GB (NOT auto-cleaned — user decision only)"

3e. Environment-Specific Detection

# Docker
$dockerData = "$env:USERPROFILE\.docker"
$dockerDesktop = "$env:LOCALAPPDATA\Docker"
if ((Test-Path $dockerData) -or (Test-Path $dockerDesktop)) {
    Write-Host "Docker detected. Check: docker system df"
    docker system df 2>$null
}

# WSL
wsl --list --verbose 2>$null
if (Test-Path "$env:LOCALAPPDATA\Packages") {
    $wslDirs = Get-ChildItem "$env:LOCALAPPDATA\Packages" -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "Canonical|WSL|Debian|Ubuntu|kali" }
    foreach ($d in $wslDirs) {
        $size = (Get-ChildItem $d.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
        if ($size -gt 100MB) { Write-Host "WSL distro $($d.Name): $([math]::Round($size/1GB,2)) GB (VHDX max size, may not represent actual usage)" }
    }
}

# OneDrive
$oneDrive = "$env:USERPROFILE\OneDrive"
if (Test-Path $oneDrive) {
    $odSize = (Get-ChildItem $oneDrive -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
    Write-Host "OneDrive local cache: $([math]::Round($odSize/1GB,2)) GB (use 'Files On-Demand' to free space)"
}

# JetBrains IDEs
$jbDirs = @("$env:LOCALAPPDATA\JetBrains", "$env:APPDATA\JetBrains")
foreach ($jb in $jbDirs) {
    if (Test-Path $jb) {
        $jbsize = (Get-ChildItem $jb -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
        Write-Host "JetBrains: $([math]::Round($jbsize/1GB,2)) GB at $jb"
    }
}

# Android Studio / AVD images (can be very large)
$avdDir = "$env:USERPROFILE\.android\avd"
if (Test-Path $avdDir) {
    $avdSize = (Get-ChildItem $avdDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($avdSize -gt 100MB) { Write-Host "Android AVD emulator images: $([math]::Round($avdSize/1GB,2)) GB" }
}

# GPU shader caches
foreach ($gpu in @("$env:LOCALAPPDATA\NVIDIA\DXCache", "$env:LOCALAPPDATA\NVIDIA\GLCache", 
                    "$env:LOCALAPPDATA\AMD\DxCache", "$env:LOCALAPPDATA\AMD\GLCache",
                    "C:\ProgramData\NVIDIA Corporation\NV_Cache")) {
    if (Test-Path $gpu) {
        $gpuSize = (Get-ChildItem $gpu -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
        if ($gpuSize -gt 10MB) { Write-Host "GPU shader cache $gpu : $([math]::Round($gpuSize/1MB,1)) MB" }
    }
}

3f. User Profile — Junk File & Pattern Audit

This phase scans for specific junk indicators across the ENTIRE user profile — files and folders that are provably safe to delete but are NOT caught by the generic cache-pattern matching in Phase 5.

Write-Host "=== User profile junk audit ==="

# 3f-1: Squirrel installer versioned app-* folders (old Electron app versions)
Get-ChildItem "$env:LOCALAPPDATA" -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '-full$' } | ForEach-Object {
    $s = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($s -gt 50MB) { Write-Host "Squirrel stale: $($_.FullName) ($([math]::Round($s/1MB,1)) MB)" }
}

# 3f-2: Electron app caches (auto-detect common apps)
foreach ($app in @('Discord', 'Slack', 'Microsoft Teams', 'Spotify', 'Zoom', 'Postman', 'Figma', 'Notion', 'GitHubDesktop')) {
    foreach ($scope in @($env:LOCALAPPDATA, $env:APPDATA)) {
        $appPath = "$scope\$app"
        if (Test-Path $appPath) {
            $s = (Get-ChildItem $appPath -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
            if ($s -gt 50MB) { Write-Host "Electron app $app : $([math]::Round($s/1MB,1)) MB at $appPath" }
        }
    }
}

# 3f-3: iTunes / Apple device backups (can be 10+ GB)
$appleBackup = "$env:APPDATA\Apple Computer\MobileSync\Backup"
if (Test-Path $appleBackup) {
    $abSize = (Get-ChildItem $appleBackup -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Write-Host "Apple device backups: $([math]::Round($abSize/1GB,2)) GB (deleting removes iPhone/iPad backup data)"
}

# 3f-4: INetCache (IE/legacy Edge cache)
$inetCache = "$env:LOCALAPPDATA\Microsoft\Windows\INetCache"
if (Test-Path $inetCache) {
    $icSize = (Get-ChildItem $inetCache -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($icSize -gt 10MB) { Write-Host "INetCache (legacy IE/Edge): $([math]::Round($icSize/1MB,1)) MB" }
}

# 3f-5: Abandoned node_modules folders in user root or shallow subdirs
Get-ChildItem $env:USERPROFILE -Directory -ErrorAction SilentlyContinue -Depth 1 | Where-Object {
    $_.Name -eq 'node_modules' -and $_.FullName -notlike '*\AppData\*'
} | ForEach-Object {
    $s = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($s -gt 50MB) { Write-Host "node_modules (non-project): $($_.FullName) ($([math]::Round($s/1MB,1)) MB)" }
}

# 3f-6: Large .log, .tmp, .dump files directly in user root
Get-ChildItem $env:USERPROFILE -File -ErrorAction SilentlyContinue | Where-Object {
    $_.Length -gt 50MB -and $_.Extension -in @('.log', '.tmp', '.temp', '.dump', '.dmp', '.etl')
} | ForEach-Object {
    Write-Host "Large junk file in user root: $($_.Name) ($([math]::Round($_.Length/1MB,1)) MB)"
}

# 3f-7: Versioned IDE/tool directories (e.g., .vscode/extensions/*/old-versions, .trae-cn)
foreach ($ideDir in @("$env:USERPROFILE\.vscode", "$env:USERPROFILE\.trae-cn", 
                       "$env:USERPROFILE\.cursor", "$env:USERPROFILE\.windsurf")) {
    if (-not (Test-Path $ideDir)) { continue }
    $ideSize = (Get-ChildItem $ideDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($ideSize -gt 100MB) { Write-Host "IDE data $ideDir : $([math]::Round($ideSize/1MB,1)) MB" }
}

Phase 4: Universal Safe Cleanup (Auto-Execute)

4a. Package Manager Caches

# pip — if installed
if (Get-Command pip -ErrorAction SilentlyContinue) {
    pip cache purge 2>&1
}
# Direct folder fallback (works even if pip not on PATH)
@("$env:LOCALAPPDATA\pip\cache", "$env:LOCALAPPDATA\pip\http", 
  "$env:LOCALAPPDATA\pip\selfcheck") | ForEach-Object {
    if (Test-Path $_) {
        $s = (Get-ChildItem $_ -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
        Write-Host "pip $(Split-Path $_ -Leaf): $([math]::Round($s/1MB,1)) MB"
        Remove-Item -Recurse -Force $_ -ErrorAction SilentlyContinue
    }
}

# uv
if (Get-Command uv -ErrorAction SilentlyContinue) { uv cache clean 2>&1 }
if (Test-Path "$env:LOCALAPPDATA\uv\cache") {
    $s = (Get-ChildItem "$env:LOCALAPPDATA\uv\cache" -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Write-Host "uv cache: $([math]::Round($s/1MB,1)) MB"
    Remove-Item -Recurse -Force "$env:LOCALAPPDATA\uv\cache" -ErrorAction SilentlyContinue
}

# npm
if (Get-Command npm -ErrorAction SilentlyContinue) { npm cache clean --force 2>&1 }

# NuGet — clean all cache subdirectories
@("$env:LOCALAPPDATA\NuGet\Cache", "$env:LOCALAPPDATA\NuGet\plugins-cache",
  "$env:LOCALAPPDATA\NuGet\http-cache", "$env:LOCALAPPDATA\NuGet\global-packages",
  "$env:LOCALAPPDATA\NuGet\v3-cache", "$env:LOCALAPPDATA\NuGet\scratch") | ForEach-Object {
    if (Test-Path $_) { Remove-Item -Recurse -Force $_ -ErrorAction SilentlyContinue }
}

# Maven — report, don't auto-clean (redownload is very slow)
if (Test-Path "$env:USERPROFILE\.m2\repository") {
    $m2Size = (Get-ChildItem "$env:USERPROFILE\.m2\repository" -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($m2Size -gt 1GB) { Write-Host "NOTE: .m2 Maven cache is $([math]::Round($m2Size/1GB,1)) GB (keeping — re-downloadable but very large)" }
}

# Gradle — only clean jars and transforms, NOT the entire caches folder
if (Test-Path "$env:USERPROFILE\.gradle\caches") {
    foreach ($sub in @('jars-9', 'jars-8', 'jars-7', 'transforms-3', 'transforms-2', 'transforms-1', 'build-cache-1')) {
        $subPath = "$env:USERPROFILE\.gradle\caches\$sub"
        if (Test-Path $subPath) {
            $gs = (Get-ChildItem $subPath -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
            if ($gs -gt 100MB) {
                Write-Host "Gradle $sub: $([math]::Round($gs/1MB,1)) MB"
                Remove-Item -Recurse -Force $subPath -ErrorAction SilentlyContinue
            }
        }
    }
}

4b. Windows System Junk

$freed = 0

# Temp directories
@("$env:TEMP", "C:\Windows\Temp") | ForEach-Object {
    if (Test-Path $_) {
        $before = (Get-ChildItem $_ -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
        Get-ChildItem $_ -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue 2>$null
        $after = (Get-ChildItem $_ -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
        $diff = $before - $after
        $freed += $diff
        Write-Host "$_ : $([math]::Round($diff/1MB,1)) MB"
    }
}

# Prefetch
$pfBefore = (Get-ChildItem C:\Windows\Prefetch\*.pf -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
Get-ChildItem C:\Windows\Prefetch\*.pf -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue
$freed += $pfBefore
Write-Host "Prefetch: $([math]::Round($pfBefore/1MB,1)) MB"

# Thumbnail cache
$thumbBefore = (Get-ChildItem "$env:LOCALAPPDATA\Microsoft\Windows\Explorer" -Filter "thumbcache_*" -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
Get-ChildItem "$env:LOCALAPPDATA\Microsoft\Windows\Explorer" -Filter "thumbcache_*" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue
$freed += $thumbBefore

# Font cache
$fontDir = "$env:LOCALAPPDATA\Microsoft\Windows\Fonts"
if (Test-Path $fontDir) {
    $fontBefore = (Get-ChildItem $fontDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Get-ChildItem $fontDir -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    $freed += $fontBefore
    Write-Host "Font cache: $([math]::Round($fontBefore/1MB,1)) MB"
}

# DNS cache
ipconfig /flushdns 2>$null

# Recycle Bin
Clear-RecycleBin -Force -ErrorAction SilentlyContinue
Write-Host "Recycle Bin emptied"

# Windows Panther setup logs
if (Test-Path C:\Windows\Panther\monitor) {
    $pmSize = (Get-ChildItem C:\Windows\Panther\monitor -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Get-ChildItem C:\Windows\Panther\monitor -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    $freed += $pmSize
    Write-Host "Panther monitor logs: $([math]::Round($pmSize/1MB,1)) MB"
}
Get-ChildItem C:\Windows\Panther -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -match '\.(log|etl|txt)$' } | ForEach-Object {
    $freed += $_.Length; Remove-Item $_.FullName -Force
}

# Delivery Optimization
$doDir = "C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Windows\DeliveryOptimization"
if (Test-Path $doDir) {
    $doSize = (Get-ChildItem $doDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Get-ChildItem $doDir -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    $freed += $doSize
}

# Windows Update download cache
$wuDir = "C:\Windows\SoftwareDistribution\Download"
if (Test-Path $wuDir) {
    $wuS = (Get-ChildItem $wuDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Get-ChildItem $wuDir -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    $freed += $wuS
    Write-Host "Windows Update cache: $([math]::Round($wuS/1MB,1)) MB"
}

# Old CBS persist logs (keep current CBS.log)
Get-ChildItem C:\Windows\Logs\CBS -Filter "CbsPersist_*.log" -ErrorAction SilentlyContinue | ForEach-Object {
    $freed += $_.Length; Remove-Item $_ -Force
}

# DISM logs > 10MB
Get-ChildItem C:\Windows\Logs\DISM -Filter "dism*.log" -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt 10MB } | ForEach-Object {
    $freed += $_.Length; Remove-Item $_ -Force
}

# Windows Error Reporting
$werDir = "C:\ProgramData\Microsoft\Windows\WER"
if (Test-Path $werDir) {
    $werS = (Get-ChildItem $werDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($werS -gt 10MB) {
        Get-ChildItem $werDir -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
        $freed += $werS
        Write-Host "WER: $([math]::Round($werS/1MB,1)) MB"
    }
}

# Live Kernel Reports
$lkDir = "C:\Windows\LiveKernelReports"
if (Test-Path $lkDir) {
    $lkSize = (Get-ChildItem $lkDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($lkSize -gt 10MB) {
        Get-ChildItem $lkDir -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
        $freed += $lkSize
        Write-Host "LiveKernelReports: $([math]::Round($lkSize/1MB,1)) MB"
    }
}

Write-Host "`nPhase 4b total: $([math]::Round($freed/1MB,1)) MB"
# Report this number so the LLM can use it in the final summary
Write-Host "PHASE4B_FREED=$([math]::Round($freed/1MB,1))"

4c. DISM Component Cleanup (long timeout: 600s)

Write-Host "DISM basic cleanup..."
dism /online /cleanup-image /startcomponentcleanup
Write-Host "DISM deep cleanup (/resetbase) — removes ALL superseded components."
Write-Host "WARNING: After /resetbase, you cannot uninstall Windows updates. This is generally fine."
dism /online /cleanup-image /startcomponentcleanup /resetbase

4d. System Restore / VSS Slim Down

Write-Host "Resizing VSS to 1GB max..."
vssadmin resize shadowstorage /for=C: /on=C: /maxsize=1GB
vssadmin list shadowstorage

4e. Windows\Installer Orphaned Patch Files

# Report only — do NOT auto-delete
$installerDir = "C:\Windows\Installer"
if (Test-Path $installerDir) {
    $instSize = (Get-ChildItem $installerDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Write-Host "Windows Installer cache: $([math]::Round($instSize/1GB,2)) GB (not auto-cleaned — if > 2GB, flag as manual cleanup item)"
}

Phase 5: Pattern-Based Application Cache Cleanup

Safety Constraints

  1. Search only within AppData and dev-tool paths — never Documents, Desktop, Downloads
  2. Match whole directory name via -contains, never substring matching
  3. Skip directories containing .git, package.json, Cargo.toml, CMakeLists.txt, or README
  4. Recurse deep enough to catch nested caches — use -Depth 8 from AppData roots

5a. Exact-Match Cache Pattern Cleaning

# EXACT directory names that are universally safe cache folders
$safePatterns = @(
    'Cache', 'cache', 'CACHE',
    'Temp', 'temp', 'TEMP',
    'Log', 'Logs', 'log', 'logs',
    'Code Cache',
    'GPUCache',
    'DawnCache',
    'ShaderCache',
    'GrShaderCache',
    'Crashpad',
    'CrashDumps',
    'Crash Reports',
    'thumbnails', 'Thumbnails',
    'preview', 'Preview',
    '__pycache__'
)

$searchRoots = @($env:LOCALAPPDATA, $env:APPDATA, "$env:USERPROFILE\AppData\LocalLow", "$env:USERPROFILE\.vscode")
$totalFreed = 0

foreach ($root in $searchRoots) {
    if (-not (Test-Path $root)) { continue }
    Get-ChildItem $root -Recurse -Directory -ErrorAction SilentlyContinue -Depth 8 | Where-Object {
        $safePatterns -contains $_.Name
    } | ForEach-Object {
        # Safety: skip project directories
        $isProject = (Test-Path "$($_.FullName)\.git") -or 
                     (Test-Path "$($_.FullName)\package.json") -or 
                     (Test-Path "$($_.FullName)\Cargo.toml") -or
                     (Test-Path "$($_.FullName)\CMakeLists.txt") -or
                     (Test-Path "$($_.FullName)\README") -or
                     (Test-Path "$($_.FullName)\README.md")
        if ($isProject) { return }
        # Safety: skip reparse points
        $item = Get-Item $_.FullName -ErrorAction SilentlyContinue
        if ($item.LinkType) { return }
        
        $size = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
        if ($size -gt 1MB) {
            Write-Host "Cache: $($_.FullName) ($([math]::Round($size/1MB,1)) MB)"
            $totalFreed += $size
            Remove-Item -Recurse -Force $_.FullName -ErrorAction SilentlyContinue
        }
    }
}
Write-Host "`nPhase 5a total: $([math]::Round($totalFreed/1MB,1)) MB"
Write-Host "PHASE5A_FREED=$([math]::Round($totalFreed/1MB,1))"

5b. GPU Shader Caches (clean BEFORE browser caches)

$gpuFreed = 0
foreach ($gpuPath in @(
    "$env:LOCALAPPDATA\NVIDIA\DXCache",
    "$env:LOCALAPPDATA\NVIDIA\GLCache",
    "$env:LOCALAPPDATA\AMD\DxCache",
    "$env:LOCALAPPDATA\AMD\GLCache",
    "$env:LOCALAPPDATA\AMD\VkCache",
    "$env:PROGRAMDATA\NVIDIA Corporation\NV_Cache"
)) {
    if (Test-Path $gpuPath) {
        $gs = (Get-ChildItem $gpuPath -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
        if ($gs -gt 5MB) {
            Write-Host "GPU cache $gpuPath : $([math]::Round($gs/1MB,1)) MB"
            $gpuFreed += $gs
            Get-ChildItem $gpuPath -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
        }
    }
}
Write-Host "GPU shader caches: $([math]::Round($gpuFreed/1MB,1)) MB"

5c. Browser Cache Deep Clean (Chromium + Firefox)

$browserFreed = 0

# --- Chromium-based browsers (auto-discover) ---
$browserDataDirs = @()
foreach ($vendor in @('Google', 'Microsoft', 'BraveSoftware', 'Opera Software')) {
    $vendorPath = "$env:LOCALAPPDATA\$vendor"
    if (Test-Path $vendorPath) {
        Get-ChildItem $vendorPath -Directory -ErrorAction SilentlyContinue | ForEach-Object {
            $userData = "$($_.FullName)\User Data"
            if (Test-Path $userData) { $browserDataDirs += $userData }
        }
    }
}

foreach ($userData in $browserDataDirs) {
    Write-Host "Chromium browser: $userData"
    # Per-profile caches
    Get-ChildItem $userData -Directory -ErrorAction SilentlyContinue | ForEach-Object {
        foreach ($cacheName in @('Cache', 'Code Cache', 'Service Worker', 'GPUCache', 
                                   'DawnCache', 'ShaderCache', 'GrShaderCache', 'Crashpad')) {
            $cp = "$($_.FullName)\$cacheName"
            if (Test-Path $cp) {
                $s = (Get-ChildItem $cp -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
                $browserFreed += $s
                Remove-Item -Recurse -Force $cp -ErrorAction SilentlyContinue
            }
        }
    }
    # Global caches (names not in 5a pattern list)
    foreach ($cacheName in @('extensions_crx_cache', 'component_crx_cache', 'SODALanguagePacks',
                               'optimization_guide_model_store', 'WasmTtsEngine', 'SwReporter', 
                               'Snapshots', 'segmentation_platform', 'MediaFoundationWidevineCdm')) {
        $gp = "$userData\$cacheName"
        if (Test-Path $gp) {
            $s = (Get-ChildItem $gp -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
            $browserFreed += $s
            Write-Host "  $cacheName : $([math]::Round($s/1MB,1)) MB"
            Remove-Item -Recurse -Force $gp -ErrorAction SilentlyContinue
        }
    }
}

# --- Firefox ---
$firefoxProfiles = @(
    "$env:APPDATA\Mozilla\Firefox\Profiles",
    "$env:LOCALAPPDATA\Mozilla\Firefox\Profiles"
)
foreach ($ffRoot in $firefoxProfiles) {
    if (-not (Test-Path $ffRoot)) { continue }
    Get-ChildItem $ffRoot -Directory -ErrorAction SilentlyContinue | ForEach-Object {
        foreach ($ffCache in @('cache2', 'startupCache', 'thumbnails', 'safebrowsing')) {
            $ffPath = "$($_.FullName)\$ffCache"
            if (Test-Path $ffPath) {
                $s = (Get-ChildItem $ffPath -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
                if ($s -gt 1MB) {
                    $browserFreed += $s
                    Write-Host "Firefox $ffCache : $([math]::Round($s/1MB,1)) MB"
                    Remove-Item -Recurse -Force $ffPath -ErrorAction SilentlyContinue
                }
            }
        }
        # Firefox storage (can be large)
        $ffStorage = "$($_.FullName)\storage\default"
        if (Test-Path $ffStorage) {
            $s = (Get-ChildItem $ffStorage -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
            if ($s -gt 50MB) {
                Write-Host "Firefox storage/default: $([math]::Round($s/1MB,1)) MB (caution: may contain extension data — flag to user)"
                # Don't auto-clean storage — may contain extension data
            }
        }
    }
}

Write-Host "Browser caches: $([math]::Round($browserFreed/1MB,1)) MB"

5d. JetBrains IDE Cache Cleanup

$jbLocal = "$env:LOCALAPPDATA\JetBrains"
if (Test-Path $jbLocal) {
    Get-ChildItem $jbLocal -Directory -ErrorAction SilentlyContinue | ForEach-Object {
        # Clean caches
        $cacheDir = "$($_.FullName)\caches"
        if (Test-Path $cacheDir) {
            $s = (Get-ChildItem $cacheDir -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
            Write-Host "JetBrains $($_.Name) caches: $([math]::Round($s/1MB,1)) MB"
            Remove-Item -Recurse -Force $cacheDir -ErrorAction SilentlyContinue
        }
        # Clean old product version directories
        Get-ChildItem $_.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d+\.\d+' } | ForEach-Object {
            $s = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
            Write-Host "JetBrains old version $($_.Name): $([math]::Round($s/1MB,1)) MB"
            Remove-Item -Recurse -Force $_.FullName -ErrorAction SilentlyContinue
        }
    }
}

5e. VS Code / VS Code Insiders Cache

foreach ($vsc in @('Code', 'Code - Insiders', 'Code - Exploration')) {
    $vscRoot = "$env:APPDATA\$vsc"
    if (-not (Test-Path $vscRoot)) { continue }
    foreach ($sub in @('Cache', 'CachedData', 'Code Cache', 'GPUCache', 'Crashpad', 
                       'DawnCache', 'ShaderCache', 'GrShaderCache')) {
        $cp = "$vscRoot\$sub"
        if (Test-Path $cp) {
            $s = (Get-ChildItem $cp -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
            Write-Host "$vsc $sub: $([math]::Round($s/1MB,1)) MB"
            Remove-Item -Recurse -Force $cp -ErrorAction SilentlyContinue
        }
    }
    # Workspace storage (contains cached workspace state, safe to clean)
    $wsStorage = "$vscRoot\User\workspaceStorage"
    if (Test-Path $wsStorage) {
        $wsSize = (Get-ChildItem $wsStorage -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
        if ($wsSize -gt 200MB) {
            Write-Host "$vsc workspaceStorage: $([math]::Round($wsSize/1MB,1)) MB"
            Remove-Item -Recurse -Force $wsStorage -ErrorAction SilentlyContinue
        }
    }
}

5f. Corrupted Database Files (Universal)

# Find SQLite/DB files with I/O error markers — unrecoverable
Get-ChildItem $env:USERPROFILE -Recurse -File -ErrorAction SilentlyContinue -Depth 6 | Where-Object {
    $_.Name -match 'SQLITE_IOERR|SQLITE_CORRUPT|_IOERR\d{10,}'
} | ForEach-Object {
    Write-Host "Corrupted DB: $($_.FullName) ($([math]::Round($_.Length/1MB,1)) MB)"
    Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
}

# Orphaned WAL/SHM journal files (parent DB no longer exists)
Get-ChildItem $env:USERPROFILE -Recurse -File -ErrorAction SilentlyContinue -Depth 6 | Where-Object {
    $ext = $_.Extension
    ($ext -eq '.db-wal' -or $ext -eq '.db-shm' -or $ext -eq '.db-journal') -and
    (-not (Test-Path ($_.FullName -replace '-(wal|shm|journal)$', '')))
} | ForEach-Object {
    Write-Host "Orphaned journal: $($_.FullName) ($([math]::Round($_.Length/1MB,1)) MB)"
    Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
}

5g. Desktop Orphaned Junk Files

$desktop = "$env:USERPROFILE\Desktop"
# Known junk: update archives, Tor cached data, Office temp files
$junkPatterns = @('update.mar', 'update.mar.*', 'cached-microdescs', 'cached-certs',
    '*.tmp', '*.temp', '~$*.docx', '~$*.xlsx', '~$*.pptx')
foreach ($pattern in $junkPatterns) {
    Get-ChildItem $desktop -Filter $pattern -ErrorAction SilentlyContinue | ForEach-Object {
        if ($_.Length -gt 10MB -or $_.Extension -match '\.(tmp|temp|mar)$') {
            Write-Host "Desktop junk: $($_.Name) ($([math]::Round($_.Length/1MB,1)) MB)"
            Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
        }
    }
}

5h. C:\ Root Orphan Cleanup (Debug Logs)

@("C:\tmp", "C:\temp") | ForEach-Object {
    if (Test-Path $_) {
        Get-ChildItem $_ -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
            $ext = $_.Extension.ToLower()
            if ($ext -in @('.log', '.tmp', '.temp', '.dump', '.etl', '.txt') -and $_.Length -gt 10MB) {
                $firstLine = Get-Content $_.FullName -TotalCount 1 -ErrorAction SilentlyContinue
                if ($firstLine -match 'TLS secrets|# TLS|SSLKEYLOGFILE|DEBUG|TRACE|PresharedKey|Generated by') {
                    Write-Host "Debug log: $($_.FullName) ($([math]::Round($_.Length/1MB,1)) MB)"
                    Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
                }
            }
        }
    }
}

5i. SDK & Tool Caches in User Root

These are re-downloadable build caches sitting directly in $env:USERPROFILE, outside AppData. Phase 3b identified them; now clean them.

# JavaFX SDK cache
$jfxCache = "$env:USERPROFILE\.openjfx\cache"
if (Test-Path $jfxCache) {
    $s = (Get-ChildItem $jfxCache -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Write-Host ".openjfx cache: $([math]::Round($s/1MB,1)) MB"
    Remove-Item -Recurse -Force $jfxCache -ErrorAction SilentlyContinue
}

# XDG .cache directory (used by Git Bash, MSYS2, Cygwin, Linux-tool ports)
$xdgCache = "$env:USERPROFILE\.cache"
if (Test-Path $xdgCache) {
    $s = (Get-ChildItem $xdgCache -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($s -gt 5MB) {
        Write-Host ".cache (XDG): $([math]::Round($s/1MB,1)) MB"
        Get-ChildItem $xdgCache -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    }
}

# Go module cache (go/pkg/mod)
$goModCache = "$env:USERPROFILE\go\pkg\mod"
if (-not (Test-Path $goModCache)) { $goModCache = "$env:USERPROFILE\.go\pkg\mod" }
if (Test-Path $goModCache) {
    $s = (Get-ChildItem $goModCache -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Write-Host "Go module cache: $([math]::Round($s/1MB,1)) MB (use 'go clean -modcache' if Go is installed)"
    # Use go clean if available, otherwise manual
    if (Get-Command go -ErrorAction SilentlyContinue) {
        go clean -modcache 2>&1
    } else {
        Remove-Item -Recurse -Force $goModCache -ErrorAction SilentlyContinue
    }
}

# Python shiv tool cache
$shivCache = "$env:USERPROFILE\.shiv"
if (Test-Path $shivCache) {
    $s = (Get-ChildItem $shivCache -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Write-Host ".shiv cache: $([math]::Round($s/1MB,1)) MB"
    Remove-Item -Recurse -Force $shivCache -ErrorAction SilentlyContinue
}

# Cargo registry cache (Rust)
$cargoRegCache = "$env:USERPROFILE\.cargo\registry\cache"
if (Test-Path $cargoRegCache) {
    $s = (Get-ChildItem $cargoRegCache -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    Write-Host "Cargo registry cache: $([math]::Round($s/1MB,1)) MB"
    Remove-Item -Recurse -Force $cargoRegCache -ErrorAction SilentlyContinue
}

5j. Squirrel Installer & Old Electron App Versions

Squirrel-based apps (Teams, Slack, Discord, WhatsApp Desktop, etc.) leave app-x.x.x or app-x.x.x-full versioned directories after auto-updates. Only the CURRENT version is needed.

# Clean stale Squirrel install folders in LocalAppData
Get-ChildItem "$env:LOCALAPPDATA" -Directory -ErrorAction SilentlyContinue | Where-Object {
    $_.Name -match '-full$'
} | ForEach-Object {
    $s = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($s -gt 50MB) {
        Write-Host "Squirrel stale: $($_.Name) ($([math]::Round($s/1MB,1)) MB)"
        Remove-Item -Recurse -Force $_.FullName -ErrorAction SilentlyContinue
    }
}

# Clean old version-numbered app subdirectories (e.g., app-1.2.3 within an Electron app)
foreach ($electronApp in @('Discord', 'Slack', 'Microsoft Teams', 'Spotify', 'GitHubDesktop')) {
    $appDir = "$env:LOCALAPPDATA\$electronApp"
    if (-not (Test-Path $appDir)) { continue }
    Get-ChildItem $appDir -Directory -ErrorAction SilentlyContinue | Where-Object {
        $_.Name -match '^app-\d+\.\d+\.\d+' 
    } | ForEach-Object {
        $s = (Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
        # Keep only if it looks like the active version (check if parent exe references it)
        if ($s -gt 30MB) {
            Write-Host "Electron old version $electronApp\$($_.Name): $([math]::Round($s/1MB,1)) MB"
            Remove-Item -Recurse -Force $_.FullName -ErrorAction SilentlyContinue
        }
    }
}

5k. INetCache & Legacy Browser Data

# INetCache (IE / legacy Edge cache)
$inetCache = "$env:LOCALAPPDATA\Microsoft\Windows\INetCache"
if (Test-Path $inetCache) {
    $s = (Get-ChildItem $inetCache -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($s -gt 5MB) {
        Write-Host "INetCache: $([math]::Round($s/1MB,1)) MB"
        Get-ChildItem $inetCache -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    }
}

# IE Cookies and History (safe to clear)
foreach ($ieDir in @("$env:LOCALAPPDATA\Microsoft\Windows\INetCookies",
                       "$env:LOCALAPPDATA\Microsoft\Windows\History")) {
    if (Test-Path $ieDir) {
        Get-ChildItem $ieDir -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue 2>$null
    }
}

# Flash cache (deprecated but may still exist)
$flashCache = "$env:APPDATA\Macromedia\Flash Player"
if (Test-Path $flashCache) {
    $s = (Get-ChildItem $flashCache -Recurse -ErrorAction SilentlyContinue | Where-Object { !$_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
    if ($s -gt 5MB) {
        Write-Host "Flash Player cache: $([math]::Round($s/1MB,1)) MB"
        Remove-Item -Recurse -Force $flashCache -ErrorAction SilentlyContinue
    }
}

Phase 6: User Decision Items

After Phases 4-5, remaining large items are user data, not cache. Categorize and present:

Auto-Classification Rules

For each remaining folder > 200MB from Phase 3:

Path PatternCategoryRecommendation
Contains \Msg\, \messages\, \chat\Chat dataUser's chat history — ask
Contains BackupFiles, \Backup\, _backupOld backupsRedundant — suggest deletion
.rustup\toolchainsSDK toolchainsReinstallable — suggest keeping only active one
.cargo, .gradle, .m2, .nugetBuild cacheRe-downloadable — suggest cleaning
\Projects\, \Workspace\, \repos\Dev projectsNEVER suggest deletion
\Recordings\, \Meetings\RecordingsMay be important — ask
VirtualBox, VMware, Docker, WSLVM/ContainerMay be important — ask
BurpSuite, Wireshark, fiddler, ZAPSecurity toolsUser projects — NEVER delete
.android\avdAndroid emulatorsRe-downloadable but large — ask
.apple, MobileSync, Apple ComputerApple backupsMay be important — ask
Squirrel, app-, -fullOld installersStale versions — suggest deletion
node_modules (in user root, not in a project)Abandoned depsCan be regenerated — suggest deletion
go/pkg, .go/pkgGo module cacheRe-downloadable — suggest cleaning
.cache, .shiv, .openjfxSDK/tool cacheSafe to delete — suggest cleaning
IntelGraphicsProfiles, Intel in LocalLowGPU driver cacheSafe to delete — auto-clean
Unknown (can't classify)UnknownErr on caution — NEVER delete

Presentation Format

Present as a table:

### Items for Your Decision (X.XX GB total)

| # | Path | Size | Category | Risk | Note |
|---|------|------|----------|------|------|
| 1 | <path> | X.XX GB | <category> | Low | <suggestion> |

Ask: "Enter numbers to clean (e.g., 1,3,5), or 'all' / 'none'."


Phase 7: Final Report

Write-Host "========================================"
Write-Host "       FINAL DISK CLEANUP REPORT"
Write-Host "========================================"
Get-PSDrive C | ForEach-Object {
    $total = [math]::Round(($_.Used + $_.Free)/1GB, 2)
    $used = [math]::Round($_.Used/1GB, 2)
    $free = [math]::Round($_.Free/1GB, 2)
    $pct = [math]::Round($_.Used/($_.Used+$_.Free)*100, 1)
    Write-Host "Total: $total GB | Used: $used GB ($pct%) | Free: $free GB"
}
Write-Host "========================================"

After the PowerShell output, manually compile a summary table from the PHASE*_FREED markers and per-phase notes:

Cleanup Summary:
  4a  Package managers:    X.XX GB
  4b  Windows junk:        X.XX GB
  4c  DISM:                completed
  4d  VSS resize:          completed
  5a  Pattern caches:      X.XX GB
  5b  GPU shader caches:   X.XX GB
  5c  Browser caches:      X.XX GB
  5d  JetBrains caches:    X.XX GB
  5e  VS Code caches:      X.XX GB
  5f  Corrupted DB files:  X.XX GB
  5g  Desktop junk:        X.XX GB
  5h  Root orphans:        X.XX GB
  ─────────────────────────────
  TOTAL FREED:             X.XX GB

Also list remaining large directories with explanations for why they were kept.


Execution Rules

  1. Scan always before cleaning — Phase 1-3 complete before Phase 4
  2. Parallelize independent scans — Phase 1a/1b/1c together; Phase 2a/2b/2c together; Phase 3a/3b/3c/3d/3e together
  3. Phase 4 sequential: 4a → 4b → 4c → 4d → 4e (dependencies between steps)
  4. Phase 5 parallel: 5a and 5b can run together; 5c-5h can be batched
  5. Use -ErrorAction SilentlyContinue everywhere — locked files are skipped
  6. DISM timeout: 600s; everything else: default 120s
  7. NEVER DELETE: System32, SysWOW64, WinSxS (manual), DriverStore, Program Files, Program Files (x86)
  8. NEVER DELETE: .exe, .dll, .sys files
  9. Use !$_.PSIsContainer (PowerShell 5.1 compatible)
  10. Use -contains for exact name matching; never -match with partial patterns
  11. If any command produces unexpected errors, stop and investigate — do not proceed with cleanup in that area
  12. After Phase 4c (DISM), continue with remaining phases even if DISM produces warnings — DISM often reports non-critical warnings
  13. Record freed numbers as you go — the final Phase 7 report needs per-phase contributions