synthesisengineering/synthesis-skills · Archived

synthesis-mac-sync

Multi-Mac configuration sync via iCloud with bidirectional config file sync, git repository sync, machine inventory, and one-time action system.

Installation

$ npx skills add synthesisengineering/synthesis-skills --skill synthesis-mac-sync

Summary

  • Multi-Mac configuration sync via iCloud with bidirectional config file sync, git repository sync, machine inventory, and one-time action system.
  • Use when asked to: mac sync, sync config, sync repos, sync with GitHub, push config to iCloud, pull config from iCloud, run mac-sync, repo status.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from synthesisengineering/synthesis-skills · top by installs.

npx skills add synthesisengineering/synthesis-skills

Browse all from synthesisengineering/synthesis-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 18
License LICENSE-APACHE
Default branch main
Open issues 3
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Version2.1.0
LicenseCC0-1.0
Declared agents claude-code codex
More metadata
author
Rajiv Pant
version
2.1.0
source_repo
github.com/synthesisengineering/synthesis-skills
source_type
public

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 46,097 B
  • docs SUMMARY.md 317 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

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
icloudsyncfolder ~/Library/Mobile Documents/com~apple~CloudDocs/workspaces/[username]/mac-sync/ iCloud Drive folder for synced config files
gitscanroot ~/workspaces/ Root directory for git repository discovery
gitscanmax_depth 3 Maximum depth for recursive .git directory search
gitreposmanifest git-repos.yaml Legacy central manifest (transitional; see v1.6.0 section)
workspacerepomanifest <workspace>/.agents/repos.yaml Per-workspace repo manifest (v1.6.0; canonical for repo entries)

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.


Agent configuration ownership

Agent instructions, lifecycle hooks, and the stable subset of client configuration use version-controlled sources plus native adapters:

  • Public reusable behavior lives in the synthesis-skills plugin.
  • Personal rules, private skills, hook policy, and owned config overlays live in

the private personal source repository.

  • Repository instructions live in tracked AGENTS.md; CLAUDE.md imports it.
  • Runtime-owned auth, trust databases, marketplace timestamps, caches, and

machine-specific paths stay local to each client.

Do not synchronize an entire client configuration file when the client also writes volatile state into it. Install or merge only the keys owned by the synthesis configuration source, then run synthesis-agent-conformance.


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

New-machine bootstrap — protection layer first

On any new Mac (or after a macOS / Xcode CLT update, which can replace interpreters and wipe user site-packages), bring up the protection layer BEFORE the first commit, not after:

  1. Install the synthesis-git-hooks engine and verify it in one step:

~/workspaces/<you>/synthesis-skills/skills/synthesis-git-hooks/scripts/install.sh — the v2 installer runs the --doctor self-check at the end and exits nonzero if the chain is not fully healthy (the engine itself fails closed, so an unhealthy install blocks commits rather than passing them unscanned).

  1. Pull the synced policy config (~/.synthesis/git-hook-config.yaml) via the config-file sync BEFORE running repos sync — the hooks read it on every commit.
  2. Install the synthesis-skills plugin into both Claude and Codex.
  3. Run the private agent-configuration installer. It renders global

instructions, installs private skills, merges owned client settings, and installs native hook adapters.

  1. Run synthesis-agent-conformance source, runtime, and instructions

before the first commit.

  1. Only then run the git-repos sync and begin work.

Ordering rationale: a fresh machine is exactly where the old failure mode lived — interpreters differ, site-packages are empty, and the first commits of a migration are often bulk imports with the highest leak surface. The v2 engine needs no third-party packages (any python3 works), so step 1 has no pip prerequisite on a fresh macOS with CLT installed.

Sync Modes

Full Sync ("run my mac-sync")

