---
name: filename_portability_preflight
description: Check a file and folder set before it moves between systems, and fix what would break: Windows reserved names and characters, trailing dots and spaces, case-only collisions that break clones on case-insensitive filesystems, Unicode normalization collisions between Mac and Windows, non-NFC names, overlong components, and projected path length under the real destination folder. Ships a tested scanner and a safe rename planner that finds references to renamed files and can rewrite them. Use before a cross-platform move, a repo handoff, a zip or archive delivery, a cloud sync migration, or whenever a clone, copy, or extraction produced missing, renamed, or overwritten files.
---

# Filename Portability Preflight

## Purpose

Names that are legal where a set was created can fail, change form, or overwrite each other where the set lands. Linux accepts a colon in a name, Windows forbids it. Git tracks ReadMe.md and README.md as two files, a default Mac or Windows disk keeps one. A name typed on a Mac can come back from disk with different bytes than an identical looking name typed on Windows. This skill catches all of that before the move, from the path list itself, then produces a rename plan that keeps the set working and keeps references pointing at the renamed files.

Existing tools cover pieces of this. A filename sanitizer cleans one name at a time and knows nothing about the rest of the set, so it misses collisions and path length. A batch renamer renames in place inside one folder and does not project the destination. This skill is the preflight for the whole set against a named destination.

## Workflow

1. Fix the destination before scanning. Get the real folder the set will live in on the target machine, in Windows form if the target is Windows (for example `C:\Users\someone\projects\client-archive-2026`), and pick the path budget. Default budget is 260. Use a tighter budget when the receiving side is known to be stricter, and a cloud sync budget when the destination is a synced folder. Rules and sources for every check are in `references/check-catalogue.md`.
2. Run the scanner. Working tree: `python3 scripts/portability_scan.py <root> --dest-prefix '<prefix>' --plan plan.json`. For a repo, prefer the index over the working tree so a clean checkout on this machine cannot hide a collision: add `--git` to scan `git ls-files`. Add `--format json` for machine readable findings. Exit code 0 means clean, 1 means error findings, 2 means the scan itself failed.
3. Read the findings by kind, not by file. Reserved names, reserved characters, and trailing dots or spaces will fail or silently change on Windows. Case and normalization collisions mean two paths become one file on the target. Path findings mean the set fits here and fails there. A not-nfc warning means the byte form of the name can change during the move even when nothing collides today.
4. Review the plan before anyone applies it. The planner sanitizes each bad component, normalizes proposed names to NFC, shortens overlong components with a hash suffix, shortens the longest components when the projected path is over budget, and adds numeric suffixes so no proposal collides with a kept name or another proposal. Every member of a pure collision group gets renamed, so the plan never depends on rename order. The planner never deletes and never merges. Two files that collide may hold different content, and deciding which content survives is a human call the plan must surface, never make.
5. Check the references in the plan. For each rename the plan lists text files that mention the old path or file name, with occurrence counts. Read those hits. A hit can be a link or import that must follow the rename, or a historical mention, a changelog entry for example, that should keep the old name. Decide per hit before applying.
6. Apply only with explicit go ahead from the person who owns the set. `python3 scripts/portability_scan.py <root> --apply plan.json --update-refs` renames deepest first, orders renames so a target freed by another rename moves first, stages case-only and normalization-only renames through a temp name, breaks true name swap cycles through a temp name, and stops on the first problem instead of pushing through. It refuses to overwrite any existing path. With `--update-refs` it also rewrites exact old paths and file names in text files. Without that flag it renames only, and step 5's hits get edited by hand.
7. Rescan the renamed set with the same destination prefix and budget. The job is done only when the rescan reports 0 errors. Report warnings that remain, with the reason each one was accepted.
8. For git sets, commit the renames as their own commit with the plan file kept alongside the work notes. Use `git mv` semantics by hand if the set was scanned from the working tree but the repo history matters, and check `git status` shows renames, not delete plus add pairs, where content is unchanged.

## Worked example

A project folder created on a Mac had to move to a Windows machine, into `C:\Users\someone\projects\client-archive-2026`, under a 200 character project budget set for that move. The folder held 11 paths, including CON.txt, report:final.md, a file named notes. with a trailing period, ReadMe.md next to README.md, café-menu.md in NFC next to the same looking name in NFD, and one deeply nested report whose projected Windows path came to 235 characters.

The scan reported 6 errors and 1 warning and wrote an 8 entry plan:

- CON.txt to CON-file.txt (reserved name)
- report:final.md to report-final.md (reserved character)
- notes. to notes (trailing period)
- ReadMe.md to ReadMe-3.md and README.md to README-2.md (case collision)
- The NFD café-menu.md to café-menu.md and the NFC one to café-menu-2.md (normalization collision)
- The nested report shortened to a-very-long-re-b41ddc.md (projected path over budget), hash suffix kept it distinct

The plan listed index.md as referencing three of the old names. Applying the plan with reference updates rewrote those links to ReadMe-3.md, CON-file.txt, and report-final.md. The rescan reported 0 errors and 0 warnings. Scanner, planner, apply, and reference rewrite were all exercised end to end on this fixture during the skill's own testing, plus a git index scan and a standalone case-only rename.

## Output Contract

Deliver these, in this order:

- The scan summary: paths scanned, destination prefix and budget used, error and warning counts.
- Findings grouped by kind, each with the paths involved and, for collisions, every member of the group.
- The rename plan as a file, with old path, new path, reasons, and reference hits per entry. State plainly that nothing has been renamed yet.
- After an approved apply: the applied renames, the files whose references were rewritten, and the rescan result. A leftover warning gets a one line reason.

## Operating Rules

- Scan before the move, never after a failed copy. A copy that already lost or merged files cannot be audited back into certainty from the destination side.
- Always project path length against the real destination folder. Scanning against the drive root or the current folder understates every path and misses the failures that actually happen.
- Treat a clean scan on the source machine as a floor. Superscript reserved name variants and target specific rules can still bite. Say so when the target is unusual, such as a NAS, a FAT or exFAT card, or a sync service with its own limits.
- Never fix a collision by deleting or merging on your own judgement. Rename both, surface the pair, and let the owner decide if one is a stray.
- Never apply a plan the owner has not seen. Scanning and planning are read only and safe to run freely. Applying changes names other tools and people depend on.
- Keep the plan file. It is the undo map and the audit trail: reversing an apply means renaming each new path back to its old path in reverse order, and the plan is the only record that pairs them.
- Count component length in bytes as well as characters when Linux is in the path of the move. A name with accented or CJK characters can pass a character count and fail the byte limit.
- When a name must stay exactly as is for an external reason, a published URL or a legal record name, flag the conflict instead of renaming around it, and let the owner choose between moving that file out of the set or accepting the breakage.
