~kris/dots

srice

srice/.local/bin/misc/sync -rw-r--r-- 4.6 KiB
e98f3b03 — Kris Yotam chore: sync local state after restore (push updates, no pull) a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/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