Performs these operations in order:

  1. Pre-pull iCloud download check — brctl download "$ICLOUD_BASE" followed by polling for .icloud placeholder files until none remain (with 5-minute timeout), per the iCloud Propagation Verification section. Ensures any pulls operate on materialized data.
  2. Config file sync — bidirectional, based on modification timestamps
  3. Post-push iCloud upload check — brctl monitor --wait-uploaded -t 300 "$ICLOUD_BASE", per the iCloud Propagation Verification section. Ensures local-newer pushes have reached Apple's servers before reporting completion.
  4. Workspace config symlinks — idempotent reconciliation against each workspace-private repo's .agents/ canonical (see Workspace Config Symlinks section below)
  5. Git repo sync — fetch, pull if behind, push if ahead
  6. 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"

Push Policy (v1.3.0)

Each repo in the manifest may have a push_policy field. When set, the sync protocol MUST respect it — refusing to push to any remote that violates the policy.

push_policy Meaning
unset (default) Push to all remotes configured in the repo
owner-only Only push to origin when origin points to the owner's personal GitHub account. Never push to a client org, another GitHub org, or a third-party Git host. Used for ai-knowledge-*-private repos that carry Type 3 (personal-client) content.
pr-required Never auto-push; user must review and open a PR

For owner-only repos:

  • Before any git push, verify git remote -v shows ONLY the owner's personal-GitHub URLs
  • If any non-owner remote exists, abort with an error and alert the user
  • These repos exist per ADR-013 (workspace-private repos); the -private suffix is also a discovery protocol filter (ADR-014)

Local session end and remote handoff

Ordinary Claude Code and Codex turn boundaries target LOCAL_READY, not a remote push. Synthesis-repo-guard records session-attributed edit manifests and Stop receipts without changing Git. Same-machine switching therefore needs no commit or network call.

  • If repo-guard reports local state: another client on this machine can recover it.
  • If repo-guard reports unattributed or unsafe state: inspect the detailed report and repair the local handoff before switching clients.

Configure repo-guard hooks per the synthesis-repo-guard skill. Mac-sync owns cross-machine publication and source-repository policy; repo-guard owns attribution, local receipts, and exact private-context publication.

Remote-handoff mode

Invoke this mode before changing computers. It refreshes the coordination lease, updates project tiers from working-tree truth, reads pending manifests, publishes owned source paths under each repository policy, flushes exact private-context paths, and verifies remote readiness. Source paths that are dirty, ahead, behind, detached, or lack an upstream keep the handoff blocked. A pr-required repository remains gated by its review policy.

Run remote-mode context doctor and continuity conformance for every project being transferred, then run the full repo detector and verify intended remote heads. Report REMOTE_READY only when no relevant pending manifest remains. Day-end performs this same transition automatically. The destination computer runs the pull side first, verifies fast-forward state and lease ownership, then uses the normal Session Start Protocol.

The source-machine gate runs the checkpoint flush, context doctor with readiness remote, and continuity conformance with readiness remote, in that order after source repositories are published. The destination repeats remote continuity after fetching and fast-forwarding.


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
}

# Direct timestamp sync is appropriate for user-owned static files.
sync_file "$ICLOUD_BASE/.gitconfig" "$HOME/.gitconfig" "no"

# Agent instructions, hooks, plugins, and client config use their
# version-controlled installer + conformance flow instead of sync_file.
# Runtime auth may remain an explicit encrypted/sensitive manifest item when
# the user has deliberately chosen cross-machine credential synchronization.

Codex configuration overlay

Codex config.toml contains both declarative preferences and runtime-owned state. The personal agent-config installer must merge only its declared keys, such as model role, feature toggles, sandbox policy, and MCP definitions. It must preserve plugin install state, marketplace update timestamps, hook trust, project trust, and unknown keys. After the merge:

codex doctor --json
python3 <conformance-skill>/scripts/conformance.py runtime

Treat the source overlay and merged runtime file as separate truth layers.

