d4fa373017
The symlink refusal lived in `_real_dir`, which runs per entry inside the copy loop. By the time it fired for `skills/`, `SOUL.md`/`mcp.json`/`cron` had already been replaced and `write_manifest` had not run yet, leaving a half-updated profile that fails identically on every retry. Add a pre-flight pass over `_owned_entries` that walks each target path (whole chain for directories, parent chain for files) and raises before any mutation; `_real_dir` keeps its check as the backstop. The `.env.template` branch used a bare `shutil.copy2`, which writes through an existing `.env.EXAMPLE` symlink into whatever it points at. Route it through `_replace_entry` so the link is unlinked first, like every other owned entry. The error text now tells the user what they can actually do (remove the link or replace it with a real directory) instead of pointing at `distribution_owned`, which is the author's manifest, not the user's. The existing symlink test now changes every other shipped entry upstream and asserts the profile is byte-for-byte untouched after the refusal.