diff --git a/.claude/rules/template-customization.md b/.claude/rules/template-customization.md index 4f48f8b..8e62ef2 100644 --- a/.claude/rules/template-customization.md +++ b/.claude/rules/template-customization.md @@ -19,17 +19,13 @@ 1. **変更する前に**,その変更が「プロジェクト固有の都合」か「テンプレート自体の改善」かを判断する - テンプレート自体の改善(誤記修正,全プロジェクトに有益な手順改善等)であれば,テンプレートリポジトリ側で直すべきことをユーザーに伝える.プロジェクト側で先に直す場合も,テンプレート側の修正に後から追従できるよう台帳には `merge` で登録する -2. **変更後**,`.claude/template-overrides.md` の「台帳」表に 1 行追加する(既に行があるファイルは理由を最新化する) - - 書式: `| \`パス\` | 方針 | 理由 | 記録日 |`(パスはリポジトリルートからの相対パス.ディレクトリ全体なら末尾 `/`) - - 方針の選び方: - - `merge`(既定): テンプレート側の改善も今後取り込みたい.プロジェクトの改変とテンプレートの差分が別の箇所なら自動で両立する - - `keep`: テンプレートと完全に別物にした/テンプレート側の変更を取り込みたくない - - `ask`: 同期のたびに差分を見て判断したい +2. **変更後**,`.claude/template-overrides.md` の「台帳」表に 1 行追加する(既に行があるファイルは理由を最新化する).書式・方針(`keep` / `merge` / `ask`)の定義は同ファイルの「記録ルール」節に従う + - 方針は,テンプレート側の改善も今後取り込みたいなら `merge`(既定.改変箇所とテンプレートの差分が別の箇所なら自動で両立する),完全に別物にしたなら `keep`,都度判断したいなら `ask` - 理由は「なぜテンプレートと変えたか」を 1 文で書く.次回同期時の判断材料になる 3. 改変をやめてテンプレートに追従するときは,台帳から行を削除する(次回 `/sync-template` で上書き/マージされる) ## 禁止事項 (Prohibitions) - 台帳に記録せずにテンプレート由来ファイルを改変したままにしない -- 台帳のパス列・方針列の書式を崩さない(`/sync-template` が機械的に読む).方針は `keep` / `merge` / `ask` 以外を書かない +- 台帳の書式を崩さない(`/sync-template` が機械的に読む) - 台帳ファイル自体の「記録ルール」節の説明文を削除しない(他メンバー・将来の自分が読む) diff --git a/.claude/skills/set-mode/SKILL.md b/.claude/skills/set-mode/SKILL.md index c5306f0..51ca579 100644 --- a/.claude/skills/set-mode/SKILL.md +++ b/.claude/skills/set-mode/SKILL.md @@ -161,3 +161,4 @@ - team ↔ solo の切替は共有設定の変更にあたる(GUIDE_03「共有設定の扱い」).team プロジェクトでは専用 PR+他メンバー 1 名 Approve を経てマージする. - team 層ファイルのリストは `/sync-template` の「モード依存ファイル」と一致させること.どちらかを増減したら両方を更新する. - solo→team で取得する team 層ファイルはテンプレート HEAD 版.版の細かな追従は以後の `/sync-template` に任せる(`template-sync-sha` は本コマンドでは変更しない). +- team 層ファイルが `.claude/template-overrides.md`(テンプレート改変台帳)に登録されている場合,3-A.1 の上書きや 3-B.1 の削除でプロジェクトの改変が失われる.実行前に台帳を確認し,該当があればユーザーに提示して同意を得てから行う. diff --git a/.claude/skills/sync-template/SKILL.md b/.claude/skills/sync-template/SKILL.md index aebdcb0..c4af4ca 100644 --- a/.claude/skills/sync-template/SKILL.md +++ b/.claude/skills/sync-template/SKILL.md @@ -7,8 +7,9 @@ あなたはテンプレート同期の担当者です. テンプレートリポジトリから最新の変更を取り込み,必要に応じてプロジェクトのコードを修正してください. +bash ヘルパーの定義と各ファイルの個別マージ手順は本スキルの `reference.md`(`.claude/skills/sync-template/reference.md`)を参照する. -実行環境: bash(Git Bash または Unix シェル)が必要.`mktemp`, `rm -rf`, `cat`, シェル変数展開を使用する. +実行環境: bash(Git Bash または Unix シェル)が必要.`mktemp`, `rm -rf`, `cat`, `awk`, `diff`, `git merge-file`, シェル変数展開を使用する. テンプレート URL: `https://github.com/rintoHasegawa/programming-template.git` @@ -42,7 +43,7 @@ | `ask` | プロジェクトの改変内容とテンプレート側の差分を表示し,ユーザーに「上書き / マージ / スキップ」を問う | - パスが末尾 `/` で終わる行はディレクトリ指定であり,配下の全ファイルに同じ方針を適用する -- 台帳に載っているのにテンプレート側に存在しないパス(リネーム・削除済み,または記入ミス)は同期の最後に警告する +- 台帳に載っているのにテンプレート側に存在しないパス(リネーム・削除済み,または記入ミス)は同期の最後に警告する(ステップ 5.8) - 初回同期(`$LAST_SHA` 無し)では base が取れないため,`merge` は `ask` として扱う ### 台帳に無い改変の検出(安全網) @@ -51,6 +52,8 @@ 前回同期版に存在しない(テンプレートで新規追加された)パスにローカルファイルが既にある場合も同様に未登録改変として扱う(プロジェクトが独自に作ったファイルをテンプレート版で潰さない). +初回同期(`$LAST_SHA` 無し)では比較対象が無いため検出は行わない. + ## 同期対象外ファイル (Skip-on-sync Files) 以下のファイルはテンプレート紹介専用であり,テンプレートから作られた各プロジェクトには反映しない.コピー・上書き・削除のいずれも行わない. @@ -116,10 +119,12 @@ `rm -rf "$TEMP_DIR"` で一時ディレクトリを削除し,「テンプレートに新しい変更はありません.既に最新です.」と報告して終了する. - `NEW_SHA != LAST_SHA` の場合: + ```bash # A=追加, M=変更, D=削除, R=リネーム の種別付きで取得 CHANGED_ENTRIES=$(git -C "$TEMP_DIR" diff --name-status "$LAST_SHA" HEAD) ``` + で変更されたファイルを種別付きで取得する. **ファイルが存在しない場合(初回同期):** @@ -132,7 +137,7 @@ ## ステップ 4: 変更一覧をユーザーに提示 -取り込み対象のファイル一覧を種別ごとに整理してユーザーに提示する.マージ必須ファイル(`.gitignore`, `CLAUDE.md`, `docs/PROGRESS.md`, `.gitattributes`, `.claude/settings.json`)に変更がある場合は,**ユーザーが取り込み前に影響範囲を把握できるよう差分サマリーを先出しする**: +取り込み対象のファイル一覧を種別ごとに整理してユーザーに提示する.マージ必須ファイルや台帳登録ファイルに変更がある場合は,**ユーザーが取り込み前に影響範囲を把握できるよう差分サマリーを先出しする**(取得方法は `reference.md`「変更一覧の差分サマリー」.ヘルパーが未定義ならステップ 5.2 の定義を先に実行してよい): 「**テンプレートに以下の変更があります:** @@ -148,6 +153,7 @@ - `docs/PROGRESS.md`(プロジェクト固有の進捗ログ.既存があれば内容を保持し,差分があれば通知のみ) - `.gitattributes`(差分 {D} 行.処理方針をユーザーに確認) - `.claude/settings.json`(既存の hooks を保持し,テンプレート側で追加・変更された hook のみ統合) +- `.claude/template-overrides.md`(台帳の行を保持し,骨組みの差分があれば通知のみ) **📒 台帳登録済みのプロジェクト固有改変ファイルに変更があります(方針に従って処理します):** @@ -155,347 +161,38 @@ 取り込みを開始します.」 -台帳登録ファイルの抽出は,ステップ 5.2 のヘルパー定義後に次で行う(`CHANGED_ENTRIES` の A/M/R 対象のうち `override_policy` が空でないもの).ステップ 4 の提示時点でヘルパーが未定義なら,5.2 の定義をここで先に実行してよい: - -```bash -echo "$CHANGED_ENTRIES" | while IFS=$'\t' read -r status file newfile; do - case "$status" in - A|M) t="$file" ;; - R*) t="$newfile" ;; - *) continue ;; - esac - p=$(override_policy "$t") - [ -n "$p" ] && echo "$t ($p)" -done -``` - -差分サマリーは以下で取得する(該当ファイルがマージ対象かつ既存ファイルがある場合のみ出力): - -```bash -# 既存ファイル行数 -wc -l .gitignore CLAUDE.md docs/PROGRESS.md .gitattributes .claude/settings.json 2>/dev/null - -# テンプレート側の実効行数(.gitignore) -grep -vE '^\s*(#|$)' "$TEMP_DIR/.gitignore" | wc -l - -# 既存ファイルとテンプレート最新版の差分プレビュー -diff -u .gitignore "$TEMP_DIR/.gitignore" | head -30 -diff -u CLAUDE.md "$TEMP_DIR/CLAUDE.md" | head -50 -diff -u docs/PROGRESS.md "$TEMP_DIR/docs/PROGRESS.md" | head -30 -diff -u .gitattributes "$TEMP_DIR/.gitattributes" | head -30 -diff -u .claude/settings.json "$TEMP_DIR/.claude/settings.json" | head -30 -``` - ## ステップ 5: ブランチ作成とファイル反映 ### 5.1 ブランチ作成 `git checkout -b chore/sync-template` でブランチを作成する.既に同名のブランチが存在する場合は削除してから作り直す. -### 5.2 マージ必須ファイル判定ヘルパー +### 5.2 判定ヘルパーの定義 -以降の処理で利用する判定関数を定義する: +`reference.md`「変数・判定ヘルパー」のブロックを実行し,以降の処理で使う変数と関数を定義する: -```bash -MERGE_FILES=(".gitignore" "CLAUDE.md" "docs/PROGRESS.md" ".gitattributes" ".claude/settings.json" ".claude/template-overrides.md") -SKIP_FILES=("README.md") -OVERRIDES_FILE=".claude/template-overrides.md" +- 一覧: `MERGE_FILES` / `SKIP_FILES` / `TEAM_LAYER_FILES`,開発モード `PROJECT_MODE`,作業ディレクトリ `WORK_DIR` +- 判定: `is_merge_file` / `is_skip_file` / `is_team_layer_file` / `override_policy`(台帳の方針)/ `is_diverged`(前回同期版からの改変有無) +- 差分表示・マージ: `show_template_delta` / `show_project_delta` / `three_way_merge` -# 作業用ファイル(5.3 のループは pipe のサブシェルで回るため,結果はファイルに書き出す) -WORK_DIR=$(mktemp -d) -: > "$WORK_DIR/overrides.tsv" # 台帳登録ファイル: statusfilepolicy -: > "$WORK_DIR/diverged.tsv" # 未登録改変ファイル: statusfile(base|nobase) -TEAM_LAYER_FILES=( - "docs/01_GUIDE/GUIDE_03_チーム開発ルール.md" - ".claude/hooks/check_sync.sh" -) +### 5.3 通常コピー対象の反映 -# プロジェクトの開発モードを取得(未設定なら安全側で solo 扱い) -if [ -f .claude/project-mode ]; then - PROJECT_MODE=$(tr -d '[:space:]' < .claude/project-mode) -else - PROJECT_MODE="solo" -fi +`reference.md`「通常コピーループ」を実行する.各エントリは次の順で判定し,最初に該当した扱いになる: -is_merge_file() { - local t="$1" - for f in "${MERGE_FILES[@]}"; do - [ "$f" = "$t" ] && return 0 - done - return 1 -} - -is_skip_file() { - local t="$1" - for f in "${SKIP_FILES[@]}"; do - [ "$f" = "$t" ] && return 0 - done - return 1 -} - -is_team_layer_file() { - local t="$1" - for f in "${TEAM_LAYER_FILES[@]}"; do - [ "$f" = "$t" ] && return 0 - done - return 1 -} - -# 台帳(.claude/template-overrides.md)から対象パスの方針(keep/merge/ask)を返す.未登録なら空 -# - 「台帳」節以外の表やコードブロック内の行は読まない -# - 末尾 / の行はディレクトリ指定(前方一致) -override_policy() { - local t="$1" - [ -f "$OVERRIDES_FILE" ] || return 0 - awk -F'|' -v target="$t" ' - /^```/ { infence = !infence; next } - infence { next } - /^## / { insec = ($0 ~ /^## 台帳/) ; next } - !insec { next } - /^\|/ { - p=$2; gsub(/^[ \t]+|[ \t]+$/, "", p); gsub(/`/, "", p) - pol=$3; gsub(/^[ \t]+|[ \t]+$/, "", pol) - if (pol != "keep" && pol != "merge" && pol != "ask") next - if (p == target) { print pol; exit } - if (substr(p, length(p)) == "/" && index(target, p) == 1) { print pol; exit } - }' "$OVERRIDES_FILE" -} - -# 台帳に登録されている全パスを列挙する(最後の整合チェック用) -override_paths() { - [ -f "$OVERRIDES_FILE" ] || return 0 - awk -F'|' ' - /^```/ { infence = !infence; next } - infence { next } - /^## / { insec = ($0 ~ /^## 台帳/) ; next } - !insec { next } - /^\|/ { - p=$2; gsub(/^[ \t]+|[ \t]+$/, "", p); gsub(/`/, "", p) - pol=$3; gsub(/^[ \t]+|[ \t]+$/, "", pol) - if (pol == "keep" || pol == "merge" || pol == "ask") print p - }' "$OVERRIDES_FILE" -} - -# ローカルの既存ファイルが前回同期版($LAST_SHA 時点のテンプレート版)から改変されているか -# 戻り値 0: 改変あり(stdout に base|nobase),1: 改変なし/判定不能 -is_diverged() { - local t="$1" - [ -n "$LAST_SHA" ] && [ -f "$t" ] || return 1 - if git -C "$TEMP_DIR" cat-file -e "$LAST_SHA:$t" 2>/dev/null; then - # 改行コード差(CRLF/LF)は改変とみなさない - if git -C "$TEMP_DIR" show "$LAST_SHA:$t" | diff -q --strip-trailing-cr - "$t" >/dev/null 2>&1; then - return 1 - fi - echo base; return 0 - fi - # 前回同期版に存在しない(テンプレート新規追加)のにローカルに既にある → 独自ファイル - echo nobase; return 0 -} -``` - -`PROJECT_MODE` が `solo` の場合,チーム層ファイル(`is_team_layer_file` が真)は同期対象外ファイルと同様に完全スキップする(ステップ 5.3 のループでは `is_skip_file` 判定の直後に `[ "$PROJECT_MODE" = "solo" ] && is_team_layer_file "$file"` を追加で判定して `continue` する).`team` の場合は通常のコピー/マージ判定に進む. - -### 5.3 通常コピー対象の反映(マージ必須ファイル以外) - -マージ必須ファイルは後段(5.4)で個別処理するため,このループではスキップする.既存ファイルがない場合は通常どおり `cp` で配置する: - -```bash -echo "$CHANGED_ENTRIES" | while IFS=$'\t' read -r status file newfile; do - [ -z "$status" ] && continue - case "$status" in - A|M) - # 同期対象外ファイルは完全にスキップ(コピーも上書きもしない) - if is_skip_file "$file"; then - continue - fi - # solo モードではチーム層ファイルを完全スキップ(再配置しない) - if [ "$PROJECT_MODE" = "solo" ] && is_team_layer_file "$file"; then - continue - fi - # マージ必須ファイルで既存ファイルがある場合は 5.4 で処理 - if is_merge_file "$file" && [ -f "$file" ]; then - continue - fi - # 台帳登録済みのプロジェクト固有改変ファイル(既存あり)は 5.5 で方針に従って処理 - policy=$(override_policy "$file") - if [ -n "$policy" ] && [ -f "$file" ]; then - printf '%s\t%s\t%s\n' "$status" "$file" "$policy" >> "$WORK_DIR/overrides.tsv" - continue - fi - # 台帳に無いが前回同期版から改変されているファイルは上書きせず 5.6 で確認 - if kind=$(is_diverged "$file"); then - printf '%s\t%s\t%s\n' "$status" "$file" "$kind" >> "$WORK_DIR/diverged.tsv" - continue - fi - mkdir -p "$(dirname "$file")" - cp "$TEMP_DIR/$file" "$file" - ;; - R*) - # file=旧パス, newfile=新パス - if is_skip_file "$newfile"; then - continue - fi - if [ "$PROJECT_MODE" = "solo" ] && is_team_layer_file "$newfile"; then - continue - fi - if is_merge_file "$newfile" && [ -f "$newfile" ]; then - continue - fi - policy=$(override_policy "$newfile") - if [ -n "$policy" ] && [ -f "$newfile" ]; then - printf '%s\t%s\t%s\n' "$status" "$newfile" "$policy" >> "$WORK_DIR/overrides.tsv" - continue - fi - # 旧パスが台帳登録されている場合は,新パスに改変を引き継ぐべきか 5.5 で確認する - oldpolicy=$(override_policy "$file") - if [ -n "$oldpolicy" ] && [ -f "$file" ]; then - printf 'RENAMED\t%s\t%s\t%s\n' "$file" "$newfile" "$oldpolicy" >> "$WORK_DIR/overrides.tsv" - continue - fi - if kind=$(is_diverged "$newfile"); then - printf '%s\t%s\t%s\n' "$status" "$newfile" "$kind" >> "$WORK_DIR/diverged.tsv" - continue - fi - mkdir -p "$(dirname "$newfile")" - cp "$TEMP_DIR/$newfile" "$newfile" - ;; - esac -done -``` - -ループ終了後,`$WORK_DIR/overrides.tsv` と `$WORK_DIR/diverged.tsv` にそれぞれ「台帳登録ファイル」「未登録改変ファイル」が溜まっている.5.4 のあと,5.5・5.6 で順に処理する. +1. 同期対象外ファイル → 完全スキップ +2. solo モードのチーム層ファイル → 完全スキップ +3. マージ必須ファイル(既存あり) → 5.4 へ +4. 台帳登録ファイル(既存あり) → `$WORK_DIR/overrides.tsv` に記録し 5.5 へ(リネームで旧パスが登録済みの場合は `RENAMED` 行として記録) +5. 未登録改変ファイル(`is_diverged` が真) → `$WORK_DIR/diverged.tsv` に記録し 5.6 へ +6. 上記以外 → `cp` で配置・上書き ### 5.4 マージ必須ファイルの個別処理 -`$CHANGED_ENTRIES` に A/M/R* で含まれ,かつ既存ファイルが存在するマージ必須ファイルについて,以下の手順で処理する. - -**`.gitignore` のマージ手順** - -1. 既存 `.gitignore` とテンプレート `$TEMP_DIR/.gitignore` を読む -2. テンプレート側の**実効行**(コメント `#` 行・空行を除く)を抽出する: - ```bash - grep -vE '^\s*(#|$)' "$TEMP_DIR/.gitignore" - ``` -3. 抽出した各行について,既存ファイルに完全一致で含まれていないものだけを収集する -4. 収集した行がある場合,既存ファイル末尾に以下のマーカー付きブロックで追記する: - ``` - - # --- from template (sync-template) --- - {追加行} - ``` - - 既存ファイル末尾に同じマーカーが既にある場合は,マーカー以降に追記して重複マーカーを作らない -5. `git diff .gitignore` で結果を表示しユーザーに確認する - -**`CLAUDE.md` のマージ手順** - -同期担当エージェント(= あなた)が Read と Edit ツールを使ってセクション単位でマージする: - -1. 既存 `CLAUDE.md` とテンプレート版 `$TEMP_DIR/CLAUDE.md` を Read する -2. テンプレート側の変更箇所を特定する(初回同期は LAST_SHA がないためテンプレート全体を「更新候補」として扱う): - ```bash - if [ -n "$LAST_SHA" ]; then - git -C "$TEMP_DIR" show "$LAST_SHA:CLAUDE.md" 2>/dev/null > /tmp/claude_md_old || true - diff -u /tmp/claude_md_old "$TEMP_DIR/CLAUDE.md" - fi - ``` -3. セクションを以下の基準で分類する: - - **共通セクション(テンプレート管理,更新対象)**: 「必須ルール」「エージェントチーム」「Git 運用」「ドキュメント」節など,テンプレートに由来する節 - - **プロジェクト固有セクション(保持対象)**: 「プロジェクト名」「開発進捗」や,プロジェクトが追加した固有規約の節 -4. テンプレート側で変更があった共通セクションのみを Edit で既存ファイルに反映する.プロジェクト固有セクションは一切触らない -5. `git diff CLAUDE.md` で結果を表示しユーザーに確認する - -**`docs/PROGRESS.md` のマージ手順** - -`docs/PROGRESS.md` はプロジェクト固有の進捗ログのため,**既存ファイルがある場合は内容を上書きしない**.テンプレート側の更新は骨組み(タイトル・案内コメント)に限定されるはずなので,差分があれば通知のみ行い手動マージを促す: - -1. 既存 `docs/PROGRESS.md` とテンプレート版 `$TEMP_DIR/docs/PROGRESS.md` を Read する -2. 差分を表示する: - ```bash - diff -u docs/PROGRESS.md "$TEMP_DIR/docs/PROGRESS.md" - ``` -3. 差分がある場合,「⚠ テンプレート側の `docs/PROGRESS.md` 骨組みに変更があります.既存の追記内容を保持したまま骨組みを反映したい場合はファイルを直接編集してください.自動マージは行いません.」と通知する -4. 既存ファイルが無い稀なケース(テンプレートを使わずに作られた古いプロジェクト等)に限り,テンプレート版をそのまま `cp` で配置する - -**`.gitattributes` のマージ手順** - -1. 既存ファイルとテンプレート版の差分を表示する: - ```bash - diff -u .gitattributes "$TEMP_DIR/.gitattributes" - ``` -2. ユーザーに以下のいずれかを選ばせる: - - **上書き**: テンプレート版で既存を置換 - - **マージ**: 両者のルールを統合(具体的な統合内容は対話で決定) - - **スキップ**: 既存ファイルを維持 -3. 選択に応じて処理する - -**`.claude/settings.json` のマージ手順** - -`settings.json` は hooks 設定を持つ.team モードのプロジェクトは PreToolUse(`restrict_repo_access.py`)に加えて SessionStart(`check_sync.sh`)を配線しているため,テンプレート版で盲目的に上書きするとプロジェクト固有の配線が失われる.同期担当エージェント(= あなた)が Read と Edit で JSON をマージする: - -1. 既存 `.claude/settings.json` とテンプレート版 `$TEMP_DIR/.claude/settings.json` を Read する -2. 差分を表示する: - ```bash - diff -u .claude/settings.json "$TEMP_DIR/.claude/settings.json" - ``` -3. **既存の hooks を保持したまま**,テンプレート側で追加・変更された hook(イベント・matcher・command)のみを統合する: - - 既存に無いイベント/hook はテンプレート版から追加する - - プロジェクト固有の hook(team の SessionStart `check_sync.sh` 等)は残す - - 同一 hook の command 変更(例: `restrict_repo_access.py` の起動方法変更)はテンプレート版に合わせる - - solo モードで SessionStart(check_sync) が無い場合は,team 専用の配線を勝手に追加しない -4. `git diff .claude/settings.json` で結果を表示しユーザーに確認する - -**`.claude/template-overrides.md` のマージ手順** - -テンプレート改変台帳はプロジェクト固有の記録のため,**既存ファイルがある場合は内容を上書きしない**(`docs/PROGRESS.md` と同じ扱い): - -1. 差分を表示する: - ```bash - diff -u .claude/template-overrides.md "$TEMP_DIR/.claude/template-overrides.md" - ``` -2. 差分が骨組み(説明文・記入例)のみであれば,「⚠ テンプレート側の `.claude/template-overrides.md` の説明文に変更があります.台帳の行を保持したまま反映したい場合はファイルを直接編集してください.自動マージは行いません」と通知する -3. 既存ファイルが無い場合(本機能導入前に作られたプロジェクト)に限り,テンプレート版の雛形をそのまま `cp` で配置し,「台帳を新設しました.テンプレート由来ファイルを意図的に変更している箇所があれば登録してください(`.claude/rules/template-customization.md`)」と案内する +`$CHANGED_ENTRIES` に A/M/R* で含まれ,かつ既存ファイルが存在するマージ必須ファイルを,`reference.md`「マージ必須ファイルの個別手順」に従ってファイルごとに処理する.各手順の最後に `git diff` で結果を表示しユーザーに確認する. ### 5.5 台帳登録ファイルの処理 -`$WORK_DIR/overrides.tsv` の各行(`statusfilepolicy`,またはリネーム時の `RENAMED旧パス新パスpolicy`)を方針ごとに処理する.共通で使う差分は次のとおり: - -```bash -# テンプレート側の差分(前回同期版 → 最新): 何が変わったのかを示す -show_template_delta() { # $1=file - if [ -n "$LAST_SHA" ] && git -C "$TEMP_DIR" cat-file -e "$LAST_SHA:$1" 2>/dev/null; then - git -C "$TEMP_DIR" diff "$LAST_SHA" HEAD -- "$1" - else - echo "(前回同期版なし: テンプレート最新版を全文表示)"; cat "$TEMP_DIR/$1" - fi -} - -# プロジェクト側の改変(前回同期版 → プロジェクト現行): 何を守るべきかを示す -show_project_delta() { # $1=file - if [ -n "$LAST_SHA" ] && git -C "$TEMP_DIR" cat-file -e "$LAST_SHA:$1" 2>/dev/null; then - git -C "$TEMP_DIR" show "$LAST_SHA:$1" | diff -u --strip-trailing-cr --label "template@$LAST_SHA" --label "project" - "$1" - else - diff -u --strip-trailing-cr --label "template@HEAD" --label "project" "$TEMP_DIR/$1" "$1" - fi -} - -# 3-way マージ: base=前回同期版, ours=プロジェクト, theirs=テンプレート最新 -# 戻り値 0: 競合なく $1 を更新, 1: 競合あり($WORK_DIR/merged/$1 に競合マーカー付き結果), 2: base 無しで実行不可 -three_way_merge() { # $1=file - local f="$1" base ours theirs out - [ -n "$LAST_SHA" ] && git -C "$TEMP_DIR" cat-file -e "$LAST_SHA:$f" 2>/dev/null || return 2 - mkdir -p "$WORK_DIR/3way/$(dirname "$f")" "$WORK_DIR/merged/$(dirname "$f")" - base="$WORK_DIR/3way/$f.base"; ours="$WORK_DIR/3way/$f.ours"; theirs="$WORK_DIR/3way/$f.theirs" - out="$WORK_DIR/merged/$f" - # 改行コードを LF に揃えてからマージする(CRLF 混在で全行競合するのを防ぐ) - git -C "$TEMP_DIR" show "$LAST_SHA:$f" | sed 's/\r$//' > "$base" - sed 's/\r$//' "$f" > "$ours" - sed 's/\r$//' "$TEMP_DIR/$f" > "$theirs" - if git merge-file -p --diff3 -L project -L "template@$LAST_SHA" -L template@HEAD "$ours" "$base" "$theirs" > "$out"; then - cp "$out" "$f"; return 0 - fi - return 1 -} -``` +`$WORK_DIR/overrides.tsv` の各行を方針ごとに処理する.差分の表示には `show_template_delta`(テンプレート側の差分: 何が変わったか)と `show_project_delta`(プロジェクト側の改変: 何を守るべきか)を使う. **`keep` の処理** @@ -556,20 +253,7 @@ ### 5.7 削除候補の確認 -削除候補(D およびリネーム元)を抽出し,**ローカルに実在するファイルだけ**に絞る: - -```bash -DELETIONS=$(echo "$CHANGED_ENTRIES" | awk -F'\t' '$1 == "D" {print $2} $1 ~ /^R/ {print $2}') - -# ローカルに実在するファイルのみを削除候補に残す -DELETIONS=$(echo "$DELETIONS" | while IFS= read -r f; do - [ -n "$f" ] && [ -f "$f" ] && echo "$f" -done) -``` - -この実在フィルタにより,テンプレート側のリネームや過去の移行でプロジェクトが持っていない旧パス,solo モードに配置されないチーム層ファイルは,削除確認に出ることなく自動的に除外される. - -さらに台帳の方針で振り分ける: +`reference.md`「削除候補の抽出」で,削除候補(D およびリネーム元)のうち**ローカルに実在するファイルだけ**を `$DELETIONS` に得る.さらに台帳の方針で振り分ける: - 台帳で `keep` のファイルは削除候補から**自動的に除外**し,「📒 `{file}` はテンプレートから削除されましたが台帳で `keep` のため残します」と通知する - 台帳で `merge` / `ask` のファイル,および 5.5 の `RENAMED` で「テンプレート版を採用」を選んだ旧パスは削除候補に残し,一覧に「(台帳: {方針}.理由: {理由})」を添えて確認する.削除する場合は台帳の行も削除する @@ -586,26 +270,11 @@ ### 5.8 台帳の整合チェック -台帳に登録されているのにテンプレート最新版に存在しないパスを警告する(リネーム・削除済み,またはパスの記入ミス): - -```bash -override_paths | while IFS= read -r p; do - [ -z "$p" ] && continue - case "$p" in - */) [ -d "$TEMP_DIR/$p" ] || echo "$p" ;; - *) [ -f "$TEMP_DIR/$p" ] || echo "$p" ;; - esac -done -``` - -該当があれば「⚠ 台帳の以下のパスはテンプレートに存在しません.リネーム/削除に追従してパスを直すか,プロジェクト独自ファイルなら台帳から行を削除してください: {一覧}」と通知する(自動では書き換えない). +`reference.md`「台帳の整合チェック」で,台帳に登録されているのにテンプレート最新版に存在しないパスを列挙する.該当があれば「⚠ 台帳の以下のパスはテンプレートに存在しません.リネーム/削除に追従してパスを直すか,プロジェクト独自ファイルなら台帳から行を削除してください: {一覧}」と通知する(自動では書き換えない). ### 5.9 同期済み SHA の記録とクリーンアップ -```bash -echo "$NEW_SHA" > .claude/template-sync-sha -rm -rf "$TEMP_DIR" "$WORK_DIR" -``` +`reference.md`「クリーンアップ」を実行する(`.claude/template-sync-sha` に `$NEW_SHA` を記録し,`$TEMP_DIR`・`$WORK_DIR` を削除). ## ステップ 6: 変更内容の分析 @@ -649,15 +318,13 @@ ## 注意事項 -- 本コマンドは一時ディレクトリ(`mktemp -d`)に clone したテンプレートを Read / `cp` / `rm -rf` する.`restrict_repo_access.py` フックはシステム一時ディレクトリを許可ゾーンとして例外扱いしており,本コマンドはそれに依存している(フックの例外を外すと本コマンドが動かなくなる) +- 本コマンドは一時ディレクトリ(`mktemp -d`)に clone したテンプレートを Read / `cp` / `rm -rf` し,作業ファイルも一時ディレクトリに置く.`restrict_repo_access.py` フックはシステム一時ディレクトリを許可ゾーンとして例外扱いしており,本コマンドはそれに依存している(フックの例外を外すと本コマンドが動かなくなる) - テンプレートリポジトリへの push は行わない - コード修正はユーザーの確認なしに実行しない -- マージ必須ファイル(`.gitignore`, `CLAUDE.md`, `docs/PROGRESS.md`, `.gitattributes`, `.claude/settings.json`, `.claude/template-overrides.md`)は必ずステップ 5.4 の手順でマージする.盲目的な `cp` で上書きしない(フレームワーク固有の除外ルールやプロジェクト固有セクションが失われる) -- テンプレート改変台帳(`.claude/template-overrides.md`)に登録されたファイルは `cp` せず,方針(`keep` / `merge` / `ask`)に従ってステップ 5.5 で処理する.台帳に無くても前回同期版と内容が異なるローカルファイルは上書きせず,ステップ 5.6 でユーザーに確認する(意図的な改変なら台帳に登録する).`/sync-template` は**プロジェクトの改変を黙って消さない**ことを最優先にし,判断に迷う場合は上書きせずユーザーに問う +- マージ必須ファイル(冒頭の表)は必ずステップ 5.4 の手順でマージする.盲目的な `cp` で上書きしない(フレームワーク固有の除外ルール,プロジェクト固有セクション,team の SessionStart(check_sync) 配線等が失われる) +- `/sync-template` は**プロジェクトの改変を黙って消さない**ことを最優先にする.テンプレート改変台帳(`.claude/template-overrides.md`)に登録されたファイルは `cp` せず方針(`keep` / `merge` / `ask`)に従ってステップ 5.5 で処理し,台帳に無くても前回同期版と内容が異なるローカルファイルは上書きせずステップ 5.6 でユーザーに確認する.判断に迷う場合は上書きせずユーザーに問う - 台帳への登録は必ず理由を添える(ユーザーから聞く).理由の無い登録は次回同期時の判断材料にならない.台帳の書式(パス列はバッククォート囲み,方針列は `keep` / `merge` / `ask`)を崩さない - 同期対象外ファイル(`README.md`)はテンプレート紹介用のためプロジェクトには反映しない.テンプレート側で追加・変更・削除があってもプロジェクトの該当ファイルは触らない - チーム層ファイル(`GUIDE_03`/`check_sync.sh`)は `.claude/project-mode` が `team` のプロジェクトにのみ同期する.`task-*` skill は共通層のためモードに関わらず同期する.`solo`(または未設定)のプロジェクトには配置・更新・削除いずれもしない.`/sync-template` は「版の追従」のみを行い,**モードの切り替えはしない**.solo↔team の切替は `/set-mode ` を使う(team 層ファイルの配置/削除・`settings.json` 配線・`CLAUDE.md` の team 化/solo 化・`project-mode` 更新を一括で行う).`.claude/project-mode` を手で書き換えるだけでは切り替わらない -- `.claude/settings.json` はマージ必須ファイル.team の SessionStart(check_sync) 配線を保持したままテンプレートの hook 変更を統合する.盲目的な `cp` で上書きしない -- 通常コピー対象でもプロジェクト固有の変更が上書きされうる場合は,`git diff` で確認してユーザーに報告する - テンプレートが管理するのは `.claude/` 配下のうち `agents/`,`skills/`,`rules/`,`hooks/`,`settings.json`,`template-sync-sha`,`template-overrides.md`(雛形のみ.登録内容はプロジェクト固有)のみ.`.claude/plans/` や `.claude/commit-context.md` 等のプロジェクト固有ファイルはテンプレートに含まれないため同期対象外 - `chore/sync-template` ブランチは他の作業ブランチと混ぜず,作成後は速やかにマージすること.複数の作業ブランチで `/sync-template` を実行すると `.claude/template-sync-sha` がコンフリクトする.コンフリクト時は新しい(HEAD 側の)SHA を採用すること. diff --git a/.claude/skills/sync-template/reference.md b/.claude/skills/sync-template/reference.md new file mode 100644 index 0000000..f8c4e65 --- /dev/null +++ b/.claude/skills/sync-template/reference.md @@ -0,0 +1,390 @@ +# テンプレート同期リファレンス (Template Sync Reference) + +`/sync-template`(`SKILL.md`)の各ステップから参照される bash ヘルパーと個別処理の詳細手順.方針(どのファイルをどう扱うか)と全体フローは `SKILL.md` が定義し,本ファイルはその「実装」を持つ. + +前提となるシェル変数(`SKILL.md` ステップ 2〜3 で設定): + +- `TEMP_DIR`: テンプレートを clone した一時ディレクトリ +- `NEW_SHA`: テンプレート HEAD の SHA +- `LAST_SHA`: 前回同期時のテンプレート SHA(`.claude/template-sync-sha`.初回同期では空) +- `CHANGED_ENTRIES`: `git diff --name-status` 形式の変更一覧(`statusfile[newfile]`) + +## 変数・判定ヘルパー (Variables & Helpers) + +ステップ 5.2 で定義する.以降のすべての処理が依存する: + +```bash +MERGE_FILES=(".gitignore" "CLAUDE.md" "docs/PROGRESS.md" ".gitattributes" ".claude/settings.json" ".claude/template-overrides.md") +SKIP_FILES=("README.md") +TEAM_LAYER_FILES=( + "docs/01_GUIDE/GUIDE_03_チーム開発ルール.md" + ".claude/hooks/check_sync.sh" +) +OVERRIDES_FILE=".claude/template-overrides.md" + +# 作業用ファイル(コピーループは pipe のサブシェルで回るため,結果はファイルに書き出す) +WORK_DIR=$(mktemp -d) +: > "$WORK_DIR/overrides.tsv" # 台帳登録ファイル: statusfilepolicy(リネームは RENAMEDpolicy) +: > "$WORK_DIR/diverged.tsv" # 未登録改変ファイル: statusfile(base|nobase) + +# プロジェクトの開発モードを取得(未設定なら安全側で solo 扱い) +if [ -f .claude/project-mode ]; then + PROJECT_MODE=$(tr -d '[:space:]' < .claude/project-mode) +else + PROJECT_MODE="solo" +fi + +is_merge_file() { + local t="$1" + for f in "${MERGE_FILES[@]}"; do + [ "$f" = "$t" ] && return 0 + done + return 1 +} + +is_skip_file() { + local t="$1" + for f in "${SKIP_FILES[@]}"; do + [ "$f" = "$t" ] && return 0 + done + return 1 +} + +is_team_layer_file() { + local t="$1" + for f in "${TEAM_LAYER_FILES[@]}"; do + [ "$f" = "$t" ] && return 0 + done + return 1 +} + +# 台帳(.claude/template-overrides.md)から対象パスの方針(keep/merge/ask)を返す.未登録なら空 +# - 「台帳」節以外の表やコードブロック内の行は読まない +# - 末尾 / の行はディレクトリ指定(前方一致) +override_policy() { + local t="$1" + [ -f "$OVERRIDES_FILE" ] || return 0 + awk -F'|' -v target="$t" ' + /^```/ { infence = !infence; next } + infence { next } + /^## / { insec = ($0 ~ /^## 台帳/) ; next } + !insec { next } + /^\|/ { + p=$2; gsub(/^[ \t]+|[ \t]+$/, "", p); gsub(/`/, "", p) + pol=$3; gsub(/^[ \t]+|[ \t]+$/, "", pol) + if (pol != "keep" && pol != "merge" && pol != "ask") next + if (p == target) { print pol; exit } + if (substr(p, length(p)) == "/" && index(target, p) == 1) { print pol; exit } + }' "$OVERRIDES_FILE" +} + +# 台帳に登録されている全パスを列挙する(整合チェック用) +override_paths() { + [ -f "$OVERRIDES_FILE" ] || return 0 + awk -F'|' ' + /^```/ { infence = !infence; next } + infence { next } + /^## / { insec = ($0 ~ /^## 台帳/) ; next } + !insec { next } + /^\|/ { + p=$2; gsub(/^[ \t]+|[ \t]+$/, "", p); gsub(/`/, "", p) + pol=$3; gsub(/^[ \t]+|[ \t]+$/, "", pol) + if (pol == "keep" || pol == "merge" || pol == "ask") print p + }' "$OVERRIDES_FILE" +} + +# ローカルの既存ファイルが前回同期版($LAST_SHA 時点のテンプレート版)から改変されているか +# 戻り値 0: 改変あり(stdout に base|nobase),1: 改変なし/判定不能 +is_diverged() { + local t="$1" + [ -n "$LAST_SHA" ] && [ -f "$t" ] || return 1 + if git -C "$TEMP_DIR" cat-file -e "$LAST_SHA:$t" 2>/dev/null; then + # 改行コード差(CRLF/LF)は改変とみなさない + if git -C "$TEMP_DIR" show "$LAST_SHA:$t" | diff -q --strip-trailing-cr - "$t" >/dev/null 2>&1; then + return 1 + fi + echo base; return 0 + fi + # 前回同期版に存在しない(テンプレート新規追加)のにローカルに既にある → 独自ファイル + echo nobase; return 0 +} + +# テンプレート側の差分(前回同期版 → 最新): 何が変わったのかを示す +show_template_delta() { # $1=file + if [ -n "$LAST_SHA" ] && git -C "$TEMP_DIR" cat-file -e "$LAST_SHA:$1" 2>/dev/null; then + git -C "$TEMP_DIR" diff "$LAST_SHA" HEAD -- "$1" + else + echo "(前回同期版なし: テンプレート最新版を全文表示)"; cat "$TEMP_DIR/$1" + fi +} + +# プロジェクト側の改変(前回同期版 → プロジェクト現行): 何を守るべきかを示す +show_project_delta() { # $1=file + if [ -n "$LAST_SHA" ] && git -C "$TEMP_DIR" cat-file -e "$LAST_SHA:$1" 2>/dev/null; then + git -C "$TEMP_DIR" show "$LAST_SHA:$1" | diff -u --strip-trailing-cr --label "template@$LAST_SHA" --label "project" - "$1" + else + diff -u --strip-trailing-cr --label "template@HEAD" --label "project" "$TEMP_DIR/$1" "$1" + fi +} + +# 3-way マージ: base=前回同期版, ours=プロジェクト, theirs=テンプレート最新 +# 戻り値 0: 競合なく $1 を更新, 1: 競合あり($WORK_DIR/merged/$1 に競合マーカー付き結果), 2: base 無しで実行不可 +three_way_merge() { # $1=file + local f="$1" base ours theirs out + [ -n "$LAST_SHA" ] && git -C "$TEMP_DIR" cat-file -e "$LAST_SHA:$f" 2>/dev/null || return 2 + mkdir -p "$WORK_DIR/3way/$(dirname "$f")" "$WORK_DIR/merged/$(dirname "$f")" + base="$WORK_DIR/3way/$f.base"; ours="$WORK_DIR/3way/$f.ours"; theirs="$WORK_DIR/3way/$f.theirs" + out="$WORK_DIR/merged/$f" + # 改行コードを LF に揃えてからマージする(CRLF 混在で全行競合するのを防ぐ) + git -C "$TEMP_DIR" show "$LAST_SHA:$f" | sed 's/\r$//' > "$base" + sed 's/\r$//' "$f" > "$ours" + sed 's/\r$//' "$TEMP_DIR/$f" > "$theirs" + if git merge-file -p --diff3 -L project -L "template@$LAST_SHA" -L template@HEAD "$ours" "$base" "$theirs" > "$out"; then + cp "$out" "$f"; return 0 + fi + return 1 +} +``` + +## 変更一覧の差分サマリー (Diff Summary) + +ステップ 4 で,マージ必須ファイル・台帳登録ファイルに変更がある場合に先出しする情報を取得する. + +マージ必須ファイルの差分プレビュー(該当ファイルが変更対象かつ既存ファイルがある場合のみ出力): + +```bash +# 既存ファイル行数 +wc -l "${MERGE_FILES[@]}" 2>/dev/null + +# テンプレート側の実効行数(.gitignore) +grep -vE '^\s*(#|$)' "$TEMP_DIR/.gitignore" | wc -l + +# 既存ファイルとテンプレート最新版の差分プレビュー +for f in "${MERGE_FILES[@]}"; do + [ -f "$f" ] && [ -f "$TEMP_DIR/$f" ] && { echo "=== $f ==="; diff -u "$f" "$TEMP_DIR/$f" | head -50; } +done +``` + +台帳登録ファイルの抽出(`CHANGED_ENTRIES` の A/M/R 対象のうち `override_policy` が空でないもの): + +```bash +echo "$CHANGED_ENTRIES" | while IFS=$'\t' read -r status file newfile; do + case "$status" in + A|M) t="$file" ;; + R*) t="$newfile" ;; + *) continue ;; + esac + p=$(override_policy "$t") + [ -n "$p" ] && echo "$t ($p)" +done +``` + +## 通常コピーループ (Copy Loop) + +ステップ 5.3 で実行する.判定は「同期対象外 → solo のチーム層 → マージ必須(既存あり)→ 台帳登録(既存あり)→ 未登録改変 → `cp`」の順.`cp` しなかったものはそれぞれ後段で処理する: + +```bash +echo "$CHANGED_ENTRIES" | while IFS=$'\t' read -r status file newfile; do + [ -z "$status" ] && continue + case "$status" in + A|M) + # 同期対象外ファイルは完全にスキップ(コピーも上書きもしない) + if is_skip_file "$file"; then + continue + fi + # solo モードではチーム層ファイルを完全スキップ(再配置しない) + if [ "$PROJECT_MODE" = "solo" ] && is_team_layer_file "$file"; then + continue + fi + # マージ必須ファイルで既存ファイルがある場合は 5.4 で処理 + if is_merge_file "$file" && [ -f "$file" ]; then + continue + fi + # 台帳登録済みのプロジェクト固有改変ファイル(既存あり)は 5.5 で方針に従って処理 + policy=$(override_policy "$file") + if [ -n "$policy" ] && [ -f "$file" ]; then + printf '%s\t%s\t%s\n' "$status" "$file" "$policy" >> "$WORK_DIR/overrides.tsv" + continue + fi + # 台帳に無いが前回同期版から改変されているファイルは上書きせず 5.6 で確認 + if kind=$(is_diverged "$file"); then + printf '%s\t%s\t%s\n' "$status" "$file" "$kind" >> "$WORK_DIR/diverged.tsv" + continue + fi + mkdir -p "$(dirname "$file")" + cp "$TEMP_DIR/$file" "$file" + ;; + R*) + # file=旧パス, newfile=新パス + if is_skip_file "$newfile"; then + continue + fi + if [ "$PROJECT_MODE" = "solo" ] && is_team_layer_file "$newfile"; then + continue + fi + if is_merge_file "$newfile" && [ -f "$newfile" ]; then + continue + fi + policy=$(override_policy "$newfile") + if [ -n "$policy" ] && [ -f "$newfile" ]; then + printf '%s\t%s\t%s\n' "$status" "$newfile" "$policy" >> "$WORK_DIR/overrides.tsv" + continue + fi + # 旧パスが台帳登録されている場合は,新パスに改変を引き継ぐべきか 5.5 で確認する + oldpolicy=$(override_policy "$file") + if [ -n "$oldpolicy" ] && [ -f "$file" ]; then + printf 'RENAMED\t%s\t%s\t%s\n' "$file" "$newfile" "$oldpolicy" >> "$WORK_DIR/overrides.tsv" + continue + fi + if kind=$(is_diverged "$newfile"); then + printf '%s\t%s\t%s\n' "$status" "$newfile" "$kind" >> "$WORK_DIR/diverged.tsv" + continue + fi + mkdir -p "$(dirname "$newfile")" + cp "$TEMP_DIR/$newfile" "$newfile" + ;; + esac +done +``` + +## マージ必須ファイルの個別手順 (Merge Procedures) + +ステップ 5.4 で,`$CHANGED_ENTRIES` に A/M/R* で含まれ,かつ既存ファイルが存在するマージ必須ファイルを以下の手順で処理する. + +### `.gitignore` + +1. 既存 `.gitignore` とテンプレート `$TEMP_DIR/.gitignore` を読む +2. テンプレート側の**実効行**(コメント `#` 行・空行を除く)を抽出する: + + ```bash + grep -vE '^\s*(#|$)' "$TEMP_DIR/.gitignore" + ``` + +3. 抽出した各行について,既存ファイルに完全一致で含まれていないものだけを収集する +4. 収集した行がある場合,既存ファイル末尾に以下のマーカー付きブロックで追記する: + + ```text + + # --- from template (sync-template) --- + {追加行} + ``` + + - 既存ファイル末尾に同じマーカーが既にある場合は,マーカー以降に追記して重複マーカーを作らない +5. `git diff .gitignore` で結果を表示しユーザーに確認する + +### `CLAUDE.md` + +同期担当エージェント(= あなた)が Read と Edit ツールを使ってセクション単位でマージする: + +1. 既存 `CLAUDE.md` とテンプレート版 `$TEMP_DIR/CLAUDE.md` を Read する +2. テンプレート側の変更箇所を特定する(初回同期は LAST_SHA がないためテンプレート全体を「更新候補」として扱う): + + ```bash + if [ -n "$LAST_SHA" ]; then + git -C "$TEMP_DIR" show "$LAST_SHA:CLAUDE.md" 2>/dev/null > "$WORK_DIR/claude_md_old" || true + diff -u "$WORK_DIR/claude_md_old" "$TEMP_DIR/CLAUDE.md" + fi + ``` + +3. セクションを以下の基準で分類する: + - **共通セクション(テンプレート管理,更新対象)**: 「必須ルール」「エージェントチーム」「Git 運用」「ドキュメント」節など,テンプレートに由来する節 + - **プロジェクト固有セクション(保持対象)**: 「プロジェクト名」「開発進捗」や,プロジェクトが追加した固有規約の節 +4. テンプレート側で変更があった共通セクションのみを Edit で既存ファイルに反映する.プロジェクト固有セクションは一切触らない +5. `git diff CLAUDE.md` で結果を表示しユーザーに確認する + +### `docs/PROGRESS.md` + +プロジェクト固有の進捗ログのため,**既存ファイルがある場合は内容を上書きしない**.テンプレート側の更新は骨組み(タイトル・案内コメント)に限定されるはずなので,差分があれば通知のみ行い手動マージを促す: + +1. 既存 `docs/PROGRESS.md` とテンプレート版 `$TEMP_DIR/docs/PROGRESS.md` を Read する +2. 差分を表示する: + + ```bash + diff -u docs/PROGRESS.md "$TEMP_DIR/docs/PROGRESS.md" + ``` + +3. 差分がある場合,「⚠ テンプレート側の `docs/PROGRESS.md` 骨組みに変更があります.既存の追記内容を保持したまま骨組みを反映したい場合はファイルを直接編集してください.自動マージは行いません.」と通知する +4. 既存ファイルが無い稀なケース(テンプレートを使わずに作られた古いプロジェクト等)に限り,テンプレート版をそのまま `cp` で配置する + +### `.gitattributes` + +1. 既存ファイルとテンプレート版の差分を表示する: + + ```bash + diff -u .gitattributes "$TEMP_DIR/.gitattributes" + ``` + +2. ユーザーに以下のいずれかを選ばせる: + - **上書き**: テンプレート版で既存を置換 + - **マージ**: 両者のルールを統合(具体的な統合内容は対話で決定) + - **スキップ**: 既存ファイルを維持 +3. 選択に応じて処理する + +### `.claude/settings.json` + +`settings.json` は hooks 設定を持つ.team モードのプロジェクトは PreToolUse(`restrict_repo_access.py`)に加えて SessionStart(`check_sync.sh`)を配線しているため,テンプレート版で盲目的に上書きするとプロジェクト固有の配線が失われる.同期担当エージェント(= あなた)が Read と Edit で JSON をマージする: + +1. 既存 `.claude/settings.json` とテンプレート版 `$TEMP_DIR/.claude/settings.json` を Read する +2. 差分を表示する: + + ```bash + diff -u .claude/settings.json "$TEMP_DIR/.claude/settings.json" + ``` + +3. **既存の hooks を保持したまま**,テンプレート側で追加・変更された hook(イベント・matcher・command)のみを統合する: + - 既存に無いイベント/hook はテンプレート版から追加する + - プロジェクト固有の hook(team の SessionStart `check_sync.sh` 等)は残す + - 同一 hook の command 変更(例: `restrict_repo_access.py` の起動方法変更)はテンプレート版に合わせる + - solo モードで SessionStart(check_sync) が無い場合は,team 専用の配線を勝手に追加しない +4. `git diff .claude/settings.json` で結果を表示しユーザーに確認する + +### `.claude/template-overrides.md` + +テンプレート改変台帳はプロジェクト固有の記録のため,**既存ファイルがある場合は内容を上書きしない**(`docs/PROGRESS.md` と同じ扱い): + +1. 差分を表示する: + + ```bash + diff -u .claude/template-overrides.md "$TEMP_DIR/.claude/template-overrides.md" + ``` + +2. 差分が骨組み(説明文・記入例)のみであれば,「⚠ テンプレート側の `.claude/template-overrides.md` の説明文に変更があります.台帳の行を保持したまま反映したい場合はファイルを直接編集してください.自動マージは行いません」と通知する +3. 既存ファイルが無い場合(本機能導入前に作られたプロジェクト)に限り,テンプレート版の雛形をそのまま `cp` で配置し,「台帳を新設しました.テンプレート由来ファイルを意図的に変更している箇所があれば登録してください(`.claude/rules/template-customization.md`)」と案内する + +## 削除候補の抽出 (Deletion Candidates) + +ステップ 5.7 で,削除候補(D およびリネーム元)を抽出し,**ローカルに実在するファイルだけ**に絞る: + +```bash +DELETIONS=$(echo "$CHANGED_ENTRIES" | awk -F'\t' '$1 == "D" {print $2} $1 ~ /^R/ {print $2}') + +# ローカルに実在するファイルのみを削除候補に残す +DELETIONS=$(echo "$DELETIONS" | while IFS= read -r f; do + [ -n "$f" ] && [ -f "$f" ] && echo "$f" +done) +``` + +この実在フィルタにより,テンプレート側のリネームや過去の移行でプロジェクトが持っていない旧パス,solo モードに配置されないチーム層ファイルは,削除確認に出ることなく自動的に除外される.台帳の方針による振り分け(`keep` は自動除外等)は `SKILL.md` ステップ 5.7 に従う. + +## 台帳の整合チェック (Override Consistency Check) + +ステップ 5.8 で,台帳に登録されているのにテンプレート最新版に存在しないパスを列挙する(リネーム・削除済み,またはパスの記入ミス): + +```bash +override_paths | while IFS= read -r p; do + [ -z "$p" ] && continue + case "$p" in + */) [ -d "$TEMP_DIR/$p" ] || echo "$p" ;; + *) [ -f "$TEMP_DIR/$p" ] || echo "$p" ;; + esac +done +``` + +## クリーンアップ (Cleanup) + +ステップ 5.9 で同期済み SHA を記録し,一時ディレクトリを削除する: + +```bash +echo "$NEW_SHA" > .claude/template-sync-sha +rm -rf "$TEMP_DIR" "$WORK_DIR" +```