Git repo sync — TWO to THREE bash calls maximum

  1. Discovery + fetch all (ONE call): find repos, then loop through all of them running git fetch origin in a single script.
  2. Status + auto-actions (ONE call): Loop through all repos checking branch, ahead/behind, uncommitted changes, stashes. In the SAME script, automatically git pull repos that are behind and git push repos that are ahead with clean working trees. Collect all output into a structured summary.
  3. 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.


iCloud Propagation Verification (CRITICAL — added v1.5.0)

iCloud sync between Macs is two-stage and both stages take time:

  1. Upload: source Mac's local file → Apple's iCloud servers (handled by bird / cloudd).
  2. Download: Apple's iCloud servers → destination Mac's local file (also bird / cloudd).

The receiving Mac CANNOT pull data that has not completed stage 1. Verifying the receiving side's download state without first verifying the source side's upload state misses the more fundamental question and can result in: stale iCloud content overwriting good local copies, new files silently skipped because they haven't propagated yet, and false "sync complete" reports when actual state diverges.

After every push to iCloud — wait for upload (MANDATORY)

After completing config-file sync (including the bidirectional sync's local-newer cases), the assistant MUST run:

brctl monitor --wait-uploaded -t 300 "$ICLOUD_BASE"

This blocks for up to 5 minutes until iCloud confirms Stopping the query because all items are now uploaded. Do NOT report sync complete — and especially do NOT draft cross-Mac handoff instructions — until this returns successfully.

If the command times out without confirming upload, alert the user. Network issues or Apple-side queue backlog may be at play. Do not proceed with handoff instructions in that case.

Before every pull from iCloud — verify download materialization (MANDATORY)

Before pulling iCloud → local (especially in cross-Mac handoff scenarios where another Mac just pushed), the assistant MUST verify all items have been downloaded from iCloud to the local Mac.

macOS brctl monitor only supports --wait-uploaded — it has NO --wait-downloaded flag. The correct download verification is trigger-then-poll:

# 1. Trigger materialization of all items in the sync folder.
#    Idempotent — no-op if everything is already local.
brctl download "$ICLOUD_BASE"

# 2. Poll for .icloud placeholder files until none remain (with timeout).
DEADLINE=$(($(date +%s) + 300))
while true; do
  PLACEHOLDERS=$(find "$ICLOUD_BASE" -type f \( -name "*.icloud" -o -name ".*.icloud" \) 2>/dev/null)
  if [ -z "$PLACEHOLDERS" ]; then echo "All items materialized."; break; fi
  if [ $(date +%s) -ge $DEADLINE ]; then
    echo "TIMEOUT: still pending downloads after 5 minutes:"; echo "$PLACEHOLDERS"
    break
  fi
  sleep 5
done

Only proceed with the pull operation when the placeholder list is empty. If the loop times out, alert the user — there may be a network or iCloud-queue issue.

Why .icloud placeholders work as the signal: when iCloud has a file but the local Mac hasn't downloaded its content yet, macOS represents it as a hidden zero-byte placeholder file with a .<filename>.icloud extension instead of the real file. Once iCloud finishes downloading, the placeholder is replaced with the actual file. So an empty placeholder set means full materialization.

Cross-Mac handoff (one-time actions involving another Mac)

When drafting a one-time action that involves running mac-sync on a different Mac (e.g., "the second Mac should now pull what the first Mac just pushed"):

  1. On the source Mac: complete remote-handoff mode and obtain

REMOTE_READY for the project and its repositories.

  1. Before drafting the handoff prompt: run brctl monitor --wait-uploaded -t 300 "$ICLOUD_BASE" and confirm upload complete.
  2. In the handoff prompt itself: include the download trigger and

placeholder poll as the first destination step, then fetch, fast-forward, and run remote continuity before project work resumes.

These two together close the two-stage gap.

Pre-response self-check

Before sending any message that includes a cross-Mac handoff prompt, the assistant must ask itself: "have I verified iCloud upload is complete on this Mac?" If no, run the check first.

This is the same discipline as the cache-vs-truth rule (verify state has reached the system that will be read), applied to iCloud propagation.

Full bidirectional sync — when to run which check

Scenario Before pulling local from iCloud After pushing local to iCloud
Single-Mac everyday sync brctl download + placeholder poll (cheap no-op if already current) brctl monitor --wait-uploaded (cheap no-op if no new content)
Cross-Mac handoff (other Mac just pushed) brctl download + placeholder poll (REQUIRED — waits for propagation) brctl monitor --wait-uploaded (REQUIRED if drafting reverse handoff)
Pull-only sync ("from iCloud") brctl download + placeholder poll (REQUIRED) N/A (no push)
Push-only sync ("to iCloud") N/A (no pull) brctl monitor --wait-uploaded (REQUIRED)

In the everyday single-Mac case the checks are cheap — they return almost immediately when iCloud is current. The cost of always running them is small; the cost of skipping them in a cross-Mac case is data loss.


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):

  1. Compare iCloud version with local version using diff
  2. If identical → skip silently
  3. If different → compare modification timestamps using stat -f %m (macOS)
  4. Copy the newer file over the older one automatically
  5. For sensitive files, ensure chmod 600 after copying
  6. Report what was synced in the summary

