Skip to main content

Changelog

All notable changes to windowsweep are documented here.

The format is based on Keep a Changelog, and the project adheres to Semantic Versioning.


[Unreleased]

[1.2.0] - 2026-09-08

Added

  • targets[].newest_write_utc in --scan --json. ISO 8601 UTC, the newest of write, access and creation time found anywhere under the target - the same rule the idle gate uses - or null when the target is absent or holds no files. It costs no extra walk: under --json the size pass already enumerates every file, so the timestamp comes out of that same enumeration. A human --scan keeps the faster path.
  • protected in --list --json. subtrees (every protected folder, resolved for this machine) and categories (the same four sentences --list-targets prints). Both readers now take the category list from one constant, so the machine-readable copy cannot quietly promise less than the console does.
  • excluded[] in the --json summary, naming every path an exclusion actually kept, once each.

Changed

  • --exclude-path is honoured in every section, not only section 17. It was parsed globally and read by exactly one consumer, so someone who excluded a folder was protected in one section and silently not in the other twenty-five. It is now enforced at the deletion chokepoint itself - the one place every section already passes through - and an excluded path is refused, logged as excluded: <path> and reported in excluded[]. The same reach applies to excludePaths in the config file. No deletion behaviour widens: --exclude-path only ever refuses more. A path that is both protected and excluded still reports the protected reason, because that is the promise no flag can lift.

Fixed

  • 🔴 --exclude-path only ever refused the path you named, never anything inside it. The normalised prefix was built with a PowerShell literal '\', which is two characters, so the prefix ended in a doubled backslash and no child path could ever match it. Excluding a folder protected the folder and nothing in it - which is the opposite of what anyone excluding a folder means. The self-test check that was supposed to cover this asserted only on the excluded path itself, so it passed throughout; it now asserts on a child, and the fix was watched failing on the original bug reintroduced.
  • 🔴 A dry-run counted files a real run would skip. Remove-StaleFiles applied the protection guard only in its real-run loop, so the rehearsal's estimate included protected and excluded files that the run then refused - for exactly the files a person most wants the two numbers to agree about. One filter now decides both, and a check proves a dry-run and a real prune report the same count and the same bytes.
  • --help said --permanent covers "Sections 18/19". It reaches 18, 19 and 23: Send-ToRecycleBin is called from modules/personal.ps1 (18, 19) and from modules/orphaned_appdata.ps1 (23). Someone who passed --permanent believing it applied to two sections would have had section 23's orphaned application data deleted outright instead of recycled. The reference docs already said 18, 19 and 23; only the engine's own help text - the one place a person actually reads it from - was wrong.
  • Section 22 declared Dev = $true with no behavioural branch, so --list --json advertised a developer flag that changed nothing. It now reads false, and the Dev column in docs/sections.md agrees.
  • 🔴 --scan parsed --developer and --not-developer and then never read them. Resolve-DeveloperMode ran only for the walkthrough, the menu, --all and --only, so in scan mode the flag reached DeveloperFlag and nothing looked at it: section 0 printed Developer mode: not decided yet whatever you passed, and --json returned developer: null. The desktop window sends those flags to --scan and could not read back the setting it had just sent. A scan now resolves it from the flag or the saved answer and stops there - it may not ask the question, may not write config.json, and may not invent a decision you never made, so with neither a flag nor a saved answer it still reports "not decided yet", which is the truth. No scan result changes: the scan reports what is on disk, not what a run would remove, and Show-ScanTable never read the developer answer in the first place.
  • The hibernation target's one-line note contradicted the section's own explanation two lines below. It said reduced keeps Fast Startup "at roughly 40% of RAM", which is the size of the whole file; the section intro says reduced is about half of that. The note now states the figure once, on the thing it belongs to: the full file is about 40% of RAM and reduced keeps roughly half that.

[1.1.0] - 2026-09-04

