# Swain Sync

> Fetch upstream, rebase, stage all changes, run ADR compliance check on modified artifacts, generate a descriptive commit message from the diff, commit, and push to the current branch's upstream. Handles merge conflicts by preferring local changes for config/project files and upstream for scaffolding.

- Skill: `cristoslc/swain-sync-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cristoslc/swain-sync-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cristoslc/swain-sync-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: cristoslc (https://skillmd.com/u/cristoslc)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cristoslc/swain-sync-2

---

<!-- swain-model-hint: sonnet, effort: low -->

Run through the following steps in order without pausing for confirmation unless a decision point is explicitly marked as requiring one.

Delegate this to a sub-agent so the main conversation thread stays clean. Include the full text of these instructions in the agent prompt, since sub-agents cannot read skill files directly.

## Step 1 — Detect worktree context and fetch/rebase upstream

First, detect whether you are running in a git linked worktree:

```bash
GIT_COMMON=$(git rev-parse --git-common-dir)
GIT_DIR=$(git rev-parse --git-dir)
IN_WORKTREE=$( [ "$GIT_COMMON" != "$GIT_DIR" ] && echo "yes" || echo "no" )
REPO_ROOT=$(git rev-parse --show-toplevel)
```

`IN_WORKTREE=yes` means the current directory is inside a linked worktree (e.g., `.claude/worktrees/agent-abc123`). Use this flag in Steps 3, 6, and the session bookmark step.

Next, check whether the current branch has an upstream tracking branch:

```bash
git --no-pager rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null
```

If there is an upstream, fetch and rebase to incorporate upstream changes BEFORE staging or committing:

```bash
git fetch origin
```

If there are local changes (dirty working tree), stash them first:

```bash
BRANCH=$(git rev-parse --abbrev-ref HEAD)
git stash push -m "swain-sync: auto-stash [$BRANCH]"
git --no-pager rebase origin/$BRANCH
git stash pop
```

If the rebase has conflicts after stash pop, abort and report:

```bash
git rebase --abort  # if rebase itself conflicts
git stash pop       # recover stashed changes
```

Show the user the conflicting files and stop. Do not force-push or drop changes.

If there is no upstream (`@{u}` returns an error) **and** `IN_WORKTREE=yes`, the worktree branch has no remote tracking counterpart. Rebase onto `origin/main` so the commits apply cleanly as a fast-forward:

```bash
git fetch origin
git rebase origin/main
```

If `origin` cannot be fetched, skip fetch/rebase and proceed to Step 2.

If there is no upstream **and** `IN_WORKTREE=no` (main worktree, new branch), skip this step entirely.

## Step 2 — Survey the working tree

```bash
git --no-pager status
git --no-pager diff          # unstaged changes
git --no-pager diff --cached # already-staged changes
```

If the working tree is completely clean and there is nothing to push, report that and stop.

## Step 3 — Stage changes

Identify files that look like secrets (`.env`, `*.pem`, `*_rsa`, `credentials.*`, `secrets.*`). If any are present, warn the user and exclude them from staging.

**If there are 10 or fewer changed files** (excluding secrets), stage them individually:

```bash
git add file1 file2 ...
```

**If there are more than 10 changed files**, stage everything and then unstage secrets:

```bash
git add -A
git reset HEAD -- <secret-file-1> <secret-file-2> ...
```

## Step 3.5 — Gitignore check

Before committing, verify `.gitignore` hygiene. This step is advisory — it warns but never blocks the commit.

1. **Check existence:** If no `.gitignore` file exists in the repo root, warn:
   > WARN: No `.gitignore` file found. Consider creating one to avoid tracking build artifacts, secrets, and OS files.

2. **Check common patterns:** If `.gitignore` exists, check whether these commonly ignored patterns are present (exact match or substring):
   - `.env`
   - `node_modules/`
   - `__pycache__/`
   - `.DS_Store`
   - `*.pyc`

   For each missing pattern, collect it. If any are missing, warn:
   > WARN: `.gitignore` is missing common patterns: `.env`, `node_modules/`. Consider adding them.

   If all patterns are present (or none are relevant to the repo), this step is silent.

3. Continue to Step 4 regardless of warnings.

## Step 3.7 — ADR compliance check

If modified files include any swain artifacts (`docs/spec/`, `docs/epic/`, `docs/vision/`, `docs/research/`, `docs/journey/`, `docs/persona/`, `docs/runbook/`, `docs/design/`), run an ADR compliance check against each modified artifact:

```bash
bash skills/swain-design/scripts/adr-check.sh <artifact-path>
```

For each artifact with findings (exit code 1 — DEAD_REF or STALE), collect the output and present a single consolidated warning after all checks complete:

> ADR compliance: N artifact(s) have findings that may need attention.
> <condensed findings summary>

This step is **advisory** — it warns but never blocks the commit. Continue to Step 4 regardless.

If the `adr-check.sh` script is not found or fails with exit code 2, skip silently — the check is only available in repos with swain-design installed.

## Step 4 — Generate a commit message

Read the staged diff (`git --no-pager diff --cached`) and write a commit message that:

- Opens with a **conventional-commit prefix** matching the dominant change type:
  - `feat` — new feature or capability
  - `fix` — bug fix
  - `docs` — documentation only
  - `chore` — tooling, deps, config with no behavior change
  - `refactor` — restructuring without behavior change
  - `test` — test additions or fixes
- Includes a concise imperative-mood subject line (≤ 72 chars).
- Adds a short body (2–5 lines) summarising *why*, not just *what*, when the diff is non-trivial.
- Appends a `Co-Authored-By` trailer identifying the model that generated the commit. Use the model name from your system prompt (e.g., `Claude Opus 4.6`, `Gemini 2.5 Pro`). If you can't determine the model name, use `AI Assistant` as a fallback.

Example shape:
```
feat(terraform): add Cloudflare DNS module for hub provisioning

