# Technical Documentation: tw93/Mole

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/tw93/Mole)

### Overview (`README.md`)

<p align="right"><b>macOS</b> · <a href="https://github.com/tw93/Mole/tree/windows">Windows</a></p>

<div align="center">
  <h1>Mole</h1>
  <p><b>Deep clean, uninstall apps, optimize, analyze disk, and monitor your Mac. Free open-source CLI, plus a native Mac app.</b></p>
  <p>English · <a href="README_CN.md">中文</a> · <a href="README_TW.md">繁體</a> · <a href="README_JA.md">日本語</a> · <a href="README_KR.md">한국어</a> · <a href="README_DE.md">Deutsch</a> · <a href="README_FR.md">Français</a> · <a href="README_UA.md">Українська</a> · <a href="README_RU.md">Русский</a></p>
  <a href="https://github.com/tw93/mole/stargazers"><img src="https://img.shields.io/github/stars/tw93/mole?style=flat-square" alt="Stars"></a>
  <a href="https://github.com/tw93/mole/releases"><img src="https://img.shields.io/github/v/release/tw93/mole?label=version&style=flat-square" alt="Version"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-GPL_v3-blue.svg?style=flat-square" alt="License"></a>
  <a href="https://github.com/tw93/mole/commits"><img src="https://img.shields.io/github/commit-activity/m/tw93/mole?style=flat-square" alt="Commits"></a>
  <a href="https://twitter.com/HiTw93"><img src="https://img.shields.io/badge/follow-Tw93-red?style=flat-square&logo=Twitter" alt="Twitter"></a>
  <a href="https://t.me/+9f9gf4ZrFSQ2OWVl"><img src="https://img.shields.io/badge/chat-Telegram-blueviolet?style=flat-square&logo=Telegram" alt="Telegram"></a>
</div>

<p align="center">
  <img src="./docs/img/big-mole.png" alt="Mole cleanup results" width="1000" />
</p>

