kata — multi-project template applier with AI-delegated merge

Claude Code と一緒に Rust 製の CLI を作るのが楽しくて、ここ最近で shun / rvpm / todoke / yui / renri といった CLI がどんどん増えていきました。これらにまったく同じボイラープレートを適用しつづけるためのメタテンプレート CLI、kata (型) を作りました。

型 — the woodblock pattern. 各プロジェクトに同じ型を押し当てる、版木のイメージです。

なぜ作ったのか

同じような CLI が増えてくると、共通ボイラープレートのメンテがどんどんしんどくなります。具体的にしんどかったのはこのあたりです。

  • Makefile.tomlcheck / clippy / test タスクの並び
  • .github/workflows/ci.yml の OS マトリクスと action のバージョン pin
  • .github/workflows/release.yml の cross-compile + cargo publish のテンプレ
  • rustfmt.toml / clippy.toml / rust-toolchain.toml の方針
  • apm.ymlAPM 経由の AI エージェント用 skill (renri など) を入れる定型
  • renovate.json の auto-merge ルール
  • そして極めつけに AGENTS.md / CLAUDE.md / GEMINI.md

最後の AGENTS.md がいちばんやっかいでした。Claude / Gemini / Codex に渡している「PR レビューはこう回す」「worktree workflow はこう」「Rust の lint/format ポリシーはこう」みたいな会話で蓄積されたノウハウを、新しいプロジェクトを生やすたびに、あるいは規約を 1 行直すたびに、N 個のリポジトリにぜんぶ手でコピペする必要があったのです。

「Gemini Code Assist と CodeRabbit 両方のレビュー待つようにしよう」と気付いたら 7 個のリポジトリの AGENTS.md を全部開いて編集する、というのを何度かやりました。1 回ならいいんですけど、規約は一度決めて終わりじゃなくて、運用しながら何度も微調整したくなる。

copier / cookiecutter / cruft といった既存ツールは「最初のプロジェクト生成」と「機械的な再適用」までは面倒を見てくれるんですが、AGENTS.md のように

  • リポジトリ共通のセクション (PR レビュー規約、worktree workflow)
  • プロジェクト固有のセクション (このプロジェクトのアーキ概要、特殊な事情)

1 枚のファイルに同居しているようなものを update し続ける機能はありませんでした。AI に判定を委譲するモードもありません。

そこで作ったのが kata です。「型 (テンプレート) を版木のように押し当てて、押し当てきれないところだけ AI に判断させる」 という発想で設計しました。

kata の特徴

  • layered templatepj-base + pj-rust + pj-rust-cli を順に重ねて、後勝ち。言語非依存な部分を pj-base に集約できる
  • how × when の二軸how (overwrite / merge-section / merge-toml / merge-yaml / ai / script) と when (once / always / manual) が独立。how="ai", when="once"how="script", when="always" がどちらも自然に書ける
  • marker-bracketed merge-sectionAGENTS.md<!-- kata:agents:base:begin --><!-- kata:agents:base:end -->間だけを kata が管理。プロジェクト固有の節は外側にいくらでも書ける
  • path-based merge-tomlMakefile.tomltasks.check / tasks.clippy / tasks.test だけを kata が所有して、tasks.install-local のような独自タスクは触らない
  • AI 委譲how = "ai" なファイルは、インストール済みの claude / gemini / codex CLI に template の diff と現在の中身を投げて、chezmoi 風の [a]ccept / [e]dit / [s]kip / [d]efer で確認
  • truth は PJ 側 — どのテンプレートをどの rev で適用したかは各 PJ の .kata/applied.toml に記録される。グローバル設定は単なる PJ パスのレジストリ
  • 並列実行 — tokio で PJ をファンアウト、AI 呼び出しは semaphore で抑制してエージェント CLI の同時起動が爆発しないようにする
  • CI 同期kata-apply.yml を pj-base が配布。daily で kata update + kata apply を回して、テンプレ上流の変更を PR として自動取り込み