Pull from iCloud

When user explicitly asks to pull:

  1. Compare each iCloud file with its local counterpart
  2. If different, copy iCloud → local automatically
  3. Preserve permissions (chmod 600 for sensitive files)

Push to iCloud

When user explicitly asks to push:

  1. Compare each local file with its iCloud counterpart
  2. 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 $HOME and {{USERNAME}} with $USER
  • When pushing: Replace current $HOME value with {{HOME}} and $USER value with {{USERNAME}}

Safety Rules

  1. Automatic for one-sided changes — if only one side changed, copy automatically
  2. Prompt only for conflicts — if both sides changed and timestamps can't resolve, show diff and ask which to keep
  3. Preserve permissions — sensitive files must be chmod 600
  4. Quote all paths — iCloud paths contain spaces ("Mobile Documents")
  5. Never overwrite with empty — if either file is empty or missing, do not overwrite the non-empty version
  6. 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
  7. Verify iCloud propagation — run brctl monitor --wait-uploaded after push and brctl download + .icloud placeholder polling before pull, per the iCloud Propagation Verification section above. Mandatory for cross-Mac handoff scenarios.

Per-Workspace Repo Manifests (v1.6.0) — the decentralized inventory

As of v1.6.0 (2026-07-08), the repo inventory is decentralized: each workspace owns its own list, and only a thin router stays central.

Canonical file: <workspace-private-repo>/.agents/repos.yaml, symlinked to <workspace>/.agents/repos.yaml (plus .claude/ and .codex/ back-compat) by the Workspace Config Symlinks layer (v1.4.0). A personal workspace without a -private repo carries it in the personal ai-knowledge repo. Cross-machine propagation is git (the context repo pushes/pulls, with its usual history, diffs, and conflict detection) — not iCloud.

Schema — facts vs policy. Fact fields (path, remotes, defaultbranches) are refreshed by scans. Policy fields (workspace-level status: active | dormant, per-repo ritualsync, push_policy, category, notes) are declared by the user or their agent and are NEVER changed by a scan.

Scan behavior per workspace (replaces the central-yaml scan for repo entries):

  • Refresh fact fields from git remote -v and local branch listings.
  • Repo on disk but not in the manifest → PROPOSE adding it (facts pre-filled); the user confirms the policy fields.
  • Repo in the manifest but not on disk → a clone decision, not an auto-clone (see Clone semantics).
  • NEVER remove an entry, and NEVER delete a local clone. Retention rule: leaving a client, or a client shutting down, retires a workspace to status: dormant — retained on disk, sync paused, nothing deleted. Deletion is a rare, explicit, manual act outside every sync flow.
  • Dead remotes (org deleted, Git host sunset): report once, keep the clone indefinitely — the local clone is the durable record.

