How Kruftle works
What it looks for, what it runs, what it will delete, and — just as importantly — what it refuses to touch.
Getting started
Install Kruftle from the download page. On first launch it shows you the Terms of Service and the Privacy Policy — nothing else in the app opens until you accept them — followed by a short tour you can skip and replay later from Settings.
Then point it at a folder. Everything else follows from that one choice: Kruftle never looks outside it, and never deletes outside it.
The five steps
1 — Folder
Choose your codebase root, or drop a folder on the window. Recently used folders are offered on the same screen, and you can forget any of them. Kruftle refuses a folder that is a system directory, your home directory, or fewer than two levels below a filesystem root.
2 — Scan
Kruftle walks everything under the folder, to the depth set in Settings (12 levels
by default), and identifies each project from the files it finds — a
Cargo.toml, a pubspec.yaml, a pom.xml and
so on. Nested projects are found too: the Rust crate inside a Flutter app is its
own project with its own build output.
It then measures every artifact directory it found. Sizes are the space actually
occupied on disk, read from the operating system, so they match what Finder and
df report. You can stop a scan at any point and still review what it
found.
3 — Review
Every project, its detected stack or stacks, and what its artifacts cost. Filter by name, path or stack; sort by size or by path; select all, none, or exactly what you mean. The panel on the right totals the selection, shows a treemap of the biggest artifact directories, and holds the deletion categories described below.
If a selected project's toolchain is not installed, Kruftle says so here rather than failing later. If an artifact directory is tracked by git, Kruftle excludes it from the default selection and tells you how many it excluded.
4 — Clean
Kruftle runs several projects at a time (four by default), showing each command as it starts. A command that runs longer than the step timeout is killed and recorded as failed; the rest of the run carries on. You can stop mid-run — in-flight processes are killed, no new steps start, and you still get the report.
5 — Report
What was actually reclaimed — measured by re-sizing the directories afterwards, not by trusting the estimate — how long it took, and what failed. A failing command's own output is shown verbatim, because a developer wants the real error rather than a paraphrase. The volume's free space before and after is read straight from the operating system and shown alongside.
The report can also export the run's log to a file.
What gets removed, and how
By default Kruftle deletes nothing itself. It runs each toolchain's own clean command and lets the tool decide which of its files are safe to remove. Anything beyond that is a category you have to switch on, on the Review step, for that run.
| Category | Examples | What it costs you |
|---|---|---|
| Build output when the SDK is missing | target, build, bin, obj |
A rebuild. Only offered for projects whose toolchain is not installed, so there is no clean command to run instead. |
| Downloaded dependencies | node_modules, .venv, deps,
vendor |
A re-download. Fully reproducible from a lockfile or manifest, but it costs network and time. |
| Tool caches | .gradle, .turbo, .mypy_cache,
.dart_tool |
A slower next build, and nothing else. |
You can pre-tick these in Settings so they start switched on. That is a convenience only — every run still shows them and still asks before anything is deleted outright.
What Kruftle refuses
Ten rules are enforced in a single file, re-checked at the moment of deletion rather than only while planning, and each has a test that fails closed. These govern a project scan. The global caches are a deliberate exception to the containment rule — they live in your home directory, and they have their own gate instead. In short, within a scan Kruftle will not:
- scan or clean
/, your home directory,/System,/Library,/etc,/usr,/var,/bin,C:\,C:\Windows,C:\Users,C:\Program Filesor a volume mount point; - touch anything less than two levels below a filesystem root;
- act on a path that resolves outside the folder you chose;
- descend a symlinked directory, or delete through a symlink;
- delete a name that is not on the matched stack's own allow-list — never a glob, never a pattern you typed, never a file;
- delete an artifact directory that is not an expected child of a directory positively detected as that stack;
- delete anything git tracks — though this check needs
gitinstalled and a real working tree to have anything to go on, and it cannot restrain a toolchain's own clean command; - delete any category you have not ticked for that run — or, for an unattended scheduled run, any category you have not pre-selected in Settings;
- let a hung clean command block the run, or refuse to stop when you cancel;
- run two cleanups at once.
The code is at
lib/src/core/clean/safety.dart.
Global caches
The globe button in the title bar opens a separate screen for the caches that live in your home directory rather than in any one project. They are shared by every project on the machine, which is why they are not part of a folder scan — and why they are often the biggest single win.
| Cache | Location |
|---|---|
| Gradle | ~/.gradle/caches, ~/.gradle/wrapper |
| Cargo registry | ~/.cargo/registry, ~/.cargo/git |
| Maven repository | ~/.m2/repository |
| Pub cache | ~/.pub-cache/hosted, ~/.pub-cache/git |
| npm cache | ~/.npm/_cacache |
| Go module cache | ~/go/pkg/mod, ~/.cache/go-build |
| Xcode DerivedData | ~/Library/Developer/Xcode/DerivedData |
The list sorts by size, largest or smallest first, and a cache still being measured waits at the bottom rather than jumping when its size arrives. Where the toolchain provides its own command for emptying a cache, Kruftle uses it; where it does not, the directory is removed and the row says so. Emptying a cache frees space now and costs a re-download later — it never loses work.
Scheduled cleanups
The clock button in the title bar opens the schedule. Pick a folder and a frequency — daily, weekly or monthly — and Kruftle will tell you when a cleanup is due, including one that came due while it was closed.
Turn on Run even when Kruftle is closed and it registers a job with your operating system's own scheduler:
| Platform | Mechanism | Registered as |
|---|---|---|
| macOS | launchd LaunchAgent | ~/Library/LaunchAgents/com.dizitart.kruftle.agent.plist |
| Linux | systemd user timer | com.dizitart.kruftle.agent under systemctl --user |
| Windows | Task Scheduler | task com.dizitart.kruftle.agent |
There is no resident process and nothing in your login items: the job is registered when you turn the switch on and removed when you turn it off. An unattended run does each toolchain's clean command and deletes only the categories you have already pre-selected in Settings — it cannot widen its own permission while you are not watching. If your system refuses to register the job, Kruftle says so and falls back to reminding you while it is open.
Kruftle can also raise a desktop notification when a cleanup is due and when one finishes.
Cleanup profiles
The puzzle-piece button opens profiles. A profile teaches Kruftle a project type it does not know yet, and consists of:
- a name, shown in the results table;
- markers — the files whose presence identifies the project. An
entry beginning
*.matches by extension; - a clean command, optional, written as you would type it;
- directories it may delete, split into build output, dependencies and caches, so your existing "also delete" choices apply to it unchanged;
- exclude globs — paths the scanner must never enter, for this profile and every built-in stack alike.
Profiles are appended to the built-in stacks as ordinary definitions. Detection, planning and every safety rail treat them identically — there is deliberately no separate code path for a user-defined stack, because that would be a second place for a rail to be forgotten. Profiles can be exported and imported as a file.
.., no wildcards — but it cannot tell you
whether the command itself is a good idea. Open a profile and read its command
before you enable it.
Settings reference
| Setting | Default | What it does |
|---|---|---|
| Theme | Match the system | Light, dark, or whatever your desktop is set to. |
| Language | Match the system | Ten languages, each shown under its own name. |
| Reduce motion | Off | Replaces the sweeping and pulsing animations with plain progress. Your system's own reduced-motion setting is honoured on top of this, never instead of it. |
| Maximum depth | 12 levels | How far below the chosen folder to look. |
| Include hidden directories | Off | Folders beginning with a dot — usually editor state and tool caches rather than projects. |
| Projects at once | 4 | Clean commands run in parallel. More is faster until the disk becomes the bottleneck. |
| Step timeout | 300 seconds | A clean command that overruns is killed and reported, so one stuck build tool cannot hold up the whole run. |
| Confirm before deleting | On | A summary dialog whenever a run will delete directories outright rather than only running clean commands. |
| Pre-select deletion categories | None | Which categories start ticked. Every run still shows them and still asks. |
| Log detail | Info | Debug, Info, Warning or Error. |
| Log files kept | 3 | Older files are removed once the active log rotates. |
| Check for updates automatically | On | Asks GitHub Releases on launch and offers a verified download. It never installs without asking. |
| How sizes are counted | Space actually used on disk | On-disk matches what the operating system reports, including block rounding and filesystem compression. It needs a native call unavailable on Windows, which falls back to the total length of the files. |
Supported stacks
Forty-two built in. A directory legitimately matches more than one — a Flutter app also has a Gradle build, a Rust crate can carry a Makefile — so Kruftle runs each matched stack's command and lets the tools sort out which files are theirs.
| Stack | Detected by | Clean command |
|---|---|---|
| Rust | Cargo.toml | cargo clean |
| Go | go.mod | go clean -cache -testcache |
| Zig | build.zig | allow-listed deletion |
| Flutter | pubspec.yaml | flutter clean |
| Dart | pubspec.yaml | allow-listed deletion — dart has no clean subcommand |
| Maven | pom.xml | ./mvnw clean, or mvn clean |
| Gradle | build.gradle, settings.gradle | ./gradlew clean, or gradle clean |
| Node.js | package.json | npm/yarn/pnpm/bun run clean, only when the project declares that script |
| Python | pyproject.toml, setup.py, requirements.txt | python3 setup.py clean --all when there is a setup.py |
| Ruby | Gemfile, Rakefile | allow-listed deletion |
| Elixir | mix.exs | mix clean |
| CMake | CMakeLists.txt | cmake --build build --target clean |
| Make | Makefile | make clean, only when the Makefile declares that target |
| .NET | *.csproj, *.sln | dotnet clean |
| Swift Package | Package.swift | swift package clean |
| Xcode | *.xcodeproj, *.xcworkspace | xcodebuild clean |
| Bazel | WORKSPACE, MODULE.bazel | bazel clean |
| Meson | meson.build | allow-listed deletion |
| Ninja | build.ninja | ninja -t clean |
| Autotools | configure.ac, Makefile.am | make distclean, only when the Makefile declares that target |
| Conan | conanfile.txt, conanfile.py | allow-listed deletion |
| vcpkg | vcpkg.json | allow-listed deletion |
| PlatformIO | platformio.ini | pio run --target clean |
| Haskell | stack.yaml, cabal.project | stack clean |
| Cabal | *.cabal | cabal clean |
| sbt | build.sbt | sbt clean |
| Clojure | project.clj, deps.edn | lein clean for a project.clj; otherwise allow-listed deletion |
| Erlang | rebar.config | rebar3 clean |
| OCaml | dune-project | dune clean |
| Gleam | gleam.toml | gleam clean |
| Nim | *.nimble, nim.cfg | allow-listed deletion |
| Crystal | shard.yml | allow-listed deletion |
| D | dub.json, dub.sdl | dub clean |
| Fortran | fpm.toml | fpm clean --all |
| Ada | alire.toml | alr clean |
| Deno | deno.json | allow-listed deletion |
| PHP / Composer | composer.json | allow-listed deletion of vendor |
| Terraform | main.tf, .terraform.lock.hcl | allow-listed deletion of .terraform |
| Unity | ProjectSettings, Assets | allow-listed deletion |
| Julia | Project.toml | allow-listed deletion |
| R | DESCRIPTION, renv.lock | allow-listed deletion |
| Perl | Makefile.PL, cpanfile | allow-listed deletion |
./gradlew or
./mvnw — Kruftle runs that wrapper, because it is the version of the
tool the project expects. Where a Node project's package.json declares
a clean script, Kruftle runs that script. Both were written by whoever
wrote that project and run under your account. Treat pointing Kruftle at somebody
else's checkout the way you would treat building it.
"Allow-listed deletion" means that stack has no official
clean command Kruftle can run, so its output can only be removed by deleting a
directory on its own allow-list — which needs your opt-in for that run. Adding a stack to Kruftle
itself is one file in
lib/src/core/registry/stacks/
and one line in the registry.
Where Kruftle keeps things
Kruftle writes to its own application-support directory and nowhere else.
| Platform | Directory |
|---|---|
| macOS | ~/Library/Application Support/com.dizitart.kruftle/ |
| Linux | ~/.local/share/com.dizitart.kruftle/ |
| Windows | %APPDATA%\Dizitart\Kruftle\ |
Inside it, logs/kruftle.log.jsonl holds the activity log — one JSON
object per line, rotated at 5 MB, with three older files kept by default. It
records what was scanned, which commands ran, what they returned and what a safety
rail refused. It never leaves your machine unless you export it yourself.
Updates
On launch, Kruftle asks GitHub's public releases API whether a newer version exists. If one does, a banner offers it. Only if you click Update does Kruftle download the installer — from GitHub's own download host — and it verifies the file against the SHA-256 published in that release before doing anything with it. A file whose checksum does not match is refused and deleted.
On macOS, Kruftle replaces itself: it quits, puts the new copy where the old one was, and reopens on the new version. If it cannot — an Applications folder that is not yours to write to — it opens the disk image instead.
Switch the check off in Settings → Updates and Kruftle makes no network connection at all, ever.
Troubleshooting
"Kruftle is already running"
Only one Kruftle may run at a time, so that two cleanups cannot take the same build directory apart at once. Close the other window — or wait for a scheduled background run to finish — and try again.
A project was found but nothing was cleaned
Its toolchain is probably not installed, so there was no clean command to run. The Review step marks those projects. Either install the SDK, or tick Build output when the SDK is missing for that run.
An artifact directory was skipped
Either git tracks it — Kruftle never deletes committed content — or a safety rail refused it. The report says how many targets were refused, and the activity log says which rail and why.
A clean command failed
The report shows the tool's own output verbatim. A Maven build missing a dependency version, for example, fails for reasons that have nothing to do with Kruftle; fix the project and run again.
The estimate was bigger than what was reclaimed
That is normal and Kruftle says so on the report. Clean commands decide for themselves what to remove, and some deliberately keep caches a rebuild can reuse — which is usually what you want.
Something else
Export the log from the report, and open an issue at github.com/dizitart/kruftle/issues or write to support@dizitart.com.