#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# SYNC — Commit and push all ~/content/ repos
#
# Maintainer: Kris Yotam <krisyotam@pm.me>
# License: MIT
# Created: 2026-02-15
# Description: Walks each git repo in ~/content/, stages changes, commits,
# and pushes. If a commit or push fails, invokes headless Claude
# to diagnose and fix the issue. Designed to be called by git.js
# before committing the main site repo.
#
# Usage:
# sync # sync all content repos
# sync --quiet # suppress per-repo output (for git.js integration)
###############################################################################
CONTENT_DIR="$HOME/.corpus/content"
CONTENT_TYPES=(blog diary essays fiction news notes ocs papers progymnasmata reviews verse)
QUIET=false
[[ "${1:-}" == "--quiet" ]] && QUIET=true
log() { $QUIET || echo "$@"; }
###############################################################################
# sync a single repo
###############################################################################
sync_repo() {
local type="$1"
local repo="$CONTENT_DIR/$type"
# Skip if not a git repo
[[ -d "$repo/.git" ]] || return 0
cd "$repo"
# Check for changes
if git diff --quiet HEAD -- 2>/dev/null && [[ -z "$(git ls-files --others --exclude-standard)" ]]; then
log " $type: clean"
return 0
fi
log " $type: changes detected, committing..."
# Stage all changes
if ! git add -A 2>/dev/null; then
log " $type: ERROR staging files"
attempt_fix "$type" "git add -A failed in $repo"
return $?
fi
# Commit with date-stamped message
local date
date="$(date +%Y-%m-%d)"
local msg="content update $date"
if ! git commit -m "$msg" 2>/dev/null; then
log " $type: ERROR committing"
attempt_fix "$type" "git commit failed in $repo"
return $?
fi
# Push
if ! git push origin HEAD 2>/dev/null; then
log " $type: ERROR pushing, retrying..."
# Try pull --rebase then push
if git pull --rebase origin main 2>/dev/null && git push origin HEAD 2>/dev/null; then
log " $type: pushed after rebase"
else
log " $type: push failed, invoking Claude..."
attempt_fix "$type" "git push failed in $repo after rebase attempt"
return $?
fi
fi
log " $type: synced"
return 0
}
###############################################################################
# claude error recovery
###############################################################################
attempt_fix() {
local type="$1"
local error_desc="$2"
local repo="$CONTENT_DIR/$type"
if ! command -v claude >/dev/null 2>&1; then
echo " $type: FAILED — claude CLI not available for auto-fix"
return 1
fi
local instruction="A git error occurred in the content repo at $repo.
Error: $error_desc
Current state:
$(cd "$repo" && git status 2>&1)
Recent log:
$(cd "$repo" && git log --oneline -5 2>&1)
Fix this git issue so the repo can be committed and pushed to origin. The repo contains .mdx content files — do not delete any content. Common fixes:
- If merge conflict: resolve by keeping both changes
- If push rejected: pull --rebase then push
- If dirty index: stage and commit
- If detached HEAD: checkout main and merge
Run the necessary git commands in $repo to fix the issue, then commit and push."
log " $type: invoking Claude for auto-fix..."
if claude -p "$instruction" --allowedTools "Bash(git *)" 2>/dev/null; then
log " $type: Claude fix succeeded"
return 0
else
echo " $type: FAILED — Claude could not fix the issue"
return 1
fi
}
###############################################################################
# main
###############################################################################
log "Syncing content repos..."
failed=0
synced=0
clean=0
for type in "${CONTENT_TYPES[@]}"; do
repo="$CONTENT_DIR/$type"
[[ -d "$repo/.git" ]] || continue
if sync_repo "$type"; then
# Check if it was clean or synced
cd "$repo"
if git diff --quiet HEAD -- 2>/dev/null && [[ -z "$(git ls-files --others --exclude-standard)" ]]; then
clean=$((clean + 1))
else
synced=$((synced + 1))
fi
else
failed=$((failed + 1))
fi
done
log ""
if [[ $failed -gt 0 ]]; then
echo "Sync complete: $failed repo(s) failed"
exit 1
else
log "Sync complete: all repos clean/synced"
exit 0
fi