Clone semantics (selective-cloning rule):

  • The manifest records CHOSEN clones. Never enumerate a remote org (GitHub/Bitbucket) to clone or list everything the user can access.
  • New-machine bootstrap (an explicit "set this machine up" act): read machines.yaml for the machine's subscribed workspaces, clone each workspace's context repo, read its repos.yaml, clone the curated list (minus any exclude: entries).
  • Ongoing: a repo listed in a workspace manifest but missing locally is surfaced as a decision; a machine subscription may set auto_clone: true to mirror automatically (default: false).

Router file — machines.yaml (same folder as the legacy central yaml): machine inventory, per-machine workspaces: subscriptions (all or an explicit list — supports restricted machines such as client-issued hardware that must never hold other clients' names), optional auto_clone and exclude:, and the workspace → context-repo bootstrap map. This is the only repo-related state that stays centralized.

Transition: while the legacy repositories: section of git-repos.yaml still exists, each sync cross-checks it against the per-workspace manifests and REPORTS any drift — no silent divergence. After a drift-free window (at least two clean daily-ritual code-syncs plus one clean mac-sync), archive that section to a dated file; the Manifest Merge Protocol below then applies only to machines.yaml and any remaining central state.

Consumers: synthesis-daily-rituals v2.13.0+ enumerates day-start/day-end source-code sync from ritual_sync: yes entries; this skill reads the same files for repo and remote reconciliation; synthesis-repo-guard scans disk independently (unaffected, and may later soften alerts for status: dormant workspaces).


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.

v1.6.0: repo entries now live per workspace in <workspace>/.agents/repos.yaml (see Per-Workspace Repo Manifests above); the central repositories: section is transitional and the scan's write target is the workspace manifest's fact fields.

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-required repos 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

  1. ALWAYS fetch first — run git fetch origin before checking any status
  2. NEVER force push — always use regular git push
  3. Never sweep uncommitted work — ordinary sync reports dirty files. In explicit remote-handoff or day-end mode, commit only session-attributed paths authorized by the repository workflow. Exact private-context batching uses synthesis-repo-guard. Unattributed, overlapping, or policy-gated paths block REMOTE_READY.
  4. NEVER bypass hooks without explicit permission — if a pre-commit hook blocks a commit, STOP and ask the user. Never use --no-verify on your own. If the user approves bypassing for a specific commit, that approval does not extend to other commits
  5. 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
  6. Push automatically if ahead — if working tree is clean and repo is ahead, push without asking
  7. Pull automatically if behind — pull new commits without asking
  8. Skip repos with no remote — report them but don't fail
  9. Skip repos with conflicts — report and let user resolve manually
  10. Skip repos mid-rebase/merge — report the state, don't interfere
  11. One branch only — only sync the current branch, don't switch branches
  12. 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.

v1.6.0: the reconciliation source for a repo's remotes is its workspace repos.yaml remotes: map (fact fields), not the central yaml.

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

  1. 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.
  2. Never overwrite origin with empty — if the manifest has no remotes: field for a repo, skip reconciliation for that repo.
  3. Report all changes — every add/update goes in the sync summary.
  4. Credentials in URLs are expected — some remotes include usernames (e.g., [email protected]). 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

v1.6.0 scope note: with per-workspace repos.yaml files live, this protocol applies to machines.yaml and the transitional central yaml only — per-workspace manifests merge via git in their context repos.

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:

  1. Read the existing yaml first. Parse all entries, their paths, categories, notes, remotes, and any other fields.
  1. Scan the local machine for repos (find ~/workspaces -maxdepth 3 -name ".git"). For each discovered repo, capture its remotes.
  1. 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.

  1. 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).
  1. 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)

  1. 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
  2. Config file conflict — both sides changed, can't determine winner from timestamps
  3. Git repo diverged — both ahead of AND behind remote
  4. Git merge conflict during pull — automatic merge failed
  5. Repo in unexpected state — mid-rebase, mid-merge, or detached HEAD
  6. Auth failure across multiple repos — stop and alert
  7. 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

  1. Detect current machine: scutil --get LocalHostName
  2. Check eligibility: Each action has a Target: field — machine-name, ALL, or ALL EXCEPT machine-name
  3. Execute or skip based on match
  4. Update status after execution: machine-name [COMPLETED 2026-01-15]
  5. 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

