| name | synthesis-repo-guard |
| description | Workspace git-sync guard: detects uncommitted/unpushed repos, reports through confidentiality-safe channels (generic audio/banner + detailed report files + synthesis-console tile), and auto-heals private context repos at workflow events via checkpoint_sync.py. Works with any AI coding tool (Claude Code, Codex, Cursor, etc.) or standalone. Use when asked to: repo guard, check repos, session end check, sync check, repo status, workspace status, checkpoint sync, auto-commit context repos. |
| license | Apache-2.0 |
| depends_on | [] |
| metadata | {"author":"Rajiv Pant","version":"2.0.0","source_repo":"github.com/synthesisengineering/synthesis-skills","source_type":"public"} |
Synthesis Repo Guard
The Problem
AI coding assistants and project-management tooling create and modify files continuously. When a session ends — completion, timeout, closed window — uncommitted or unpushed changes are stranded on that machine, breaking the multi-machine sync chain. Tools that write files outside agent sessions (a project console writing status markers, manual edits) never commit at all.
v1 of this skill detected stranded state and alerted with a count ("N repositories have unsynced changes"). Two failures emerged in practice:
- The alert was unactionable — and leaky if made actionable. A count says nothing useful; speaking repo names would fix that, but repo/workspace names are often client names, and audio reaches whoever is nearby or on an unmuted call. Notification banners leak the same way during screen-shares.
- The alert fired on machine-fixable states. Most unsynced state is exactly what automation should heal at the next sensible checkpoint. Alerting humans about machine-fixable problems trains them to ignore alerts.
The Architecture — three layers
| Layer | Component | Job |
|---|
| Detector | repo_sync_check.py (scan) | Find dirty / ahead / behind / detached repos under a workspace root |
| Messenger | repo_sync_check.py (output) | Generic audio/banner ping + detailed report files + console tile data |
| Remediator | checkpoint_sync.py | Auto-commit + push the configured private context-repo class at workflow events |
End state: routine changes are checkpointed silently, an always-on synthesis-console tile shows ambient status, and audible/banner alerts fire only for states automation cannot heal (blocked pre-commit hook, divergence, out-of-class dirt, stale lock) — rare, and always actionable.
Confidentiality rule for alert surfaces (ABSOLUTE)
Audio (say, alert sounds) and macOS notification banners never carry repo names, workspace names, or client names — only counts and a pointer ("details are in your synthesis console"). This holds at all times, not only while screen-sharing: presence detection is unreliable, and one leak outweighs the convenience. Identifying detail belongs exclusively in pull channels the user deliberately opens:
~/.synthesis/repo-guard/last-report.txt / last-report.json / history.jsonl — written on every scan
~/.synthesis/repo-guard/checkpoint-state.json — written on every checkpoint run
- the synthesis-console sync tile / page, which renders both
Mute toggle: all audible output (speech AND alert sounds) is suppressed while ~/.synthesis/quiet-audio exists. synthesis-console exposes this as a header button; touch/rm the file works too. Muting loses nothing — reports and tile stay current.
Detection vs. commit — scoping rules
repo_sync_check.py detects and never modifies — correct scope: every repo in the workspace.
Commits happen in exactly two sanctioned ways:
- Per-invocation agent commits (unchanged rule): an agent commits only the files IT modified in the current invocation — never workspace-wide, never other sessions' work. See
synthesis-daily-rituals "Commit Protocol".
checkpoint_sync.py — the deliberate, narrowly-scoped exception: it auto-commits repos in the configured auto-sync class only (private, single-writer context repos whose history is an operational log), protected by a runtime guard (below). Source-code repos and shared/public repos are structurally out of reach.
The remediator: event-driven, never scheduled
checkpoint_sync.py runs ONLY at workflow events — moments when the user is demonstrably present and the machine awake:
- AI-tool turn/session end (throttled via stamp file, default 10 min): catches agent sessions — and manual edits too, since sessions are usually live.
- After a console cockpit write: the console calls
checkpoint_sync.py --repo <written-file> --now, so producers own their commits through the one shared mechanism.
- Rituals / mac-sync: the existing day-boundary sweeps.
Deliberately NOT a launchd/cron job. Wall-clock daemons run detached from presence: they race the same repos across machines and create close-the-lid anxiety. Event-driven runs execute on the origin machine seconds after the change — which also shrinks multi-machine divergence windows, because work is pushed before you switch machines. (Read-only polling — the console tile refreshing — is exempt: interrupting a read is harmless.)
The auto-sync class + runtime guard
Config ~/.synthesis/checkpoint-sync.yaml (copy checkpoint-sync.example.yaml) lists the class by explicit path and glob. Membership criteria: private, single-writer knowledge/context repos (personal ai-knowledge repos, *-<person>-private workspace repos, daily plans). Never source-code repos, never shared/public repos.
The runtime remote guard is independent of config: a repo is touched only if EVERY push remote starts with an allowed prefix (your private GitHub namespace). A glob that accidentally matches a repo with a client/org remote is excluded at run time, every time — config declares intent; the guard verifies reality. Empty allowed_remote_prefixes fails closed.
Safety properties
- Ordering:
add -A → commit → fetch → push only if fast-forward.
- Distinct commit author (e.g. "Synthesis Checkpoint") — automated checkpoints are always distinguishable from curated commits.
- Divergence → the commit stays local (durable, resolvable) + alert. Never rebase, never force-push.
- Pre-commit hooks run normally; a rejection aborts that repo with an alert. Never
--no-verify.
- Quiescence: skip repos with files modified in the last N minutes (default 15) — no mid-thought commits.
--now bypasses this for producer writes that are known-complete.
- Stale
index.lock (>10 min): reported, never deleted.
- Interruption-safe by construction: runs at interactive moments; an interrupted git op doesn't corrupt a repo (killed pushes are retryable; sleep suspends rather than kills).
Quick Start
./repo_sync_check.py
./repo_sync_check.py --json --quiet
./repo_sync_check.py --speak --notify --dirty-only
./checkpoint_sync.py --dry-run
./checkpoint_sync.py --hook --quiet --notify
./checkpoint_sync.py --repo ~/workspaces/example/daily-plans/today.md --now
Exit codes (both scripts)
| Code | repo_sync_check.py | checkpoint_sync.py |
|---|
| 0 | all clean & synced | nothing needed / all healed |
| 1 | repos need attention | alerts raised (detail in state file) |
| 2 | error | error |
What the detector reports
| Condition | Marker |
|---|
| Uncommitted changes (modified/staged/untracked) | [dirty] + file list + fix hint |
| Unpushed commits | [ahead] + count |
| Unpulled commits | [behind] + count |
| Detached HEAD | [detached] |
| Git errors | [error] |
AI Tool Integration
Claude Code (~/.claude/settings.json)
Turn-end remediation (throttled — cheap no-op most turns) plus optional session-end verification:
{
"hooks": {
"Stop": [
{ "hooks": [ { "type": "command",
"command": "python3 ~/.claude/skills/synthesis-repo-guard/checkpoint_sync.py --hook --quiet --notify",
"timeout": 120 } ] }
],
"SessionEnd": [
{ "hooks": [ { "type": "command",
"command": "python3 ~/.claude/skills/synthesis-repo-guard/repo_sync_check.py --dirty-only --speak --notify",
"timeout": 60 } ] }
]
}
}
OpenAI Codex (~/.codex/hooks.json, with codex_hooks = true)
{ "hooks": { "Stop": [ { "hooks": [ { "type": "command",
"command": "python3 ~/.codex/skills/synthesis-repo-guard/checkpoint_sync.py --hook --quiet --notify",
"timeout": 120 } ] } ] } }
The throttle stamp is shared across tools — multiple agents coexist without duplicate runs.
Cursor (.cursor/settings.json)
{ "task.onEnd": "python3 /path/to/checkpoint_sync.py --hook --quiet --notify" }
synthesis-console (command center)
- Always-on sync tile: polls
repo_sync_check.py --json --quiet (read-only; lid-safe) and renders checkpoint-state.json outcomes.
- Quiet-audio toggle button: creates/removes
~/.synthesis/quiet-audio.
- "Sync now" button:
checkpoint_sync.py --no-throttle (throttle bypassed; quiescence still honored).
- Producer commits: after writing a plan marker, the console fires
checkpoint_sync.py --repo <file> --now.
Scheduled execution — read-only only
If a tool supports no hooks at all, a scheduled detector run (repo_sync_check.py --quiet, reports only, no audio flags) is acceptable — it's read-only and interruption-safe. Do not schedule checkpoint_sync.py: mutation stays event-driven (see design rationale above). The console tile's polling normally makes scheduled detection unnecessary.
Relationship to Other Skills
- synthesis-mac-sync — the full multi-machine sync operation (config files, credentials, all repos, with user approval). Repo-guard keeps context repos continuously clean so mac-sync's day-boundary sweep mostly finds nothing; run mac-sync when the report shows out-of-class problems or when switching machines.
- synthesis-context-lifecycle / synthesis-daily-rituals — those skills commit their own changes at the point of modification (the primary mechanism). checkpoint_sync is the safety net beneath them;
repo_sync_check.py is the final verification gate at day-end.
Command Reference
repo_sync_check.py [--workspace W] [--max-depth N] [--quiet] [--json]
[--dirty-only] [--alert] [--speak] [--notify]
[--report-dir D] [--no-report]
checkpoint_sync.py [--config C] [--repo PATH] [--hook] [--now]
[--no-throttle] [--dry-run] [--quiet] [--json]
[--speak] [--notify]
--speak/--notify/--alert are generic + mute-aware on both scripts.
checkpoint_sync --repo accepts any path inside a repo (resolved via git rev-parse --show-toplevel).
--hook honors the shared throttle; --now bypasses throttle + quiescence (producer mode); --no-throttle bypasses throttle only (manual button).
Design Principles
- Zero AI and zero external dependencies — Python stdlib + git CLI (PyYAML used if present, minimal built-in parser otherwise)
- LLM-agnostic — same scripts for Claude Code, Codex, Cursor, console, or manual use
- Detector never modifies; remediator modifies only the guarded auto-sync class
- Identifying names (repo, workspace, client) never on audio/banner surfaces — counts and pointers only
- Mutation is event-driven; only reads may poll
- Fail closed, never force — empty guard config disables; divergence/hook-failures stop and alert
- Composable — exit codes, JSON output, shared state files
Changelog
- 2.0.0 (2026-07-08): three-layer redesign. Generic-only audio/banner (confidentiality rule),
~/.synthesis/quiet-audio mute flag, report files + history, remediation hints, new checkpoint_sync.py (event-driven auto-commit/push: runtime remote guard, quiescence, shared throttle, ff-only push, distinct author, stale-lock detection), synthesis-console integration contract, scheduled-mutation explicitly disallowed. Origin: 2026-07-08 design review (lesson: alert-channel confidentiality + event-driven checkpoints).
- 1.1.0: detector + count-only audio alerts.