sync-to-git Skill
Spec:
~/.claude/docs/specs/sync-to-git-spec.mdVersion: 1.0.0
Purpose
Automate Git synchronization operations following corporate protocols (C04, C07).
Quick Reference
| Command | Description |
|---------|-------------|
| sync-status | Show repository state |
| sync-fetch | Fetch from remotes |
| sync-pull | Pull with conflict detection |
| sync-push | Push with branch protection |
| sync-commit | Commit with Co-Author |
| sync-pr | Create/check PRs |
| sync-merge | Merge after approval |
Execution Flow
1. Pre-flight Checks
Before any operation:
# Check if in git repo
git rev-parse --git-dir >/dev/null 2>&1 || exit 1
# Check if in worktree (REGRA 7 C04)
WORKTREE_PATH=$(git rev-parse --show-toplevel)
MAIN_REPO=$(git worktree list | head -1 | awk '{print $1}')
if [ "$WORKTREE_PATH" = "$MAIN_REPO" ]; then
# Em repo principal - verificar se há worktrees disponíveis
CURRENT_BRANCH=$(git branch --show-current)
if [[ "$CURRENT_BRANCH" != "main" && "$CURRENT_BRANCH" != "master" ]]; then
# VIOLAÇÃO: está em branch feature no repo principal
echo '{"jsonrpc":"2.0","error":{"code":-32011,"message":"Worktree required","data":{"instructions":"Create worktree: git worktree add .worktrees/$(git branch --show-current)"}}}' >&2
exit 1
fi
fi
2. sync-status
# Get comprehensive status
BRANCH=$(git branch --show-current)
REMOTE=$(git config --get branch.$BRANCH.remote || echo "origin")
UPSTREAM=$(git rev-parse --abbrev-ref @{u} 2>/dev/null || echo "none")
# Count ahead/behind
if [ "$UPSTREAM" != "none" ]; then
AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo "0")
BEHIND=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo "0")
else
AHEAD="?"
BEHIND="?"
fi
# Uncommitted changes
UNCOMMITTED=$(git status --porcelain | grep -c "^[MADRC]" || echo "0")
UNTRACKED=$(git status --porcelain | grep -c "^??" || echo "0")
# Remote info
REMOTE_URL=$(git remote get-url $REMOTE 2>/dev/null || echo "none")
# Detect provider
if [[ "$REMOTE_URL" == *"github.com"* ]]; then
PROVIDER="github"
elif [[ "$REMOTE_URL" == *"bitbucket.org"* ]]; then
PROVIDER="bitbucket"
else
PROVIDER="unknown"
fi
echo "Branch: $BRANCH"
echo "Remote: $REMOTE ($PROVIDER)"
echo "Ahead: $AHEAD | Behind: $BEHIND"
echo "Uncommitted: $UNCOMMITTED | Untracked: $UNTRACKED"
3. sync-fetch
git fetch --all --prune
4. sync-pull
# Check for uncommitted changes
if [ -n "$(git status --porcelain)" ]; then
echo '{"jsonrpc":"2.0","error":{"code":-32013,"message":"Uncommitted changes","data":{"instructions":"Commit or stash changes first: git stash"}}}' >&2
exit 1
fi
# Detect potential conflicts
git fetch origin
CONFLICTS=$(git diff --name-only HEAD...origin/$(git branch --show-current) 2>/dev/null)
if [ -n "$CONFLICTS" ]; then
echo "Potential conflicts in: $CONFLICTS"
# Continue anyway with merge
fi
git pull --rebase
5. sync-push
BRANCH=$(git branch --show-current)
PROTECTED="main master develop"
# Branch protection gate
if [[ " $PROTECTED " =~ " $BRANCH " ]]; then
echo '{"jsonrpc":"2.0","error":{"code":-32010,"message":"Branch protection","data":{"instructions":"Cannot push directly to protected branch. Create PR instead."}}}' >&2
exit 1
fi
# Check if upstream exists
if ! git rev-parse --abbrev-ref @{u} >/dev/null 2>&1; then
git push -u origin $BRANCH
else
git push
fi
6. sync-commit
# Usage: sync-commit "type(scope): description"
MESSAGE="$1"
CO_AUTHOR="Co-Authored-By: Claude-Code (Anthropic/Claude-4-Sonnet) <noreply+claude-code@anthropic.com>"
# Validate conventional commits
if ! echo "$MESSAGE" | grep -qE "^(feat|fix|docs|chore|refactor|test|style|perf|ci|build)(\(.+\))?:"; then
echo "Warning: Message doesn't follow Conventional Commits format"
fi
# Check if there are staged changes
if [ -z "$(git diff --cached --name-only)" ]; then
echo '{"jsonrpc":"2.0","error":{"code":-32013,"message":"No staged changes","data":{"instructions":"Stage changes first: git add <files>"}}}' >&2
exit 1
fi
git commit -m "$(cat <<EOF
$MESSAGE
$CO_AUTHOR
EOF
)"
7. sync-pr (GitHub)
# Create PR
BRANCH=$(git branch --show-current)
TITLE="$1"
# Base: argumento explicito > base PERSISTIDA pelo worktree > fail-closed.
# `${2:-main}` era um default fixo -- num repo cuja default e `develop`, ou
# num PR empilhado, o PR seria aberto contra a branch errada.
BASE_REF="${2:-}"
[ -n "$BASE_REF" ] || BASE_REF=$(cat "$(git rev-parse --git-dir)/BASE_REF" 2>/dev/null) || BASE_REF=""
[ -n "$BASE_REF" ] || {
echo '{"jsonrpc":"2.0","error":{"code":-32018,"message":"Base indeterminate","data":{"instructions":"Pass the base explicitly or recreate the worktree via worktree-policy (which persists BASE_REF)"}}}' >&2
exit 1; }
# Ensure branch is pushed
git push -u origin $BRANCH 2>/dev/null
# Create PR using gh CLI
# --base OBRIGATORIO: sem ele o gh assume a branch default do repo.
gh pr create --base "$BASE_REF" --title "$TITLE" --body "$(cat <<EOF
## Summary
[Description of changes]
## Test Plan
- [ ] Tests pass
- [ ] Manual testing done
---
Co-Authored-By: Claude-Code (Anthropic/Claude-4-Sonnet) <noreply+claude-code@anthropic.com>
EOF
)"
# Get PR URL
PR_URL=$(gh pr view --json url -q '.url')
echo "PR created: $PR_URL"
8. sync-pr status
# Check PR status
PR_STATUS=$(gh pr view --json state,reviews,statusCheckRollup)
echo "$PR_STATUS"
9. sync-merge
# Pre-merge checks
PR_STATE=$(gh pr view --json state -q '.state')
if [ "$PR_STATE" != "OPEN" ]; then
echo "PR is not open"
exit 1
fi
# Check reviews -- CONTAR nao basta: `reviews|length > 0` aceita um
# CHANGES_REQUESTED como se fosse aprovacao. Avalie o VEREDITO.
DECISION=$(gh pr view --json reviewDecision -q '.reviewDecision // ""')
if [ "$DECISION" = "CHANGES_REQUESTED" ]; then
echo '{"jsonrpc":"2.0","error":{"code":-32017,"message":"Changes requested","data":{"instructions":"Address the findings, then request re-review"}}}' >&2
exit 1
fi
REVIEWS=$(gh pr view --json reviews -q '.reviews | length')
if [ "${REVIEWS:-0}" -eq 0 ]; then
echo '{"jsonrpc":"2.0","error":{"code":-32015,"message":"Review pending","data":{"instructions":"Wait for review or delegate to code-reviewer agent"}}}' >&2
exit 1
fi
# Check CI -- TODOS os contexts, nao so o primeiro.
# `statusCheckRollup[0]` e fail-open: o check[0] verde autorizava o merge
# ainda que qualquer outro estivesse vermelho.
ROLLUP=$(gh pr view --json statusCheckRollup -q '.statusCheckRollup') || {
echo '{"jsonrpc":"2.0","error":{"code":-32016,"message":"CI state unreadable"}}' >&2
exit 1; }
TOTAL=$(printf '%s' "$ROLLUP" | jq 'length')
PENDING=$(printf '%s' "$ROLLUP" | jq '[.[]?|select((.conclusion//.state)|IN("PENDING","QUEUED","IN_PROGRESS","EXPECTED"))]|length')
FAILED=$(printf '%s' "$ROLLUP" | jq '[.[]?|select((.conclusion//.state) as $s
| ($s|IN("SUCCESS","NEUTRAL","SKIPPED","PENDING","QUEUED","IN_PROGRESS","EXPECTED"))|not)]|length')
# ZERO checks nao e "CI verde": e ausencia de sinal. O contador de falhas
# daria 0 e liberaria o merge sem nenhuma verificacao ter rodado.
if [ "${TOTAL:-0}" -eq 0 ]; then
echo '{"jsonrpc":"2.0","error":{"code":-32016,"message":"No CI checks reported","data":{"instructions":"Confirm CI is configured and has reported, or merge explicitly out-of-band"}}}' >&2
exit 1
fi
if [ "${PENDING:-1}" -ne 0 ]; then
echo '{"jsonrpc":"2.0","error":{"code":-32016,"message":"CI still running","data":{"instructions":"Wait for all checks to report"}}}' >&2
exit 1
fi
if [ "${FAILED:-1}" -ne 0 ]; then
echo '{"jsonrpc":"2.0","error":{"code":-32016,"message":"CI failed","data":{"instructions":"Fix CI failures before merge"}}}' >&2
exit 1
fi
# Merge -- metodo resolvido por autoridade LOCAL do repo (Step 9 de
# rules/pr-governance-unified.md). `--merge` incondicional contraria os repos
# que declaram squash e e REJEITADO por repo squash-only.
# ⚠️ A variavel precisa ser DEFINIDA aqui: usa-la sem origem apenas move o
# problema, porque um valor vazio produz `gh pr merge --` e um valor arbitrario
# nao foi validado contra a capacidade efetiva da base.
# Carregado do Step 9a (resolve+persiste). Nao "rode o Step 9 e exporte": export
# nao atravessa shell, e o Step 9 completo ja mergeia -- seria um segundo merge.
MERGE_METHOD=$(cat "$(git rev-parse --git-dir)/MERGE_METHOD" 2>/dev/null) || MERGE_METHOD=""
[ -n "$MERGE_METHOD" ] || {
echo '{"jsonrpc":"2.0","error":{"code":-32019,"message":"Merge method unresolved","data":{"instructions":"Run Step 9a (resolve + persist) in pr-governance-unified; it writes $(git rev-parse --git-dir)/MERGE_METHOD"}}}' >&2
exit 1; }
case "$MERGE_METHOD" in
merge|squash|rebase) ;;
*) echo '{"jsonrpc":"2.0","error":{"code":-32019,"message":"Invalid merge method","data":{"instructions":"MERGE_METHOD must be exactly merge, squash or rebase"}}}' >&2
exit 1;;
esac
# PIN do head auditado: sem ele este merge aceita um push chegado DEPOIS da
# checagem, e o commit mergeado nunca passou pelos checks que autorizaram.
REVIEWED_OID=$(cat "$(git rev-parse --git-dir)/REVIEWED_OID" 2>/dev/null) || REVIEWED_OID=""
case "$REVIEWED_OID" in
[0-9a-f][0-9a-f]*) ;;
*) echo '{"jsonrpc":"2.0","error":{"code":-32020,"message":"Audited head OID missing","data":{"instructions":"Run Step 7 in pr-governance-unified; it writes $(git rev-parse --git-dir)/REVIEWED_OID"}}}' >&2
exit 1 ;;
esac
gh pr merge --"$MERGE_METHOD" --match-head-commit "$REVIEWED_OID"
Safety Gates
Branch Gate
- NEVER
push --forceto protected branches - NEVER merge directly (always via PR)
- WARN on rebase of published branches
Worktree Gate (C04)
- If in main repo on feature branch → ERROR
- Must create worktree first
Conflict Gate
- Detect conflicts before pull/merge
- List affected files
- Require manual resolution
PR Gate (C07)
- Verify PR exists before merge
- Verify reviews received
- Verify CI passed
- Respect TTL (30min default)
Error Codes
| Code | Name | Recovery | |------|------|----------| | -32010 | Branch protection | Use PR instead | | -32011 | Worktree required | Create worktree | | -32012 | Conflict detected | Resolve manually | | -32013 | Uncommitted changes | Commit or stash | | -32014 | PR not found | Create PR first | | -32015 | Review pending | Wait or delegate | | -32016 | CI failed | Fix failures |
Integration Notes
With C04 (Git Worktree Protocol)
- Always check if in worktree before modifications
- Respect REGRA 7: NEVER switch branches in main repo
- Use
cd .worktrees/{name}instead ofgit checkout
With C07 (PR Review Protocol)
- Create PR after push (never merge direct)
- Wait for review with TTL
- Analyze review feedback
- Auto-delegate if timeout
Examples
# Full workflow
sync-to-git status
sync-to-git fetch
sync-to-git pull
# ... make changes ...
git add .
sync-to-git commit "feat(auth): add JWT validation"
sync-to-git push
sync-to-git pr create "feat(auth): add JWT validation"
# ... wait for review ...
sync-to-git pr status
sync-to-git merge
Skill Version: 1.0.0 | Protocols: C04, C07, C06