0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

安全ガードだけは、あえて参照しない。親リポ1つで7リポジトリのClaude Codeを指揮する設計

0
Posted at

はじめに

😩「web で直したスキル、admin と service にもコピーしなきゃ…」
🤔「あれ、admin の create-pr って、こんな手順だったっけ?」
😱「このリポのガード、PR のマージを止めてない…?」

AI と開発するリポジトリが増えると、こんなことが起きませんか?
1 つのリポで育てたスキルや設定を、コピーで配った瞬間から、ずれ始めます。

個人開発している口コミサービス あじぴた は、いま 7 つのリポジトリで動いています。
そのうち 3 つのリポで、Claude Code のスキルと安全ガードがそれぞれ別物に育っていました😇

この記事では、

  • 同じはずのスキルとガードが、どれだけずれていたか
  • スキルは Claude Code のプラグインにして、1 か所を参照させた話
  • 安全ガードだけは、あえてコピーを PR で配ることにした理由
  • ほかのリポジトリを、親リポ 1 つから指揮するときの落とし穴

を書きます🧭

この記事は、個人開発サービス「あじぴた」の設計を書くシリーズの 1 本です。
単体で読めるように書いていますが、サービスの全体像はハブ記事にまとめています。

7 つのリポジトリと、ずれていったもの

まず、あじぴたのリポジトリの構成です。

リポジトリ 役割 コード
web アプリ本体。DB のスキーマもここで管理 あり
admin 運営コンソール あり
service LP・サービスサイト あり
growth 施策・数字・お知らせの原稿 なし
articles 技術記事の下書き なし
extractor 運用のための Chrome 拡張 あり
親 ほかの 6 つを束ねて指揮する(この記事の主役) なし

AI と一緒に開発の手順を回しているのは、web・admin・service の 3 つです。
Issue を詰める、実装する、PR を作る、レビューに対応する。こうした手順を、Claude Code のスキルにしています📝

スキルは web で育ち、ほかは手でコピーしていた

スキルが一番育つのは、一番よく触る web です。
レビューで指摘を受けるたびに手順を直し、admin と service には思い出したときに手でコピーしていました。

結果、同じ名前のスキルがこうなっていました。

スキル web admin service
create-pr 193 行 151 行 127 行
work-on-issue 163 行 87 行 86 行
extract-component 259 行 103 行 103 行

行数の違いは、そのまま手順の違いです。
たとえば web の create-pr には、受け入れ条件を 1 件ずつ「満たしている/足りない/この PR では無理」に分けてから PR を作る手順がありました。
admin と service には、それがありません。service には、Copilot のレビューは自動で付くのに、手動で追加する古い手順まで残っていました😅

安全ガードは、もっと危なかった

スキルより困ったのが、安全ガードです。

Claude Code の hook で、コマンドを実行する直前に文字列を検査し、.env への書き込みや force push を止めています。
これも 3 リポで別々に育っていました。

リポジトリ 止める規則の数 例
web 8 main / develop への直接 push・PR のマージ・supabase db push などリモート DB への変更
admin 5 ガード自身の書き換え・node -e などのワンライナーで .env を触る抜け道
service 4 .env・Claude Code の設定ファイル・force push・git reset --hard などだけ

web にしか無い規則があり、admin にしか無い規則もある。
どのリポで作業するかで、AI が止められる操作が違う状態でした🙈

方針:正本は 1 つ。配り方は、性質で分ける

やることは「正本を 1 か所に置く」です。
置き場所は、ほかの 6 つのリポを束ねる親ディレクトリにしました。もともと全リポをまとめて置いていた場所を、親リポとして Git で管理し始めました。

ただ、何でも同じ方法で配ったわけではありません。

共有するもの 正本 各リポへの届け方 なぜその方法か
共通スキル(7 本) 親リポ プラグインとして参照(コピーしない) 直したら次のセッションから全リポに効いてほしい
安全ガード 親リポ コピーを同期 PR で配る 親が無い環境でも効かせたい。変更は人が見てから効かせたい
規約・デザイントークン web コピーを同期 PR で配る トークンやロゴはコードが import する。規約はリポ単体で開いた AI も読む

