mcpField
S
swain-sync

swain-sync

github.com/cristoslc開発288 回インストール26 週間前に更新
リポジトリ

swain-sync について

リポジトリの変更を上流と同期し、ステージング、検査、コミット、プッシュするワークフロースキルです。セッション、秘密情報、Gitignore パターン、成果物、コミットフックを確認する手順を含みます。

主な機能

  1. 01アクティブなセッションの確認
  2. 02上流の取得とリベースまたはマージ
  3. 03秘密情報を除外した変更のステージング
  4. 04Gitignore とリポジトリ成果物の確認
  5. 05Conventional Commit 形式のメッセージ生成
  6. 06変更のコミットとプッシュ

活用シーン

  1. 01コミット前にブランチを上流と同期
  2. 02検査を経てリポジトリ更新をステージング・コミット

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/cristoslc/swain --skill swain-sync

インストール後、Claude Code で/swain-syncを入力して読み込まれたか確認します。

同梱ファイルscripts/swain-trunk.shscripts/test-swain-trunk.sh

詳細

状態を変更する操作を行う前に、アクティブなセッションがあるか確認してください:

REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
bash "$REPO_ROOT/.agents/bin/swain-session-check.sh" 2>/dev/null

JSON 出力の "status" が "active" 以外の場合は、オペレーターに「アクティブなセッションがありません。/swain-init で開始しますか?」と伝えてください。オペレーターが断った場合は続行してください。

確認が必要であると明示された判断ポイントを除き、確認を求めずに以下の手順を順番に実行してください。

メインの会話スレッドを整理された状態に保つため、これをサブエージェントに委任してください。サブエージェントはスキルファイルを直接読めないため、これらの指示の全文をエージェントへのプロンプトに含めてください。

Step 1 — ワークツリーのコンテキストを検出し、アップストリームを取得してリベースする

まず、git のリンクされたワークツリー内で実行しているかどうかを検出してください:

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)
TRUNK=$(bash "$REPO_ROOT/.agents/bin/swain-trunk.sh")

IN_WORKTREE=yes は、現在のディレクトリがリンクされたワークツリー(例:.worktrees/agent-abc123)内にあることを意味します。このフラグは Step 3、6、セッションのブックマーク手順で使用してください。

次に、現在のブランチにアップストリームの追跡ブランチがあるか確認してください:

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

アップストリームがある場合は、ステージングやコミットを行う前に、フェッチしてリベースし、アップストリームの変更を取り込んでください:

git fetch origin

ローカルの変更(作業ツリーがダーティな状態)がある場合は、まずそれらをスタッシュしてください。他のワークツリーのスタッシュに干渉しないよう、対象を指定して pop してください(SPEC-252:共有スタッシュは競合の原因になります):

BRANCH=$(git rev-parse --abbrev-ref HEAD)
STASH_MSG="swain-sync: auto-stash [$BRANCH]"
git stash push -m "$STASH_MSG"
STASH_REF=$(git stash list --format='%gd %s' | grep -F "$STASH_MSG" | head -1 | cut -d' ' -f1)
git --no-pager rebase origin/$BRANCH
git stash pop "$STASH_REF"

スタッシュを pop した後にリベースで競合が発生した場合は、中止して報告してください:

git rebase --abort  # if rebase itself conflicts
git stash pop "$STASH_REF"  # recover stashed changes (targeted, not bare pop)

競合しているファイルをユーザーに示して、停止してください。強制プッシュしたり、変更を破棄したりしないでください。

アップストリームがなく(@{u} がエラーを返し)、かつ IN_WORKTREE=yes の場合、ワークツリーブランチにはリモート追跡ブランチがありません。ブランチ作成後に反映された変更とエージェントの変更を統合するため、トランクブランチをマージしてください:

git fetch origin
git merge "origin/$TRUNK" --no-edit

マージで競合が発生した場合は、報告して停止してください。自動解決を試みないでください。