Workspace Config Symlinks (v1.4.0)

For workspace-scoped agent configs that should be identical across all your Macs AND available to any agent tool (Claude Code, Codex, Cursor, etc.), the canonical source lives in the workspace-private repo at <workspace>/<workspace-private>/.agents/. mac-sync ensures the expected symlinks exist at the workspace level (and known repos) on every invocation.

Why this layer (vs iCloud-based config sync)

iCloud-based sync is the right pattern for global personal configs (.gitconfig, .zshrc, .codex/config.toml). Workspace-scoped agent configs are different:

  • They're already in a git repo (the workspace-private repo) that pushes to GitHub — so cross-machine sync is "free" via git
  • They're agent-tool-agnostic — .agents/ is the convention multiple tools should read from, not .claude/
  • They're version-controlled with diff history per change (which iCloud sync isn't)

So: keep iCloud sync for global personal files; use git + symlinks for workspace agent configs.

Discovery

For each workspace under ~/workspaces/<workspace>/:

  1. Check if a workspace-private repo exists at ~/workspaces/<workspace>/ai-knowledge-<workspace>-<owner>-private/.agents/
  2. If yes, list all .yaml and .json files in that .agents/ directory (skip README.md and any non-config files)
  3. For each canonical file: ensure the symlinks below exist

Symlinks to create per canonical file

Symlink path Target (relative) Purpose
<workspace>/.agents/<file> ../<workspace-private>/.agents/<file> Agent-agnostic location (preferred)
<workspace>/.claude/<file> ../<workspace-private>/.agents/<file> Back-compat for tools that look in .claude/
<workspace>/.codex/<file> ../<workspace-private>/.agents/<file> Back-compat for Codex

All paths use relative symlinks so they work on any Mac regardless of ~/<username>.

Per-repo symlinks (conservative)

A canonical config may also need to appear inside a specific repo (e.g., a skill that walks up from CWD looking for .claude/<file>.yaml in the current repo first). The skill handles this conservatively — it does NOT auto-create per-repo symlinks for arbitrary repos. Instead:

  • If a repo already has .claude/<file>.yaml (or .agents/<file>.yaml) AS A REGULAR FILE, AND its content matches the canonical exactly: replace with a symlink to canonical.
  • If a repo already has .claude/<file>.yaml AS A SYMLINK pointing to canonical: leave alone (already correct).
  • If a repo already has .claude/<file>.yaml AS A SYMLINK pointing somewhere else, OR as a regular file with different content: WARN. Do not overwrite — manual decision.
  • If neither exists in the repo: do nothing. Per-repo symlinks for "new" repos are an explicit setup step, not auto-created.

Idempotence rules (apply to every symlink action)

Current state at symlink path Action
Symlink exists, points correctly Skip silently
Symlink exists, points wrong Replace with ln -sf
Regular file exists, content matches canonical rm then create symlink
Regular file exists, content differs from canonical WARN — do not overwrite
Nothing exists at the path Create symlink
Parent directory doesn't exist mkdir -p parent, then create symlink

The skill must report what it did per file, not silently overwrite anything that diverged from canonical.

Bootstrap script (single batched bash call, per the Performance section)

Run this with bash -c '...' (not zsh) so shopt -s nullglob is available — that makes empty globs expand to nothing instead of throwing or expanding to the literal pattern.

shopt -s nullglob 2>/dev/null  # or: [ -n "$ZSH_VERSION" ] && setopt NULL_GLOB

