Fonctionnement
lu-cleaner fait trois choses distinctes, et les garde strictement séparées :
- Les scanners examinent votre disque et décrivent ce qu'ils trouvent sous forme d'éléments. Ils ne suppriment jamais rien.
- Vous choisissez des éléments, dans le sélecteur interactif ou avec des filtres en ligne de commande.
- Le pipeline de nettoyage revérifie chaque élément choisi au dernier moment, puis le supprime, exécute une commande ou retire un worktree git, et écrit une ligne dans l'historique.
scanners ──emit(item)──▶ engine ──stream──▶ picker · scan table · --json │ votre sélection ▼ clean pipeline ── re-checks ──▶ delete · Trash · command · git worktree remove └──▶ history.jsonl + free space before/afterComme un seul composant peut supprimer des fichiers, toutes les règles de sécurité se trouvent au même endroit. Un bug dans un scanner peut au pire proposer quelque chose qu'il ne devrait pas. Il ne peut pas le supprimer : le pipeline revalide chaque chemin avant d'y toucher. Voir Modèle de sécurité.
Les scanners tournent en parallèle et diffusent leurs éléments au fil de l'eau
Section intitulée « Les scanners tournent en parallèle et diffusent leurs éléments au fil de l'eau »Une analyse démarre tous les scanners (appelés providers en interne) en parallèle. Chacun publie ses éléments dès qu'il les trouve : le sélecteur se remplit en quelques secondes pendant que les mesures lentes continuent en arrière-plan.
Les éléments sont mis à jour par identifiant (upsert) : un scanner émet d'abord un élément dont la taille est encore inconnue (affichée …), puis l'émet à nouveau avec la taille mesurée. L'interface remplace l'ancienne version sur place. Un scanner qui plante est signalé par un avertissement (warning: provider apple: …) au lieu de faire tomber tout le programme.
| Identifiant du scanner | Couvre | Catégories |
|---|---|---|
worktrees |
Worktrees git liés (Codex, Cursor, Conductor, Claude Code, créés à la main…) et métadonnées de worktrees périmées | worktrees |
artifacts |
Sorties de build et dépendances dans les projets : node_modules, ios/Pods, android/app/build, .expo, dist… |
artifacts |
apple |
DerivedData, archives, DeviceSupport, simulateurs, runtimes, installations supplémentaires de Xcode | xcode, simulators |
android |
AVD, snapshots, paquets du SDK, Gradle user home, Android Studio, JDK | android |
ai |
Sessions de Claude Code, Codex, Cursor, Conductor… selon leur âge, données orphelines, anciens binaires, modèles locaux | ai, ide |
js |
Versions de Node, npx, stores pnpm et Yarn Berry, builds Expo Go, navigateurs Playwright, dossiers temporaires, surveillances watchman périmées | js |
system |
Corbeille, Docker et VM, Homebrew, Go/Rust/Ruby, VS Code, JetBrains, Zed, ~/Downloads, état de macOS |
system, containers, langs, ide |
catalog |
Environ 200 emplacements connus de caches, de journaux et de données d'outils | toutes les catégories sauf worktrees et artifacts |
L'identifiant du scanner est aussi accepté par --kind : lu-cleaner scan -k apple ne liste que ce qu'a trouvé le scanner apple. Seuls les scanners capables de produire les catégories demandées sont démarrés : lu-cleaner scan -c js lance donc les scanners js et catalog, et rien d'autre. Il en va de même pour disabled_categories (voir Configuration) : les éléments de ces catégories ne sont jamais affichés, et les scanners qui ne produisent que des catégories désactivées ne sont pas démarrés.
Entrées du catalogue et scanners dynamiques
Section intitulée « Entrées du catalogue et scanners dynamiques »lu-cleaner s'appuie sur deux types de connaissances :
- Le catalogue est une donnée : une liste de chemins connus comme
~/.npm/_cacache,~/Library/Caches/CocoaPodsou$TMPDIR/metro-cache, chacun avec une catégorie, un risque, une méthode de nettoyage et une note qui explique comment les données reviennent. Ajouter un cache situé à un chemin fixe tient en quelques lignes de données Go, sans logique. Parcourez-le dans la référence du catalogue ou aveclu-cleaner catalog. - Les scanners dynamiques traitent tout ce qui demande du discernement : cette version de node est-elle la version par défaut ? Ce worktree contient-il des modifications non commitées ? Ce runtime de simulateur est-il encore utilisé par un appareil ? Ce dossier DerivedData appartient-il à un worktree qui n'existe plus ? La référence des scanners liste tous les types d'éléments qu'ils produisent.
Où lu-cleaner regarde
Section intitulée « Où lu-cleaner regarde »- Les racines de projets contiennent vos checkouts. Elles viennent de la clé de configuration
roots, ou sont détectées automatiquement (~/dev,~/Developer,~/Projects,~/code,~/src,~/conductor/repos…). Les scanners artifacts et worktrees les parcourent, jusqu'àmax_depthniveaux (8 par défaut). - Les racines de worktrees sont les dossiers où les outils d'IA créent leurs worktrees :
~/.codex/worktrees,~/.cursor/worktrees,~/conductor/workspaces,~/.claude/worktrees,~/.worktrees… ainsi que lesworktree_rootsque vous configurez. Elles sont aussi parcourues à la recherche d'artefacts de projet. - Les emplacements fixes dans
~/Library, les dossiers cachés de votre dossier personnel et votre dossier temporaire utilisateur ($TMPDIR), pour les caches, les SDK, les simulateurs et les données d'outils.
Les racines passées en ligne de commande (--root <dossier>, ou lu-cleaner artifacts <dossier>) limitent l'analyse des artefacts de projet à exactement ces dossiers : pas de racines de worktrees, pas de dossiers supplémentaires intégrés, et tout artefact situé en dehors est écarté. Les autres scanners gardent les racines configurées, parce qu'ils les lisent pour savoir ce qu'utilisent vos projets (versions de Node, de Ruby et du SDK Android, dépôts principaux des worktrees) : avec --root ~/dev/un-projet seul, toutes les versions utilisées par vos autres projets sembleraient inutilisées. / est refusé comme racine.
Tout ce dont l'emplacement réel se trouve sur un autre volume (un SSD externe derrière un lien symbolique, par exemple) est affiché à titre d'information uniquement, avec l'avertissement on external volume — no internal gain : le supprimer ne libérerait pas d'espace sur votre disque interne.
Sans accès complet au disque, les données des autres applications (~/Library/Containers/<app>, ~/Library/Group Containers/<group>) ne sont jamais lues : sur macOS 14 et versions ultérieures, le premier accès resterait bloqué sur une demande d'autorisation système. Les entrées du catalogue qui s'y trouvent sont listées en rapport seul avec l'avertissement needs Full Disk Access. Voir Installation.
Mesure des tailles
Section intitulée « Mesure des tailles »Les tailles répondent à une seule question : combien d'espace libre vais-je récupérer ? Cette question guide plusieurs choix.
- La taille allouée, pas la taille apparente. lu-cleaner compte les blocs qu'un fichier occupe réellement sur le disque (
st_blocks × 512), commedu. Une image disque creuse (sparse) de 64 Go qui en utilise 3 compte pour 3 Go. - Les liens physiques (hardlinks) sont comptés une seule fois. Un fichier qui a plusieurs liens physiques dans la même arborescence est compté une fois. Quand certains de ses liens se trouvent en dehors de l'arborescence mesurée, supprimer l'arborescence ne libère rien pour ce fichier. lu-cleaner indique alors une taille réellement libérée plus petite (
reclaimen JSON). Cas typique : unnode_modulesinstallé par pnpm est lié physiquement au store pnpm. Il peut mesurer 1,2 Go alors que sa suppression ne libère que 80 Mo. - Les clones APFS ne comptent que leurs octets propres. Un fichier cloné (installations bun et pnpm,
cp -c, doublons du Finder) partage ses blocs de données avec l'original : le supprimer ne libère que les blocs qu'il ne partage plus, sauf si tous les fichiers qui partagent ces données se trouvent dans l'arborescence supprimée. La taille réellement libérée est une estimation : les données partagées entre deux arborescences mesurées séparément (le cache de bun et unnode_modulesinstallé à partir de lui) ne reviennent qu'une fois les deux supprimées. - Les liens symboliques ne sont jamais suivis, et les autres systèmes de fichiers montés ne sont jamais parcourus pendant la mesure.
- Les unités sont décimales (1 Go = 1 000 000 000 octets), comme dans le Finder et avec
df -H.
Deux techniques gardent une analyse complète rapide :
- Sur macOS, les répertoires sont lus avec
getattrlistbulk(2), qui renvoie la taille, le type, l'inode, le nombre de liens et la date de modification de nombreuses entrées en un seul appel système, au lieu d'unlstatpar fichier. Les parcours s'exécutent en parallèle sur plusieurs répertoires, avec une limite globale du nombre de lecteurs simultanés. - Un cache partagé par analyse évite le travail en double : quand deux scanners mesurent le même répertoire (le scanner worktrees et le scanner artifacts mesurent tous deux le
node_modulesd'un worktree), le répertoire est parcouru une seule fois et les deux obtiennent le résultat. Le cache ne vit que le temps d'une analyse : l'analyse suivante mesure toujours des données fraîches.
Ce que vous dit un élément
Section intitulée « Ce que vous dit un élément »Chaque ligne du sélecteur, du tableau de scan et de la sortie JSON est un élément. Le panneau de détails du sélecteur (affiché ou masqué avec enter sur un élément) montre tous ses champs.
| Affiché comme | Champ JSON | Signification |
|---|---|---|
| Nom | name |
Libellé court, par exemple my-app · feature/login ou node v18.20.4 (nvm). |
| Chemin / emplacement | path, paths, location |
Ce qui est supprimé. Un élément groupé supprime plusieurs chemins d'un coup (Metro / Haste file maps (18)) ; un élément commande indique où agit sa commande. |
| Taille | size |
Octets alloués sur le disque, liens physiques comptés une fois. |
| Réellement libéré (« really freed ») | reclaim, freed |
Ce que la suppression libère réellement, quand c'est moins que la taille (données partagées par des liens physiques ou des clones APFS). scan marque ces tailles d'un *. |
| Âge / dernière utilisation | last_used, age_days |
Meilleure estimation de la dernière activité réelle, et non la date de modification du dossier. |
| Risque | risk |
Ce que vous perdez : safe, moderate, caution ou never. Voir Niveaux de risque et sélection intelligente. |
| Méthode | method |
La façon dont il est nettoyé : delete, trash, command, worktree ou report. |
| ★ | recommended |
Choisi par la sélection intelligente (la touche a, --smart). |
Exige --force |
require_force |
Refusé sans --force, en simulation aussi (dossiers de worktrees orphelins). |
| Avertissement | warn |
Un point qui mérite votre attention (worktree modifié, application en cours d'exécution, version la plus récente conservée…). |
| Note | note |
Ce qu'est l'élément et comment il revient après le nettoyage. |
| Méta | meta |
Détails propres au scanner : branche, nombre de fichiers modifiés, niveau d'API, projets qui l'utilisent… |
La dernière utilisation mérite une explication. Les dates de modification des dossiers ne sont pas fiables (les outils les touchent en permanence) : chaque scanner utilise donc le meilleur signal dont il dispose.
- Les artefacts de projet utilisent l'activité du projet (sources, lockfiles, index git), pas celle de
node_moduleslui-même. - Les worktrees utilisent l'activité git (commits, index, HEAD) et les sessions de l'agent qui les a créés.
- DerivedData utilise la
LastAccessedDatetenue par Xcode lui-même. - Les caches utilisent le fichier le plus récent écrit à l'intérieur.
Cette date pilote --older-than, le marquage stale (âge en jaune dans le sélecteur) et les éléments moderate que recommande la sélection intelligente.
Les éléments en rapport seul (méthode report, affichés info dans la colonne RISK de scan) ne peuvent pas être sélectionnés. Ils expliquent où part l'espace quand lu-cleaner ne le libère pas lui-même : snapshots locaux de Time Machine, installations supplémentaires de Xcode, SDK Android sur un disque externe, image disque de Docker. Leur note indique comment récupérer cet espace à la main.
Le pipeline de nettoyage
Section intitulée « Le pipeline de nettoyage »-
Sélection. Dans le sélecteur, rien n'est sélectionné au départ : vous sélectionnez des éléments (space, ou a pour la sélection intelligente) et appuyez sur d. Avec
clean --yes, ce sont les filtres et--smartqui décident (voir la commandeclean). Les sélections imbriquées sont fusionnées : si vous sélectionnez un worktree et lenode_modulesqu'il contient, seul le worktree est traité et rien n'est compté deux fois. -
Confirmation (sélecteur uniquement). Une boîte de dialogue résume la sélection par risque, la méthode (suppression définitive, déplacement dans la Corbeille ou simulation), les commandes qui seront exécutées et les applications en cours d'exécution qui bloqueraient certains éléments. Si la sélection contient un élément
caution(sélectionné, ou situé dans un élément sélectionné) ou un élément qui exige--force, vous devez taperyes. -
Revérifications. Juste avant d'agir sur un élément, le pipeline le revalide : applications en cours d'exécution, revérification propre à l'élément (par exemple « ce simulateur est toujours éteint »), puis, pour chaque chemin, son identité d'inode, les bases SQLite ouvertes, le garde-fou de sécurité, les fichiers marqueurs, les volumes montés et les processus qui travaillent à l'intérieur, et, pour un worktree, les vérifications git (verrou, modifications non commitées, commits sur aucune branche, dépôts imbriqués). Une vérification qui échoue fait ignorer l'élément, avec une raison ; elle n'en supprime jamais une partie. Les détails sont dans Modèle de sécurité.
-
Exécution. Les suppressions de fichiers et les déplacements dans la Corbeille s'exécutent quatre à la fois. Les commandes (
xcrun simctl delete …,pnpm store prune…) et les suppressions de worktrees git s'exécutent l'une après l'autre, chaque commande avec un délai maximal de 15 minutes. En mode Corbeille, les worktrees et les commandes sont ignorés (not possible in Trash mode), puisqu'ils supprimeraient définitivement. Ctrl+C annule : les éléments pas encore commencés sont signalés comme ignorés (cancelled). Avecclean --yes, un second Ctrl+C arrête lu-cleaner immédiatement, même au milieu d'une longue suppression. -
Récapitulatif. Chaque élément se termine en
done,skippedoufailed. lu-cleaner donne deux chiffres : l'espace libéré estimé (somme des tailles réellement libérées des éléments, y compris ce qu'une suppression en échec a libéré avant d'échouer) et l'espace libéré mesuré (l'espace libre de votre volume personnel avant et après, tel que le rapporte le système de fichiers). Les déplacements dans la Corbeille sont totalisés à part et jamais comptés comme libérés. Quand le chiffre mesuré est bien plus bas,lu-cleaner doctorvous dit pourquoi : snapshots locaux, Corbeille, fichiers ouverts, clones. Voir Pourquoi l'espace libéré n'apparaît pas.
Avec --dry-run (-n), les étapes 1 à 3 s'exécutent réellement, y compris toutes les revérifications et les vérifications git de la suppression d'un worktree, et l'étape 4 se contente d'afficher ce qui se passerait (would delete …, would run: xcrun simctl delete …). Une simulation est donc un aperçu fidèle : un élément qui serait ignoré pour une raison de sécurité, ou refusé faute de --force, est signalé comme ignoré. Seuls les refus de git lui-même au moment de la suppression ne peuvent pas être prévus.
Historique
Section intitulée « Historique »Chaque vrai nettoyage ajoute une ligne JSON par élément traité à ~/.local/state/lu-cleaner/history.jsonl ($XDG_STATE_HOME/lu-cleaner/ quand cette variable est définie) : date, type, catégorie, nom, chemin ou commande (l'emplacement et les premiers chemins d'un élément groupé), méthode, statut, taille, octets réellement libérés, erreur et message (la raison pour laquelle un élément a été ignoré, par exemple). Les simulations ne sont pas enregistrées, pas plus que les éléments qui avaient déjà disparu.
lu-cleaner history # les 30 dernières actions, les plus récentes d'abord, et le total réellement libérélu-cleaner history --limit 0 # toutlu-cleaner history --json # pour les scriptsLe format est décrit dans Sortie JSON.