Session log: /save + /sync multi-user change summaries

Enhance /save and /sync slash commands to attribute commits by author
so Mike and Howard can see at a glance what the other person did.

- sync.sh: loads identity.json, shows incoming/outgoing commits with
  author + age before pull/push, groups by author in final summary
- sync.md: describes the new output format + conflict attribution
- save.md: pre-commit Change Summary block + post-commit Summary

Motivation: repo is now shared across team, `git log` alone made it
hard to see "when did Howard change that?" without hunting.
This commit is contained in:
2026-04-16 19:08:25 -07:00
parent 100a491ac6
commit 6f6a77f8e4
4 changed files with 430 additions and 76 deletions

View File

@@ -72,9 +72,32 @@ Format credentials as:
## After Saving
Before committing, emit a **Change Summary** block for the user to review:
```
## Change Summary (this session)
User: <full_name> (from .claude/identity.json)
Machine: <HOSTNAME>
Files changed:
<output of: git status --short>
Stats:
<output of: git diff --stat HEAD>
```
Then:
1. Commit with message: "Session log: [brief description of work done]"
2. Push to gitea remote (if configured)
3. Confirm push was successful
3. After push, emit a **Post-commit Summary**:
- New commit SHA + message
- Author (from `git log -1 --format='%an <%ae>'`)
- Files in the commit (from `git show --stat HEAD`)
4. Confirm push was successful
### Why the summary
In the multi-user setup, commits can land in `main` from either team member. Always attributing author + files makes it obvious who made what change when someone else pulls the repo. Saves re-reading diffs to figure out "wait, when did that happen?"
## Purpose

View File