ensure_symlink() {
  local link_path="$1" target_relative="$2"
  local link_dir=$(dirname "$link_path")
  mkdir -p "$link_dir"
  if [ -L "$link_path" ]; then
    local current_target=$(readlink "$link_path")
    if [ "$current_target" = "$target_relative" ]; then
      echo "OK (symlink correct): $link_path"
      return
    fi
    echo "FIX (symlink wrong target): $link_path → $target_relative (was: $current_target)"
    ln -sf "$target_relative" "$link_path"
    return
  fi
  if [ -f "$link_path" ]; then
    local canonical_abs="$link_dir/$target_relative"
    if diff -q "$link_path" "$canonical_abs" > /dev/null 2>&1; then
      echo "MIGRATE (file matches canonical, replacing with symlink): $link_path"
      rm "$link_path"
      ln -s "$target_relative" "$link_path"
      return
    fi
    echo "WARN (regular file differs from canonical, NOT touching): $link_path"
    return
  fi
  echo "CREATE: $link_path → $target_relative"
  ln -s "$target_relative" "$link_path"
}

# Discover workspace-private repos and process each canonical file
for workspace_dir in "$HOME"/workspaces/*/; do
  workspace=$(basename "$workspace_dir")
  # Look for any ai-knowledge-<workspace>-*-private repo (the -private suffix marks it)
  for private_repo in "$workspace_dir"ai-knowledge-"$workspace"-*-private/; do
    [ -d "$private_repo/.agents" ] || continue
    for canonical in "$private_repo".agents/*; do
      [ -f "$canonical" ] || continue
      filename=$(basename "$canonical")
      # Skip README and other non-config files
      case "$filename" in README.md|*.md) continue ;; esac
      private_repo_name=$(basename "${private_repo%/}")
      # Workspace-level: .agents/ + .claude/ + .codex/ (back-compat)
      ensure_symlink "$workspace_dir.agents/$filename" "../$private_repo_name/.agents/$filename"
      ensure_symlink "$workspace_dir.claude/$filename" "../$private_repo_name/.agents/$filename"
      ensure_symlink "$workspace_dir.codex/$filename" "../$private_repo_name/.agents/$filename"
    done
  done
done

This script is fully idempotent: run it 100 times, every line reports OK after the first successful run. Run it on a fresh Mac (after cloning the workspace-private repo): every line reports CREATE. Run it after manually editing a config to a different value: report WARN and don't clobber.

When the skill runs this

Workspace symlink reconciliation runs as part of every full mac-sync, AFTER config-file iCloud sync and BEFORE git-repo sync. This ordering matters:

  • After iCloud sync: ensures any iCloud-synced files are up to date before checking workspace-level symlinks (in case an old workflow had a config in both iCloud and workspace-private)
  • Before git-repo sync: any new symlinks being committed (none today, but theoretically) get included in the next git status check

Report symlink results in the summary under "Workspace Symlinks":

### Workspace Symlinks
- Reconciled: 5 symlinks across 1 workspace
- Warnings: 0 regular-file divergences

Migration: moving a config from iCloud-synced to workspace-private

When a workspace-scoped config moves from iCloud-based sync to workspace-private + symlink:

  1. Delete the entry from your mac-sync config's "Sync Manifest — Direct Copy Files" table
  2. Add a one-time action with Target: ALL to delete the iCloud copy on every Mac (so it doesn't get re-synced from any Mac that still has it)
  3. Add the canonical to <workspace-private>/.agents/<file>, commit, push
  4. The next mac-sync invocation creates the symlinks on every Mac via the bootstrap above

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

  1. Copy it to the sync folder (maintaining directory structure)
  2. Add it to the sync manifest table
  3. Note if it contains secrets (for permissions)

Template file (contains machine-specific paths)

  1. Create a .template version with {{HOME}} and {{USERNAME}} placeholders
  2. Add it to the template files table
  3. Document which placeholders are used