分け方の基準は、**「各リポにファイルの実物が要るか」と「変更が勝手に効いて困るか」**の 2 つです💡

スキルは手順書なので、直したらすぐ効いてほしい。
ガードは事故を止める装置なので、変更は一度人が見てから効かせたい。
この違いが、届け方の違いになりました。

スキルは「プラグイン」にして、1 か所を参照させる

Claude Code には、スキルや hook をまとめて配るプラグインの仕組みがあります。
プラグインは「マーケットプレイス」から入れますが、ローカルのディレクトリをマーケットプレイスにすることもできます🔌

親リポに、マーケットプレイスの定義を置きました。

// .claude-plugin/marketplace.json(親リポ・抜粋)
{
  "name": "ajipita",
  "plugins": [
    { "name": "aj", "source": "./plugins/aj" }
  ]
}

ユーザー設定(~/.claude/settings.json の extraKnownMarketplaces)で、このディレクトリをマーケットプレイスとして登録しておきます。
各リポでは、プラグインを有効にするだけです。

// .claude/settings.json(web・admin・service)
{
  "enabledPlugins": { "aj@ajipita": true }
}

ローカルのディレクトリを登録しているので、親のスキルを直せば、入れ直しもコピーもなしで、次のセッションから全リポに届きます✨

確認用のスキルを親に足して、web と admin で呼んでみました。
中身を書き換えると、次に起動したセッションでは書き換えた後の内容が返ってきます。
プラグインのキャッシュ(~/.claude/plugins/cache/)は古いまま変わっていなかったので、キャッシュではなく元のディレクトリを読んでいるようです🔍

プラグインのスキルは /aj:create-pr のような接頭辞付きで呼べます。
名前がほかと重ならなければ、/create-pr のまま呼べることも確かめました。手が覚えたコマンドを変えずに済みます。

リポごとに違う値は、profile に追い出す

ただ、スキルを 1 本にすると困ることがあります。
リポによって違う値があるからです。PR のマージ先ブランチ、検証のコマンド、独立レビュー(文脈を持たないサブエージェントによるレビュー)で見る観点などです。

そこで、スキルの本文からリポ固有の値を消し、各リポの .claude/profile.md に書くことにしました。

<!-- Before(web の create-pr/SKILL.md・抜粋) -->
### 3. lint と build を確認する
npm run lint
npm run build

<!-- After(共通の create-pr/SKILL.md・抜粋) -->
profile の「検証コマンド」をすべて実行する。エラーがあれば修正してから次へ進む。

たとえば service はテストのコマンドが web と違うので、profile にはこう書きます。

<!-- .claude/profile.md(service) -->
## 検証コマンド
- npm run lint
- npm run build
- ロジックを変えたら npm run test(web・admin は npm run test:unit)

## 独立レビューの観点
- 公開ページとしての品質(SEO のメタ情報・OGP・SP 表示の崩れ)
- テストが少ないリポなので、lint / build で確かめにくい箇所を特に読む

スキルは、## 検証コマンド のような見出しを目印に profile から値を読みます。
当てはまらない項目も見出しは消さずに「なし」と書き、書き忘れと、本当に無いものを区別します。
profile 自体が無いときは、推測で値を埋めずに止まるようにしています🔍

結果はこうなりました。

Before After
スキル本文 3 リポで計 約 2,500 行(ずれあり) 親に 1,117 行(1 つだけ)
リポごとの違い 本文に混ざっている profile に 48〜89 行
直したときの反映 手で 3 回コピーする 親で 1 回直すだけ

直す場所は、どのリポからでも

スキルの改善は、たいてい各リポで作業している最中に思いつきます。
そこで、各リポのセッションから親のスキルを編集できるようにしました。

// .claude/settings.json(各リポ)
{
  "permissions": {
    "additionalDirectories": ["../plugins/aj/skills/"]
  }
}

ポイントは、許すのを skills/ だけにしていることです。
同じ親リポにあるガードの正本は、各リポの設定で編集を deny にし、ガード自身も Bash からの書き換えを止めています。
ガードは、各リポのセッションからは直せません。理由は次の節で書きます🔒

