Aller au contenu

Fonctionnement

lu-cleaner fait trois choses distinctes, et les garde strictement séparées :

  1. Les scanners examinent votre disque et décrivent ce qu'ils trouvent sous forme d'éléments. Ils ne suppriment jamais rien.
  2. Vous choisissez des éléments, dans le sélecteur interactif ou avec des filtres en ligne de commande.
  3. 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/after

Comme 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.

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/CocoaPods ou $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 avec lu-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.
  • 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_depth niveaux (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 les worktree_roots que 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.

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), comme du. 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 (reclaim en JSON). Cas typique : un node_modules installé 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 un node_modules installé à 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'un lstat par 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_modules d'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.

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_modules lui-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 LastAccessedDate tenue 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.

  1. 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 --smart qui décident (voir la commande clean). Les sélections imbriquées sont fusionnées : si vous sélectionnez un worktree et le node_modules qu'il contient, seul le worktree est traité et rien n'est compté deux fois.

  2. 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 taper yes.

  3. 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é.

  4. 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). Avec clean --yes, un second Ctrl+C arrête lu-cleaner immédiatement, même au milieu d'une longue suppression.

  5. Récapitulatif. Chaque élément se termine en done, skipped ou failed. 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 doctor vous 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.

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.

Fenêtre de terminal
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 # tout
lu-cleaner history --json # pour les scripts

Le format est décrit dans Sortie JSON.