The operator has more repos than they can hold in their head, and the question that actually costs them is "who is waiting on me, and what does answering cost". board.sh already scans for it; nothing rendered it in a form an unattended job could leave behind. --brief is a second RENDERING of that scan, never a second scan. It prints NESTE uncut, because the 38-character cut is the table column's property and not the record's - the value used to be truncated at record-build time, which left the cut string as the only copy. Each startup command is derived by CALLING route.sh with that repo's own four traits; next-cost alone cannot produce it, since the advisor flag is a property of the ROW and two rows can share a model/effort pair while differing on it. A repo with no route line is told so rather than handed a guess. It cross-checks itself against coord-count.sh, and that is the substance of the change rather than a nicety. The repo scan and the mailbox are two different populations: a mailbox can carry a name no scan will ever produce - a declared non-git surface (CLAUDE_COORD_REPO, e.g. ~/repos) or a checkout outside the roots. Measured on the real mailbox: 11 repos / 21 messages in the briefing against coord-count's 12 / 22, the missing one being the declared surface `repos`. A briefing that only walked the scan would answer "who is waiting on you" with a number it quietly knew was short. Zero model calls, which was the deciding property. Measured against 2.1.220 under subscription auth: --max-budget-usd DOES bite (terminal_reason budget_exhausted, exit 1), but it aborts AFTER turn one - floor ~0.25 USD-equivalent per turn on claude-opus-5[1m]. It is a runaway brake, not a pre-flight gate, so a nightly claude -p job would draw on the same quota pool as interactive work every night. Determinism removes the question. board.sh stays read-only: the file write lives in brief-nightly.sh, which renders to a temp file and renames it into place, and treats an EMPTY render as a FAILED one - board prints nothing when its scan roots do not exist, which is what a mistyped path or a moved home looks like, and a plain `> file` redirect would destroy yesterday's briefing on a bad launchd environment. The launchd template carries placeholders, not absolute paths: this repo is mirrored publicly and a plist is the one file here that would need a home directory in it. It points at a checkout rather than the plugin cache, which is version-pinned and would break silently on the next bump. board-selftest 36 -> 49. One check pins a defect caught only by eye against the real tree: fold copies its input's missing trailing newline, which ran the command onto the tail of the NESTE prose and produced a briefing whose commands could not be copied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017orCFDkmp88fLnqDR3chdJ
67 lines
2.6 KiB
Bash
Executable file
67 lines
2.6 KiB
Bash
Executable file
#!/bin/bash
|
|
# brief-nightly.sh - render the cross-repo briefing to a file, atomically.
|
|
#
|
|
# This is the ONLY writer in the briefing path, and it exists so that board.sh
|
|
# does not become one. board.sh is read-only by construction - it writes to no
|
|
# repo, no STATE.md and no mailbox - and a `--brief --out FILE` flag would have
|
|
# ended that for the sake of one redirect.
|
|
#
|
|
# Why not just `board.sh --brief > file` from launchd:
|
|
#
|
|
# 1. A plain redirect TRUNCATES the target before the renderer has produced
|
|
# a byte. An unattended job that fails, or is read mid-run, then leaves
|
|
# the operator an empty or half-written briefing - and the briefing is
|
|
# read exactly when nobody is watching it being made. Rendering to a temp
|
|
# file in the same directory and rename()-ing it into place means a reader
|
|
# sees either the old briefing or the new one, never a partial one.
|
|
# 2. An EMPTY render is treated as a FAILED render and never replaces a good
|
|
# briefing. Board prints nothing at all when its scan roots do not exist,
|
|
# which is exactly what a mistyped path or a moved home directory looks
|
|
# like - silent truncation to zero would destroy yesterday's briefing on
|
|
# a bad launchd environment. A repo tree where nobody owes anything is a
|
|
# different case entirely: that renders a valid, non-empty briefing saying
|
|
# so, and is written normally.
|
|
#
|
|
# Zero model calls, by construction: it runs two shell scripts. That is the
|
|
# whole point - a nightly job on subscription auth draws from the same quota
|
|
# pool as interactive work, and every turn it would spend is a turn the
|
|
# operator does not get. Measured floor for one headless turn on
|
|
# claude-opus-5[1m]: ~0.25 USD-equivalent, which --max-budget-usd cannot
|
|
# prevent (it aborts AFTER turn one, never before it).
|
|
#
|
|
# ASCII only, bash 3.2 safe.
|
|
set -u
|
|
|
|
SELFDIR="$(cd "$(dirname "$0")" && pwd)"
|
|
BOARD="$SELFDIR/board.sh"
|
|
OUT="${CLAUDE_BRIEF_FILE:-$HOME/.claude/briefing.md}"
|
|
|
|
case "${1:-}" in
|
|
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
esac
|
|
|
|
OUTDIR="$(dirname "$OUT")"
|
|
mkdir -p "$OUTDIR" 2>/dev/null || {
|
|
echo "brief-nightly: cannot create $OUTDIR" >&2; exit 1; }
|
|
|
|
# Same directory as the target: rename() is only atomic within one filesystem.
|
|
TMP="$OUT.tmp.$$"
|
|
trap '/bin/rm -f "$TMP" 2>/dev/null' EXIT
|
|
|
|
bash "$BOARD" --brief "$@" > "$TMP" 2>/dev/null
|
|
rc=$?
|
|
|
|
if [ "$rc" -ne 0 ]; then
|
|
echo "brief-nightly: board.sh --brief exited $rc, keeping previous briefing" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -s "$TMP" ]; then
|
|
echo "brief-nightly: empty render, keeping previous briefing" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mv -f "$TMP" "$OUT" || {
|
|
echo "brief-nightly: could not install $OUT" >&2; exit 1; }
|
|
|
|
exit 0
|