インストール

sh
1
cargo install kata

kata --version で動作確認できれば OK です。

クイックスタート

sh
1
2
3
4
5
6
7
8
9
10
11
12
# 新しい Rust CLI プロジェクトに rust-cli プリセットを適用
mkdir my-rust-cli && cd my-rust-cli
kata init github.com/yukimemi/pj-presets:rust-cli --non-interactive

# テンプレが進化したら再適用 (idempotent)
kata apply --non-interactive

# 適用前のドライラン
kata status

# 何が tracked か確認
kata list

これで Makefile.toml / apm.yml / renri.toml / .github/workflows/ci.yml / release.yml / AGENTS.md / CLAUDE.md / GEMINI.md / rustfmt.toml / clippy.toml / rust-toolchain.toml / renovate.json などがまとめてプロジェクトに落ちてきます。

preset = テンプレートの束

pj-presets:rust-cli は単に「どのテンプレートを、どの順に重ねるか」を書いた小さなファイルです。

toml
1
2
3
4
5
6
7
8
9
10
11
# pj-presets/rust-cli.toml
name = "rust-cli"

[[templates]]
source = "github.com/yukimemi/pj-base"

[[templates]]
source = "github.com/yukimemi/pj-rust"

[[templates]]
source = "github.com/yukimemi/pj-rust-cli"

順に書かれた順番で適用され、同じファイルが衝突したら後勝ちです。これによって

  • pj-base — 言語非依存 (LICENSE, .gitignore, AGENTS.md の共通節, apm.yml, renri.toml の base, kata-apply.yml ……)
  • pj-rust — Rust 共通 (Makefile.toml, CI matrix, rust-toolchain.toml, rustfmt.toml, clippy.toml)
  • pj-rust-cli — CLI 用追加 (release.yml の cross-compile + cargo publish)

という役割分担ができて、たとえば「Rust ライブラリだから CLI 用 release は要らない」というケースには pj-rust-lib を組み合わせた別 preset を用意するだけで済みます。

ライブラリ向けに rust-lib、Web フロント向けに web-react、Firebase まで含む web-react-firebase も用意していて、preset 単位で気軽に「型」を切り替えられます。

howwhen を独立に持つということ

kata の設計でいちばんこだわったのが how (適用方法) と when (タイミング) を別の軸として持つことです。

how 何をするか
overwrite テンプレ通りにファイルを上書き
merge-section <!-- kata:*:begin -->end の間だけ差し替え
merge-toml toml_edit で指定パスだけマージ
merge-yaml serde_yaml で YAML の指定パスだけマージ
ai claude / gemini / codex に判断委譲
script 任意のシェルコマンドを実行
when いつ適用するか
once 初回だけ。以降は .kata/applied.tomlonce_applied = true で skip
always 毎回適用 (kata apply のたびに同期)
manual 明示的に --file を指定したときだけ

この 2 軸が独立なので、たとえば

  • release.ymloverwrite, when=once — 初回だけ配って、以降はプロジェクト側で自由に編集
  • ci.ymloverwrite, when=always — CI は常に上流追従
  • Makefile.tomlmerge-toml, when=always — kata 所有のタスクだけ追従
  • AGENTS.mdmerge-section, when=always — マーカーの中だけ追従
  • apm.ymloverwrite, when=once — 初回テンプレ、以降はプロジェクト側
  • LICENSEoverwrite, when=once — 初回だけ

という細かいポリシーを 1 つの manifest で表現できます。「mode」として 1 つの enum にまとめなかったのは、how="ai", when="once" (ROADMAP.md の初期生成だけ AI に任せる) のような組み合わせを潰したくなかったからです。

AGENTS.md の merge-section が刺さるところ

kata を入れて一番恩恵を感じているのが AGENTS.md の扱いなので、ここは少し詳しく書きます。