Operators can now point DNS at Cloudflare without migrating their zone.
Module is activated by dns_provider=cloudflare and requires only
CLOUDFLARE_API_TOKEN — no other provider credentials are validated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
```

## Step 4.5 — Pre-commit hook check

Check if pre-commit hooks are configured:

```bash
test -f .pre-commit-config.yaml && command -v pre-commit >/dev/null 2>&1 && echo "hooks-configured" || echo "no-hooks"
```

If `no-hooks`, emit a one-time warning (do not repeat if the same session already warned):
> WARN: No pre-commit hooks configured. Run `/swain-init` to set up security scanning.

Continue to Step 5 regardless — hooks are recommended but not required.

## Step 5 — Commit

```bash
git --no-pager commit -m "$(cat <<'EOF'
<generated message here>
EOF
)"
```

Use a heredoc so multi-line messages survive the shell without escaping issues.

**IMPORTANT:** Never use `--no-verify`. If pre-commit hooks are installed, they MUST run. There is no bypass.

If the commit fails because a pre-commit hook rejected it:

1. Parse the output to identify which hook(s) failed and what was found
2. Present findings clearly:
   > Pre-commit hook failed:
   >   gitleaks: 2 findings (describe what was flagged)
   >
   > Fix the findings and run `/swain-sync` again.
   > Suppress false positives: add to `.gitleaksignore`
3. **Stop execution** — do not push. Do not retry automatically.

## Step 6 — Push

**If `IN_WORKTREE=yes`:** push the worktree's commits directly to `main` rather than creating a remote worktree branch:

```bash
git push origin HEAD:main
```

If this push is rejected with a non-fast-forward error:
- Check whether the rejection message mentions branch protection rules or required reviews.
  - If **branch protection is the cause**, open a PR instead:
    ```bash
    BRANCH=$(git rev-parse --abbrev-ref HEAD)
    SUBJECT=$(git log -1 --pretty=format:'%s')
    BODY=$(git log -1 --pretty=format:'%b')
    gh pr create --base main --head "$BRANCH" --title "$SUBJECT" --body "$BODY"
    ```
    Report the PR URL. Do not retry the push. Proceed to worktree pruning below.
  - If **diverged history is the cause** (not branch protection), report the conflict and stop. Do not force-push.

After a successful push or PR creation, remove the worktree:
```bash
WORKTREE_PATH=$(git worktree list --porcelain | grep -B2 "HEAD" | awk '/worktree/{print $2}' | grep -v "$(git rev-parse --git-common-dir | sed 's|/.git$||')")
git -C "$(git rev-parse --show-toplevel 2>/dev/null || git rev-parse --git-common-dir | sed 's|/.git||')" worktree remove --force "$WORKTREE_PATH" 2>/dev/null || true
git -C "$(git rev-parse --git-common-dir | sed 's|/.git||')" worktree prune 2>/dev/null || true
```

**If `IN_WORKTREE=no`** (main worktree, normal case):

```bash
git push          # or: git push -u origin HEAD (if no upstream)
```

If push fails due to divergent history (shouldn't happen after Step 1 rebase, but as a safety net):

```bash
git --no-pager pull --rebase
git push
```

## Step 7 — Verify

Run `git --no-pager status` and `git --no-pager log --oneline -3` to verify the push landed and show the user the final state. Do not prompt for confirmation — just report the result.

## Index rebuild (SPEC-047)

Before committing (after staging, before Step 5), check whether any artifact index files (`list-*.md`) are stale. If `skills/swain-design/scripts/rebuild-index.sh` exists, run it for each artifact type that had changes staged:

```bash
REBUILD_SCRIPT="$REPO_ROOT/skills/swain-design/scripts/rebuild-index.sh"
if [[ -x "$REBUILD_SCRIPT" ]]; then
    # Detect which types had staged changes
    for type in spec epic spike adr persona runbook design vision journey; do
        if git diff --cached --name-only | grep -q "^docs/$type/"; then
            bash "$REBUILD_SCRIPT" "$type"
            git add "docs/$type/list-${type}.md" 2>/dev/null || true
        fi
    done
fi
```

This ensures the index is current when the session's commits land.

## Session bookmark

After a successful push, update the bookmark. Use `$REPO_ROOT` (set in Step 1) as the search root so this works from both main and linked worktrees:

```bash
bash "$(find "$REPO_ROOT" -path '*/swain-session/scripts/swain-bookmark.sh' -print -quit 2>/dev/null)" "Pushed {n} commits to {branch}"
```