> 💡 This repo is the free open-source CLI (`mo`). Prefer a native app? [Mole for Mac](https://mole.fit/) is a separate download with review-before-delete cleanup, tested leftovers for 800+ apps, maintenance, disk drill-downs, live status, and fan controls on supported Macs. On Windows, the native app is [Mole for Windows](https://mole.fit/windows). `brew install mole` installs the CLI only.

## Features

- **All-in-one CLI toolkit**: Combines CleanMyMac, AppCleaner, DaisyDisk, and iStat Menus style workflows in one terminal command
- **Deep clean**: Removes caches, logs, leftovers, and orphaned app data to reclaim disk space
- **Smart uninstaller**: Removes apps together with LaunchAgents, preferences, and leftovers
- **Disk analyzer**: Visualizes disk usage with an interactive TUI, finds large files, and navigates directories
- **System maintenance**: Flushes DNS, refreshes QuickLook/icons, and optimizes system databases
- **Live monitoring**: Shows real-time CPU, memory, disk I/O, network traffic, and process stats

## Quick Start

Mole requires macOS 12 or newer and supports both Intel and Apple Silicon Macs. If Homebrew no longer supports your macOS version, use the script install instead.

**Install via Homebrew**

```bash
brew install mole
```

**Or via script**

```bash
curl -fsSL https://raw.githubusercontent.com/tw93/mole/main/install.sh | bash
```

**With an AI agent**

Tell your agent:

> Install Mole for me by reading https://raw.githubusercontent.com/tw93/Mole/main/llms.txt

Or install the skill yourself. With it, Claude Code, Codex, Cursor and other agents show every cleanup with `--dry-run` before anything is removed, and leave app uninstalls and administrator steps to you. Update with `npx skills update -g -y`.

```bash
npx skills add tw93/Mole -a claude-code codex cursor -g -y
```

As a host plugin: `/plugin marketplace add tw93/Mole` then `/plugin install mole@mole` in Claude Code, or `codex plugin marketplace add tw93/Mole` then `codex plugin add mole@mole` in Codex.

**Run**

```bash
mo                           # Interactive menu
mo clean                     # Deep cleanup + already-uninstalled app leftovers
mo uninstall                 # Remove installed apps + their leftovers
mo optimize                  # Refresh caches & services
mo analyze                   # Visual disk explorer (or 'mo analyse')
mo status                    # Live system health dashboard
mo purge                     # Clean project build artifacts
mo installer                 # Find and remove installer files
mo slim                      # Remove Intel code from universal apps

mo touchid                   # Configure Touch ID for sudo
mo completion                # Set up shell tab completion
mo update                    # Update Mole
mo update --nightly          # Update to latest unreleased main build, script install only
mo remove                    # Remove Mole from system
mo --help                    # Show help
mo --version                 # Show installed version
```

**Preview safely**

```bash
mo clean --dry-run
mo uninstall --dry-run
mo optimize --dry-run
mo purge --dry-run
mo installer --dry-run
mo slim --dry-run
mo history
mo history --json

mo clean --dry-run --debug   # Preview + detailed logs
mo optimize --whitelist      # Manage protected optimization rules
mo clean --whitelist         # Manage protected caches
mo purge --paths             # Configure project scan directories
mo analyze /Volumes          # Analyze external drives only
mo analyze /private/tmp      # Review user-owned temporary directories
```

<details>
<summary><strong>Other install options</strong></summary>

To install a specific release, pass any tag from the [releases page](https://github.com/tw93/mole/releases), with or without its leading `V`. To track the development branch instead, pass `main`:

```bash
curl -fsSL https://raw.githubusercontent.com/tw93/mole/main/install.sh | bash -s -- 1.51.0
curl -fsSL https://raw.githubusercontent.com/tw93/mole/main/install.sh | bash -s -- main
```

`main` installs unreleased code from the default branch, so expect rough edges. `latest` still works as a legacy alias for `main`; despite the name it does not install the newest stable release.

The script normally installs to `/usr/local/bin`, which may ask for an administrator password. Install into a user-owned directory if you want future `mo update` runs to stay password-free:

```bash
mkdir -p "$HOME/.local/bin"
curl -fsSL https://raw.githubusercontent.com/tw93/mole/main/install.sh | bash -s -- --prefix "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"
```

Add the same `PATH` export to `~/.zshrc` or your shell profile for new terminals. Mole updates the installation you invoked, so it keeps using this directory. Commands that change system-owned files may still request administrator access.

**Nix**

On macOS, Nix users can install the flake from `main`, which contains unreleased changes:

```bash
nix profile install github:tw93/mole/main#mole
nix profile upgrade mole
nix profile remove mole
```

For a declarative installation, add `github:tw93/mole/main` as a flake input and use its `packages.${system}.mole` package. Update or remove Mole through Nix; `mo update` and `mo remove` leave Nix-managed installations unchanged.

</details>

**On Windows** ([Windows CLI](https://github.com/tw93/Mole/tree/windows))

```powershell
iwr -useb https://raw.githubusercontent.com/tw93/Mole/windows/quick-install.ps1 | iex
```

Mole is only distributed through Homebrew, this repository and [mole.fit](https://mole.fit/). `molemac.com`, `moleapp.net` and `molefit.com` are not Mole, and the Windows app at `mole.bluecatbot.com` is a separate community project.

## Safety

Mole can remove files, so it validates paths, protects shared and system-owned locations, and asks for confirmation when an action needs it. When Mole cannot prove an item is safe to change, it skips or refuses it.

- `clean`, `uninstall`, `purge`, `installer`, `slim`, and `remove` can delete files, so review them with `--dry-run` first and add `--debug` when needed
- Run Mole without `sudo`; it requests administrator access only when needed
- `mo uninstall`, `mo analyze`, and `mo slim` route deletions to Trash by default after confirmation; `clean`, `purge`, and `installer` delete permanently. `mo uninstall --permanent` skips Trash
- Cleanup activity is recorded in `~/Library/Logs/mole/operations.log`; review it with `mo history` or disable it with `MO_NO_OPLOG=1`
- Protect caches with `mo clean --whitelist`, or maintenance items with `mo optimize --whitelist`
- If the whitelist file cannot be read, `clean`, `purge`, and `optimize` stop instead of running without it

Review [SECURITY.md](SECURITY.md) and [SECURITY_AUDIT.md](SECURITY_AUDIT.md) for reporting guidance, safety boundaries, and current limitations.

## Features in Detail

The examples below are shortened. Available items, sizes, and skip reasons depend on your Mac.

### Clean

`mo clean` reviews known-safe caches, logs, temporary files, developer artifacts, and leftovers from apps that are no longer installed. It empties `~/.Trash` by default; to keep it, select Trash in `mo clean --whitelist`, the same menu that protects caches you want to keep. Volume Trashes such as `/Volumes/*/.Trashes` are not emptied with it. Selections persist in `~/.config/mole/whitelist`. Before adding custom paths, open the menu and press Enter to save the selections, then append one path per line. An existing file replaces the optional defaults; built-in safety protections still apply.

```text
$ mo clean

Clean Your Mac

⚙ Apple Silicon | Free space: 219.0GB

➤ User essentials
  ✓ User app cache · 18 items, 2.4GB
  ✓ User app logs · 7 items, 12.8MB
  ✓ Trash · emptied, 9 items

➤ App caches
  ✓ App Store cache · 8 items, 248.5MB

➤ Browsers
  ✓ Safari cache · 24 items, 642.1MB
  ✓ Chrome cache · 31 items, 1.2GB

➤ Developer tools
  ✓ npm cache · cleaned
  ◎ pnpm cache · skipped (pnpm busy)

======================================================================
Cleanup complete
Tracked cleanup: 4.5GB | Items cleaned: 97 | Categories: 4
Free space: 223.5GB (+4.5GB)
======================================================================
```

### Uninstall

`mo uninstall` removes an installed app together with related files that Mole can tie back to that app. It keeps shared data when another installed copy still uses it. If the app is already gone, use `mo clean` to look for leftovers.

Personal data, including settings and plugins, is kept by default. Press `D` in the removal preview to select individual data paths. `mo uninstall --with-data` starts with that data selected, except browser profiles and App Group containers; the preview lists it separately and `D` still lets you keep any path. After removal, references in shell, Hook, or MCP configuration are listed by file and line only; Mole does not edit those configurations.

```text
$ mo uninstall

Select Apps to Remove  1/3 selected

➤ ● Photoshop 2024                4.20GB | 2mo ago
  ○ IntelliJ IDEA                 2.80GB | 3d ago
  ○ Premiere Pro                  3.40GB | 2w ago

Files to be removed:

✓ Photoshop 2024, 4.20GB
  ✓ /Applications/Adobe Photoshop 2024/Adobe Photoshop 2024.app

Personal data kept:
  ~/Library/Application Support/Adobe/Adobe Photoshop 2024
  ~/Library/Preferences/com.adobe.Photoshop.plist

D choose personal data

======================================================================
Uninstall complete
Removed 1 app, freed 4.20GB: Photoshop 2024
======================================================================
```

### Optimize

`mo optimize` runs bounded maintenance for supported Finder, network, database, and macOS services. Tasks that are unnecessary, unsafe at the moment, or unavailable are skipped with a reason. Use `mo optimize --whitelist` to exclude tasks or path patterns, such as a long-lived mounted disk image at `/Volumes/mail` that should not show up as a detach candidate.

```text
$ mo optimize

Optimize

⚙ System  18/32 GB RAM | 616/926 GB Disk | Uptime 6d

PERFORMANCE DIAGNOSIS
  ✓ No sustained high-CPU bottleneck detected

➤ Finder Cache Refresh
  → QuickLook thumbnails refreshed
  → Icon services cache rebuilt

➤ Network Cache Refresh
  → DNS cache refreshed

➤ Database Optimization
  ◎ Close these apps before database optimization: Safari

➤ Disk Health
  → Disk verify skipped

======================================================================
Optimization Complete
Applied 2 optimizations
13 unchanged | 3 skipped | 1 unavailable
======================================================================
```

### Analyze

`mo analyze` opens a terminal disk explorer. It supports arrow keys and Vim navigation, filtering, multi-selection, Finder preview, and confirmed moves to Trash. External drives are skipped from the default overview; inspect them with `mo analyze /Volumes` or a specific mount path. Use `mo analyze /private/tmp` to review user-owned temporary files without turning them into automatic cleanup targets. File-by-file scans count APFS clones that share all blocks once; fast folder estimates can still count shared blocks more than once.

A size ending in `+` contains measured bytes from a partial scan; `unknown` means the size could not be measured. Results cut short by temporary failures such as timeouts do not replace complete cached measurements, and a later refresh can recover the missing data. Folders macOS will not let the terminal read stay partial until access changes. The terminal list keeps the 30 largest entries, so an unreadable entry may fall outside that list; the total still indicates a partial scan. Directory JSON output includes all scanned entries.

`mo analyze --json /path` includes `scan_status` on the result and each entry: `complete`, `partial`, or `unavailable`. Numeric sizes contain measured bytes; a zero with `unavailable` does not mean an empty directory. Partial scans still exit successfully, so automation should inspect `scan_status`. Completeness applies within Mole's existing scan exclusions and does not promise an atomic filesystem snapshot.

```text
$ mo analyze

Analyze Disk  (302.1GB free)
Select a location to explore:

 ▶  1. ████████████████████████  47.9%  |  Home                       75.4GB
    2. ███████████               22.0%  |  User Library               34.6GB
    3. ███████                   14.2%  |  Applications               22.4GB
    4. █████                     10.7%  |  System Library             16.9GB
    5. ███                        5.2%  |  Old Downloads (90d+)       8.2GB  >3mo
```

### Status

`mo status` is a read-only dashboard for hardware, system pressure, disk activity, network traffic, power, and processes.

When the IPv4 default route uses a tunnel, network graphs use that interface’s rates to avoid counting the same traffic again on its physical carrier. JSON retains per-interface rates, including the routed tunnel; idle non-default tunnels stay hidden.

```text
$ mo status

Mole Status  Health ● 92  MacBook Pro · M4 Pro · 32GB · macOS 26

⚙ CPU                                    ▦ Memory
Total   ████████████░░░░░░░  45.2%       Used    ███████████░░░░░░░  58.4%
Load    0.82 / 1.05 / 1.23 (8 cores)     Total   18.7 / 32.0 GB
Core 1  ███████████████░░░░  78.3%       Free    ████████░░░░░░░░░░  41.6%
Core 2  ████████████░░░░░░░  62.1%       Avail   13.3 GB

▤ Disk                                   ⚡ Power
Used    █████████████░░░░░░  67.2%       Level   ██████████████████  100%
Free    156.3 GB                         Status  Charged
Read    ▮▯▯▯▯  2.1 MB/s                  Health  Normal · 423 cycles
Write   ▮▮▮▯▯  18.3 MB/s                 Temp    58°C · 1200 RPM

⇅ Network                                ▶ Processes
Down    ▁▁█▂▁▁▁▁▁▁▁▁▇▆▅▂  0.54 MB/s      Zombies 3 · Chrome (4242) ×3
Up      ▄▄▄▃▃▃▄▆▆▇█▁▁▁▁▁  0.02 MB/s      Code       ▮▮▮▮▯  42.1%
Proxy   HTTP · 192.168.1.100             Chrome     ▮▮▮▯▯  28.3%
```

The health score combines CPU, memory, disk capacity, SMART status, I/O, thermals, battery state, and uptime, with color-coded ranges. Press `k` to toggle the cat, `c` to cycle the number of CPU cores shown, or `q` to quit; display preferences are saved.

<details>
<summary><strong>JSON, NDJSON, and process alerts</strong></summary>

- `mo analyze --json ~/Documents` returns a one-time disk report as JSON
- `mo status --json` returns a one-time status snapshot as JSON
- `mo status | jq '.health_score'` switches to JSON automatically when output is piped
- `mo status --watch --interval 2s` streams newline-delimited JSON from a warm collector
- `mo history --json` returns cleanup activity as JSON. Sessions include `run_id` (an opaque string, empty when no identity was logged) and `attribution`: `run` for identified runs, `command` for legacy command-based grouping, or `ambiguous` when legacy markers cannot distinguish interruption from overlapping runs. Recorded actions remain available; ambiguous counts cannot be assigned reliably to individual runs. An empty `ended_at` means no end marker was recorded
- `mo uninstall --list` returns installed apps as JSON with `name`, `bundle_id`, `source`, `uninstall_name`, `path` and `size`

```text
$ mo analyze --json ~/Documents
{
  "path": "/Users/you/Documents",
  "overview": false,
  "entries": [
    { "name": "Library", "path": "...", "size": 80939438080, "is_dir": true }
  ],
  "large_files": [
    { "name": "backup.zip", "path": "...", "size": 8796093022 }
  ],
  "total_size": 168393441280,
  "total_files": 42187
}

$ mo status --json
{
  "host": "MacBook-Pro",
  "health_score": 92,
  "cpu": { "usage": 45.2, "logical_cpu": 8 },
  "memory": { "total": 34359738368, "used": 20078972109, "used_percent": 58.4 },
  "disks": [],
  "process_collected_at": "2026-08-29T12:30:00Z",
  "process_stale": false,
  "zombie_count": 3,
  "zombie_parents": [
    { "pid": 4242, "name": "Google Chrome for Testing", "count": 3 }
  ],
  "zombie_parents_complete": true,
  "uptime": "3d 12h 45m"
}
```

Zombie diagnostics are read-only and do not affect the health score or terminate processes. Before
Mole has a successful process sample, `process_collected_at`, `process_stale`, `zombie_count`, and
`zombie_parents_complete` are omitted and `zombie_parents` is `null`. Later fast/watch snapshots
reuse the latest successful sample with its original `process_collected_at` and set
`process_stale: true`; a live process sample sets it to `false`. A count of `0` means Mole measured
no zombies. Parent summaries contain at most three known owners;
`zombie_parents_complete: false` means attribution was unavailable, incomplete, or truncated.

If one collector fails, `mo status --json` still prints the metrics that were collected, reports the
failure on stderr, and exits successfully, the same way `--watch` keeps streaming. It exits 1
when none of CPU, memory, disk, or process metrics are available, or when JSON output fails.

Status also supports read-only alerts for processes that stay above a CPU threshold. Use `--proc-cpu-threshold`, `--proc-cpu-window`, or `--proc-cpu-alerts=false` to tune or disable them.

</details>

### Purge

`mo purge` finds rebuildable project artifacts such as `node_modules`, `target`, `.build`, `build`, and `dist`. It groups artifacts by project and permanently deletes only the items you confirm. Artifacts with file activity in the last 7 days, or activity Mole cannot verify, are unselected by default. Mole uses `fd` when available and falls back to `find`, and protects directories containing deployment keypair files, nested Git repositories, or Git-tracked files. Non-interactive runs require `mo purge --yes`; use `mo purge --dry-run` to review the candidates first.

Use Page Up/Down or `h`/`l` to move a page, `[`/`]` to jump between projects, and `X` to skip a project and advance. `/` searches project paths and artifact names, `n` finds the next match without changing selections, and Enter opens the final path review. Reported space is an estimate; unmeasured artifacts and incomplete scans are identified explicitly.

<details>
<summary><strong>Purge example output</strong></summary>

```text
$ mo purge

Purge Project Artifacts

Select Artifacts to Purge
6.00GB, 2 selected

➤ ● ┌ ~/Projects/website        3.80GB | node_modules | 28d
  ○ └ ~/Projects/website         186MB | dist         | <1d
  ● ┌ ~/Projects/rust-app       2.20GB | target       | 2mo
  ○ └ ~/Projects/rust-app         22MB | dist         | <7d

======================================================================
Purge complete
Estimated space freed: 6.00GB | Items: 2 | Free: 223.5GB
======================================================================
```

</details>

<details>
<summary><strong>Custom Scan Paths</strong></summary>

Run `mo purge --paths` to configure scan directories, or edit `~/.config/mole/purge_paths` directly:

```shell
~/Documents/MyProjects
~/Work/ClientA
~/Work/ClientB
```

When custom paths are configured, Mole scans only those directories. Otherwise, it uses defaults like `~/Projects`, `~/GitHub`, `~/dev`, and supported agent worktree containers. Discovery does not save an incomplete result. Artifact scans reach six levels below each configured root; add a nearer root for deeper projects. Purge removes rebuildable artifacts inside worktrees, never the worktrees themselves.

</details>

### Installer

`mo installer` finds DMG, PKG, MPKG, ISO, XIP, and installer ZIP files in Downloads, Desktop, Homebrew caches, iCloud, Mail, Telegram, and other supported locations, and shows each item's size and source before removal. Discovery has a cumulative time limit: if a scan or metadata probe fails or times out, Mole discards the list and exits without selecting files. Corrupt and unreadable ZIP archives are skipped, symlinked scan roots are supported but symlinks beneath them are not followed, and selected files are checked again against their confirmed identity at the deletion boundary.

<details>
<summary><strong>Installer example output</strong></summary>

```text
$ mo installer

Select Installers to Remove, 3.83GB, 5 selected

➤ ● Photoshop_2024.dmg          1.20GB | Downloads
  ● IntelliJ_IDEA.dmg          850.6MB | Downloads
  ● Illustrator_Setup.pkg      920.4MB | Downloads
  ● PyCharm_Pro.dmg            640.5MB | Homebrew
  ● Acrobat_Reader.dmg         220.4MB | Downloads
  ○ AppCode_Legacy.zip         410.6MB | Downloads

======================================================================
Installers cleaned
Removed 5 installers, freed 3.83GB
======================================================================
```

</details>

### Slim

`mo slim` removes the Intel code that universal apps carry on Apple Silicon Macs. It lists the apps you own that are signed with a Developer ID, with the size of each app's Intel code, and changes only the apps you select. Each app is slimmed on a copy, touching only main executables and nested code that carries its own signature; the copy replaces the app only after it keeps its Apple Silicon code and passes code signing and Gatekeeper checks, and the original moves to the Trash. App Store apps, Apple apps, apps set to open using Rosetta, apps owned by another user, and running apps are left alone. It needs no developer tools, and an app update brings the Intel code back.

## Quick Launchers

<details>
<summary><strong>Raycast and Alfred setup</strong></summary>

Install five launchers for Clean, Uninstall, Optimize, Analyze, and Status:

```bash
curl -fsSL https://raw.githubusercontent.com/tw93/Mole/main/scripts/setup-quick-launchers.sh | bash
```

The script adds Raycast commands and, when Alfred preferences are present, matching Alfred workflows with the keywords `clean`, `uninstall`, `optimize`, `analyze`, and `status`.

Raycast needs one manual setup:

1. Open **Raycast Settings > Extensions > Script Commands**.
2. Add `~/Library/Application Support/Raycast/script-commands` as a script directory.
3. Run **Reload Script Directories** in Raycast.

The launchers auto-detect Terminal, iTerm2, Alacritty, kitty, WezTerm, Ghostty, Hyper, WindTerm, and Warp. Set `MO_LAUNCHER_APP=<name>` to choose one; you can also run Mole directly in [Kaku](https://github.com/tw93/Kaku).

</details>

## Community

Thanks to everyone who helped build Mole. Go follow them. ❤️

<a href="https://github.com/tw93/Mole/graphs/contributors">
  <img src="./CONTRIBUTORS.svg?v=2" alt="Mole contributors" width="1000" />
</a>

<br/><br/>
Real feedback from users who shared Mole on X.

<img src="./docs/img/mole-love.png" alt="Community feedback on Mole" width="1000" />

Watch the [Mole tutorial video](https://www.youtube.com/watch?v=UEe9-w4CcQ0) by PAPAYA 電腦教室.

## Support

- Getting [Mole for Mac](https://mole.fit) is the most direct way to support Mole's development
- If Mole helped you, give it a star, [share it](https://twitter.com/intent/tweet?url=https://github.com/tw93/Mole&text=Mole%20-%20Deep%20clean%20and%20optimize%20your%20Mac.), or open an issue or PR
- I have two cats, TangYuan and Coke, and if Mole makes your life a little easier, feel free to treat them to <a href="https://cats.tw93.fun?name=Mole" target="_blank">canned food 🥩</a>

<details>
<summary>These lovely people already did 🐱</summary>
<br/>
<a href="https://cats.tw93.fun?name=Mole"><img src="https://cdn.jsdelivr.net/gh/tw93/sponsors@main/assets/sponsors.svg" alt="Mole supporters" width="1000" loading="lazy" /></a>
</details>

## License

Mole is open source under GPL-3.0; see [LICENSE](LICENSE). Any modified version you share must remain under the same license. If you fork Mole, please use a distinct name and credit Mole as the upstream source. [Mole for Mac](https://mole.fit) is a separate proprietary app, and Mole is here for the long run.

---

### Overview (`.claude/rules/readme.md`)

---
paths:
  - "README*.md"
---

# README copies

- README copies in every language keep sample output and code blocks in English, because the CLI itself is English-only; only the `#` comments that explain commands are localized. The detailed Analyze, Status, history JSON, and zombie-process paragraphs and the `mo analyze --json` example are kept on purpose as the best demonstration of what Mole reports; do not trim or move them in README cleanups (maintainer, 2026-10-10).
- Keep the README install URL on unpinned `main` (`install.sh` rule in `AGENTS.md`). README Quick Start lists the official sources in every language; README says in every language that the `Trash` whitelist row keeps `~/.Trash` (`.claude/rules/clean.md`).

---

### Overview (`plugins/mole/README.md`)

# Mole agent skill

This plugin installs the official skill for the free, open-source Mole CLI (`mo`) on macOS and Windows. With it, Claude Code and Codex answer "what is using my disk?" from Mole's own JSON output, show every cleanup with `--dry-run` and wait for your consent before anything is removed, and leave app uninstalls and administrator steps for you to run in your own terminal.

Install it in Claude Code with `/plugin install mole --marketplace tw93/Mole`, or in Codex with `codex plugin marketplace add tw93/Mole` then `codex plugin add mole@mole`.

The skill needs the Mole CLI on the machine: `brew install mole` on macOS, `scoop install mole` on Windows. Source, license (GPL-3.0) and other install paths are at https://github.com/tw93/Mole.

`skills/mole` here is a copy of `skills/mole` at the repository root, since Codex drops symbolic links when it installs a plugin.

---

### SECURITY DESIGN (`docs/SECURITY_DESIGN.md`)

# Mole Security Design

This document describes the safety mechanisms that prevent Mole from
destroying data it shouldn't. It is written for reviewers, contributors,
and anyone evaluating Mole for production use.

The corresponding implementation lives in `lib/core/file_ops.sh`,
`lib/core/app_protection.sh`, and `lib/core/app_protection_data.sh`. Path
validation has machine-checked fuzz tests in `cmd/analyze/delete_fuzz_test.go`
and `tests/path_validation_fuzz.bats`.

## Threat model

Mole is a user-invoked CLI that performs three classes of destructive
operations on the local machine:

1. **Cleanup**: remove caches, logs, and temp data the OS or apps regenerate.
2. **Uninstall**: remove an app bundle and its data directories.
3. **Trash routing**: move user-selected files in `mo analyze` to Trash.

We assume:
- The invoking user has shell access and runs mole intentionally.
- The user is **not** trying to attack their own machine.
- The user **does** make mistakes (typo a path, click wrong menu, run
  cleanup with stale config).
- Third-party apps writing into `~/Library` may have arbitrary names and
  may not follow Apple naming conventions.

We are **not** defending against:
- A user who runs `sudo mole` with malicious flags they typed in deliberately.
- A compromised macOS host where SIP is disabled and `/System` is writable.
- Supply-chain compromise of the mole binary itself (covered separately
  by signed releases + SHA256SUMS attestations in `release.yml`).

The lines we will not cross, regardless of input:
- Never delete a path inside `/System`, `/bin`, `/sbin`, `/usr` (except
  `/usr/local`, where Homebrew and user software live), `/etc`,
  `/Library/Extensions`, or `/var/db` (system databases).
- Never delete a bare top-level root itself, even when children are
  deletable: `/Applications`, `/Library`, `/Library/Application Support`,
  `/Volumes`, `/opt`, `/Users`, a user home root `/Users/<name>`, or
  `/var/root`. This blocks the empty-variable collapse where `"$dir/$name"`
  with an empty `$name` resolves to the parent root.
- Never delete a path that resolves (after symlink chasing) into one of
  the above.
- Never uninstall a `com.apple.*` system app, except the explicit list
  of App Store / developer-portal Apple apps that users actually buy
  (Xcode, Final Cut Pro, Logic, GarageBand, iWork, MainStage, etc.).

## Layer 1: `validate_path_for_deletion`

Every cleanup and app-data removal in mole funnels through `mole_delete` /
`safe_remove` / `safe_sudo_remove`, which all call
`validate_path_for_deletion` before touching the filesystem. Raw `rm` is
limited to the annotated bypasses of Layer 2 and to mole's own files:
`install.sh` removes its staged and temporary files (its `safe_rm` refuses
anything outside temp roots), and `mo remove` unlinks launchers that passed
its launcher-signature check with plain `rm -f` and removes the Mole-owned
cache and log directories with `rm -rf` annotated `# SAFE:`. The validator
applies six independent checks. Any one rejecting kills the operation.

Location: `validate_path_for_deletion` in `lib/core/file_ops.sh`.

1. **Non-empty + absolute.** Empty paths and any path not starting with
   `/` are rejected. Eliminates ambiguity from relative paths interacting
   with caller `$PWD`.

2. **Symlink resolution.** If the path is itself a symlink, the validator
   reads the link target, resolves it to an absolute path, and re-checks
   the target against the protected-path list. Prevents an attacker (or
   an accidental config bug) from pointing `/tmp/foo` at `/System` and
   getting the validator to wave it through.

3. **Ancestor symlink resolution.** Check 2 only inspects the leaf. If an
   *ancestor* component is a symlink, the literal path string matches
   nothing dangerous while the actual `rm` follows the link into the real
   target: a redirected `~/Library/Caches` would let a cache sweep walk
   into `~/Documents` or a system tree. So the validator canonicalizes the
   parent (a physical `cd` resolves every ancestor link) and re-runs the
   deny predicates on the resolved leaf. Two properties matter here:
   - **Deny-only.** A resolved path never *grants* permission the literal
     path lacked, so legitimate targets keep their existing verdict. This
     is why `/var` is deliberately not in the fuzz test's critical-root
     list: `/var/folders` temp trees are cleanable literally, so they must
     stay cleanable through a link too.
   - **Runs before the allow-list** in check 6, which would otherwise
     early-return past this gate for `/private/*` paths.

   The common case stays fork-free: ancestors are walked with the `[[ -L ]]`
   builtin and the canonicalizing subshell is only paid for when one of them
   really is a symlink. Doing it unconditionally cost ~2ms per call, about
   +23% on validation, on a function that runs once per deletion candidate.

4. **Path traversal.** `..` is rejected only when it appears as a full
   path component (`/foo/../bar`, `/..`, `../bar`, `foo/..`). This is
   tighter than naive substring matching: it allows legitimate names
   like Firefox's `name..files` directory while still blocking
   `/Users/me/Library/../../etc`.

5. **Control characters.** Any path containing `\n`, `\t`, or other
   `[[:cntrl:]]` bytes is rejected. Defends against log-injection and
   surprising-shell-interpretation scenarios.

6. **Allow-then-deny match.**
   - First, explicit allow-list for known-safe subtrees under `/private`
     (`/private/tmp`, `/private/var/log`, `/private/var/folders`,
     `/private/var/db/diagnostics`, etc.) and `/System/Library/Caches/com.apple.coresymbolicationd/data` (rebuildable).
   - Then, deny-list for `/`, `/bin*`, `/sbin*`, `/usr*`, `/System*`,
     `/Library/Extensions*`, `/etc*`, `/var/db*`, `/private`, and
     `/private/etc*`.
   - Finally, calls `should_protect_path` (Layer 2) for fine-grained
     bundle / app / data protection.

The allow-then-deny ordering matters: rebuildable system caches we
*want* to clean live under paths we'd otherwise block. Listing them
first means a maintainer adding a new safe path doesn't have to surgically
weaken the deny rules.

## Layer 2: `# SAFE: <reason>` contract for raw `rm`

The validator is opt-in: a contributor could bypass it by writing `rm -rf`
directly. To make that bypass loud and reviewable,
`scripts/audit_destructive_sinks.py`, run from `scripts/check.sh` and from the
CI security job in `.github/workflows/test.yml`, requires an explicit
annotation on every raw recursive deletion:

```bash
rm -rf "$temp_file" # SAFE: created by mktemp in this function, never user input
```

The audit rejects any recursive forced `rm` (`rm -rf` and its spelled-out
equivalents) or `find ... -delete` that is not either:
- A pure documentation line (comment-only or echoed help text), or
- Annotated with `# SAFE: <one-sentence reason>` on the same line.

The validated wrappers are not exempt: the `rm -rf` inside `safe_remove` and
`safe_sudo_remove` carries the annotation too.

Every annotated bypass in the codebase currently has a reason that
constrains the input: confined to `$temp_file` from `mktemp`, confined
to a stub container we just created, confined to `tests/tmp-*` from a
test runner. The annotation forces the author to articulate the constraint
before the code can land.

For current uses, grep `# SAFE:`. Anchored by symbol rather than line number,
because line refs rot: `_remove_verified_container_stub` in `lib/clean/apps.sh`,
`cleanup_temp_files` and the Mole-temp-root sweeps in `lib/core/base.sh`, and
the orphan-tmp cleanup in `scripts/test.sh`.

`_remove_verified_container_stub` is the one bypass worth understanding before
you "fix" it: it *must* use raw `rm`, because `should_protect_path` blankets
`~/Library/Containers`, so routing it through `safe_remove` would have the
validator refuse both stub paths and silently kill the feature. That reasoning
is pinned by a test in `tests/clean_apps.bats`.

## Layer 3: App protection, split fast vs. detailed lists

Uninstall and per-app cleanup decisions go through
`should_protect_from_uninstall` and `should_protect_data` in
`lib/core/app_protection.sh`. They consult the data lists kept in
`lib/core/app_protection_data.sh` plus a wildcard fast path written inline in
`should_protect_data`:

| List | Used by | Shape | Purpose |
|---|---|---|---|
| Inline `case` patterns in `should_protect_data` | Cleanup paths (`should_protect_data`) | Wildcard patterns | Fast `com.apple.*` and family-pattern guards. Misses are acceptable here; cleanup of an unknown system component just means leftover files, not deletion of a live app. |
| `SYSTEM_CRITICAL_BUNDLES` | Uninstall (`should_protect_from_uninstall`) and path protection (`should_protect_path`) | Explicit bundle IDs | Detailed list of every `/System/Applications` and Apple system service. Must be exhaustive: a miss here would let a user uninstall Finder. |
| `APPLE_UNINSTALLABLE_APPS` | Uninstall | Explicit bundle IDs | Allow-list of Apple-developed apps the user actually installed (Xcode, FCP, Logic, etc.). Required because `com.apple.*` cannot be a blanket block. |
| `DATA_PROTECTED_BUNDLES` | Cleanup (`should_protect_data`, `should_protect_path`) | Wildcard patterns | Third-party apps with sensitive state (1Password, JetBrains, IM tools, VPNs, etc.) whose caches must not be touched. |

The deliberate redundancy between the inline patterns and CRITICAL is **not** a bug:
- The inline patterns are a wildcard fast-path used in tight loops during
  cleanup, where a `com.apple.*` blanket is correct.
- CRITICAL is the detailed allow-list used at uninstall time, where the
  blanket is wrong (it would block Xcode uninstall) so individual bundles
  must be enumerated.

### Keeping the lists honest

A new macOS major release can ship new system apps and daemons. The
monthly `.github/workflows/bundle_audit.yml` job runs
`scripts/audit_bundle_drift.sh` against the latest `macos-latest`
runner. The script enumerates every `.app` under `/System/Applications`,
computes its `CFBundleIdentifier`, and reports any bundle ID not matched
by CRITICAL + DATA_PROTECTED. Any miss opens a tracking issue.

Each macOS major release should also get a human pass over: bundle
drift, mdls timeout regression, SIP path changes, and CI matrix updates.

## Layer 4: Trash routing default

`mo analyze` and `mo uninstall` (default since #723) route deletions to the
macOS Trash. `mo analyze` (`moveToTrash` in `cmd/analyze/delete.go`) tries
Apple's `/usr/bin/trash` first, then an atomic no-overwrite rename into the
per-volume Trash, and uses Finder AppleScript only as the last fallback.
This gives users the standard Apple-native "Put Back" recovery flow.
`mo clean`'s batched cleanup path, `mo purge`, and `mo installer` delete
permanently; `mo uninstall --permanent` opts out of Trash, and
`mo analyze` always uses Trash.

The `trash` and `osascript` calls use a 30-second timeout (`trashTimeout`)
so a hung Finder can't wedge the binary, and the Finder path escapes both
`\\` and `"` in the path before substituting into the AppleScript
literal. Defense in depth: `validateTrashTarget` (which wraps
`validatePath`) runs on the raw path and again on the resolved absolute path
before any of the three routes, so even if escape logic missed a case, a
path containing `..` or null bytes is rejected before it reaches Finder.

## Layer 5: Test mode + dry run + property tests

Three orthogonal mechanisms make the safety claims testable and
prevent live-machine test runs from doing real damage:

- `MOLE_DRY_RUN=1`: every safe-remove logs what it would do and
  returns 0 without touching the filesystem. Used in CI for the
  no-mock path coverage and recommended before any local cleanup.
- `MOLE_TEST_NO_AUTH=1`: refuses to call `sudo`, `osascript`,
  `launchctl`, or any path that would prompt the user. Required for
  bats and the integration tests. Enforced by `scripts/test.sh` PATH
  stubs that fail loudly when called.
- `tests/path_validation_fuzz.bats` and `cmd/analyze/delete_fuzz_test.go`
  harden the validators. The bats test asserts that every line in
  `tests/fuzz_corpus/dangerous_paths.txt` (96 adversarial paths today)
  is rejected. The Go fuzz target runs its seed corpus during normal
  `go test`; maintainers can run `go test -fuzz=FuzzValidatePath ./cmd/analyze`
  when changing path validation. It asserts the invariant:
  anything accepted must be absolute, free of null bytes, and free of
  `..` components.

If you add a new way to bypass these layers, you are expected to add
a corresponding test that fails before your code lands.

## What this design intentionally does not do

- **No code signing of the cleanup config.** We rely on filesystem
  permissions to protect the protection lists from tampering. If a
  user can edit `lib/core/app_protection_data.sh` they can already
  edit `mole` itself; the threat model says we don't defend that.
- **No anti-rollback.** A user who restores an old mole binary or
  installs a forked build with weaker lists gets weaker protection.
  We address this through release signing, not runtime checks.
- **No protection for arbitrary user paths.** `~/Documents/important`
  has no special status. The user is responsible for selecting safe
  cleanup targets; mole only guarantees system integrity.
- **No telemetry.** We never report what was scanned, deleted, or
  attempted. Mistakes are diagnosed locally via the operation log
  `~/Library/Logs/mole/operations.log` (`MO_NO_OPLOG=1` disables it) and
  the deletions log `~/Library/Logs/mole/deletions.log` that `mole_delete`
  appends to.

## When to update this document

- A new layer (e.g., a notarization check, a per-volume policy) is added.
- The validator gains a new check class or relaxes an existing one.
- A new app protection list is introduced.

Last reviewed: 2026-10-08 (mole V1.59.0).

---

### Weekly Audit (`docs/weekly-audit.md`)

# Weekly Audit Log

One entry per run of `.claude/skills/weekly-audit`, newest first, covering Mole CLI (macOS) on `main` and Mole CLI (Windows) on `windows`. The next run reads the newest entry for its commit window (`Next window from`), for the items it must not report again (`Decided, do not re-report`), and rechecks `Open` first. Facts here are a run record; lasting invariants live in `AGENTS.md` on each branch.

## 2026-10-18 (dual CLI post-release audit)

- Frozen source: main `3041d86c`, windows `7b2da730`, both clean and matching their remote heads. Release comparison: `V1.61.0` and `V1.36.0-windows`. Both desktop apps were outside this review.
- Source repairs pushed through: main `8b49a13a`, windows `9b010223`; the initial delivery heads were `7ef8e8f0` and `eccdbb8c`.
- Next window from: main `8b49a13a`, windows `9b010223`.

### Fixed and verified

| Branch | Finding | Commit | Regression surface |
| --- | --- | --- | --- |
| windows | Canonical whitelist aliases misaligned parallel rule metadata, missing later rules or indexing past the list | `d44b1e70` | `tests/CleanupBoundary.Tests.ps1`, mixed rules through `Remove-SafeItem` |
| main | Completion normalization and self-removal could erase user commands that merely mention completion | `332d879f` | `tests/completion.bats`, `tests/uninstall.bats`, byte-preserved quoted and mixed commands |
| main | Failed owner-root discovery could still authorize cache mutation; Conda omitted configured package roots | `e9fbcf8e` | `tests/clean_dev_caches.bats`, uv/npm/pip/mise discovery failures and every Conda `pkgs_dirs` entry |
| main | Delayed same-path scans and cached results could replace newer Analyze requests | `8f2671cd` | `cmd/analyze/analyze_test.go`, pre-dispatch cancellation, fresh/bypass scans and cached results |
| both | E2E `--keep-going` bypassed failed prerequisites | `8b49a13a`, `9b010223` | `tests/e2e_harness_test.py` on each branch, integrity rejection and dependent-action assertions |
| windows | E2E combined committed PowerShell with Go built from the dirty checkout | `9b010223` | Frozen-commit archive and build-cwd provenance assertions |
| windows | The canonical-path comment incorrectly attributed existing 8.3/trailing-dot normalization to the handle resolver | `d44b1e70` | Corrected to distinguish `GetFullPath` normalization from junction/symlink resolution |

Sibling review covered ten owner-cache consumer families, changing five; Corepack, pnpm, Nix, GitHub CLI and Go had no matching failed-query/default-root authorization path. Completion uses the shared pattern at five rewrite sites. The Windows whitelist review checked five association sites and changed only the constructor. Analyze review covered seven dispatch entrypoints and all three scan APIs, including ordinary, stale and warmed cache paths; the Windows request lifecycle was already correct.

Local verification: `check.sh --format`, `check.sh --no-format`, and the complete `TERM=xterm-256color MOLE_TEST_NO_AUTH=1 ./scripts/test.sh` all exited 0; the runner reported `All tests passed!`, including Go and isolated installation checks. Targeted Analyze race tests passed. Mocked E2E suites passed 5 macOS and 9 Windows tests; workflow syntax and the Windows cross-build passed. Red runs exercised the old implementations for the repaired ownership, scan identity, whitelist and E2E failures. Windows native receipts are in `docs/development-record.md` on `windows`: both PowerShell hosts passed 37 CleanupBoundary tests, and each full suite reported 514 passed, 2 failed, 0 skipped. The two failures require symbolic-link privilege and reproduced on the original HEAD before product assertions; they are not recorded as passing.

### Codified and simplified

- Existing area rules now lock complete owner-root evidence, every Conda root, pre-dispatch scan identity, cached-result identity, recent-directory mtime invalidation, and E2E prerequisite/provenance checks. Windows `AGENTS.md` and `docs/development-pitfalls.md` retain canonical rule associations and empty/single/multiple-result boundaries.
- `install-update.md` merges two overlapping shell-integration bullets into one; zero effective constraints were deleted. Independent semantic review preserved rc paths, full symlink chains, target mode, Fish files, dry-run, all log categories, failure exits and existing test locks. The intentional ownership correction is matching whole generated lines rather than mentions. The file changed from 10,340 to 10,353 bytes and 1,413 to 1,399 words; new regression anchors account for the byte increase. Frontmatter is unchanged. AGENTS.md plus area rules total 108,264 bytes, below the 20% trigger from the 99,723-byte split baseline.
- The existing Windows CLI-SYNC-04 entry owns the Analyze backport; CLI-SYNC-02 records the whitelist follow-up. Source implementation is separate from release delivery.

### Decided, do not re-report

- Keep existing features, flags and safety checks; this review found no evidence that restoring previously removed dead helpers would improve behavior. No new product flag or dependency was added.
- Keep the current explicit media-selection boundary; CLI-SYNC-09 remains the owner of the broader recording-policy decision. The prior DeviceSupport menu, memory-pressure helper and dry-run logging decisions remain unchanged.
- Canonical-path probing stays despite its measured cost because junction and symlink protection consumes it. Consolidating a duplicated rule does not authorize removing its constraints.

### Open

- The source-push blocker is closed. After two SSH closures, the maintainer authorized resumption on 2026-10-18; both branches pushed successfully and remote SHAs matched the initial delivery heads above. CI is a separate gate checked at the final pushed heads, including the two privilege-limited Windows fixtures. No tag, package, release, real application E2E or user-data cleanup was performed.

## 2026-10-17 (review remediation, not a weekly run)

- Audited read-only: main `d7980364`, windows `78e537bb`. Landed through: main `7f09d2a4`, windows `4fa94339`.
- Next window from: main `7f09d2a4`, windows `4fa94339`.
- Fixed: executable-name probes now escape ERE metacharacters before every `pgrep`/`pkill` (`7c2713c7`, `tests/uninstall_process_pattern.bats`); broken-preference repair lints through `/usr/bin/plutil` and retains unreadable plists instead of permanently deleting them (`f7916075`); `mo clean --external` keeps a volume Trash and the Finder-metadata row now covers AppleDouble `._*` (`4285c82f`); wrong UTM bundle id corrected (`87a50062`); `MOLE_TEST_TRASH_DIR` requires a test flag (`e51c32ae`); installer reports a conflicting Homebrew Mole instead of uninstalling it and caches release checksums/attestation per tag (`49a138cd`); `mo remove` clears shell completion, names failed paths and exits nonzero, and by-name uninstall asks once, and the batch preview/summary names are terminal-safe (`8bba89b8`, `81aa95f1`); `mo status` reads one diskutil plist per volume and CloudStorage roots are prechecked (`8ee1d254`); READMEs and SECURITY_DESIGN now state the real Trash routing (`db412b36`, `697be923`); CI runs the fd installer suite, serializes the three flaky suites, tightens child-shell perf budgets, and macOS/Windows release gates preflight the tag and required runs (`47636abb`, `f770a926`, `f7d5368f`); Windows protected paths resolve existing 8.3/trailing-dot aliases, scans are bounded, and `mo remove` drops the cache (`901ce2d4`); Windows README/SYNC/TRADEMARK corrected (`8adda1f2`).
- Post-review fixes (same day, after `ed594a28`): non-interactive `mo uninstall` now refuses a missing terminal instead of confirming, the release preflight allows docs-only commits to lack CodeQL/Nix and every required workflow accepts `workflow_dispatch`, the release-tag ruleset has a repository-admin bypass for recovery, Windows keeps whitelisted junction roots protected and logs cache removal, the completion regex needs a command boundary, multi-level rc symlink chains survive `mo remove`, rc/fish/cache removals write operations.log rows, and the `get_file_size` budget is 400ms local / 600ms CI. Windows `update.ps1` no longer says only source installs can update and then suggest a ZIP. Tests: `tests/uninstall_review.bats`, `tests/uninstall.bats`, `tests/core_performance.bats`, Windows Pester junction/cache cases; CI green on `5f7d50b5` and `d3976cc2` (a later `TestDrive` teardown artifact in the first Windows run was fixed in `d3976cc2`).
- Third-pass fixes after the next review: non-interactive refusal now exits 1 with a next step, only `MOLE_TEST_MODE` may pipe the confirmation, and the docs-only release exemption requires green CodeQL/Nix on the nearest code-changing ancestor; `mo completion` preserves rc symlink chains; launcher removals write operations.log; `get_file_size` is compared with its stat reference so one added fork is caught; Windows `Test-SafePath` reuses the resolved path and adds a junction-to-protected test plus an incomplete-scan caller test. Landed through main `aac88e4a`, windows `7960f39c`.
- Fourth-pass fixes: `mo completion` and `mo remove` share one completion-line pattern and one rc link resolver in `lib/core/base.sh`, so the "already installed" check no longer takes `gizmo completion` for Mole and an unresolvable rc link names the file (`fb0d658d`, `tests/completion.bats`); the docs-only release preflight walks past `[skip ci]` contributors commits to the real code ancestor (`7f09d2a4`, stub-`gh` checked for inherited green, ancestor Nix failure, failed run listing and code targets); Windows `Test-SafePath` judges the typed spelling against the protected rules too and checks an unlinked path once (`4fa94339`). Windows 11 VM, PowerShell 5.1 and pwsh 7: 496 Pester cases pass outside `UI.Tests.ps1` with the 2 known non-admin symlink failures, the new linked-segment case fails on `7960f39c`, the 8.3 and trailing-dot cases also pass on `78e537bb` (so `GetFullPath` already covered them; AGENTS.md corrected), and 1500 `Test-SafePath` calls take 1.54s/1.01s against 2.06s/1.27s before this pass and 0.51s/0.76s on `78e537bb`.
- Deferred: `docs/img/big-mole.png` is a collage of real cleanup results whose summary wording predates the current `Cleanup complete` output, so refreshing it needs new real screenshots rather than a generated image; Homebrew `info --cask` prefetch stays because the #1558 timeout/cancellation contract requires the batch probe; `install.ps1 -Uninstall` now removes `~\.cache\mole` like `mo remove` (windows `bcf0b7a4`); rulesets protect branch deletion/force-push and `V*` tag update/deletion, and required status checks stay off because the maintainer and the contributors bot push to `main` directly and the bot's `[skip ci]` commits would never satisfy them, while the release preflight already requires the checks on the tagged commit; `Test-SafePath` stays about 2x slower than `78e537bb` because every existing path now gets one final-path handle probe, which the link and alias protection needs.
- Rule volume after this batch: AGENTS.md 31,054 bytes, AGENTS.md plus `.claude/rules` 103,829 bytes (+4,106 over the 2026-10-17 split baseline, below the 20% trigger).

## 2026-10-17 (rule split, not a weekly run)

- Next window from: unchanged, main `e78caf97` and windows `9bbebb89` from the 2026-10-16 entry below.
- `cb32af9f` moved area sections of AGENTS.md (89.7 KB to 30.5 KB) into 12 path rules. A second-agent review found no lost sentence but six loading gaps, fixed in `d531a0c4`: `github.md` was read only at reply time, after a fix already existed, and three rules sat under globs that miss their code.
- Rule volume after the split, the baseline for the 20% trigger in `.claude/skills/weekly-audit/references/rule-compression.md` (bytes): AGENTS.md 30,745, `clean.md` 15,486, `uninstall.md` 10,140, `install-update.md` 7,979; AGENTS.md plus `.claude/rules` 99,723.

## 2026-10-16 (first run)

- Audited: main `eb531ba2`, windows `159657d5`.
- Landed through: main `e78caf97`, windows `9bbebb89` (PR #1751).
- Next window from: main `e78caf97`, windows `9bbebb89`.

### Fixed and verified

| Branch | Finding | Commit | Lock |
| --- | --- | --- | --- |
| main | `mo clean` ran `nix-collect-garbage --delete-older-than`, deleting profile and home-manager generations, with no timeout or owner-planned preview | `c0ea3d48` | `clean_dev_nix collects only unreachable store paths under a timeout` |
| main | Uninstall offered the whole `~/Library/Application Support/Steam` game library as one data row | `6c777967` | `uninstall never offers the Steam library root or anything inside it` |
| windows | `mo remove` recursed through a junction in the config folder into its target | `53b94f11` | `Unlinks a junction in the config folder and keeps its target's files` (CI only, see Open) |
| main | npm cache clean had no owner-command timeout | `22bb8a43` | `clean_dev_npm runs npm cache clean under the owner-command timeout` |
| main | macOS and xrOS DeviceSupport never pruned | `17c74457` | `clean covers every DeviceSupport platform the Xcode uninstall sweep knows` |
| main | Whitelisted Xcode cache entries counted as skipped protected | `ba08ab8f` | `... keeps whitelisted entries out of the skipped count` in `tests/dev_extended.bats` |
| main | `mo uninstall <name>` lowercased per app, `--list` ran `brew info` per cask, report and bundle scans forked per file | `3cc7aa86`, `107d4a83`, `07d78dea` | existing uninstall suites; 9.44 s to 0.059 s and 7.15 s to 2.68 s measured |
| windows | Validated trees walked twice to size | `7b92e1d9` | Windows CI |
| both | Thirteen undocumented tuning env vars on main, `MOLE_ANALYZE_LIVE_SORT` on windows | `654fcef4`, `165d79fd`, `ba791f85` | `removed tuning environment variables are not read again` |
| both | Dead helpers (4 on main, 32 on windows) and two Pester tests that could not fail | `885cb6e0`, `180b62f6`, `754fcb43`, `5904e4d5` | existing suites |
| both | Uncodified oplog contract, recognizer parity, dataless probing, launcher signal stories, nightly-verified issues; stale SYNC release lines, comments and unpinned actions | `5a210db4`, `b9223ebf`, `e78caf97`, `945adbea`, `003157c4` | AGENTS.md, SYNC.md |

### Decided, do not re-report

- macOS and xrOS DeviceSupport get no whitelist rows: only iOS has one, and watchOS, tvOS and visionOS have none either; adding all five clutters the menu.
- `opt_diag_memory_pressure` stays: it prints the memory pressure warning and top processes, so it is not a no-op (an agent claimed otherwise).
- Mole CLI (Windows) writes no operation-log rows in dry runs; recorded as an intentional difference in SYNC.md.
- Existing flags are never proposed for removal without the maintainer.

### Closed after the run (same day)

- windows: `Clear-TempFiles` had three callers but nothing ever registered a temp file, so it only emptied empty lists; removed with its exit event and the two finally blocks in `c6ce917e` (PR #1753).
- windows: the junction test in `53b94f11` is now proven. Restoring the pre-fix `install.ps1` only failed the file at discovery, because the test extracts the new `Remove-MoleConfigEntry`; disabling just its reparse-point branch failed `Unlinks a junction in the config folder and keeps its target's files` on both `powershell` and `pwsh` (draft PR #1754, closed).
- main: GNU coreutils or findutils first in `PATH` aborted `mo clean` (#1752) and silently misread mtimes elsewhere; every `stat` now goes through `$STAT_BSD`, BSD `date -r/-j` through `$_DATE_CMD`, purge's dataless probe and walks through `/usr/bin/find`, and Service Worker discovery uses `-mindepth/-maxdepth` (`c0705ac0`, `66bd98a3`). A source gate refuses new bare forms.
- main: `firmware guard detects a real open file and permits it after close` failed about half the time under the parallel suite because real lsof outlived the 2 s probe budget; the case now runs with 30 s (`420cb224`).
- main: a full run with Homebrew `coreutils findutils gnu-sed grep gawk` first in `PATH` (every command dry-run against the BSD baseline, then `scripts/test.sh` and `go test ./...`) found two continued-line `stat` calls, GNU find error quoting and a File Provider mount failure, byte-wise GNU `tr` case folding, `\-` regex escapes and GNU `du` lacking `-I`; all routed to the system binaries and both suites pass under that `PATH` (`d7e9f6fc`). The same run exposed that an unreadable AI agent versions folder cancelled every later developer cleanup (`5422a73d`).

### Open

- None.

---

--- METRICS ---
- Files Extracted: 5
- Estimated Token Budget: ~14446 tokens
- Canonical Reference: https://codewiki.google/github.com/tw93/Mole