安全ガードは、あえてコピーを PR で配る

ガードもプラグインで参照させれば、コピーは要りません。
それでもコピーを配ることにした理由は 2 つです。

1 つ目は、親ディレクトリが無い環境でも効かせたいからです。
クラウドの実行環境などでリポを単体で開くと、../plugins/ はありません。
そのとき、スキルが無いだけなら困りませんが、ガードが無いのは困ります。だから、各リポの .claude/hooks/ にコピーを置きます。

2 つ目は、ガードの変更は、人が見てから効かせたいからです。
スキルと同じように「直したら次から効く」だと、ガードを緩める変更も、誰も見ないまま効いてしまいます😨

親リポの Action が、同期 PR を作る

コピーは、親リポの GitHub Actions が配ります。

親リポの main でガードが変わる
  ↓
① 親でガードの回帰テストを回す
  ↓
② web・admin・service をそれぞれ取得して、ガードをコピーする
   (admin・service には、web の規約・デザイントークンもあわせてコピーする)
  ↓
③ コピーした先でも、もう一度テストを回す
  ↓
④ 差分があるリポにだけ、develop 向けの同期 PR を作る

同期 PR はマージしない限り反映されません。
ここで人が差分を見て、「止めたい操作が止まり、ふだんの作業が止まらないか」を確かめます👀

コピーを AI が直接直さないよう、各リポの設定で .claude/hooks/ の編集を禁止しています。
直すなら親の正本を直し、PR で届くのを待つ。ガードを変える道を 1 本にしています。

規則は全部合わせて、テストで固める

共通のガードは、3 リポの規則を全部合わせた 9 規則にしました。
web の保護ブランチの規則と、admin の抜け道対策が、どのリポでも効くようになります。

ただ、全部合わせると困る規則が 1 つありました。保護ブランチへの直接 push です。
web・admin・service は PR 経由が正規の運用ですが、growth や articles は直接コミットが正規の運用です。

そこで、push 先のリポを origin のリポ名で判定し、リポごとに切り替えています。

repo_has_protected_branches() {
  name=$(git -C "$1" remote get-url origin 2>/dev/null | sed -E 's#.*/##; s/\.git$//')
  case "$name" in
    ajipita-web | ajipita-admin | ajipita-service | "") return 0 ;;  # 保護する(リポ不明も含む)
    *) return 1 ;;                                                     # 直接コミットが正規
  esac
}

リポが分からないときは、止める側に倒しています。

回帰テストは 102 件です。そのうち 33 件は「止めてはいけないコマンドを止めない」ことの確認です。
ガードは、止めすぎるとふだんの作業が全部止まるからです🧪

親リポから、ほかの 6 リポを指揮する

正本を親に置いたついでに、作業の依頼も親で受けるようにしました。
「お気に入りを一覧で見たい」「今日の記事を書いて」のような雑な一言を、/do というスキルで受けます。

/do がやるのは、判断と、各リポのスキルへの橋渡しだけです。

  • どのリポで何をするかを、コードと Issue を読んで決める
  • 依頼の言葉から、どこで止まるかを決める
  • 各リポのルールとスキルを読み、その手順どおりに進める

止まる地点は、依頼の言葉で決めています。

依頼の言葉 止まる地点
〜を追加したい / 〜を直したい Issue の起票
実装して / 進めて PR の作成(レビュー対応まで)
書いておいて 公開できる完成稿(公開はしない)
どうなってる? 状況の報告

言われた地点より先へは進みません。
PR のマージ、本番への適用、利用者に届く配信は、どの依頼でも自分の手で行います🔑

つまずいたポイント

① 親で cd を忘れた git は、親のリポに対して成功する

親ディレクトリ自体も Git で管理し始めたので、サブリポへの cd を忘れても、git はエラーになりません。
親のリポに対してコミットや push が成功してしまいます😱

対策は、Bash を毎回 cd <リポの絶対パス> && … で始めることです。
/do の手順とサブエージェントへの指示の両方に、これを書いています。

② 変数で cd すると、ガードをすり抜けた