@@ -7,23 +7,33 @@ bash .claude/scripts/sync.sh
```
The script automatically:
1. Stages and commits local changes (if any)
2. Fetches and pulls remote changes
3. Pushes local changes
4. Reports sync status
1. Stages and commits local changes (attributed to the current user from `.claude/identity.json`)
2. Fetches remote and shows **incoming commits with authors** before pulling
3. Shows **outgoing commits with authors** before pushing
4. Pulls (rebase), then pushes
5. Prints a final change summary (who committed what, on which side)
After the script completes, report the 3 most recent session logs:
## After the script completes
The script emits a "Sync Summary" block. Relay the key bits to the user:
- **Incoming from remote:** N commits. If N > 0, list commits as `<short-sha> <author> — <message>` so the user immediately sees what Howard / Mike / other teammates pushed since their last sync.
- **Outgoing to remote:** M commits by the current user (this is what they're publishing).
- **Net file changes in this sync:** output of `git diff --stat <prev-HEAD>..HEAD -- . ':(exclude)session-logs'` (or similar scoping) so the user sees the meaningful edits, not noise.
Then report the 3 most recent session logs:
```bash
ls -t session-logs/*.md projects/*/session-logs/*.md clients/*/session-logs/*.md 2>/dev/null | head -3
```
## Conflict Resolution
## Conflict resolution
- **Session logs:** Keep both, rename with machine suffix
- **credentials.md:** Do NOT auto-merge, report to user
- **Other files:** Standard git conflict resolution
- **Session logs:** Keep both, rename with machine suffix. Note which user authored each conflicting side.
- **credentials.md:** Do NOT auto-merge, report to user.
- **Other files:** Standard git conflict resolution. When presenting a conflict, include the author of the conflicting commits on each side so the user can coordinate (e.g., "Howard changed this in commit abc123 on 2026-04-15").
## Error Handling
## Error handling
If push fails with auth error, retry once (transient Gitea auth issue).
If pull fails with conflicts, report affected files and ask for guidance.
- **Auth failure on push:** retry once (transient Gitea auth issue).
- **Pull conflicts:** report affected files + author of each conflicting side, then ask for guidance.
- **No identity.json yet:** follow the onboarding flow in CLAUDE.md before syncing.

View File

@@ -1,118 +1,158 @@
#!/bin/bash
# ClaudeTools Bidirectional Sync Script
# Ensures proper pull BEFORE push on all machines
# Prints incoming/outgoing change summary with author attribution
set -e # Exit on error
set -e
# Colors for output
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
CYAN='\033[0;36m'
NC='\033[0m'
# Detect machine name
# Machine + timestamp
if [ -n "$COMPUTERNAME" ]; then
MACHINE="$COMPUTERNAME"
else
MACHINE=$(hostname)
fi
# Timestamp
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
echo -e "${GREEN}[OK]${NC} Starting ClaudeTools sync from $MACHINE at $TIMESTAMP"
# Navigate to ClaudeTools directory
if [ -d "$HOME/ClaudeTools" ]; then
cd "$HOME/ClaudeTools"
elif [ -d "/d/ClaudeTools" ]; then
cd "/d/ClaudeTools"
elif [ -d "D:/ClaudeTools" ]; then
cd "D:/ClaudeTools"
else
echo -e "${RED}[ERROR]${NC} ClaudeTools directory not found"
# Navigate to ClaudeTools directory (check common locations)
for candidate in "$HOME/ClaudeTools" "/d/ClaudeTools" "D:/ClaudeTools" "/d/claudetools" "D:/claudetools"; do
if [ -d "$candidate" ]; then
cd "$candidate"
break
fi
done
if [ ! -d ".git" ]; then
echo -e "${RED}[ERROR]${NC} Not in a git working tree"
exit 1
fi
echo -e "${GREEN}[OK]${NC} Working directory: $(pwd)"
# Phase 1: Check and commit local changes
# Load user identity
USER_DISPLAY="unknown"
USER_GITEA=""
if [ -f ".claude/identity.json" ]; then
USER_DISPLAY=$(python -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('full_name', d.get('user','unknown')))" 2>/dev/null || echo "unknown")
USER_GITEA=$(python -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('user',''))" 2>/dev/null || echo "")
fi
echo -e "${GREEN}[OK]${NC} Syncing as: $USER_DISPLAY (machine: $MACHINE)"
# Phase 1: Local changes
echo ""
echo "=== Phase 1: Local Changes ==="
echo "=== Phase 1: Local changes ==="
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
echo -e "${YELLOW}[INFO]${NC} Local changes detected"
# Show status
echo -e "${YELLOW}[INFO]${NC} Local changes detected:"
git status --short
echo ""
# Stage all changes
echo -e "${GREEN}[OK]${NC} Staging all changes..."
git add -A
# Commit with timestamp
COMMIT_MSG="sync: Auto-sync from $MACHINE at $TIMESTAMP
Synced files:
- Session logs updated
- Latest context and credentials
- Command/directive updates
# Commit message (Co-Authored-By uses local git user if configured)
COMMIT_MSG="sync: auto-sync from $MACHINE at $TIMESTAMP
Author: $USER_DISPLAY
Machine: $MACHINE
Timestamp: $TIMESTAMP
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
Timestamp: $TIMESTAMP"
git commit -m "$COMMIT_MSG"
echo -e "${GREEN}[OK]${NC} Changes committed"
echo -e "${GREEN}[OK]${NC} Committed."
else
echo -e "${GREEN}[OK]${NC} No local changes to commit"
echo -e "${GREEN}[OK]${NC} No local changes to commit."
fi
# Phase 2: Sync with remote (CRITICAL: Pull BEFORE Push)
# Phase 2: Remote sync
echo ""
echo "=== Phase 2: Remote Sync (Pull + Push) ==="
echo "=== Phase 2: Fetch + inspect ==="
# Fetch to see what's available
echo -e "${GREEN}[OK]${NC} Fetching from remote..."
git fetch origin
LOCAL_BEFORE=$(git rev-parse HEAD)
# Check if remote has updates
LOCAL=$(git rev-parse main)
REMOTE=$(git rev-parse origin/main)
echo -e "${GREEN}[OK]${NC} Fetching from origin..."
git fetch origin --quiet
if [ "$LOCAL" != "$REMOTE" ]; then
echo -e "${YELLOW}[INFO]${NC} Remote has updates, pulling..."
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/main 2>/dev/null || git rev-parse origin/master 2>/dev/null || echo "$LOCAL")
REMOTE_BRANCH="origin/main"
if ! git rev-parse origin/main >/dev/null 2>&1; then
REMOTE_BRANCH="origin/master"
fi
# Pull with rebase
# Count and show incoming
INCOMING_COUNT=$(git rev-list --count HEAD..$REMOTE_BRANCH 2>/dev/null || echo 0)
OUTGOING_COUNT=$(git rev-list --count $REMOTE_BRANCH..HEAD 2>/dev/null || echo 0)
if [ "$INCOMING_COUNT" -gt 0 ]; then
echo ""
echo -e "${CYAN}--- Incoming: $INCOMING_COUNT commits from remote ---${NC}"
git log --oneline --format=' %C(yellow)%h%Creset %C(cyan)%an%Creset %s %C(dim)(%ar)%Creset' HEAD..$REMOTE_BRANCH | head -30
echo ""
echo -e "${CYAN}--- Files touched by incoming commits ---${NC}"
git diff --stat HEAD..$REMOTE_BRANCH | tail -20
else
echo -e "${GREEN}[OK]${NC} No incoming changes."
fi
if [ "$OUTGOING_COUNT" -gt 0 ]; then
echo ""
echo -e "${CYAN}--- Outgoing: $OUTGOING_COUNT commits to remote ---${NC}"
git log --oneline --format=' %C(yellow)%h%Creset %C(cyan)%an%Creset %s %C(dim)(%ar)%Creset' $REMOTE_BRANCH..HEAD | head -30
fi
# Phase 3: Pull (if needed)
if [ "$INCOMING_COUNT" -gt 0 ]; then
echo ""
echo "=== Phase 3: Pull (rebase) ==="
if git pull origin main --rebase; then
echo -e "${GREEN}[OK]${NC} Successfully pulled remote changes"
git log --oneline "$LOCAL..origin/main"
echo -e "${GREEN}[OK]${NC} Pulled successfully."
else
echo -e "${RED}[ERROR]${NC} Pull failed - may have conflicts"
echo -e "${YELLOW}[INFO]${NC} Resolve conflicts and run sync again"
echo -e "${RED}[ERROR]${NC} Pull failed (likely conflicts). Resolve and re-run sync."
exit 1
fi
fi
# Phase 4: Push (if needed)
OUTGOING_AFTER_PULL=$(git rev-list --count $REMOTE_BRANCH..HEAD 2>/dev/null || echo 0)
if [ "$OUTGOING_AFTER_PULL" -gt 0 ]; then
echo ""
echo "=== Phase 4: Push ==="
if git push origin main; then
echo -e "${GREEN}[OK]${NC} Pushed successfully."
else
echo -e "${RED}[ERROR]${NC} Push failed. Check auth / network."
exit 1
fi
else
echo -e "${GREEN}[OK]${NC} Already up to date with remote"
echo -e "${GREEN}[OK]${NC} Nothing to push."
fi
# Push local changes
# Phase 5: Summary
echo ""
echo -e "${GREEN}[OK]${NC} Pushing local changes to remote..."
if git push origin main; then
echo -e "${GREEN}[OK]${NC} Successfully pushed to remote"
else
echo -e "${RED}[ERROR]${NC} Push failed"
exit 1
echo "=== Sync Summary ==="
if [ "$INCOMING_COUNT" -gt 0 ]; then
# Count commits by author
INCOMING_AUTHORS=$(git log --format='%an' $LOCAL_BEFORE..HEAD 2>/dev/null | sort | uniq -c | sort -rn | awk '{printf "%s (%s), ", substr($0, index($0,$2)), $1}' | sed 's/, $//')
echo -e "${CYAN}Pulled in:${NC} $INCOMING_COUNT commit(s) — authors: ${INCOMING_AUTHORS:-unknown}"
fi
if [ "$OUTGOING_AFTER_PULL" -gt 0 ]; then
echo -e "${CYAN}Pushed out:${NC} $OUTGOING_AFTER_PULL commit(s) by $USER_DISPLAY"
fi
if [ "$INCOMING_COUNT" -eq 0 ] && [ "$OUTGOING_AFTER_PULL" -eq 0 ]; then
echo -e "${GREEN}Already in sync — no commits moved in either direction.${NC}"
fi
# Phase 3: Report final status
echo ""
echo "=== Sync Complete ==="
echo -e "${GREEN}[OK]${NC} Local branch: $(git rev-parse --abbrev-ref HEAD)"
echo -e "${GREEN}[OK]${NC} Current commit: $(git log -1 --oneline)"
echo -e "${GREEN}[OK]${NC} Remote status: $(git status -sb | head -1)"
echo -e "${GREEN}[OK]${NC} HEAD: $(git log -1 --oneline)"
echo -e "${GREEN}[OK]${NC} Status: $(git status -sb | head -1)"
echo ""
echo -e "${GREEN}[SUCCESS]${NC} All machines in sync. Ready to continue work."
echo -e "${GREEN}[SUCCESS]${NC} Sync complete."