← back to Ticket System
nightly-cleanup.sh
72 lines
#!/usr/bin/env bash
# Nightly 4am board hygiene (Steve 2026-07-28) — the scheduled companion to the
# on-demand self-completion we built this session. Two conservative, reversible
# passes; neither ever EXECUTES a gated action or falsely claims work shipped:
#
# 1. CNCP approvals auto-complete sweep — flips approvals to done when a ticket
# they reference is done, the memo is marked complete, or an approve has
# settled (> APPROVAL_AUTODONE_DAYS). POST /api/approvals/sweep.
# 2. Ticket auto-done — closes doing/blocked tickets whose LATEST line declares
# the whole ticket complete (idle ≥6h, no live worker, not neg/gated).
# 3. CNCP panel retire — hard-deletes items older than CNCP_RETIRE_DAYS (default 7)
# across the accumulating tabs (parking-lot, wins, lessons, session-recaps) and
# drops stopped/archived loops. Snapshots every file first; restarts CNCP so
# caches reload. project TODO.md files are NEVER touched.
#
# Installed as launchd com.steve.nightly-approvals-tickets (04:00 daily). Skip a
# pass with CNCP_APPROVAL_AUTODONE=0 / TICKET_AUTODONE=0 / CNCP_RETIRE=0.
set -u
ROOT="$HOME/Projects/ticket-system"
LOG="$ROOT/nightly-cleanup.log"
NODE="$(command -v node || echo /opt/homebrew/bin/node)"
CNCP="http://localhost:3333"
TS="$(date '+%F %T')"
{
echo "=== nightly-cleanup $TS ==="
# 1) CNCP approvals self-completion sweep
if [ "${CNCP_APPROVAL_AUTODONE:-1}" != "0" ]; then
echo "[1/2] approvals sweep → POST $CNCP/api/approvals/sweep"
curl -s -m 30 -X POST "$CNCP/api/approvals/sweep" -H 'Content-Type: application/json' -d '{}' \
|| echo " (approvals sweep unreachable — CNCP down?)"
echo
else
echo "[1/2] approvals sweep SKIPPED (CNCP_APPROVAL_AUTODONE=0)"
fi
# 2) Ticket auto-done (conservative; reversible)
if [ "${TICKET_AUTODONE:-1}" != "0" ]; then
echo "[2/3] ticket auto-done → node ticket-autodone.js --apply"
( cd "$ROOT" && "$NODE" ticket-autodone.js --apply )
else
echo "[2/3] ticket auto-done SKIPPED (TICKET_AUTODONE=0)"
fi
echo
# 3) CNCP panel retire (>N days across accumulating tabs; snapshots first)
if [ "${CNCP_RETIRE:-1}" != "0" ]; then
CNCP_DIR="$HOME/cncp-starter"
if [ -f "$CNCP_DIR/cncp-retire.py" ]; then
echo "[3/3] cncp panel retire → python3 cncp-retire.py --days ${CNCP_RETIRE_DAYS:-7}"
OUT="$( cd "$CNCP_DIR" && python3 cncp-retire.py --days "${CNCP_RETIRE_DAYS:-7}" )"
echo "$OUT"
# restart CNCP only if something was actually retired (so caches reload)
if ! echo "$OUT" | grep -q 'retired 0$'; then
echo " retire changed files — restarting CNCP (com.user.cncp) to reload caches"
launchctl kickstart -k "gui/$(id -u)/com.user.cncp" >/dev/null 2>&1 || echo " (CNCP restart failed — check com.user.cncp)"
fi
else
echo "[3/3] cncp panel retire SKIPPED (cncp-retire.py not found at $CNCP_DIR)"
fi
else
echo "[3/3] cncp panel retire SKIPPED (CNCP_RETIRE=0)"
fi
echo "=== done $TS ==="
} >> "$LOG" 2>&1
# keep the log bounded
tail -1200 "$LOG" > "$LOG.tmp" 2>/dev/null && mv "$LOG.tmp" "$LOG"
exit 0