ガードは、コマンドの中の cd を追って、push 先のリポを判定しています。
ところが cd $R/ajipita-web && git push origin main のように変数で cd すると、行き先が分かりません。

共通化より前、親のガードの最初の実装では、分からないときに直前の場所のまま判定していました。
growth(直接コミットが正規)にいる状態でこれを打つと、web の main への push が通ってしまいます🕳️

# Before: 解決できないと、直前の場所(growth)のまま判定 → 通る
# After: 変数を含むパスは「リポ不明」にして、止める側へ倒す
case "$p" in *'$'*) printf '%s' "$unresolved_dir"; return ;; esac

親から複数リポを扱うようになって、初めて出てきた穴でした。

③ additionalDirectories は、書いただけでは効かない

各リポから親のスキルを直せるように、additionalDirectories に ../plugins/aj/skills/ を足しました。
ところが最初は、CLI が「この設定を無視する」と警告を出していました😵

原因は、そのフォルダをまだ信頼していなかったことです。
対話モードで一度フォルダを開いて信頼すると、効くようになりました。
設定ファイルを配っただけでは効かないので、新しい PC やリポでは最初に一度開く手順が要ります。

④ 親で起動すると、サブリポの設定が効かない

親ディレクトリで Claude Code を起動すると、自分の環境では、サブリポの .claude/ にあるスキル・権限・hooks が効きませんでした。
サブリポのスキルを呼ぶと Unknown skill になります。CLAUDE.md も、起動時には読み込まれません。

そのため /do では、作業の前に対象リポのルールを自分で読み、スキルは SKILL.md を読んで手順どおりに進める、と決めています。
親から指揮するときは、サブリポの「当たり前」が効いていない前提で動く必要があります📌

⑤ 同期を毎日回すと、GitHub Actions の実行時間がもったいない

最初は、同期の Action を毎日回す設定で作りました。
でも規約やガードは、毎日は変わりません。ほとんどの実行が「差分なし」で終わるはずです💸

そこで、一度も定期実行が走らないうちに外し、親リポでガードが変わったときと、web の develop で同期対象のファイルが変わったときだけ起動するようにしました。
web 側に、変わったファイルで絞って親の Action を起動する workflow を置いています。

web からの自動起動は、実際に動くところまではまだ確かめていません(同期 PR そのものは、Action を手動で起動して届くことを確認済みです)。

まとめ

  • コピーで配ったスキルや設定は、配った瞬間からずれ始める。同じ create-pr が 193・151・127 行になっていた
  • 正本は 1 か所に。ただし配り方は、各リポにファイルの実物が要るか・変更が勝手に効いて困るかで分ける
    • スキルは Claude Code のプラグインにして参照させる。リポごとの違いは profile に追い出す
    • 安全ガードはコピーを同期 PR で配る。変更は一度、人が見てから効かせる
  • ガードは各リポの規則を全部合わせ、リポで違う規則は origin のリポ名で切り替える。分からないときは止める側へ
  • 親から指揮するときは、cd 忘れと、サブリポの設定が効いていないことに気をつける

リポジトリが 2 つ目になった時点で、「どれが正本か」を決めておけばよかったな、と思っています。
複数のリポで AI と開発している方の、参考になれば幸いです🙏

シリーズの他の記事も、よろしければ📚

このシリーズでは、個人開発サービス「あじぴた」の設計をテーマごとに書いています。
サービスの全体像や、ほかの記事の一覧はハブ記事にまとめています👇

🔗 「低評価を公開しない」口コミサービスを個人開発した話 — 5,800コミット・7リポジトリの全体像

この記事と関係の深い記事です。

🔗 新人が初日に迷うことは、AIも毎回迷う。だから暗黙のルールを、すべて言葉にした(1 つのリポで、スキル・hooks・メモリを作った話)
🔗 CI全緑でも、マージしてはいけないPRがあった。GitHub Actionsは「どこで止めるか」で設計する(PR のマージを人に残す考え方)

設計の中身を通して読みたい方には、解剖ドキュメントも公開しています🔬
🔗 あじぴたの内側(ajipita-inside)

0
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?