Added

  • Section 22 - global packages audit. Lists what npm, pnpm, yarn, bun and deno installed globally with size and idle days, flags the ones no recent project references, and prints the exact uninstall command. It never uninstalls anything and declares no deletable target: several of those roots are protected paths.
  • Section 23 - orphaned application data. Top-level folders under %APPDATA% and %LOCALAPPDATA% that no installed program, Store package or running process claims and that nothing has touched for --days+ days. Interactive, Recycle Bin, and it fails closed - an unreadable uninstall registry produces zero candidates rather than treating everything as orphaned. Every vendor folder another section already cleans is excluded automatically, derived from the declared target list so the exclusions cannot drift.
  • Section 24 - installed programs not modified for N+ days. Report only, largest first, with winget uninstall --id when one winget list call resolves the program and its own UninstallString otherwise; Store apps listed separately. It never runs an uninstaller.
  • Section 25 - startup items audit. Run and RunOnce keys, both Startup folders, logon-triggered scheduled tasks and Win32_StartupCommand in one table with each item's enabled state. It changes nothing.
  • --select L and --select-file P. Answer an interactive section's selection in advance, by index or by full path. Either flag lets sections 17, 18, 19 and 23 run unattended - a person did choose - and the selection answers that section's final confirmation. --yes alone still selects nothing.
  • --notify. A Windows notification when a run ends: a real toast on Windows PowerShell 5.1, a tray balloon on PowerShell 7. It never changes the exit code and never writes to stdout. --install-task adds it to the weekly task, so the Sunday run reports itself.
  • --json additions. candidates[] (what an interactive section offered) and targets[] (what scan mode measured), both always present so a caller can rely on the shape, plus per-section progress lines on stderr: ##windowsweep section=NN event=start|end status=<status> freed_bytes=<n>.
  • --list --json prints the section catalogue - sections, tiers, batch policy, the safe batch, the profiles and the walkthrough order - so a front end reads it instead of hard-coding it.
  • Section 1 gained the Hugging Face model cache (~\.cache\huggingface\hub, pruned by the idle gate, developer-gated). Section 9 lists wsreset.exe as the Microsoft Store cache lever and offers it as a next step rather than running it: wsreset has no silent mode and always opens the Store.
  • Section 17 recognises .nx, .mypy_cache, .ruff_cache, .tox, .eggs, .output and .serverless, and treats .cache as an artefact only when a Gatsby or Parcel config sits beside it. .venv, venv and .terraform are deliberately still excluded.
  • docs/sections.md records the candidate targets that are researched but not shipped because the software is not installed on the build machine, and why C:\Intel was inspected and rejected.

Fixed

  • Get-OrphanExclusions returned its exclusion set as an unrolled array, whose .Contains is case-sensitive, so a folder named slack would have slipped past the Slack entry. Found by a new check before the section shipped.
  • Section 24's idle measurement used the newest of write, access and creation time. Last-access is live on many systems, so every install folder read as touched today and the section could never report anything; it now reads last-write only, which is what "not modified" means.

Changed

  • The target table is collected from the section catalogue instead of a literal 0..21 range, so a new section is reachable by --scan, --list-targets and the safety checks the moment it is declared. The menu's prompt range is derived the same way.
  • Profile audit is now 0, 21, 22, 24, 25. The three new report sections are read-only and safe, but stay out of --all so a cleanup run remains a cleanup run.
  • Self-test: 27 more checks (151 total) cover the catalogue, section 23's fail-closed gate and derived exclusions, section 22 declaring nothing deletable, the scripted-selection flags, the --json and catalogue shapes, the progress-line format, the global-package verdict, the startup-state rule and the artefact list. Each was proved red against a planted defect before it was kept.
  • Self-test: ten more checks cover the argument parser, section lists, docker size text, the layout-guard leaf test, the --json shape, superseded versions, the Chromium layout, workspace storage, stale artefacts and the report exporters. Test-KnownCacheLeaf and Get-JsonSummary were split out of Invoke-TargetList and Write-JsonSummary so the logic can be exercised directly; no behaviour changed.
  • AI-INTEGRATION-GUIDE.md ships in the npm package and is mirrored on the documentation site: the contract an agent or a script relies on - the safe command sequence, what --yes never covers, exit codes, the --json line, output paths and the guarantees.
  • A documentation site at windowsweep-docs.aoneahsan.com (Docusaurus on GitHub Pages), mirroring docs/.