origin をフェッチできない場合は、フェッチ/リベースをスキップして Step 2 に進んでください。

アップストリームがなく、かつ IN_WORKTREE=no(メインワークツリー、新しいブランチ)の場合は、このステップ全体をスキップしてください。

Step 2 — 作業ツリーを確認する

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

作業ツリーが完全にクリーンで、プッシュするものもない場合は、その旨を報告して停止してください。

Step 3 — 変更をステージングする

シークレットに見えるファイル(.env、*.pem、*_rsa、credentials.*、secrets.*)を特定してください。該当するものがある場合はユーザーに警告し、ステージング対象から除外してください。

変更ファイルが 10 個以下の場合(シークレットを除く)、個別にステージングしてください:

git add file1 file2 ...

変更ファイルが 10 個を超える場合は、すべてをステージングしてからシークレットをステージング解除してください:

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

Step 3.5 — Gitignore の確認

コミットする前に、.gitignore が適切か確認してください。このステップは必須です。該当するパターンが不足している場合は停止し、続行する前にユーザーに .gitignore を修正してもらってください。

1. ファイルの存在を確認する

リポジトリのルートに .gitignore ファイルがない場合:

停止:.gitignore ファイルが見つかりません。コミットする前に作成してください。作成しないと、シークレット、ビルド成果物、OS ファイルが git の履歴に含まれる可能性があります。 最小限の初期設定:curl -sL https://www.toptal.com/developers/gitignore/api/macos,linux,node,python > .gitignore

実行を停止してください。 コミットしないでください。

2. 該当するパターンを検出する

ディスク上に実際に存在するものに基づき、このリポジトリに該当するパターンを確認してください:

パターン該当する条件
.env.env.example が存在する、または追跡対象か未追跡かを問わず .env または .env.* ファイルが存在する(.env.example を除く)、または package.json か requirements.txt に dotenv が記載されている
node_modules/リポジトリのルートまたはサブディレクトリに package.json が存在する
__pycache__/*.py ファイルが存在する
*.pyc__pycache__/ と同じ
.DS_Storeリポジトリが macOS 上にある(uname が Darwin を返す)

該当する各パターンについて、.gitignore に含まれているか(完全一致または部分一致)を確認してください。不足しているものを一覧にしてください。

3. ブロックするかどうかを判断する
  • 関連するパターンに不足がない場合:このステップでは何も行いません。ステップ 3.7 に進みます。

  • 関連するパターンに不足がある場合:停止して、次のように報告します。

    STOP: .gitignore にこのリポジトリに関連するパターンがありません:

    • .env — .env.example が見つかりました。これがないと、ローカルの .env ファイルがコミットされる可能性があります
    • node_modules/ — package.json が見つかりました

    コミットする前に、不足しているパターンを追加してください: echo ".env" >> .gitignore echo "node_modules/" >> .gitignore

    特定のパターンのチェックを恒久的に抑制する(意図的な省略)には、.gitignore にコメントを追加してください:

    swain-sync: allow .env

    実行を停止してください。 ユーザーが解決するまでコミットしないでください。

4. スキップのロジック

.gitignore に特定のパターンに対する # swain-sync: allow <pattern> が含まれている場合、そのパターンは意図的に省略されているものとして扱い、警告しないでください。

ステップ 3.7 — ADR 準拠チェック

変更されたファイルに swain アーティファクト(docs/spec/、docs/epic/、docs/vision/、docs/research/、docs/journey/、docs/persona/、docs/runbook/、docs/design/、docs/train/)が含まれる場合は、変更された各アーティファクトに対して ADR 準拠チェックを実行します。

bash "$REPO_ROOT/.agents/bin/adr-check.sh" <artifact-path>

指摘事項のある各アーティファクト(終了コード 1 = RELEVANT の助言的な指摘事項、終了コード 2 = 対応が必要な DEAD_REF または STALE の指摘事項)について、出力を収集し、すべてのチェックが完了した後に、統合した警告を 1 件提示します。

ADR 準拠:N 個のアーティファクトに対応が必要な可能性のある指摘事項があります。 <指摘事項の要約>

このステップは助言のみです。警告は出しますが、コミットをブロックすることはありません。いずれの場合もステップ 4 に進みます。

adr-check.sh スクリプトが見つからない場合、または終了コード 3 で失敗した場合は、何も出さずにスキップします。このチェックは swain-design がインストールされているリポジトリでのみ利用できます。

ステップ 3.8 — 設計の乖離チェック

引数なしで design-check.sh を実行し(有効な DESIGN をすべてスキャン)、設計とコードの乖離を検出します。

bash "$REPO_ROOT/.agents/bin/design-check.sh" 2>/dev/null

指摘事項のある各 DESIGN(STALE または BROKEN の sourcecode-refs)について、出力を収集し、チェックが完了した後に、統合した警告を 1 件提示します。

設計の乖離:N 個の DESIGN に古い、または壊れた sourcecode-refs があります。 <指摘事項の要約>

このステップは助言のみです。警告は出しますが、コミットをブロックすることはありません。いずれの場合もステップ 4 に進みます。

design-check.sh スクリプトが見つからない場合、または終了コード 2 で失敗した場合は、何も出さずにスキップします。このチェックは swain-design がインストールされているリポジトリでのみ利用できます。

ステップ 3.85 — README の整合性確認(ADR-023)

README.md が存在し、ステージされた変更に README の記載内容に影響を与える可能性のあるアーティファクトやコードが含まれている場合は、軽量な乖離チェックを実行します。

REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
if [ -f "$REPO_ROOT/README.md" ]; then
  STAGED_ARTIFACTS=$(git diff --cached --name-only | grep -E "^docs/(spec|epic|vision|design)/" || true)
  STAGED_CODE=$(git diff --cached --name-only | grep -vE "^docs/" | grep -E "\.(py|js|ts|sh|go|rs)$" || true)
  if [ -n "$STAGED_ARTIFACTS" ] || [ -n "$STAGED_CODE" ]; then
    echo "README_CHECK: staged changes touch artifacts or code — verify README alignment"
  fi
fi

このステップは助言のみです。コミットで README の記載内容に影響する可能性のあるコードやアーティファクトを変更した際、エージェントに README の正確性を確認するよう促します。コミットをブロックすることはありません。README の完全なゲートは swain-release(ステップ 5.7)にあります。

ステップ 3.9 — アーティファクト番号の重複チェック

detect-duplicate-numbers.sh を実行して、マージや並行する worktree 作業によって発生したアーティファクト番号の重複を検出します。

bash "$REPO_ROOT/.agents/bin/detect-duplicate-numbers.sh" 2>/dev/null

重複が見つかった場合(終了コード 1)、このステップはブロッキングです。解決するまでコミットしないでください。

アーティファクト番号の重複が検出されました: <重複の出力>

自動修正を利用できます:fix-collisions.sh を実行して、新しいアーティファクトの番号を振り直してください。 または、先に変更をプレビューするには fix-collisions.sh --dry-run を実行してください。

fix-collisions.sh を自動実行するか確認します。オペレーターが承諾した場合は、それを実行し、変更をステージして、ステップ 4 に進みます。オペレーターが断った場合は、実行を停止してください。番号が重複したままコミットしないでください。

スクリプトが見つからない場合は、何も出さずにスキップします。このチェックは swain-design がインストールされているリポジトリでのみ利用できます。

ステップ 4 — コミットメッセージを生成する

ステージ済みの差分(git --no-pager diff --cached)を読み、次の条件に従ってコミットメッセージを作成します。

  • 変更の主な種類に合ったConventional Commits のプレフィックスで始めます。
    • feat — 新機能または新しい機能
    • fix — バグ修正
    • docs — ドキュメントのみの変更
    • chore — 動作を変更しないツール、依存関係、設定の変更
    • refactor — 動作を変更しない構造の変更
    • test — テストの追加または修正
  • 簡潔な命令形の件名行を含めます(72 文字以内)。
  • 差分が些細でない場合は、単に「何を」ではなく「なぜ」を要約した短い本文を 2~5 行追加します。
  • モデル参加者ごとに Co-Authored-By トレーラーを 1 つ追加します。モデル名はシステムプロンプトから特定します(例:Claude Opus 4.6、Gemini 2.5 Pro、GLM-5.1)。モデルの識別情報が得られない場合は、代わりに AI Assistant を使用します。セッションに別のモデルで動作するサブエージェントが含まれていた場合は、モデルごとにトレーラーを 1 つ含めます。最初にスーパーバイザーを記載し、続けて各サブエージェントのモデルをそれぞれ独立した行に記載します。モデル名をハードコードしないでください。

例(単一モデル):

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 <[email protected]>

例(スーパーバイザーとサブエージェント):

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: GLM-5.1 <noreply@unknown>
Co-Authored-By: Kimi-K2.5 <noreply@unknown>

ステップ 4.5 — コミット前フックを確認する

コミット前フックが設定されているか確認します。

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

no-hooks の場合は、一度だけ警告を出力します(同じセッション内で既に警告している場合は繰り返しません)。

WARN: コミット前フックが設定されていません。セキュリティスキャンを設定するには、/swain-init を実行してください。

いずれの場合もステップ 5 に進みます。フックは推奨されていますが、必須ではありません。

ステップ 5 — コミットする

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

複数行のメッセージをエスケープせずにシェルで保持できるよう、ヒアドキュメントを使用します。

重要: --no-verify は絶対に使用しないでください。コミット前フックがインストールされている場合は、必ず実行する必要があります。回避してはいけません。

コミット前フックによって拒否され、コミットに失敗した場合:

  1. 出力を解析して、失敗したフックと検出された内容を特定します
  2. 検出結果を明確に示します。

    コミット前フックが失敗しました: gitleaks:2 件の検出(検出内容を記述)

    検出内容を修正し、もう一度 /swain-sync を実行してください。 誤検知を抑制するには、.gitleaksignore に追加してください。

  3. 実行を停止します。プッシュしてはいけません。自動で再試行してはいけません。

ステップ 6 — プッシュする

IN_WORKTREE=yes の場合: 上流の変更をフェッチして統合してから、ワークツリーのコミットを開発ブランチである trunk に直接プッシュします。

# Always fetch and merge trunk before the first push attempt.
# This prevents avoidable rejections when trunk moved since the worktree was created.
git fetch origin
git merge "origin/$TRUNK" --no-edit || {
  echo "Merge conflict with upstream trunk. Resolve before pushing."
  git merge --abort
  exit 1
}

# Link safety — scan changed files for worktree-specific path links (SPEC-216/217/218)
# Runs after merge, before push, so any auto-fixes land in the same push.
DETECT_SCRIPT="$(git rev-parse --show-toplevel)/.agents/bin/detect-worktree-links.sh"
RESOLVE_SCRIPT="$(git rev-parse --show-toplevel)/.agents/bin/resolve-worktree-links.sh"
if [ -x "$DETECT_SCRIPT" ]; then
  MERGE_BASE=$(git merge-base HEAD "origin/$TRUNK" 2>/dev/null || true)
  CHANGED_FILES=$([ -n "$MERGE_BASE" ] && git diff --name-only "$MERGE_BASE" HEAD 2>/dev/null || true)
  if [ -n "$CHANGED_FILES" ]; then
    if ! echo "$CHANGED_FILES" | xargs "$DETECT_SCRIPT" --repo-root "$(git rev-parse --show-toplevel)" > /dev/null 2>&1; then
      echo "[link-safety] Found suspicious links. Resolving automatically..."
      if ! echo "$CHANGED_FILES" | xargs "$RESOLVE_SCRIPT" --repo-root "$(git rev-parse --show-toplevel)"; then
        echo "[link-safety] UNRESOLVABLE links remain — merge aborted. Fix before pushing."
        exit 1
      fi
      git add -u
      ORIG_MSG=$(git log -1 --pretty=%B)
      git commit --amend -m "$ORIG_MSG

[link-safety: auto-resolved worktree path links]"
    fi
  fi
fi

MAX_RETRIES=3
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_RETRIES ]; do
  git push origin "HEAD:$TRUNK" && break
  ATTEMPT=$((ATTEMPT + 1))
  if [ $ATTEMPT -lt $MAX_RETRIES ]; then
    echo "Push rejected (attempt $ATTEMPT/$MAX_RETRIES). Fetching and re-merging..."
    git fetch origin
    git merge "origin/$TRUNK" --no-edit || {
      echo "Merge conflict during retry. Reporting to operator."
      git merge --abort
      break
    }
    # Run tests on the merged result before retrying push
    # (project-specific test command — detect from project structure)
  fi
done

if [ $ATTEMPT -ge $MAX_RETRIES ]; then
  echo "Push failed after $MAX_RETRIES attempts. Reporting to operator."
fi

ブランチ保護ルールまたは必須レビューが原因でプッシュが拒否された場合(拒否メッセージを確認)、代わりに PR を作成します。

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 "$TRUNK" --head "$BRANCH" --title "$SUBJECT" --body "$BODY"

PR の URL を報告します。プッシュを再試行してはいけません。その後、以下のワークツリー整理に進みます。

プッシュまたは PR の作成が成功したら、ロックファイルを整理可能な状態にします(SPEC-249、EPIC-056)。swain-sync 自体はワークツリーを削除しません。整理は bin/swain に委ねられます。これはランタイム終了後に実行され、整理前にコミットハッシュを検証できます。

REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Mark lockfile ready_for_cleanup if lockfile library is available
LOCKFILE_SCRIPT="$REPO_ROOT/.agents/bin/swain-lockfile.sh"
if [ -f "$LOCKFILE_SCRIPT" ]; then
  bash "$LOCKFILE_SCRIPT" mark-ready "$BRANCH" 2>/dev/null || true
  echo "Worktree marked ready_for_cleanup — bin/swain will prune after session ends."
else
  echo "Note: lockfile library not found — worktree cleanup must be done manually."
fi

swain-sync から git worktree remove を呼び出してはいけません。ランタイムは自身のワークツリーディレクトリから離れられず(多くのランタイムでは CWD が永続化されません)、実行中のランタイムが使用しているワークツリーを削除すると ENOENT エラーが発生します。


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

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

履歴の分岐が原因でプッシュに失敗した場合(ステップ 1 のリベース後には起こらないはずですが、念のため):

git --no-pager pull --rebase
git push

ステップ 7 — 確認する

git --no-pager status と git --no-pager log --oneline -3 を実行し、プッシュが反映されたことを確認して、最終状態をユーザーに示します。確認を求めず、結果を報告してください。

インデックスの再構築(SPEC-047)

コミット前(ステージング後、ステップ 5 の前)に、アーティファクトのインデックスファイル(list-*.md)が古くなっていないか確認します。再構築スクリプトが存在する場合は、変更がステージされた各アーティファクトタイプについて実行します。

REBUILD_SCRIPT="$REPO_ROOT/.agents/bin/rebuild-index.sh"
if [[ -x "$REBUILD_SCRIPT" ]]; then
    # Detect which types had staged changes
    for type in spec epic initiative spike adr persona runbook design vision journey train; 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

これにより、セッションのコミットが反映される時点でインデックスが最新の状態になります。

セッションのブックマーク

プッシュが成功したら、ブックマークを更新します。メインワークツリーとリンクされたワークツリーの両方で動作するよう、検索ルートには $REPO_ROOT(ステップ 1 で設定)を使用します。

bash "$REPO_ROOT/.agents/bin/swain-bookmark.sh" "Pushed {n} commits to {branch}"

コメント