synthesis-mac-sync
Synthesis Mac Sync
A methodology for keeping multiple Macs in sync using iCloud for configuration files and Git for repositories. Designed to run fully automated via an AI coding assistant — the user says "run my mac-sync" and the assistant handles everything.
This skill provides the protocol — the sync methodology, safety rules, conflict resolution, and manifest format. Your personal config file (a README or manifest in your iCloud sync folder) provides the specifics: which files, which machines, which repos.
Configuration
These values are user-specific. Update them for your environment.
| Setting | Value | Description |
|---|---|---|
icloud_sync_folder |
~/Library/Mobile Documents/com~apple~CloudDocs/workspaces/[username]/mac-sync/ |
iCloud Drive folder for synced config files |
git_scan_root |
~/workspaces/ |
Root directory for git repository discovery |
git_scan_max_depth |
3 |
Maximum depth for recursive .git directory search |
git_repos_manifest |
git-repos.yaml |
Manifest file caching discovered repos |
Architecture
synthesis-mac-sync (this skill)
= the HOW — sync protocol, safety rules, conflict resolution, manifest format
Your config file (in your iCloud sync folder)
= the WHAT — your specific file list, machine inventory, repo paths
The skill is invoked by the AI assistant. The assistant reads both this skill (for methodology) and your config file (for specifics), then executes the sync.
Setup
1. Create a sync folder in iCloud
Create a folder in iCloud Drive to hold your synced configuration files. Example path:
~/Library/Mobile Documents/com~apple~CloudDocs/workspaces/[username]/mac-sync/
2. Create a config file (README.md)
Your config file lives in the sync folder. It contains:
- Sync manifest — which files to sync and where they map locally
- Machine inventory — your Mac hostnames and details
- Git repo configuration — optional repo sync settings
- One-time actions — machine-specific tasks to run once
See the Config File Format section below for the template.
3. Copy your config files into the sync folder
Mirror the directory structure. For example:
mac-sync/
.gitconfig → syncs to ~/.gitconfig
.zshrc → syncs to ~/.zshrc
.config/app/keys.yaml → syncs to ~/.config/app/keys.yaml
Sync Modes
Full Sync ("run my mac-sync")
Performs all three operations:
- Config file sync — bidirectional, based on modification timestamps
- Git repo sync — fetch, pull if behind, push if ahead
- One-time actions — execute any pending machine-specific actions
Config-Only Sync
- Pull from iCloud: "sync my Mac config files from iCloud"
- Push to iCloud: "push my Mac config files to iCloud"
Git-Only Sync
- Full: "sync my repos with GitHub"
- Status only: "show me the status of my repos"
Session-End Check
Every AI coding session should end with all repos committed and pushed. Use synthesis-repo-guard (the repo_sync_check.py script) to verify. This is not the same as a full mac-sync — it's a fast read-only check that detects problems without fixing them.
- If repo-guard reports clean: session can end safely.
- If repo-guard reports dirty: commit and push before ending, or run a full mac-sync.
Configure repo-guard as a session-end hook so it runs automatically — see the synthesis-repo-guard skill for integration instructions for Claude Code, Codex, Cursor, and other tools.
Performance — Minimize Tool Calls
CRITICAL: Mac-sync must run with minimal interactive tool calls. The user should NOT face dozens of approval prompts.
Config file sync — ONE bash call
Write a single bash script that loops through ALL files in the manifest, compares them with diff, checks timestamps with stat -f %m if different, copies the newer version, and applies chmod 600 to sensitive files. Execute this entire script in ONE Bash tool call. Do NOT run separate diff, stat, or cp commands for each file.
Example pattern:
ICLOUD_BASE="$HOME/Library/Mobile Documents/com~apple~CloudDocs/workspaces/[username]/mac-sync"
sync_file() {
local icloud="$1" local_path="$2" sensitive="$3"
if [ ! -f "$icloud" ] && [ ! -f "$local_path" ]; then echo "SKIP (neither exists): $local_path"; return; fi
if [ ! -f "$icloud" ]; then echo "COPY local→iCloud: $local_path"; cp "$local_path" "$icloud"; return; fi
if [ ! -f "$local_path" ]; then echo "COPY iCloud→local: $local_path"; mkdir -p "$(dirname "$local_path")"; cp "$icloud" "$local_path"; [ "$sensitive" = "yes" ] && chmod 600 "$local_path"; return; fi
if diff -q "$icloud" "$local_path" > /dev/null 2>&1; then echo "IDENTICAL: $local_path"; return; fi
local icloud_ts=$(stat -f %m "$icloud") local_ts=$(stat -f %m "$local_path")
if [ "$icloud_ts" -gt "$local_ts" ]; then echo "SYNC iCloud→local (newer): $local_path"; cp "$icloud" "$local_path"; [ "$sensitive" = "yes" ] && chmod 600 "$local_path"
else echo "SYNC local→iCloud (newer): $local_path"; cp "$local_path" "$icloud"; fi
}
sync_file "$ICLOUD_BASE/.claude/CLAUDE.md" "$HOME/.claude/CLAUDE.md" "no"
# ... one line per manifest entry ...
Git repo sync — TWO to THREE bash calls maximum
- Discovery + fetch all (ONE call):
findrepos, then loop through all of them runninggit fetch originin a single script. - Status + auto-actions (ONE call): Loop through all repos checking branch, ahead/behind, uncommitted changes, stashes. In the SAME script, automatically
git pullrepos that are behind andgit pushrepos that are ahead with clean working trees. Collect all output into a structured summary. - Follow-up actions (ONE call, only if needed): Handle any repos that need individual attention (diverged, conflicts).
Example pattern for step 2:
REPOS=(
"/path/to/repo1"
"/path/to/repo2"
# ...
)
for repo in "${REPOS[@]}"; do
name=$(basename "$repo")
branch=$(git -C "$repo" branch --show-current 2>/dev/null)
if [ -z "$branch" ]; then echo "DETACHED: $name"; continue; fi
counts=$(git -C "$repo" rev-list --left-right --count "origin/$branch...$branch" 2>/dev/null)
behind=$(echo "$counts" | awk '{print $1}')
ahead=$(echo "$counts" | awk '{print $2}')
dirty=$(git -C "$repo" status --porcelain 2>/dev/null | head -5)
if [ "$behind" -gt 0 ]; then git -C "$repo" pull origin "$branch" 2>&1 | sed "s/^/PULLED $name: /"; fi
if [ "$ahead" -gt 0 ] && [ -z "$dirty" ]; then git -C "$repo" push origin "$branch" 2>&1 | sed "s/^/PUSHED $name: /"; fi
[ -n "$dirty" ] && echo "DIRTY $name: $(echo "$dirty" | wc -l | tr -d ' ') files"
done
Do NOT run individual tool calls per repo. The whole point of mac-sync is automation without interaction.
Config File Sync Protocol
Bidirectional Sync (default)
For each file in the sync manifest (executed as a SINGLE batched script per the Performance section above):
- Compare iCloud version with local version using
diff - If identical → skip silently
- If different → compare modification timestamps using
stat -f %m(macOS) - Copy the newer file over the older one automatically
- For sensitive files, ensure
chmod 600after copying - Report what was synced in the summary
Pull from iCloud
When user explicitly asks to pull:
- Compare each iCloud file with its local counterpart
- If different, copy iCloud → local automatically
- Preserve permissions (chmod 600 for sensitive files)
Push to iCloud
When user explicitly asks to push:
- Compare each local file with its iCloud counterpart
- If different, copy local → iCloud automatically
Template File Expansion
For config files containing machine-specific paths, use template files with placeholders:
- When pulling: Replace
{{HOME}}with$HOMEand{{USERNAME}}with$USER - When pushing: Replace current
$HOMEvalue with{{HOME}}and$USERvalue with{{USERNAME}}
Safety Rules
- Automatic for one-sided changes — if only one side changed, copy automatically
- Prompt only for conflicts — if both sides changed and timestamps can't resolve, show diff and ask which to keep
- Preserve permissions — sensitive files must be
chmod 600 - Quote all paths — iCloud paths contain spaces ("Mobile Documents")
- Never overwrite with empty — if either file is empty or missing, do not overwrite the non-empty version
- Skip machine-specific path differences — if the only differences are username-specific paths (e.g.,
/Users/alice/vs/Users/bob/), skip and note in summary
Git Repository Sync Protocol
Repository Discovery
Scan a configured directory (e.g., ~/workspaces/) recursively:
find ~/workspaces -maxdepth 3 -name ".git" -type d 2>/dev/null
Maintain a manifest file (git-repos.yaml) that caches discovered repos, their categories, and their remote configurations for quick status checks and cross-machine remote sync. Update using the merge protocol on each sync — never overwrite the yaml from scratch. See Git Remote Sync Protocol and Manifest Merge Protocol.
Per-Repo Sync Procedure
IMPORTANT: All steps below must be executed as batched shell scripts, NOT individual tool calls per repo. See the Performance — Minimize Tool Calls section above.
Step 1: Fetch all — loop through every discovered repo and git fetch origin in a single script.
Step 2: Status + auto-actions — in a single script, loop through all repos:
- Get current branch, ahead/behind counts, uncommitted changes, stashes
- Pull if behind (automatic)
- Push if ahead and clean working tree (check push policy first —
pr-requiredrepos are reported, not pushed) - If push fails (non-fast-forward), report but do not force push
Step 3: Handle repos with uncommitted changes — show the user what's uncommitted (file list per repo) and ask whether to commit and push each dirty repo. Present a suggested commit message for each. Do NOT silently skip — uncommitted changes are unsynced state.
Safety Rules
- ALWAYS fetch first — run
git fetch originbefore checking any status - NEVER force push — always use regular
git push - NEVER silently auto-commit — always show uncommitted files and ask before committing. Uncommitted changes are unsynced state; ignoring them defeats the purpose of the sync. Group files into separate commits by topic — never bundle unrelated files
- NEVER bypass hooks without explicit permission — if a pre-commit hook blocks a commit, STOP and ask the user. Never use
--no-verifyon your own. If the user approves bypassing for a specific commit, that approval does not extend to other commits - Sanitize ALL commit messages — never include people's names, company names, project codenames, article titles, or meeting topics in commit messages. Use generic descriptions like "Add meeting transcript", "Update context", "Add new blog post". Git history is persistent and can leak confidential information
- Push automatically if ahead — if working tree is clean and repo is ahead, push without asking
- Pull automatically if behind — pull new commits without asking
- Skip repos with no remote — report them but don't fail
- Skip repos with conflicts — report and let user resolve manually
- Skip repos mid-rebase/merge — report the state, don't interfere
- One branch only — only sync the current branch, don't switch branches
- Prompt only for diverged repos — when both ahead and behind, ask for merge strategy
Git Remote Sync Protocol
Remote configurations (name + URL pairs) are per-machine state stored in each repo's .git/config. Without explicit sync, a remote added on one Mac won't exist on the other.
Capture (during scan/refresh)
When refreshing git-repos.yaml, capture all remotes per repo:
# For each discovered repo:
git -C "$repo_path" remote -v | grep '(fetch)' | awk '{print $1, $2}'
CRITICAL: Use the Manifest Merge Protocol (below) to update git-repos.yaml. NEVER generate a fresh yaml from scratch and overwrite the existing file. The existing yaml may contain repos, remotes, categories, notes, and metadata contributed by other machines that this machine doesn't have locally. A destructive overwrite causes data loss.
This runs as part of the single batched scan script — not as separate tool calls.
Reconcile (during pull/sync)
When syncing to a Mac, reconcile local remotes against the manifest:
# For each repo in git-repos.yaml, for each remote in the manifest:
existing_url=$(git -C "$repo_path" remote get-url "$remote_name" 2>/dev/null)
if [ -z "$existing_url" ]; then
git -C "$repo_path" remote add "$remote_name" "$manifest_url"
echo "ADDED $repo_name: $remote_name → $manifest_url"
elif [ "$existing_url" != "$manifest_url" ]; then
git -C "$repo_path" remote set-url "$remote_name" "$manifest_url"
echo "UPDATED $repo_name: $remote_name → $manifest_url (was: $existing_url)"
fi
Execute this as a SINGLE batched script across all repos — not individual tool calls.
Safety Rules
- Never auto-remove remotes — if a local remote exists but isn't in the manifest, flag it in the summary but do not delete. The manifest captures the last scan from one machine; the local remote may be intentionally machine-specific.
- Never overwrite origin with empty — if the manifest has no
remotes:field for a repo, skip reconciliation for that repo. - Report all changes — every add/update goes in the sync summary.
- Credentials in URLs are expected — some remotes include usernames (e.g.,
user@bitbucket.org). These are not secrets (the password is in the credential helper, not the URL). Do not strip or redact them.
Relationship to setup-git-remotes.sh
If you maintain a setup-git-remotes.sh bootstrap script, it serves as a fallback for initial machine setup (before repos are cloned and before the first mac-sync). Once git-repos.yaml captures remotes, the yaml is the authoritative source of truth. Keep the bootstrap script consistent with the yaml, or auto-generate it from the yaml during push.
Manifest Merge Protocol
CRITICAL SAFETY RULE: git-repos.yaml must NEVER be generated from scratch and overwritten. The yaml is a shared manifest across multiple machines. Each machine may have repos, remotes, or metadata that other machines don't. A destructive overwrite from one machine's scan silently destroys data contributed by other machines.
The merge procedure
When refreshing git-repos.yaml on any machine:
-
Read the existing yaml first. Parse all entries, their paths, categories, notes, remotes, and any other fields.
-
Scan the local machine for repos (
find ~/workspaces -maxdepth 3 -name ".git"). For each discovered repo, capture its remotes. -
Merge — additive updates only:
- Repo exists in yaml AND locally: Update remotes from local scan (add new remotes, update changed URLs). Preserve all existing fields (category, notes, etc.) unless the local scan has a reason to change them.
- Repo exists locally but NOT in yaml: Add it as a new entry.
- Repo exists in yaml but NOT locally: Keep it. This machine may not have it cloned. Do NOT remove it.
- Remote exists in yaml but NOT locally: Keep it. Another machine may have added it. Do NOT remove it.
-
Update metadata: Refresh the header comment (date, machine name). Recount total_repos as the number of entries in the merged yaml (not the local scan count).
-
Write the merged result.
What this prevents
- Repo discovered on Machine A doesn't disappear when Machine B runs a scan
- Remotes configured on Machine A survive Machine B's refresh
- Categories, notes, and other metadata contributed by any machine persist
- A machine running an older version of the skill that doesn't know about
remotes:doesn't strip the field
Never generate yaml from local scan alone
The correct mental model: the yaml is a multi-machine document that each machine contributes to. It is not a point-in-time capture of any single machine's state.
Automation Policy
Mac-sync is designed to run fully automated. The assistant should complete the entire sync without prompting, except in specific situations.
Automated (never prompt)
- Config files identical → skip silently
- Config file changed on one side only → copy the changed version
- Git fetch → always do
- Git pull when behind → always do (fast-forward only)
- Git push when ahead, clean working tree → always do
- Clean repos → skip silently
- Repos with no remote → skip, note in summary
- Manifest update → always refresh
- One-time actions for a different machine → skip silently
Must prompt (show details + ask for action)
- Repos with uncommitted changes → show file list per repo, suggest commit message, ask whether to commit+push. Uncommitted local changes won't reach the other Mac — treating them as informational breaks the sync guarantee
- Config file conflict — both sides changed, can't determine winner from timestamps
- Git repo diverged — both ahead of AND behind remote
- Git merge conflict during pull — automatic merge failed
- Repo in unexpected state — mid-rebase, mid-merge, or detached HEAD
- Auth failure across multiple repos — stop and alert
- Destructive action needed — force push, hard reset, or branch deletion
Report but take no action (never prompt)
- Repos with stashes → note in summary
- Non-fast-forward push failures → report, move on
- Repos with
push_policy: pr-required→ report unpushed commits
Machine Inventory
Use LocalHostName as the unique machine identifier:
scutil --get LocalHostName
Why LocalHostName:
- Set explicitly per machine in System Settings → Sharing → Local hostname
- Won't accidentally collide (unlike usernames)
- Persists across OS updates
- Easy to check
Machine Inventory Table Format
| LocalHostName | Model | Username | Notes |
|---------------|-------|----------|-------|
| my-mac-mini | Mac mini M2 Pro | alice | Primary development |
| my-macbook | MacBook Pro M4 | alice | Secondary laptop |
One-Time Actions
Machine-specific tasks that should run once per machine.
Safety Protocol
- Detect current machine:
scutil --get LocalHostName - Check eligibility: Each action has a
Target:field —machine-name,ALL, orALL EXCEPT machine-name - Execute or skip based on match
- Update status after execution:
machine-name [COMPLETED 2026-01-15] - Delete action only when ALL targeted machines show
[COMPLETED]
Template
### YYYY-MM-DD: Brief Description
**Target:** [LocalHostName(s) or ALL or ALL EXCEPT LocalHostName]
**Status:** [LocalHostName] [PENDING]
**Background:** Why this action is needed
**Action Required:**
\`\`\`bash
# Commands to run
\`\`\`
**Verification:** How to confirm it worked
Summary Format
Present a summary after sync completes:
## Mac Sync Complete
### Actions Taken
- Pulled X repos (list with commit counts)
- Pushed X repos (list with commit counts)
- Synced X config files from iCloud/to iCloud
### Needs Attention (prompt user for these)
- Repos with uncommitted changes: [file list per repo + suggested commit message] — ask whether to commit+push
- Diverged repos: [list] — need merge strategy decision
- Merge conflicts: [list] — need manual resolution
- Repos in unexpected state: [list] — mid-rebase/merge/detached HEAD
### Informational (no action needed)
- Repos with stashes: [list]
- Push failures (non-fast-forward): [list]
- Repos with no remote: [list]
- Clean repos: X repos
Only prompt for items in "Needs Attention." Everything else is informational.
Config File Format
Your config file (README.md in the sync folder) should include these sections. Adapt to your needs:
Sync Manifest — Direct Copy Files
| iCloud Path (relative) | Local Path | Purpose | Sensitive? |
|------------------------|------------|---------|------------|
| `.gitconfig` | `~/.gitconfig` | Git identity | No |
| `.zshrc` | `~/.zshrc` | Shell config | No |
| `.config/app/keys.yaml` | `~/.config/app/keys.yaml` | API keys | **Yes** |
Sync Manifest — Template Files
| iCloud Path (relative) | Local Path | Placeholders | Sensitive? |
|------------------------|------------|-------------|------------|
| `.ssh/config.template` | `~/.ssh/config` | `{{HOME}}`, `{{USERNAME}}` | No |
Git Repository Manifest (git-repos.yaml)
scan_root: ~/workspaces
max_depth: 3
total_repos: 12
repositories:
- path: ~/workspaces/personal/my-app
category: personal
remotes:
origin: https://github.com/user/my-app.git
- path: ~/workspaces/work/app
category: work
push_policy: pr-required
remotes:
origin: https://github.com/org/app.git
mirror: https://github.com/other-org/app.git
excluded:
# - ~/workspaces/personal/some-fork # Reason: upstream only
The remotes field captures all configured git remotes per repo. This is the source of truth for remote topology across machines — when mac-sync runs on a second Mac, it reconciles local remotes against this manifest. See Git Remote Sync Protocol below.
Machine Inventory
Document your machines with the table format above.
One-Time Actions
Use the template above for machine-specific tasks.
Adding New Files to Sync
Direct copy file
- Copy it to the sync folder (maintaining directory structure)
- Add it to the sync manifest table
- Note if it contains secrets (for permissions)
Template file (contains machine-specific paths)
- Create a
.templateversion with{{HOME}}and{{USERNAME}}placeholders - Add it to the template files table
- Document which placeholders are used
More from rajivpant/synthesis-skills
synthesis-fact-checking
Systematic fact-checking process for verifying claims in articles and blog posts, particularly those synthesized from multiple AI deep-research outputs. Use when asked to: fact-check, verify claims, verify sources, check accuracy, citation verification, review factual accuracy, validate references.
17synthesis-thinking-framework
Five-mode thinking methodology (first principles, systems thinking, complexity thinking, analogical thinking, design thinking) with a pre-response protocol for non-trivial problems. Provides the foundational reasoning approach that other synthesis skills build upon.
15synthesis-article-writing
>
14synthesis-codebase-review
Enterprise-scale codebase audit methodology with tiered review system (Essential through Mission-Critical). Use when asked to: codebase review, code audit, code review, review codebase, architecture review, security audit, full code review, enterprise review, codebase health check.
14synthesis-context-lifecycle
Three-tier context architecture for managing AI working memory across long-running projects. Use when asked to: manage context, project context, session management, context lifecycle, working memory, archival, archive sessions, context maintenance, garbage collection for context, tiered context.
14synthesis-code-planning
Structured approach to code generation, implementing features, and writing code. Use when asked to generate code, implement a feature, write code, or tackle a coding task. Analyzes the task, generates multiple approaches with trade-offs, selects the optimal solution, and implements it.
14