[1.0.1] - 2026-09-03

Fixed

  • Sections 17, 18 and 19 no longer pre-select every item under --yes. In the walkthrough and the menu with auto-yes on, section 17 could remove every listed build artefact without a person choosing one, and 18/19 pre-selected everything before their final question. The selection prompt now appears even with --yes, defaults to none, and section 17's final confirmation is never auto-answered. Batch mode already refused these sections and is unchanged. (RW-002)
  • Section 19's title no longer names Desktop; only Downloads is scanned. (RW-003)
  • Sections 18 and 19 report the tier recycle instead of permanent in --list. (RW-004)
  • --purge-all from a console asks you to type purge once per run, as documented; --yes remains the confirmation in batch runs. (RW-005)
  • A running editor's VSIX download cache is cleared as documented: it is its own target without the running-editor guard. (RW-006)
  • --install-task and --install-alias refuse to run under npx (exit 3) and print the global-install steps; the npx cache is evicted and the task or alias would break later. (RW-007)
  • The engine exits 130 when a run is interrupted before it finished; previously only the Node launcher did. (RW-008)
  • --uninstall-data always asks; --yes no longer removes your history unattended. (RW-010)

Changed

  • Internal rename Write-Log -> Write-LogLine (18 call sites) so PSScriptAnalyzer's PSAvoidOverwritingBuiltInCmdlets passes under PowerShell 7 in CI. No behaviour change.
  • Self-test: four new checks prove the --yes asymmetry at the helper and at the section 17/18/19 call sites, and that every picker call in modules/ carries -NoAutoYes (114 checks).
  • Keywords trimmed to twelve (temp-files dropped). (RW-011)

[1.0.0] - 2026-09-03

First release. The Windows member of the cleanup family beside linux-cleanup and macleanup.

Added

  • 22 numbered sections (0-21): package-manager caches, build-tool caches, test-runner browsers, Android emulators, Docker, editor caches, browser caches, desktop-app caches, Windows user caches, user temp, Recycle Bin, Windows Update cache, the Disk Cleanup engine, DISM component-store cleanup, the hibernation file, event logs, stale project build artefacts, partial downloads, large stale personal files, disk-image compaction and a disk-usage report. Section numbers are a public contract from this release on.
  • Developer mode. The first interactive run asks whether you are a developer. Yes means package, build and test-runner caches are pruned only when idle 100+ days and the newest version of every versioned tool cache is kept; no means those caches are cleared completely. The answer is saved and can be re-asked with --forget-developer.
  • A dry-run that writes nothing (--dry-run), a read-only scan (--scan) and --list-targets.
  • One deletion chokepoint that refuses drive roots, Windows, Program Files, the profile root, personal folders, credentials, toolchains and browser/editor state, asserts every deletion lies inside its declared target root, never follows junctions or symlinks, handles paths longer than 260 characters and skips files another program has open.
  • Batch policy. --all runs the safe batch only; deep sections (11, 15, 16, 20) need --i-understand-deep; personal sections (17, 18, 19) never run unattended and use the Recycle Bin.
  • Admin awareness. Admin sections skip with the exact command when not elevated; --elevate relaunches through a UAC prompt.
  • Session reports as schema-versioned JSON with Markdown and HTML export, a reports manager, run history (--stats), history pruning and --json for scripting.
  • Weekly Scheduled Task (--install-task) and a PowerShell profile alias (--install-alias).
  • Self-test (--self-test): script syntax, ASCII-only source, the protection lists, a real junction fixture, the dry-run guarantee, keep-newest, long paths and the extension-leftover rule.
  • Crash bundles written locally on unexpected exit; --debug-bundle and --report-issue for bug reports. Nothing is ever transmitted.
  • Node launcher for npx windowsweep, a .cmd launcher for machines without Node, and a ci workflow that runs the self-test and a dry-run on both Windows PowerShell 5.1 and PowerShell 7.