Hardening pass over the script. Every fix below was reproduced before being
made; two findings that looked serious were tested and dropped.
THE ONE THAT MATTERED. The write was not atomic and could destroy a day's
todos. It did:
cp -p "$src" "$dst" # yesterday's UNPRUNED note lands at the target
cat "$tmp" > "$dst" # then gets overwritten with the real content
Interrupted between those, today's note silently becomes yesterday's,
unpruned. Interrupted during the redirect, it is truncated. Content is now
built in a temp inside the notes directory and renamed over the target, so
the target is either the old file or the new one and never a half-written
mixture.
⚠ `cp -p` was NOT redundant, and a review pass that recommended removing it
was wrong. A bare `cat > "$dst"` preserves the mode only when the target
already exists; on the normal path the target is new and takes 0644 from the
umask. Measured: source 600 -> new file via redirect 644, pre-existing file
600. The mode is now set explicitly with `chmod --reference` before the
rename, which is correct in both cases.
OUTPUT THAT LIED. The success line always said "(pruned)", including when
the prune script was missing and the plain-copy fallback ran. That is the
same defect as the error message that advertised a --force flag before one
existed: a user-facing string asserting work the code did not do. The
message now reports which path was taken, and a prune script that FAILS is
reported instead of aborting on set -e with no explanation.
ARGUMENT HANDLING. Anything that was not --force became a date, so
`todo_rollover --dry-run` failed with "source note .../--dry-run.md not
found" -- a missing-file error for what was really a bad flag. Unknown
options are now rejected, `--` and `-h/--help` work, arity is checked, and
dates are validated for shape AND realness (2026-13-45 is caught up front).
ALSO FIXED
- `[[ -x $PRUNE ]]` was the wrong test: PRUNE is run as `python3 "$PRUNE"`
and needs no execute bit, so a working prune script was silently skipped.
Now -f.
- src == dst aborted on a raw `cp: ... are the same file`. Checked explicitly
up front with a clear message. (It did fail safe -- a review pass claiming
the source was destroyed was tested and is not reproducible.)
- The heading regex rejected "# 2026-09-21 " with a trailing space and
prepended a duplicate heading. Now tolerates surrounding whitespace.
- `${tmp}.new` escaped the EXIT trap and leaked on interrupt.
- Dead `mkdir -p "$NOTES"` after the source check had already proven the
directory exists, and a `mode=` variable that was assigned and never used.
Verified: unknown flag/bad date/missing source exit 1, existing target exits
2, --help exits 0, mode 600 survives the rollover, a non-executable prune
script is used, a failing one leaves the target untouched, the trailing-space
heading is retitled rather than duplicated, and no temp files are left behind.