Reference
Finding kinds
| kind | zone | when |
|---|---|---|
ignored | VE | gitignored, not regenerable (or regenerable-looking but listed under permanent) |
untracked | VE | untracked, not regenerable |
no-git | VE | outside every git work tree (dir/, or * for loose files) |
symlink-escape | VE | a link inside the map whose target is outside it and writable by you; bytes are the target's |
no-remote | VE | repo with commits and no remote: .git is the only copy |
regenerable | AE | known build output by name, or by marker: pyvenv.cfg, CACHEDIR.TAG, ELF/Mach-O/PE headers |
dirty | AE | tracked file with uncommitted changes |
unpushed | AE | commits on local branches not on any remote |
snapshot | AE | would be VE, but a snapshot probe covers it |
backed-up | AE | would be VE, but .undomap.json says it's backed up |
cloud | AE | would be VE, but it's in a sync folder whose version history undomap could verify (see cloud sync) |
tracked | X | clean tracked files, one aggregate per repo |
Cloud sync
A sync client copies a delete to the cloud within seconds, so the real undo is the provider's version history and deleted-files bin. undomap only counts it when it can show the history covers the path. The probe is read-only and can't fail the map.
| Provider | Detected from | History | Known gaps (stay Zone VE) |
|---|---|---|---|
| Dropbox | ~/.dropbox/info.json (or %APPDATA%/%LOCALAPPDATA%\Dropbox\info.json) paths; weaker: a .dropbox marker above the map, ~/Dropbox* | versions and deleted files, 30+ days by plan | com.dropbox.ignored / com.apple.fileprovider.ignore#P on the path or a folder above it; rules.dropboxignore; desktop.ini, .DS_Store, ~$…, .~…, ~…tmp |
| OneDrive | %OneDrive%, %OneDriveConsumer%, %OneDriveCommercial%, ~/Library/CloudStorage/OneDrive-*; weaker: ~/OneDrive* | version history and recycle bin, 30 days (93 for work) | files over 250 GB; .tmp, desktop.ini, .DS_Store, ~$…, .lock, CON/NUL/…, _vti_, " * : < > ? | |
| Google Drive | ~/Library/CloudStorage/GoogleDrive-*, G:\My Drive-style virtual drives; weaker: legacy ~/Google Drive | file versions for 30 days or 100 versions, trash for 30 days | files over 5 TB; .tmp, desktop.ini, .DS_Store, ~$… |
| iCloud Drive | ~/Library/Mobile Documents (macOS), ~/iCloudDrive (Windows) | Recently Deleted keeps deleted files 30 days; no older versions of overwritten files | always: an overwrite is permanent. Also *.nosync, *.tmp, files over 50 GB |
The demotion rule. A path moves to Zone AE (kind: "cloud") only when all of these hold:
- the client's own config (Dropbox
info.json, the OneDrive environment variables, the~/Library/CloudStoragefolder the client creates) points at a folder containing it; - the client is running right now (
ps/tasklist), so the folder isn't a stale copy; - no known gap applies to the path or any folder above it, and its largest file is under the provider's per-file limit;
- for Dropbox, the ignore attribute could be read and isn't set, and
rules.dropboxignoredoesn't match.
iCloud Drive never demotes: it keeps deletes but not overwrites, and an agent overwriting a file is as final as deleting it. Anything undomap can't confirm (client not running, a folder name or .dropbox marker with no client config, attributes it can't read) stays in Zone VE with possible cloud versioning, unverified in the reason, the probe line, and one cloud sync line under the report with the cheapest way to check. A snapshot or backedUp still wins over all of this.
What it can't see. A paused sync, a full quota, or a subfolder inside a place that's marked ignored (undomap checks the place itself and the folders above it). If the client shows errors, treat cloud Zone AE as Zone VE. iCloud's Desktop & Documents sync isn't detected, which only errs towards Zone VE.
JSON
{
"tool": "undomap", "version": "0.1.0", "root": "/home/me/app", "scannedAt": "…", "durationMs": 41,
"repos": [{ "path": ".", "remote": "origin", "unpushed": 2, "branch": "main" }],
"probes": [{ "name": "git", "status": "ok", "detail": "2.47.3", "counts": true }, …],
"totals": { "VE": { "bytes": 1300000000, "places": 4, "files": 3155 }, "AE": {…}, "X": {…} },
"findings": [{ "zone": "VE", "kind": "ignored", "path": "textures_sorted/", "bytes": 1300000000,
"files": 3112, "capped": false, "why": "gitignored", "secret": false,
"forcedBy": "textures_sorted/", // only when a .undomap.json "permanent" pattern matched
"cloud": { "provider": "Dropbox", "status": "unverified", "note": "Dropbox app not running" },
"fix": { "say": "…", "run": "…", "cost": "minutes" } }, …],
"skipped": [{ "path": "private/", "reason": "EACCES" }],
"exitCode": 1, "thresholdBytes": 0,
"reach": { // only with --reach
"credentials": [{ "path": ".env", "type": "dotenv", "where": "tree" },
{ "path": "/home/me/.aws/credentials", "type": "AWS credentials", "where": "home" },
{ "path": "GITHUB_TOKEN", "type": "token-like name", "where": "env" }],
"credentialsCapped": false,
"network": { "status": "open", "detail": "eth0 has an address (no connection attempted)" },
"writable": [{ "path": "/home/me", "why": "your home folder" }],
"legs": 3, "trifecta": true,
"fix": { "say": "break one leg: …", "run": "env -u GITHUB_TOKEN "$SHELL"", "cost": "minutes" }
}
}
cloud is present for a path inside a sync folder: status is covered (graded AE), unverified, partial (iCloud: deletes only) or gap (the provider skips it); note says why. forcedBy is present when a permanent pattern from .undomap.json matched the path (see precedence); why then says the config forced it. Paths are relative to root, use POSIX separators, and directories end in /. capped: true means sizing stopped at 250,000 entries for that place, so bytes is a lower bound (shown with a +). Remote URLs are never printed, only remote names, because URLs can carry tokens.
reach (only with --reach): where is tree (path relative to root), home (absolute) or env (the variable name: never its value). network.status is open, closed (loopback only, or the Codex sandbox's network-disabled flag) or unknown (loopback plus a proxy, as sandboxes with an allowlist set up). legs counts the three conditions; trifecta is all three.
MCP tools
| tool | args | returns |
|---|---|---|
map_report | path?, reach? (false) | Markdown report + compact JSON; with reach, the credential reach section too |
list_permanent | path?, limit? (25) | JSON: summary, permanent[] with path, bytes, why, forcedBy, cloud, fix, run; more |
Both are annotated readOnlyHint: true, destructiveHint: false.
Next sheet: FAQ