=============================================================================== APPSWEEP 1.0.0 Find what an uninstaller left behind -- by snapshot diff. Techlosoft "App Janitor" product line. =============================================================================== WHY THIS EXISTS ------------------------------------------------------------------------------- Most leftover cleaners work from a pattern list: somebody wrote a rule that says "AcmeApp leaves a folder in AppData\Roaming\Acme", and the cleaner looks for that folder. Such a tool can only ever find the leftovers that somebody already knew about. Anything the vendor changed last release, anything from an app nobody wrote a rule for, anything dropped in an unexpected place -- all of it is invisible. AppSweep does not use patterns at all. It takes a snapshot of your filesystem BEFORE you install an app, and another snapshot AFTER you uninstall it. The difference between those two snapshots IS the list of leftovers. Every orphan is found, including the ones nobody anticipated, because the method does not depend on knowing anything about the app in advance. The trade is simple and worth stating plainly: AppSweep needs you to have taken a "before" snapshot. If you did not, it has nothing to compare against. It is a tool you run on purpose, around an installation, not a tool you point at an already-dirty machine. HOW IT IS USED ------------------------------------------------------------------------------- 1. Snapshot the places an installer is likely to write: appsweep snapshot "C:\Program Files" "%APPDATA%" --out before.json 2. Install the app. Use it. Later, uninstall it with its own uninstaller. 3. Snapshot the same roots again: appsweep snapshot "C:\Program Files" "%APPDATA%" --out after.json 4. See exactly what the uninstaller orphaned: appsweep diff --before before.json --after after.json 5. Preview the cleanup (this is a dry run -- nothing is touched): appsweep sweep --before before.json --after after.json \ --quarantine ./quarantine 6. Do it for real. Files are MOVED into the quarantine directory, never deleted: appsweep sweep --before before.json --after after.json \ --quarantine ./quarantine --apply COMMANDS ------------------------------------------------------------------------------- appsweep snapshot [ ...] --out Records the path, size and modification time of every regular file under each root. Multiple roots in one snapshot are supported and are recorded individually, so a snapshot can cover, for example, Program Files and AppData at the same time. Flags: --out, -o where to write the snapshot JSON ("-" = stdout) -h, --help help for this command appsweep diff --before --after [--json] Compares two snapshots and reports four categories: ADDED in --after but not in --before. THESE ARE THE LEFTOVERS. REMOVED in --before but not in --after. MODIFIED in both, but the size OR the modification time differs. UNCHANGED in both, same size and same modification time. Each category is reported with a file count and a byte total. ADDED and MODIFIED totals are summed from the --after sizes; REMOVED from the --before sizes. Flags: --before the snapshot taken before installation --after the snapshot taken after uninstallation --json emit the full report as JSON on stdout -h, --help help for this command appsweep sweep --before --after --quarantine [--apply] Quarantines the ADDED files -- and only the ADDED files. Without --apply this is a DRY RUN: it prints every move it would make and changes nothing on disk. The quarantine directory is not even created. Flags: --before the snapshot taken before installation --after the snapshot taken after uninstallation --quarantine, -q directory the leftovers are moved into --apply actually perform the moves -h, --help help for this command THE SAFETY MODEL ------------------------------------------------------------------------------- This tool touches user data, so its rules are deliberately narrow and boring. * DRY RUN IS THE DEFAULT. sweep does nothing at all unless you pass --apply. The dry run runs exactly the same checks as the real run, so what it prints is what would actually happen -- including files it would refuse to touch. * NOTHING IS EVER DELETED. Leftovers are MOVED into the quarantine directory. If AppSweep gets something wrong, the file is still there and you can move it back. There is no code path in this program that removes a file without first placing a copy of it in quarantine. * ONLY ADDED FILES ARE ELIGIBLE. A file that appears in BOTH snapshots is never a candidate, no matter what happened to it. That explicitly includes MODIFIED files: if an installer edited a config file you already had, that file is yours, not a leftover, and AppSweep leaves it alone. * DRIFT IS REFUSED, NOT GUESSED. Before moving anything, AppSweep re-checks the file on disk against the --after snapshot. If the size or the modification time no longer matches, the file changed after the snapshot was taken -- something else is using it -- and AppSweep skips it and says so, rather than moving a file it no longer recognises. * QUARANTINE NEVER OVERWRITES. The full directory structure of each leftover is mirrored under the quarantine directory, so /opt/acme/lib/x.so lands at /opt/acme/lib/x.so. Relative paths are preserved exactly, and leftovers from different snapshot roots cannot collide with each other. If a destination somehow already exists, a numeric suffix is added instead of overwriting it. * FILES ALREADY INSIDE THE QUARANTINE DIRECTORY ARE SKIPPED, so pointing the quarantine somewhere inside a scanned root cannot cause the tool to chase its own tail. WHY SIZE AND MODIFICATION TIME ------------------------------------------------------------------------------- A file counts as MODIFIED when its size OR its modification time changed. Checking the modification time is not decoration. An installer or an app can rewrite a file in place with different content of exactly the same length -- a licence key swapped for another key of the same width, a config value changed from "0" to "1", a padded binary header. A size-only comparison sees nothing at all in those cases. AppSweep reports them. Snapshots deliberately do NOT hash file contents. Hashing every file under Program Files would turn a snapshot from a few seconds into many minutes of full disk reads, which is the difference between a tool people run before an install and a tool people skip. Size plus modification time catches everything that normal software does. It would not catch a file deliberately rewritten with identical length AND its timestamp restored -- that is a thing malware does to hide, and detecting it is a different product. See the roadmap below. EXIT STATUS ------------------------------------------------------------------------------- 0 Success. Note that this includes "differences were found" -- a diff that reports leftovers is a successful diff, not an error. 1 Bad invocation, a missing or corrupt snapshot file, or a move that failed during --apply. SNAPSHOT FILE FORMAT ------------------------------------------------------------------------------- Snapshots are plain JSON and are meant to be readable and scriptable: { "schema": "appsweep/snapshot/1", "tool": "appsweep", "version": "1.0.0", "created": "2026-08-10T03:43:01.481637585Z", "os": "linux", "host": "workstation", "roots": ["/opt/acme"], "files": [ { "path": "/opt/acme/bin/main", "root": "/opt/acme", "rel": "bin/main", "size": 34, "mtime": "2026-08-10T03:42:54.006195858Z" } ] } The file list is sorted by path and all paths are absolute, so two snapshots of an unchanged tree differ only in the "created" field. Timestamps are UTC, RFC3339 with nanoseconds. `diff --json` emits a similarly plain report with added / removed / modified / unchanged sections, each carrying a count, a byte total and the full file list. WHAT IS IMPLEMENTED ------------------------------------------------------------------------------- Everything described above is implemented and working: * Snapshotting one or many directory roots, recording path, size and modification time for every regular file found. * Full ADDED / REMOVED / MODIFIED / UNCHANGED diffing between two snapshots, with per-category file counts and byte totals. * Detection of same-size content changes via modification time. * Human-readable and JSON output for diff. * Quarantining of ADDED files, with the directory structure preserved, dry run by default, --apply to act. * Refusal to move files that changed after the snapshot was taken. * Clean, non-panicking errors with exit code 1 for missing, empty, truncated, corrupt or foreign snapshot files. * Windows, macOS (Intel and Apple silicon) and Linux builds. WHAT IS NOT IMPLEMENTED ------------------------------------------------------------------------------- These are honest gaps, not oversights. AppSweep 1.0 diffs FILES. That is all it diffs. * WINDOWS REGISTRY-KEY DIFFING. On Windows a large share of what an uninstaller orphans is not a file at all -- it is registry keys under HKCU\Software, HKLM\Software, uninstall entries, file associations and COM registrations. AppSweep 1.0 does not read, snapshot or diff the registry. Snapshotting the registry alongside the filesystem, and reporting orphaned keys in the same ADDED / REMOVED / MODIFIED form, is the single largest planned addition. * SERVICE AND SCHEDULED-TASK DIFFING. Installers commonly register background services (Windows services, launchd agents, systemd units) and scheduled tasks that survive uninstallation and keep running or keep failing forever. AppSweep 1.0 sees the on-disk unit file if it happens to sit under a scanned root, but it does not enumerate the service or task databases, does not know whether an orphan is registered or running, and will not unregister anything. Diffing the service and scheduled-task inventories is planned. * AUTOMATIC BEFORE-SNAPSHOTTING TRIGGERED BY AN INSTALLER LAUNCH. Today you must remember to run `appsweep snapshot` yourself before you install something, and if you forget there is nothing to compare against later. The planned improvement is for AppSweep to watch for an installer being launched (setup.exe, .msi, .pkg, .dmg and friends) and take the "before" snapshot automatically at that moment, so the comparison is always available whether or not you thought about it in advance. * Also not implemented: content hashing (see the section above on why), recording empty directories, following or recording symbolic links, recording ownership and permission bits, restoring files out of quarantine with a single command, and any kind of scheduling, service or background daemon. AppSweep runs when you run it. NOTES AND LIMITATIONS ------------------------------------------------------------------------------- * Only regular files are recorded. Directories, symbolic links, sockets and device nodes are skipped, which means an empty directory left behind by an uninstaller is not reported and not quarantined. * Directories that cannot be read are reported as warnings on stderr and skipped; the snapshot still completes for everything else. Snapshot the same roots with the same privileges both times, or the difference in what you could read will show up as bogus ADDED and REMOVED entries. * Both snapshots should cover the same roots. Diffing snapshots of different roots is allowed and will simply report everything in one and not the other, which is rarely what you want. * Snapshot the narrowest set of roots that could plausibly contain the app. Snapshotting an entire drive works but produces a large snapshot and a diff full of unrelated churn from every other program on the machine. * Some filesystems store modification times at one-second granularity. Two different writes to a file inside the same second may therefore be indistinguishable to a timestamp comparison. * Flags may be given before or after positional arguments; both orders behave identically. `-h`, `--help` and `help` all work, and `appsweep help ` gives per-command help. BUILDING FROM SOURCE ------------------------------------------------------------------------------- AppSweep is a single Go source file with no dependencies beyond the Go standard library. No module downloads, no network access, no build tags: go build -o appsweep . Prebuilt binaries in dist/: appsweep-windows-amd64.exe Windows, 64-bit appsweep-darwin-arm64 macOS, Apple silicon appsweep-darwin-amd64 macOS, Intel appsweep-linux-amd64 Linux, 64-bit ===============================================================================