実装コードを 1 行も持たないリポジトリが全体を指揮する
「Hora Kit の設計」シリーズの 3 本目。Hora Kit でプロジェクトを始めると、アプリケーションのコードを 1 行も持たないのに全体を指揮するリポジトリができます。弊社が「オケレポ構成」と呼んでいるこの入れ子の git リポジトリ構成と、その git モデルについて書きます。
1. はじめに
この記事は、弊社(Open Reach Tech)が OSS にした AI 開発フレームワーク『Hora Kit』の設計を解説するシリーズの 3 本目です。メイン記事はこちらです。
本当の意味での自動開発を実現するためのAI開発フレームワーク『Hora Kit』をOSSにしました
用語の確認。 Hora Kit で打つコマンドは
/horaの 1 つだけです。/horaが状況に応じて/hora-spec(仕様書を書く)、/hora-setup(実装リポジトリを作る)、/hora-plan(版を確定し、機能一覧と契約を書く)、/hora-build(1 機能を 18 の関所に通す)、/hora-accept(検収する)を順に呼びます。/hora-hotfixだけは/horaが呼ばず、人が直接打ちます。docs はこのうち/hora-specを「決める側」、残りを「作る側」と呼んでいて、この記事でもその呼び方を使います。
Hora Kit でプロジェクトを始めると、<myproject>-app というリポジトリができます。初めて中を見た人はたいてい戸惑うのですが、このリポジトリにはアプリケーションのコードが 1 行もありません。それなのに、このリポジトリが全体を指揮します。
弊社ではこの管理手法を「オケレポ構成」と呼んでいます。オーケストレーション用のリポジトリ(オケレポ)と、プロジェクトの実体コードのリポジトリを切り離す構成、という意味です。今回はその構成と、なぜそうしたのかを書きます。git の話も出てくるので、普段ブランチ運用に悩んでいる方は、自分のチームと比べながら読んでみてください。
2. 入れ子になった git リポジトリ

