Files
dotfiles/common
connor fb62fdfc8e todo_rollover: atomic write, honest output, real argument handling
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.
2026-09-22 11:01:26 -04:00
..
2026-09-14 15:18:50 -04:00