pj-base 側の AGENTS.md.base (テンプレ) には共通規約だけが書かれています。

markdown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## Shared conventions

This file is the agent-agnostic source of truth (per the
[agents.md](https://agents.md) convention)...

### Git workflow
- **No direct push to `main`.** Open a PR.
- Branch names: `feat/...`, `fix/...`, `chore/...`.
- **PR titles + bodies in English.**
...

### PR review cycle
- Every PR runs reviews from **Gemini Code Assist** and **CodeRabbit**...
- **After opening a PR, immediately enter the review-monitoring loop...**
...

### Worktree workflow
Use [`renri`](https://github.com/yukimemi/renri) for any commit-bound change...

これを pj-base/template.toml でこう宣言しています。

toml
1
2
3
4
5
6
[[file]]
src = "AGENTS.md.base"
dst = "AGENTS.md"
how = "merge-section"
when = "always"
marker = { begin = "<!-- kata:agents:base:begin -->", end = "<!-- kata:agents:base:end -->" }

kata apply を実行すると、各プロジェクトの AGENTS.md の対応マーカーの中だけがテンプレ内容で置き換わります。マーカーの外にはそのプロジェクトの固有の事情 (アーキテクチャ、設計判断、ドメイン用語、Phase n の状況……) をいくらでも書いておけて、それは kata apply で一切触られません。

さらに layered なので、pj-rust<!-- kata:agents:rust:* -->pj-rust-cli<!-- kata:agents:rust-cli:* -->それぞれ自分のマーカーブロックを所有します。これによって 1 枚の AGENTS.md の中に「言語非依存 / Rust 共通 / Rust CLI 専用 / プロジェクト固有」の 4 層が綺麗に同居する、という構造になります。

Makefile.toml の merge-toml で「kata 所有タスク」だけ追従させる

AGENTS.md の marker と並んでよく使うのが、Makefile.tomlmerge-toml です。pj-rust/template.toml ではこう宣言しています。

toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[[file]]
src = "Makefile.toml"
how = "merge-toml"
when = "always"
# kata owns these specific tasks; everything else is left untouched
paths = [
    "tasks.default",
    "tasks.check",
    "tasks.fmt",
    "tasks.fmt-check",
    "tasks.clippy",
    "tasks.test",
    "tasks.lock-check",
    "tasks.publish-dry",
    "tasks.hook-install",
    "tasks.apm-install",
    "tasks.setup",
    "tasks.on-add",
]

paths で「kata が所有する TOML のパス」を明示します。tasks.check / tasks.clippy / tasks.test のような kata-managed task は毎回上流から上書きされますが、consumer 側で勝手に追加した tasks.install-local とか tasks.deploy のような独自タスクは paths に含まれないので一切触られない という挙動になります。

merge-tomltoml_editpaths で指定した key だけを replace していくので、

  • 同じ key の値は更新される (例: tasks.check.script の中身が変わる)
  • paths に出てこない key は consumer の手書きが完全に保持
  • インデント / コメント / 順序も toml_edit レベルで保持

という、overwrite だと潰してしまう手書き内容を尊重した同期ができます。

GHA の action バージョンは .kata/vars.toml に切り出して Renovate に任せる

merge-toml × when = "once" のもう 1 つの実用例が、GitHub Actions の version pin を .kata/vars.toml (Tera 変数ファイル) に切り出す パターンです。

考えたい問題はこうです。

  • ci.yml / release.yml / kata-apply.yml の version pin (actions/checkout@v6.0.2 等) を Renovate に自動 bump させたい
  • しかし workflow 本体は overwrite, when=always で kata-managed なので、Renovate が直接 ci.yml を編集しても次の kata apply で潰されてしまう

解決策は、pin だけを .kata/vars.toml に切り出して、workflow 本体は Tera テンプレでそれを参照 することです。

pj-base/vars.toml (universal pin):

toml
1
2
3
[actions]
checkout = "actions/checkout@v6.0.2"
create_pull_request = "peter-evans/create-pull-request@v8.1.1"

pj-rust/vars.rust.toml で Rust 専用の pin を追加マージ:

toml
1
2
[actions]
swatinem_rust_cache = "Swatinem/rust-cache@v2"

template.toml 側で .kata/vars.toml の所有関係を宣言:

toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# pj-base — universal pin を初回だけ seed
[[file]]
src = "vars.toml"
dst = ".kata/vars.toml"
how = "overwrite"
when = "once"

# pj-rust — Rust 専用 pin を merge-toml で重ねる (初回だけ)
[[file]]
src = "vars.rust.toml"
dst = ".kata/vars.toml"
how = "merge-toml"
when = "once"
paths = ["actions.swatinem_rust_cache"]

両方とも when = "once" なので、初回 apply で seed したあとは consumer の .kata/vars.toml を kata は一切触らない、という所有関係になります。ci.yml.tera の中では:

yaml
1
2
- uses: {{ vars.actions.checkout }}
- uses: {{ vars.actions.swatinem_rust_cache }}

として参照されているので、.kata/vars.toml の pin が変われば次の kata applyci.yml 全体が新しい version で再レンダリングされる、という流れになります。

その上で consumer 側の .kata/vars.toml を Renovate の customManager が scan していて、新しい action リリースが出たら .kata/vars.toml の pin 値を bump する PR を作ってくれます。Renovate が触るのは .kata/vars.toml の 1 行だけ、workflow 本体は kata-apply の再レンダリングで反映、という分業になります。

まとめると、

  • workflow の構造変更 (新ステップ追加、jobs の整理など) → 上流テンプレへの push → daily kata-apply で降りてくる
  • action の version bump → consumer の .kata/vars.toml を Renovate が自走で書き換える → 次の kata applyci.yml が再レンダリング

という、役割分担の明快な並列同期merge-tomlwhen = "once" の組み合わせだけで組めます。

ところで、上の ci.yml.tera{{ vars.actions.checkout }} のように書けているのは Rust 製のテンプレートエンジン Tera のおかげです。.tera サフィックスの付いたファイルが apply 時に Tera で render されて、suffix を落とした名前で consumer に書き出される、というシンプルな仕組み。{% if is_windows() %} のような分岐も {{ env.HOME }} のような環境変数参照も全部 Tera の機能で、kata 側はほぼ何も再発明していません。

実はこの「設定を Tera で書ける」感覚は、rvpmtodoke といった他の Rust 製 CLI でも同じスタックで提供していて、内部では teravars (Tera + vars + include + system context を統一した薄いラッパー) を共有しています。rvpm で見慣れた {% if is_windows() %} がそのまま kata の template でも通る、というのが地味に効いていて、Rust で「設定が宣言的に書ける小さな CLI」を作るときの定番スタックとして teravars はかなりおすすめです。

ここが本題: pj-base を直せば全プロジェクトに反映される

これが kata を作って一番うれしかったところです。

「あ、AGENTS.md のこの一節、ちょっと書き方が悪かったな。Claude が誤解しがちだから直したい」 「PR review cycle の節、CodeRabbit の rate-limit notice の扱いを追記したい」 「Worktree workflow の節に新しい renri prune の説明を入れたい」

こういう気付きは規約を運用してるとめちゃくちゃ頻繁にあります。kata を入れる前は N 個の AGENTS.md を全部開いて同じ編集を N 回繰り返す ことになっていました。

kata を入れた今はこうなっています。

sh
1
2
3
4
5
6
7
8
# pj-base 側でだけ直す
cd ~/src/github.com/yukimemi/pj-base
$EDITOR AGENTS.md.base
git commit -am "docs(agents): clarify PR review cycle"
git push

# あとは何もしなくていい — 翌日になれば、
# kata-apply ワークフローが全 PJ に PR を作って auto-merge する

具体的には、各プロジェクトの .github/workflows/kata-apply.yml (これも pj-base 配布) が毎日 03:17 UTC に走って、

  1. 上流テンプレ (pj-base / pj-rust / pj-rust-cli) の最新 rev を取得
  2. kata update で applied.toml の rev を更新
  3. kata apply --non-interactive --no-ai で再レンダリング
  4. 差分が出たら kata-apply/auto ブランチに PR を作成
  5. CI が緑なら auto-merge

を全リポジトリで自動的に回します。規約や設定の更新が該当するテンプレレイヤへの 1 push だけで N プロジェクトに伝播していくわけです。AGENTS.md の共通節なら pj-baseMakefile.toml / rustfmt.toml / clippy.toml / ci.yml といった Rust 共通の規約なら pj-rustrelease.yml の cross-compile + cargo publish なら pj-rust-cli、というレイヤ分担そのままに、それぞれの上流に 1 push すれば全 consumer PJ が翌日には追従します。

ワークフロー本体もテンプレ管理なので、ワークフロー自体の改善 (新しい action バージョン、ステップの追加) も同じ仕組みで自動的に各プロジェクトに降りていきます。テンプレが自分自身のメンテも見るかたち。

CI で kata-apply を回す

このフローの肝は CI で kata apply を回せることです。設計時点でここを最優先に考えていて、

  • --non-interactive でプロンプトを完全にスキップできる
  • --no-ai で AI ファイルを丸ごとスキップ (= 機械的なテンプレ同期だけ自動で回せる)
  • --non-interactive --yes で「全部 accept」モードもあり (= 信頼できるテンプレなら AI も自動 accept)

という 3 つのフラグで CI 適性が担保されています。

pj-base が配布する kata-apply.yml.tera の中身は essentially こんな感じです (一部抜粋)。

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
name: kata-apply

on:
  schedule:
    - cron: "17 3 * * *"     # 03:17 UTC daily
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

concurrency:
  group: kata-apply
  cancel-in-progress: false

jobs:
  apply:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6.0.2
        with:
          token: ${{ secrets.KATA_APPLY_TOKEN }}    # ← PAT 必須

      - name: Install kata
        run: |
          KATA_VERSION="$(curl -fsSL https://api.github.com/repos/yukimemi/kata/releases/latest | jq -r .tag_name)"
          curl -fsSL "https://github.com/yukimemi/kata/releases/download/${KATA_VERSION}/kata-x86_64-unknown-linux-gnu.tar.gz" \
            | tar xz -C /tmp
          sudo mv /tmp/kata /usr/local/bin/kata

      - name: kata update + apply
        run: |
          kata update
          kata apply --non-interactive --no-ai

      - name: Open / update PR if there are changes
        id: cpr
        uses: peter-evans/create-pull-request@v8.1.1
        with:
          token: ${{ secrets.KATA_APPLY_TOKEN }}
          branch: kata-apply/auto
          title: "chore(kata): auto-apply"

      - name: Enable auto-merge
        if: steps.cpr.outputs.pull-request-number != ''
        env:
          GH_TOKEN: ${{ secrets.KATA_APPLY_TOKEN }}
        run: |
          gh pr merge --auto --squash ${{ steps.cpr.outputs.pull-request-number }}

要点だけハイライトしておきます。

  • KATA_APPLY_TOKENGITHUB_TOKEN じゃダメGITHUB_TOKEN 経由で開いた PR は GitHub のループ防止仕様で下流ワークフロー (CI) を triggers しません。auto-merge の前提が CI 緑判定なので、CI が走らないと永遠にマージされません。適切な権限 (contents: write + pull-requests: write) を付けた PAT (fine-grained でも classic でも可) を KATA_APPLY_TOKEN リポジトリシークレットとして設定する、というのが consumer 側の唯一のセットアップ作業です
  • ブランチは kata-apply/auto 固定create-pull-requestdelete-branch: true と組み合わせると、毎日新しい差分を同じブランチに rolling で上書きしていく動きになるので、PR が大量にスタックしません
  • CI が落ちたら PR は open のまま残る。auto-merge は CI 緑になったときだけ発火するので、何か壊れたら人間が見るタイミングが自然に生まれます
  • cron: "17 3 * * *" は意図的な off-peak かつ off-the-hour pin0 0 * * *0 9 * * * みたいに :00 ちょうどでスケジュールする人が地球上に多すぎて、GitHub Actions の runner プールが :00 / :30 で枯渇する (cron-storm) という現象があります。:17 のような半端な分にずらすと runner 確保がスムーズ。さらに 3 UTC は日本時間 12:00 / 米西海岸の夜 / 欧州早朝で、runner 自体も比較的空いている時間帯なので、daily な機械的同期にはちょうどいい枠です

「全プロジェクトに同じワークフローが入っている」という事実が、全プロジェクトに同じ自動同期がかかっている という安心感に繋がっていて、これは入れた価値が大きかったです。

AI 委譲モード

how = "ai" を指定したファイルは、インストール済みの AI CLI に判断を投げます。

template.toml にこう書くと:

toml
1
2
3
4
5
6
7
8
9
10
[[file]]
src = "ROADMAP.md.tera"
dst = "ROADMAP.md"
how = "ai"
when = "always"
agent = "auto"                # claude > codex > gemini で最初に見つかったやつ
prompt = """
Merge the template's structural changes into the project's
ROADMAP.md. Preserve project-specific phases and dated entries.
"""

kata は template の diff、現在の dst の中身、prompt をまとめて指定エージェントの CLI (claude -p, gemini -p, codex exec のいずれか) に投げます。返ってきた full body または patch に対して、chezmoi 風の対話プロンプトが出ます。

text
1
2
3
4
5
6
proposed change for ROADMAP.md:
  + Phase 5 — opencode adapter
  + ## Crate structure (regenerated section)
  ...

[a]ccept / [e]dit / [s]kip / [d]efer ?
  • a = そのまま採用
  • e = $EDITOR で開いて手で直してから採用
  • s = この回はスキップ (次回 kata apply でもう一度提案される)
  • d = defer (今回は見送り、ただし「次回必ず再提案」を applied.toml に記録)

--non-interactive だけだと安全側に倒れて AI ファイルはスキップ、--non-interactive --yes だと全部 accept という CI 完全自動モードになります。

backend は trait で抽象化されていて、claude / gemini / codex の 3 つを実装済みです。agent = "auto" は PATH を見て上から順にフォールバックしていく挙動。MockAiAgent も組み込まれていて、テストでは決定的な応答が返せます。

並列度の制御もあって、AI 呼び出しはグローバル semaphore (default 4) で絞られます。kata apply --all で 10 個のプロジェクトを並列で回したときに、エージェント CLI の同時起動が爆発しないようにするためです。

.kata/applied.toml が source of truth

kata の状態は全部 PJ 側の .kata/applied.toml に書かれます。グローバル設定 (~/.config/kata/config.toml) は単なる PJ パスのレジストリで、何が適用されているかは知りません。

applied.toml はだいたいこんな感じです。

toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
preset = "github.com/yukimemi/pj-presets:rust-cli"
applied_at = "2026-05-17T04:40:43Z"

[[templates]]
source = "github.com/yukimemi/pj-base"
rev = "f04151faf4f0678be9621bb724c8f3120a5e4d8b"
version = "0.10.0"

[[templates]]
source = "github.com/yukimemi/pj-rust"
rev = "9f103ca5aaf39e1dcf1a4d84b11685821aabc62f"
version = "0.5.0"

[[templates]]
source = "github.com/yukimemi/pj-rust-cli"
rev = "9263751c3f94f3415147e546db33170157fb1503"
version = "0.2.0"

[files."AGENTS.md"]
content_hash = "e4f146a1c66d11e6b6c707f52507b3e37da2fe52d8d7cf13f75edcf9ad5d3a7f"

[files."Makefile.toml"]
content_hash = "27e3f57b9efd6c177843d9b0d248f40af5843739bcde6ceaf985f2ce518ecfcf"

[files."LICENSE"]
once_applied = true

これを git に commit しておくと、

  • teammate が clone → kata apply で同じ状態が再現できる
  • CIapplied.toml を見るので、ローカル設定なしで CI が完結する
  • rev が pin されている ので、上流の HEAD が動いても勝手に当たらない (kata update で明示的に上げる)

という運用になります。「状態は対象 (PJ) 側に置く、グローバル設定は単なるレジストリに留める」という設計を kata でも採用しました。

kata apply が冪等であること

設計でずっと気をつけたのが「何度走らせても結果が変わらない」ことです。apply が走るたびに差分が出るようなツールは CI で回せないので、

  • content_hashapplied.toml に記録して、変化がないファイルはそもそも書き換えない
  • once_applied = true のファイルは 2 回目以降は完全 skip
  • merge-section / merge-toml のマージはべき等なように実装 (同じ入力で何度マージしても同じ出力)
  • AI モードも --non-interactive --no-ai で完全に固定動作 (= AI モードを除いて再現可能)

という不変条件を守るようにしてあります。

おかげで kata apply --non-interactive --no-ai を毎日 CI で回すと、変更が必要なときだけ PR が立ち、なければ何も起きないという静かな動きになります。

関連プロジェクト

kata の周りに必要な template repo は以下です。すべて単体で意味があるので、好きな組み合わせで preset を組めます。

repo 役割
pj-base 言語非依存 (LICENSE, .gitignore, AGENTS.md 共通節, kata-apply ワークフロー, …)
pj-rust Rust 共通 (Makefile.toml, CI matrix, rust-toolchain, rustfmt, clippy)
pj-rust-cli Rust CLI 用 (release.yml の cross-compile + cargo publish)
pj-rust-lib Rust ライブラリ用 (crates.io publish のみ、バイナリなし)
pj-pnpm pnpm / TypeScript 共通
pj-react-web Vite + React + TS + Tailwind
pj-firebase Firebase Hosting + Firestore
pj-presets rust-cli / rust-lib / web-react / web-react-firebase のバンドル

kata 自身も dogfood で pj-presets:rust-cli を適用しています — README の表が示す通り、Makefile.toml も CI も AGENTS.md も全部 kata-apply 経由で同期されています。

おわりに

kata を入れる前は、AGENTS.md を 1 行書き換えるのに 7 リポジトリの編集が必要でした。今は pj-base に 1 push すれば、翌日には全プロジェクトに PR が立って auto-merge されています。Claude / Gemini / Codex に渡しているノウハウが全プロジェクトで瞬時に揃う、というのが体験として相当よかったです。

「規約を更新する心理的コストが下がると、規約をもっと細かく洗練させたくなる」というポジティブフィードバックが回り始めていて、Claude Code との PR レビューサイクル運用 (/loop での 60s ポーリング、CodeRabbit の rate-limit notice の扱い、version-bump-only PR の特例……) みたいな細かい知見が、書いた次の日には全 PJ の Claude に届くようになりました。

Rust + Tera + tokio + AI CLI 委譲という shun / rvpm / todoke のときから使い倒している組み合わせに、teravars (Tera engine + vars + include の共通エンジン) を載せた構成で、いつもの定番スタックの延長で書けたのも開発体験として良かったところです。

複数のリポジトリのボイラープレートに疲れている方、特に AGENTS.md などの AI への指示書 を複数プロジェクトに散らかしてしまっている方は、ぜひ kata を試してみてください — 型を押して、版木を当てて、揃えていきましょう