* edit: a config that is a hard link stays one when magpie saves or clears it — WriteAtomic renamed a temp file over the path even when the file had other names, so a models.yml that Orca keeps hard-linked into its overlay split in two: omp's name got the new text, the overlay kept the old one, and each later write drifted further; clearing an instructions file unlinked only magpie's name and left the old text under the others. Now a regular file with more than one link (st_nlink on unix, NumberOfLinks from GetFileInformationByHandle on Windows, one elsewhere) is written in place: the new text first goes to a temp file beside it and is synced, then is written over the file, cut to length and synced, and the temp file goes; a crash part way leaves the whole new text beside it, and a failed write keeps it and names it in the error. Mode, owner and every name stay. Remove empties such a file instead of unlinking one name, as it already did for a symlink, and a failed edit rolled back by Atomically goes back in place too. A file with one link is still replaced by rename, atomically, as before.
* edit: a read-only hard-linked config is still written, and temp files a crashed write left beside a config go at its next write
Review follow-up on #328:
- A hard-linked file that can't be opened for writing (0444 on unix,
read-only on Windows) fell over with "permission denied" where main's
rename replaced it. WriteAtomic now opens it first and, on
fs.ErrPermission, falls back to temp file + rename as before, which
splits that name from the others as main always did. On Windows main
couldn't rename over a read-only file either, so nothing changes there.
- writeInPlace says why it writes over and then cuts to length: a reader
can briefly see the new text with the old one's tail, but emptying first
would show an empty or half file instead, which a tool may load as a
config with nothing set; a mangled one fails to parse.
- writeInPlace names the trade with hard-link backups (rsnapshot, cp -al,
rsync --link-dest): they change with the file, as with vim's
backupcopy=auto.
- Remove says that emptying every name is meant: with AGENTS.md and
CLAUDE.md one file, clearing one empties both, as #323 does for a
symlink's target, since deleting one name would leave the text magpie is
taking back under the other.
- After a write goes through, both paths remove .<name>.<digits>.tmp
beside the file older than ten minutes: the file then holds newer text
than any of them, a fresh one may be another write's still under way,
and the digits keep other files' temps (.models.yml.bak.1.tmp) out.