===============================================================================
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
===============================================================================