This guide is the deep reference for administrators, power users, contributors, and architects who need to understand how Mole works internally: its architecture, configuration surface, security model, operational behavior, build and release pipeline, and the design constraints that shape it. For task-oriented usage, see the companion User Guide.
Table of Contents
- Executive Summary
- Architecture Overview
- Component Inventory
- System Requirements
- Installation & Filesystem Layout
- Configuration Reference
- Security Architecture
- Operations
- Build & Release Pipeline
- Module Reference (Internals)
- The Go Binaries
- Testing Strategy
- Code Quality & Conventions
- Potential Improvements
- Quick Reference
1. Executive Summary
Mole is a macOS system-maintenance toolkit delivered as a command-line program. It consolidates disk cleanup, application uninstallation, system optimization, disk-usage analysis, and live system monitoring into a single tool. It is licensed MIT and authored by tw93.
The codebase is a hybrid of Bash and Go:
- A Bash core implements the CLI dispatcher, the destructive cleanup/uninstall/optimize/purge/installer flows, the safety and path-validation logic, the operation logs, and the interactive menus.
- Two Go binaries (
analyze,status) implement the two interactive TUIs that benefit from concurrency and rich rendering — the disk-space explorer and the live system dashboard — built on the Bubble Tea / Lip Gloss stack with system metrics from gopsutil.
The central engineering principle, stated explicitly in the source and its design docs, is safety over speed: because Mole deletes files and shells out to system tools, every destructive path passes through layered validation, an extensive protected-bundle/protected-path list, Trash-by-default semantics for uninstalls, and a forensic deletion log.
| Attribute | Value |
|---|---|
| Version (documented) | 1.42.0 |
| Platform | macOS only (Intel & Apple Silicon) |
| Language | Bash (CLI + safety) + Go 1.25 (TUIs) |
| License | MIT |
| Distribution | Homebrew formula + install.sh script |
| Tagline | Deep clean and optimize your Mac. |
2. Architecture Overview
Mole's runtime is a layered shell program with two compiled helpers invoked as subprocesses.
mo (alias) ──exec──► mole (main entrypoint)
│
┌─────────────────────────┼──────────────────────────────┐
│ mole: arg parse, dispatch, update/remove flows, │
│ interactive 5-item menu, version/help │
└─────────────────────────┬──────────────────────────────┘
│ exec $SCRIPT_DIR/bin/<cmd>.sh
┌───────────────┬───────────────┬───────┴───────┬───────────────┬───────────────┐
▼ ▼ ▼ ▼ ▼ ▼
bin/clean.sh bin/uninstall.sh bin/optimize.sh bin/purge.sh bin/installer.sh bin/{analyze,status}.sh
│ │ │ │ │ │
│ source │ source │ source │ source │ │ exec
▼ ▼ ▼ ▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────┐ ┌─────────────────────┐
│ lib/ (sourced Bash modules) │ │ bin/analyze-go │
│ core/ clean/ optimize/ uninstall/ manage/ check/ ui/ │ │ bin/status-go │
│ ── safety, file_ops, app_protection, logging, timeouts, menus ── │ │ (Go / Bubble Tea) │
└──────────────────────────────────────────────────────────────────┘ └─────────────────────┘
Key flows
- Dispatch.
molecollects args (peeling off the global--debug), then a singlecasestatement routes the first token to the matchingbin/<cmd>.shviaexec.historyis dispatched early (before sourcing the full library) for speed. Unknown tokens print a usage hint and exit non-zero. - Library loading. Each
bin/*.shsourceslib/core/common.sh, which is the single include point that pulls inbase.sh,log.sh,timeout.sh,timeouts.sh,file_ops.sh,help.sh,ui.sh,app_protection.sh,bundle_resolver.sh,pkg_receipts.sh, andsudo.sh. Every module guards against double-sourcing with a*_LOADEDsentinel. - The filesystem is the state. There is no daemon or database. Configuration, channel metadata, whitelist, and logs live as plain files under
~/.config/mole,~/.cache/mole, and~/Library/Logs/mole. - Go helpers are leaf processes.
bin/analyze.shandbin/status.share 15-line wrappers thatexecthe bundledanalyze-go/status-gobinaries and print a "reinstall /mo update" hint if the binary is missing.
Note
The
molescript is relocatable. At install time the installer rewrites theSCRIPT_DIR=line to point at the installed library root (~/.config/mole), so the single dispatcher in/usr/local/binfinds its sourced modules regardless of where it was installed from.
3. Component Inventory
| Path | Type | Responsibility |
|---|---|---|
mole |
Bash | Main entrypoint: dispatch, menu, update/remove, version/help |
mo |
Bash | Thin alias → exec mole "$@" |
install.sh |
Bash | Standalone installer/updater (fetch, verify, place files, build binaries) |
bin/clean.sh |
Bash | clean command (largest flow, ~51 KB) |
bin/uninstall.sh |
Bash | uninstall command (~68 KB) |
bin/optimize.sh |
Bash | optimize command |
bin/purge.sh |
Bash | purge command (project artifacts) |
bin/installer.sh |
Bash | installer command (installer-file cleanup) |
bin/history.sh |
Bash | history command (log reader/renderer) |
bin/completion.sh |
Bash | completion command (bash/zsh/fish) |
bin/touchid.sh |
Bash | touchid command (PAM sudo configuration) |
bin/analyze.sh / bin/status.sh |
Bash | Thin launchers for the Go binaries |
bin/analyze-go / bin/status-go |
Go binary | Compiled TUIs (built/downloaded at install) |
lib/core/ |
Bash (15 files) | Safety, file ops, app protection, logging, timeouts, UI primitives |
lib/clean/ |
Bash (12 files) | Cleanup flows per category (apps, browsers, dev, system, …) |
lib/optimize/ |
Bash (3 files) | Diagnostics, maintenance, tasks |
lib/uninstall/ |
Bash | Batch + Homebrew uninstall helpers |
lib/manage/ |
Bash | Whitelist and purge-path management |
lib/check/ |
Bash | Health JSON, diagnostics |
lib/ui/ |
Bash | Reusable menus, paginated/simple selectors, app selector |
cmd/analyze/ |
Go (24 files) | Disk analyzer: scanner, cache, delete, model, view, JSON |
cmd/status/ |
Go (24 files) | Status dashboard: metrics collectors, process watch, view |
internal/units/ |
Go | Byte-formatting helpers |
tests/ |
Bats + shell (49 files) | Behavior, safety, fuzz corpora |
scripts/ |
Bash | check.sh, test.sh, release/tap helpers |
4. System Requirements
| Requirement | Detail |
|---|---|
| Operating system | macOS (Intel amd64 or Apple Silicon arm64). The installer hard-fails on non-Darwin. |
| Shell | Bash. The code is carefully written to remain Bash 3.2-compatible (the macOS system default) — no ${var,,}, no namerefs; lowercasing uses tr, indirect writes use eval. |
curl or wget |
For fetching the installer and updates. |
| Go toolchain | Only needed to build the helper binaries from source (e.g. edge installs without prebuilt assets). go.mod requires Go 1.25.0. |
| Homebrew | Optional, for the Homebrew install/update path. |
fd |
Optional but recommended for purge performance. |
gh (GitHub CLI) |
Optional, enables Sigstore build-provenance verification of release assets. |
| Full Disk Access | Recommended for the terminal so deep cleans can reach protected cache locations. |
Go dependencies (direct)
| Module | Version | Use |
|---|---|---|
github.com/charmbracelet/bubbletea |
v1.3.10 |
TUI event loop (Elm-style) |
github.com/charmbracelet/lipgloss |
v1.1.0 |
Terminal styling/layout |
github.com/shirou/gopsutil/v4 |
v4.26.5 |
CPU/mem/disk/net/process metrics |
github.com/cespare/xxhash/v2 |
v2.3.0 |
Fast hashing (scan cache keys) |
golang.org/x/sync |
v0.20.0 |
Concurrency primitives |
5. Installation & Filesystem Layout
Install methods
| Method | Command | Channel |
|---|---|---|
| Homebrew | brew install mole |
stable |
| Script (stable) | curl -fsSL …/install.sh | bash |
stable |
| Script (edge/nightly) | … | bash -s latest |
nightly (main) |
| Script (pinned) | … | bash -s 1.17.0 |
that tag |
install.sh flags: --prefix DIR (install dir, default /usr/local/bin), --config DIR (config root, default $HOME/.config/mole), --update, --verbose/-v. Version tokens latest/main/dev/<tag> select the channel.
On-disk layout after install
/usr/local/bin/
mole # dispatcher (SCRIPT_DIR rewritten to config root)
mo # alias
~/.config/mole/
bin/ # analyze-go, status-go helper binaries
lib/ # full sourced library tree (core, clean, …)
install_channel # CHANNEL=stable|nightly|dev [+ COMMIT_HASH=...]
purge_paths # user-configured purge scan roots
status_prefs # mo status preferences (e.g. cat_hidden=true)
whitelist # protected paths for `clean`
whitelist_optimize # protected items for `optimize`
README.md, LICENSE, install.sh
~/.cache/mole/
version_check, update_message # background update-notice cache
~/Library/Logs/mole/
mole.log # general log
operations.log # every destructive op (clean/uninstall/…)
deletions.log # forensic Trash/permanent deletion audit (TSV)
Important
File installs are atomic: the installer copies to
mole.new,chmod +x, thenmv -finto place, so an interrupted update never leaves a half-written dispatcher. The same pattern is used for themoalias.
6. Configuration Reference
Mole has no config-file DSL; behavior is governed by a small set of files plus environment variables, with the bulk of internal tuning held as readonly constants in lib/core/base.sh and lib/core/timeouts.sh.
6.1 User-facing environment variables
| Variable | Default | Effect |
|---|---|---|
NO_COLOR |
unset | Any non-empty value disables all ANSI color (honors no-color.org) |
MO_NO_OPLOG |
unset | 1 disables operation logging to operations.log |
MO_DEBUG |
0 |
1 enables verbose debug logging (equivalent to --debug) |
MO_LAUNCHER_APP |
auto | Override terminal auto-detection for quick launchers |
MO_ANALYZE_PATH |
unset | Default target path for mo analyze |
MOLE_VERSION |
unset | Installer channel/version selector (main/dev/tag) |
MOLE_EDGE_INSTALL |
unset | true forces the edge/nightly install path |
MOLE_REQUIRE_ATTESTATION |
0 |
1 makes update fail unless the build-provenance attestation verifies |
MOLE_DRY_RUN |
0 |
1 routes all deletions through dry-run logging (set by --dry-run) |
MOLE_DELETE_MODE |
permanent |
permanent or trash; selects the deletion contract in mole_delete() |
6.2 Operational tuning constants (selected, from base.sh)
| Constant | Value | Meaning |
|---|---|---|
MOLE_TEMP_FILE_AGE_DAYS |
7 | Temp-file retention |
MOLE_ORPHAN_AGE_DAYS |
30 | Orphaned app-data retention |
MOLE_DOTDIR_ORPHAN_AGE_DAYS |
60 | Orphan dotfile hint threshold |
MOLE_MAX_PARALLEL_JOBS |
15 | Parallel job ceiling |
MOLE_LOG_AGE_DAYS |
7 | Log retention |
MOLE_CRASH_REPORT_AGE_DAYS |
7 | Crash-report retention |
MOLE_SAVED_STATE_AGE_DAYS |
30 | Saved-state retention |
MOLE_GPU_CACHE_AGE_DAYS |
1 | Rebuildable GPU cache retention |
MOLE_TM_BACKUP_SAFE_HOURS |
48 | Time Machine backup safety window |
MOLE_MAX_DS_STORE_FILES |
500 | Max .DS_Store per scan |
MOLE_MAX_ORPHAN_ITERATIONS |
100 | Orphan-scan iteration cap |
MOLE_HISTORY_DEFAULT_LIMIT |
20 | Default mo history entries |
MOLE_HISTORY_MAX_LIMIT |
200 | Max mo history entries |
Note
Dozens of additional
MOLE_*variables exist as internal timeouts, caches, and test-mode toggles (e.g.MOLE_TEST_MODE,MOLE_TEST_NO_AUTH,MOLE_TIMEOUT_*). These are implementation details and test seams, not a supported public configuration surface.
6.3 Whitelists
Two whitelist files protect paths from cleanup and optimization:
~/.config/mole/whitelist— managed bymo clean --whitelist~/.config/mole/whitelist_optimize— managed bymo optimize --whitelist(legacy:whitelist_checks)
Whitelist entries support globs and are matched with normalization (trailing/duplicate slashes collapsed) and parent/child awareness, so protecting a child path also protects its parent directory entry. A built-in DEFAULT_WHITELIST_PATTERNS array additionally protects high-value caches out of the box, including:
~/Library/Caches/ms-playwright* ~/.gradle/caches/* ~/.ollama/models/*
~/.cache/huggingface* ~/.gradle/daemon/* ~/Library/Caches/JetBrains*
~/.m2/repository/* ~/Library/Caches/pypoetry/virtualenvs*
~/Library/Application Support/JetBrains* ~/Library/Caches/com.jetbrains.toolbox*
7. Security Architecture
Mole treats deletion boundaries, symlink handling, sudo behavior, and release integrity as security-sensitive. The design is documented in docs/SECURITY_DESIGN.md and reviewed in SECURITY_AUDIT.md.
7.1 Threat model
Mole is a user-invoked CLI performing three classes of destructive operation: cleanup (regenerable caches/logs), uninstall (app bundle + data), and Trash routing (mo analyze). It assumes the user runs it intentionally but makes mistakes (typos, stale config, wrong menu choice), and that third-party apps may use arbitrary, non-Apple naming under ~/Library.
It explicitly does not defend against a user who deliberately runs sudo mole with malicious flags, a host with SIP disabled and /System writable, or supply-chain compromise of the binary itself (covered separately by signed releases).
7.2 Hard deletion boundaries
validate_path_for_deletion() (in lib/core/file_ops.sh) is the chokepoint every removal passes through. It rejects:
- Empty or non-absolute paths.
..path-traversal components (while still allowing legitimate names that merely contain..).- Paths with control characters or newlines.
- Critical system paths:
/,/bin,/sbin,/usr/*,/System/*,/etc,/private/etc,/Library/Apple,/Library/Extensions,/Library/Keychains,/var/db,/var/audit,/Applications/Finder.app,/Applications/Safari.app,/Usersroots, and more. - Symlinks whose resolved target lands inside a protected system path.
A curated allowlist permits a few known-safe deep paths (e.g. coresymbolicationd cache, /private/tmp, /private/var/log, receipt .bom/.plist).
7.3 Application & data protection
lib/core/app_protection.sh plus the data-only app_protection_data.sh hold large pattern arrays:
should_protect_path()blocks System Settings, Control Center, Finder, Dock, Keychains, Mail, Contacts, Calendars, iCloud Drive (Mobile Documents), NetworkExtension/VPN state, CoreAudio caches, audio plug-in licenses (iZotope, Native Instruments, iLok), and more.should_protect_data()protects the data directories of password managers (1Password, Bitwarden, LastPass, Dashlane), dev tools (JetBrains, VS Code, Xcode), AI tools (Claude, Codex, Ollama), proxy/VPN apps (Surge, Clash family), and Apple subsystems.- A distinct uninstall mode (
MOLE_UNINSTALL_MODE=1) relaxes data protection — when the user explicitly chooses to remove an app, its data may go too — while still protecting system-critical components. - Independent-CLI guard (
_path_belongs_to_independent_cli) avoids nuking standalone CLI state (~/.claude,~/.codex,~/.gemini,~/.opencode) when uninstalling a same-named GUI app.
7.4 Deletion routing & forensic log
mole_delete() is the unified destructive helper for high-risk paths (e.g. uninstall). It honors MOLE_DELETE_MODE:
trashmoves the path to the macOS Trash via thetrashCLI, an AppleScript/Finder fallback, or (for sudo-owned paths) a careful move into the invoking user's~/.Trash. If Trash is unavailable it fails closed rather than silently deleting.permanentdelegates to the validatedsafe_remove/safe_sudo_remove/safe_remove_symlinkhelpers.
Every call appends a tab-separated line to ~/Library/Logs/mole/deletions.log: timestamp, mode, size_kb, status, path. Size is recorded as unknown (never 0) when it cannot be measured, so the audit trail never understates a large delete. Log-write failures are surfaced once per session.
7.5 Privilege & authentication
sudois requested only when needed; a keepalive prevents mid-operation credential expiry during updates.mo touchidconfigures PAM (pam_tid.so), preferring/etc/pam.d/sudo_localon Sonoma+ so it survives OS updates.- Test modes (
MOLE_TEST_MODE,MOLE_TEST_NO_AUTH) block all real auth and destructive sudo so the Bats suite never touches the host.
7.6 Release integrity
install.sh verifies downloaded release assets two ways before trusting them:
- Checksum against the release's published
SHA256SUMS. - Sigstore build-provenance attestation via
gh attestation verify --owner tw93 --deny-self-hosted-runners. VerifyingSHA256SUMSanchors every binary's checksum to an attested file. Withghabsent the install falls through to checksum-only (matching prior behavior) unlessMOLE_REQUIRE_ATTESTATION=1forces failure.
Caution
The
goseclinter is intentionally configured to exclude rules likeG204(subprocess with variable) andG304/G703(file inclusion / path traversal). This is deliberate: shelling out todu/mdls/osascript/mdfindon user-supplied paths is Mole's core function. Safety is enforced structurally byvalidatePath(fuzz-tested) and the protection lists — not by avoiding these operations. Contributors must keep the validation chokepoints intact rather than relaxing them.
8. Operations
8.1 Channels & updates
| Channel | Source | Selected by |
|---|---|---|
stable |
Latest tagged release | default |
nightly |
Newest main commit |
mo update --nightly, or install latest/main |
dev |
dev branch |
install dev |
The active channel is recorded in ~/.config/mole/install_channel. For nightly, the file also stores COMMIT_HASH, and update checks compare commit hashes rather than version numbers. The background update notice is written to ~/.cache/mole/update_message and shown in the menu.
update_mole() resolves the invoked Mole (never just PATH) so update/remove act on the copy the user actually ran. Homebrew installs update via brew upgrade mole; nightly is script-install-only. The updater downloads the channel-appropriate install.sh from GitHub raw and runs it with --prefix/--config pointing at the existing install.
8.2 Logs & auditing
| File | Format | Written by |
|---|---|---|
operations.log |
line-based | every clean/uninstall/optimize/purge/installer/remove op |
deletions.log |
TSV | mole_delete() for Trash/permanent routing |
mole.log / mole_debug_session.log |
line-based | general + debug session logs |
Review with mo history [--json] [--limit N] (N = 1–200). Disable operation logging with MO_NO_OPLOG=1.
8.3 Rollback / recovery
- Uninstalls default to Trash — recover from there until emptied.
- An interrupted install never corrupts the dispatcher (atomic
mv). mo removecleanly removes binaries plus~/.config/mole,~/.cache/mole, and~/Library/Logs/mole;--dry-runpreviews the exact targets.
9. Build & Release Pipeline
The Makefile drives Go builds; the shell side ships as-is.
| Target | Action |
|---|---|
make build |
Local-arch build of bin/analyze-go and bin/status-go (-ldflags="-s -w") |
make mod-download |
go mod download with retry/backoff for flaky proxies |
make release-amd64 / release-arm64 |
Cross-compile per-arch release binaries (GOOS=darwin GOARCH=…) run natively for CGO support |
make check |
scripts/check.sh --no-format (lint) |
make format |
scripts/check.sh --format |
make test |
MOLE_TEST_NO_AUTH=1 scripts/test.sh (Bats) |
make test-go |
go test ./... |
make verify |
check + test-go |
make clean |
Remove built binaries |
Release assets are named analyze-darwin-<arch> / status-darwin-<arch>. The release workflow generates actions/attest-build-provenance attestations covering SHA256SUMS, the per-arch binaries, and the Homebrew tarballs — the trust root the installer verifies (Section 7.6).
Linting (.golangci.yml) enables govet (all checks except shadow/fieldalignment), staticcheck, errcheck, ineffassign, unused, modernize, revive, unparam, and gosec (with the documented core-function exclusions). Secret scanning uses gitleaks (.gitleaks.toml) with allowlists for test fixtures and the password/Touch-ID UI strings that are not credentials.
10. Module Reference (Internals)
lib/core/ — the shared foundation
| File | Responsibility |
|---|---|
common.sh |
Single include point; sources all core modules; update_via_homebrew(), remove_apps_from_dock() |
base.sh |
Colors, icons, global tuning constants, default whitelist, LaunchServices locator |
file_ops.sh |
validate_path_for_deletion(), safe_remove*, safe_sudo_remove, mole_delete(), Trash routing, size calc, find-and-delete with whitelist enforcement |
app_protection.sh |
should_protect_path/data(), uninstall-mode logic, vendor/shared/nested path discovery for find_app_files() |
app_protection_data.sh |
Data-only readonly bundle-ID / pattern arrays |
bundle_resolver.sh |
bundle_has_installed_app() — Spotlight + filesystem scan to detect orphaned helpers/agents |
log.sh |
Operation logging, debug logging, log rotation |
timeout.sh / timeouts.sh |
run_with_timeout() and centralized timeout constants |
history.sh |
Parse + render operations.log/deletions.log (text + JSON) |
pkg_receipts.sh |
Read macOS package receipts (/var/db/receipts) |
sudo.sh |
sudo request + keepalive, Touch ID detection |
ui.sh |
Cursor, spinner, key-reading primitives |
lib/clean/ — cleanup flows
Per-category modules: app_caches.sh, apps.sh, brew.sh, caches.sh, dev.sh, hints.sh, launch_services.sh, maven.sh, project.sh, purge_shared.sh, system.sh, user.sh. Each contributes a category to mo clean (and project.sh backs mo purge).
lib/optimize/ — diagnostics.sh, maintenance.sh, tasks.sh
Drives the rebuild-databases / reset-network / refresh-Finder-Dock / clear-logs / rebuild-LaunchServices-Spotlight task set, with JSON parsed in pure Bash (no jq dependency).
lib/uninstall/ — batch.sh, brew.sh
Batch app removal and Homebrew-managed uninstall helpers.
lib/manage/ — whitelist.sh, purge_paths.sh
Whitelist persistence/matching and purge scan-path configuration.
lib/ui/ — app_selector.sh, menu_paginated.sh, menu_simple.sh
Reusable interactive selectors.
11. The Go Binaries
Both Go programs are macOS-gated (//go:build darwin) and follow the Bubble Tea Model/Update/View pattern.
cmd/analyze — disk-space explorer
- Entry
main.gois bootstrap only;model.goholds themodelstruct and ~12 message types;update.goholds the Bubble Tea update chain;view.gorenders. scanner.gomeasures directory sizes (preferringdu, withmdlslogical size for.appbundles), guarded by timeouts.cache.gocaches overview snapshots (schema-versioned, with staleness/expiry checks).delete.goperforms Trash moves with the samevalidatePathdiscipline as the shell side, fuzz-tested indelete_fuzz_test.go.- Two modes: a TUI (
tea.WithAltScreen()) and a one-shot--jsonexporter. The default target is/(system overview) unless a path orMO_ANALYZE_PATHis given.
cmd/status — live system dashboard
- A
Collector(metrics.go) gathers CPU, GPU, memory, disk, disk I/O, network, battery/power, thermal, Bluetooth, and process metrics viagopsutiland macOS tools (system_profiler,diskutil,uptime,osascript). Per-domain collectors live inmetrics_*.go. - Three collection cadences balance freshness against cost:
collectionFast(every tick),collectionProcess(process watch), andcollectionFull(every 30 s). AProcessWatcher(process_watch.go) raises sustained-high-CPU alerts per the--proc-cpu-*flags. - Output auto-switches to JSON when stdout is not a character device (piped), so
mo status | jq …works without--json. Acat_hiddenpreference persists to~/.config/mole/status_prefs.
Note
AST/CLI extraction reports zero "CLI commands" for this repo because neither Go binary uses a command framework (Cobra/urfave) — they use stdlib
flagwith a couple of flags each, and the real command surface is the shell dispatcher'scasestatement. The authoritative command list isMOLE_COMMANDSinlib/core/commands.sh.
12. Testing Strategy
Testing is dual-stack and safety-focused:
- Bats (shell): 40+
.batsfiles undertests/cover clean/uninstall/optimize/purge/installer flows, path validation, whitelist behavior, installer integrity, Bash 3.2 compatibility, and regressions. Run withmake test(setsMOLE_TEST_NO_AUTH=1so no real auth or destructive sudo occurs). - Go:
go test ./...(make test-go) covers the analyzer and status collectors, including*_test.gofor metrics parsing and disk logic. - Fuzzing:
cmd/analyze/delete_fuzz_test.go(FuzzValidatePath) andtests/path_validation_fuzz.bats(fed bytests/fuzz_corpus/dangerous_paths.txt) property-test the path-validation boundary — the single most safety-critical surface. - Performance:
tests/core_performance.batsguards per-PR performance;scripts/audit_bundle_drift.shbacks a monthly bundle-protection audit.
Tip
When adding a new
clean_*flow, route deletions throughsafe_find_delete/safe_sudo_find_delete. Whitelist and protection enforcement is built into those helpers, so new flows inherit it structurally rather than re-implementing (and possibly missing) it — a recurring class of past bugs (#710, #724, #738, #744, #757).
13. Code Quality & Conventions
- Bash 3.2 floor. The macOS system Bash is 3.2; the code avoids
${var,,}, namerefs, andmapfile, usingtr,eval-by-name, and explicit loops. New contributions must keep this floor. - Single chokepoints. Path validation (
validate_path_for_deletion), deletion (mole_delete/safe_*), and protection (should_protect_*) are centralized so safety can be reasoned about and fuzzed in one place. - Data/logic split. Protection data lives in
app_protection_data.sh; logic inapp_protection.sh. - Idempotent sourcing. Every module short-circuits on a
*_LOADEDsentinel. - No-color & non-TTY awareness. Spinners and alt-screen behavior degrade gracefully when output is piped or
NO_COLORis set. - Pure-Bash JSON. Optimize/health parsing avoids a
jqdependency. - Go layering.
main.gobootstrap-only; types/accessors inmodel.go; update chain inupdate.go— a deliberate separation the contributor guide enforces.
14. Potential Improvements
These are observations from the source, not defects:
- Sensor coverage gaps. Several status sensors (temperature, fan, battery detail) depend on
system_profiler/hardware availability and degrade silently. Community reports (#1058, #1066, #1060) suggest model-specific handling could be expanded. - Scan robustness. Reports of scans stalling on huge or vanished directories (#1053, #1049) point to opportunities for tighter per-item timeouts and progress feedback in the long-tail cases.
- Sudo prompt timing. Issue #1059 (prompt appearing to skip input) suggests the interactive sudo flow could pre-flight credentials more consistently; Touch ID side-steps it.
- LaunchServices hygiene. Issue #1057 proposes cleaning stale LaunchServices registrations during
mo clean— a natural extension of the existinglaunch_services.shflow. - Windows divergence. The experimental
windowsbranch is separate; keeping the safety chokepoints portable would ease eventual convergence.
15. Quick Reference
Command surface (MOLE_COMMANDS)
clean uninstall optimize analyze status history purge installer
touchid completion update remove help version
Key paths
/usr/local/bin/{mole,mo} # executables
~/.config/mole/{bin,lib,install_channel,purge_paths,status_prefs,whitelist*}
~/.cache/mole/{version_check,update_message}
~/Library/Logs/mole/{mole.log,operations.log,deletions.log}
/etc/pam.d/sudo[_local] # Touch ID (pam_tid.so)
Critical functions
| Function | File | Role |
|---|---|---|
main() / dispatch case |
mole |
Route subcommands |
validate_path_for_deletion() |
lib/core/file_ops.sh |
Deletion boundary |
mole_delete() |
lib/core/file_ops.sh |
Trash/permanent routing + audit |
should_protect_path() / should_protect_data() |
lib/core/app_protection.sh |
Protection policy |
find_app_files() |
lib/core/app_protection.sh |
Uninstall leftover discovery |
verify_release_asset_checksum() |
install.sh |
Release integrity |
Collector.Collect() |
cmd/status/metrics.go |
System metrics |
Build/test
make build # local Go binaries
make verify # lint + go test
make test # Bats suite (MOLE_TEST_NO_AUTH=1)