外側のリポジトリ(<myproject>-app)が仕様書と /hora skill を持ちます。/hora は renchan-boilerplate と furo-boilerplate-nuxt から backend / frontend のリポジトリをその内側に clone し、仕様書を読んで実装します。
myproject-app/
specs/<version>/spec.md 何を作るか。人間と、人間に代わって書く 2 つの skill が書く
.hora/ 何が走ったかの記録。状態そのものと、その履歴
docs/stack/ スタック・ハンドブック
kit/skills/hora-setup/ このリポジトリが自分で書く唯一の skill
.claude/ npm install が生成。git 管理外
backend/ ← gitignore 済み。独立した git リポジトリ
frontend-employee/ ← gitignore 済み。独立した git リポジトリ
内側のリポジトリは gitignore されているので、外側のリポジトリを clone しても実装コードは付いてきません。
ちなみに、入れ子にしているのは git の都合ではなく Claude Code の都合です。セッションは作業ディレクトリの外に書けないので、触る必要のあるリポジトリは中に入っている必要があります。
3. 誰が何を書いてよいか
この構成の本質は、ディレクトリの形ではなく「誰が書けるか」にあります。
| ディレクトリ | 書くもの | それ以外 |
|---|---|---|
specs/ | 人間と、人間に代わって書く 2 つの skill:/hora-spec(承認された 1 節ずつ)、/hora-plan(承認された 1 編集ずつ) | 読み取りのみ |
.hora/ | その作業を記録する skill と、ダイジェストを書く hora-digester。各パッケージのインストーラは自分の記録だけ | 人間は読むだけ |
| 実装リポジトリ | 作成して値を埋める /hora-setup、コードとテストを書く hora-implementer、git 操作すべてを担うメインセッション | — |
弊社がこの境界をディレクトリで切った理由は、規則で守るより構造で守るほうが破られにくいからです。「AI は仕様を書き換えないこと」と指示しても、エージェントは状況によってはそれを破ります。仕様が別のリポジトリにあって、書き込み経路が 2 つしかないほうが確実でした。
docs はこの境界の意味を、「守られているのは『書く行為』ではなく、人間がその文言を実際に読まないまま要件が specs/ に入ることは決してない、ということだ」と説明しています。
4. .claude/ は生成物。ignore は許可リスト方式
npm install を実行すると hora:init が走り、@openreachtech/hora と 4 つのスキルパッケージを .claude/ に配置します。
"hora:init": "hora-core install && hora-skills-ort-core install && hora-skills-ort-renchan install && hora-skills-ort-furo install && hora-skills-ort-support install && node kit/scripts/equip-own-skills.mjs",
"postinstall": "npm run hora:init"
ここに着地するのはプロジェクトのソースではなく、各パッケージのビルド成果物です。だから .gitignore は 2 つのペイロードディレクトリを丸ごと無視します。
#### implementation repositories, fetched or adopted by /hora-setup
/*-backend*/
/*-frontend*/
#### the kit equipped by postinstall (regenerated, not authored here)
/.claude/agents/*
/.claude/skills/*
# The five equip manifests. These are the only entries under .hora/ that are ignored
/.hora/equip-core.json
/.hora/hora-skills-ort-core.json
/.hora/hora-skills-ort-furo.json
/.hora/hora-skills-ort-renchan.json
/.hora/hora-skills-ort-support.json
リポジトリ自身が書く /hora-setup skill も kit/skills/ に置かれ、hook が他と同じように .claude/ へコピーします。だから .claude/ 配下は誰が書いたものでも全部生成物で、名指しで戻すものがありません。
「丸ごと無視」という向きにしているのは意図的です。「パッケージが配布する名前を並べて拒否する」形も考えられますが、各パッケージが配布する名前はリリースごとに変わるので、今日の名前に対して書いた拒否リストは黙って古くなります。そして一致しなくなった拒否リストは、一致しなくなったことを何も告げません。配置された項目が、ただ静かにコミットされ始めるだけです。許可リストなら、その壊れ方はしません。
5. 実装リポジトリの中へリンクしてはいけない
これは docs のクイックスタートに書かれている注意の中で、一番踏みやすいものです。
仕様書から実装リポジトリのファイルへ相対リンクを張ると、自分のディスクでは普通に開けます。ところが、実装リポジトリは gitignore されているので、他の全員の clone ではそのリンクが壊れます。必要なものは specs/<version>/ へコピーしてください。
6. git モデル
git 操作はすべてメインセッションで行われます。/hora 自身か、それが走らせた skill です。エージェントは決して git に触れません。

外側と内側の両方で、同じ名前のブランチが切られます。
| 切る時点 | マージする時点 | |
|---|---|---|
バックエンド行の feature/<id> | 関所 3 に入るとき | 関所 9 を通過したとき |
フロントエンド行の feature/<id> | 関所 10 に入るとき | 関所 17 を通過したとき |
マージするのは検収(関所 18)の後ではなく、ゲートの境界です。これには理由があって、検収のスイートはその時点の全機能に及び、どの機能でも落ちうるので、検収を待つとこの機能のブランチが他の機能の作業をまたいで開きっぱなしになります。検収が見つけたものは retake/ ブランチで戻ってきます。「マージ済みだが後から不足が判明した」に既にある名前が、まさにそれです。
ブランチ名は意図して説明的にしてあります。
| 変更の種類 | ブランチ名 |
|---|---|
| 1 機能の実装 | feature/<feature-id> |
| 新しい依存 | install/<package>-<version> |
| 依存の更新 | update/<package>-to-<version> |
| E2E 環境の拡張(関所 17) | update/e2e-<what>-for-<feature-id> |
| 実装済みのやり直し | retake/<member>-of-<class>-for-<why> |
依存に専用ブランチを与えているのは、package-lock.json のように 2 つの変更が同時にきれいに編集できないファイルがあるからです。「1 度に 1 つ、次を始める前にマージ」という、人間のチームが衝突を避けるやり方をそのまま使っています。
7. このリポジトリが自分で書く唯一の skill
/hora-setup だけは、パッケージではなく boilerplate が配布します。
この skill がやることは、最初から最後までスタックの話です。どのリポジトリが要るか、何がそれを満たすか、届いた後に何を読むか。スタックを知らないパッケージには、その答えを持てません。
ただし、答えそのものは skill の中にもありません。docs/stack/ にあって、skill が実行時に読みます。スタックが変わればそのディレクトリが変わり、skill は編集不要です。
8. まとめ
この構成の利点だと弊社が考えていることを 3 つ挙げます。
1 つ目は、仕様と記録が、実装と別の寿命を持つことです。実装リポジトリを作り直しても、specs/ と .hora/ は残ります。逆に、仕様書を読める人に実装コードを渡す必要もありません。
2 つ目は、git log .hora/ がプロジェクトの履歴そのものになることです。何が走り、何が止まり、何が質問として残ったかが、実装のコミットと混ざらずに読めます。
3 つ目は、外側のリポジトリだけで「今このプロダクトは何ができるか」が分かることです。specs/ が何を作ると決めたかを持っていて、.hora/acceptance/ が何が検収を通ったかを持っているからです。
git リポジトリの入れ子は最初は奇妙に見えると思いますが、「誰が書けるか」で切っていると分かると自然に見えてきます。同じような構成を検討している方の参考になれば幸いです。
原典はこちらです。 https://github.com/openreachtech/hora-boilerplate/blob/main/docs/architecture.ja.mdhttps://github.com/openreachtech/hora-core/blob/main/kit/skills/hora/references/commits.md