diff options
Diffstat (limited to '.ai/scripts/capture-guard')
| -rwxr-xr-x | .ai/scripts/capture-guard | 55 |
1 files changed, 55 insertions, 0 deletions
diff --git a/.ai/scripts/capture-guard b/.ai/scripts/capture-guard new file mode 100755 index 0000000..1958309 --- /dev/null +++ b/.ai/scripts/capture-guard @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# capture-guard — detect live org-capture buffers visiting a target file +# before a workflow edits that file on disk. +# +# Editing a file on disk while Emacs has an indirect org-capture buffer +# cloned from it reverts the base buffer underneath the capture, wedging it: +# the capture can no longer finalize cleanly with C-c C-c, and a freshly-typed +# item can be lost or written back against post-edit content. inbox-zero +# Phase D edits ~/org/roam/inbox.org, the file Craig captures into constantly, +# so it calls this guard first. See claude-rules/emacs.md. +# +# Usage: capture-guard [TARGET_FILE] (default ~/org/roam/inbox.org) +# exit 0 — safe to edit: no Emacs, daemon unreachable, or no capture buffer +# visits TARGET_FILE. +# exit 1 — a live capture buffer visits TARGET_FILE; its name(s) printed to +# stdout, comma-separated. +# +# Conservative by construction: any uncertainty (no Emacs, query failure) +# resolves to "safe," so the guard never blocks a workflow that would have +# been fine. It only stops the one case it can positively confirm. + +set -euo pipefail + +TARGET="${1:-$HOME/org/roam/inbox.org}" + +# No Emacs to collide with → nothing to guard against. +command -v emacsclient >/dev/null 2>&1 || exit 0 +emacsclient -e t >/dev/null 2>&1 || exit 0 + +# Names of capture buffers whose base buffer visits TARGET. file-equal-p +# normalizes symlinks and ./.. so the match survives path spelling; it also +# returns nil when TARGET doesn't exist, which collapses to "safe" below. +lisp='(let ((target (expand-file-name "'"$TARGET"'"))) + (mapconcat (function buffer-name) + (seq-filter + (lambda (b) + (and (string-prefix-p "CAPTURE" (buffer-name b)) + (let* ((base (or (buffer-base-buffer b) b)) + (f (buffer-file-name base))) + (and f (file-equal-p f target))))) + (buffer-list)) + ","))' + +bufs="$(emacsclient -e "$lisp" 2>/dev/null)" || exit 0 + +# emacsclient prints an elisp string with surrounding double quotes: +# "" for none, "CAPTURE-inbox.org,..." for matches. Strip them. +bufs="${bufs#\"}" +bufs="${bufs%\"}" + +if [ -n "$bufs" ]; then + echo "$bufs" + exit 1 +fi +exit 0 |
