Skip to main content
Glama
README.md
# claude-factory

音声対話で駆動する、個人用「ループエンジニアリング」システム。

Claude アプリのボイスモードで話しかけると、裏の **Claude Code** がリポジトリで作業し、
判断が必要になると質問を返してくる。それに音声か画面で答えると作業が続く。
企画は `docs/01_企画書.md`、実装方針は `docs/02_制作指示書.md`、
セッション管理は `docs/03_セッション管理.md`。

```
あなた(音声)
  └ Claude アプリ/ボイスモード(秘書)
      └ カスタムコネクタ = MCP Bridge Server(Bearer 認証)
          ├ Orchestrator ── Claude Code(claude-agent-sdk)── 各リポジトリ
          └ SQLite ── Dashboard(FastAPI + React)
```

中核は **計画 → 承認 → 実行** のゲート。書き込みが起きる作業は必ず一度計画として返り、
あなたが承認するまで実行されない。

---

## 1. セットアップ

Python 3.12 以上、Node.js 18 以上、Claude Code CLI(Max アカウントでログイン済み)。

```bash
# Mac / Linux
uv sync --extra dev              # または: pip install -r requirements.txt
cp .env.example .env
python -c "import secrets; print(secrets.token_urlsafe(32))"   # → .env の CF_MCP_TOKEN
python -c "import secrets; print(secrets.token_urlsafe(16))"   # → .env の CF_DASHBOARD_PASSWORD
```

```powershell
# Windows
python -m venv .venv; .\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
copy .env.example .env    # 中身のトークンを実値に置き換える
```

Claude Code の認証は SDK が引き継ぐので、実行ホストで一度 `claude` を起動して
Max アカウントでログインしておく。

### config.yaml を自分の環境に合わせる

最低限、**触ってよいディレクトリ**を書き換える。ここに無いパスは全て拒否される。

```yaml
security:
  repo_allowlist:
    - ~/Private_Project           # Mac
    # - C:\Users\<you>\repos      # Windows
```

---

## 2. 起動

```bash
./scripts/run_mcp.sh          # MCP サーバー(秘書の窓口 + ジョブのワーカー)
./scripts/run_dashboard.sh    # ダッシュボード(初回はフロントも自動ビルド)
```

```powershell
.\scripts\run_mcp.ps1
.\scripts\run_dashboard.ps1
```

- MCP: `http://127.0.0.1:8010/mcp`
- ダッシュボード: `http://127.0.0.1:8787`

> **ジョブを実際に走らせるのは MCP サーバープロセス**。ダッシュボードだけ起動していても
> キューは進まない。常時動かすのは `run_mcp` のほう。

疎通確認:

```bash
curl -i http://127.0.0.1:8010/mcp                    # 401 = 認証が効いている
curl -s -X POST http://127.0.0.1:8010/mcp \
  -H "Authorization: Bearer $CF_MCP_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

---

## 3. コネクタとして登録

```bash
./scripts/tunnel.sh quick     # 使い捨て(URL は起動ごとに変わる)
```

表示された `https://<ランダム>.trycloudflare.com` の末尾に `/mcp` を付けて、
Claude アプリの「+」→ コネクタ → カスタムコネクタを追加、に貼る。

**トークンの渡し方**が 2 通りある:

| 方式 | 登録する URL | 備考 |
|---|---|---|
| ヘッダ(推奨) | `https://.../mcp` | コネクタ設定で `Authorization: Bearer <CF_MCP_TOKEN>` を追加 |
| パス | `https://.../t/<CF_MCP_TOKEN>/mcp` | 登録画面がヘッダを設定できない場合の逃げ道 |

パス方式はトークンが URL に載るぶん漏れやすい(ログに残る)。ヘッダが使えるなら
`config.yaml` の `mcp.allow_path_token` を `false` にして塞ぐこと。

---

## 4. 使い方

秘書(音声)に対して、たとえばこう話しかける:

**状況を知る**
- 「今どうなってる?」 → `get_org_status`(組織全体を 1 回で。まずこれ)
- 「あの仕事は?」 → `get_job`(`detail` で summary / report / log を切り替え)
- 「調査の結果を読んで」 → `read_board`(部署が出した成果は全部ここに載る)

**組織を動かす**(重い順)
- 「新しいタスクを『請求書パーサ』で始めて」 → `create_task`(ディレクトリ作成 + git init + 登録を一手で。`config.yaml` の編集も再起動も不要)
- 「保存方式をどうするか、設計班にかけて」 → `start_council`(**ファイルは変更されない**ので気軽に使える)
- 「会議の結論は?」 → `get_council`(結論・争点ごとの議論・**残った反対意見**)
- 「demo に、テストの失敗を直すよう依頼して」 → `dispatch_to_code`(**計画だけ**作られる)
- 「承認して」 → `answer_question`(ここで初めて**実行される**)
- 「この目標で任せる」 → `grant_mandate`(**自走が始まる**。`revoke_mandate` で止まる)

**セッション**
- 「今のセッションを分岐して、別のアプローチを試して」 → `fork_session`(git worktree で隔離)

### 組織のかたち(設計書 `docs/04_組織化設計書.md`)

```
子会社 = プロジェクト(互いに不干渉)
  部署 = 役割        調査 / 設計班 / 実装 / デザイン / 統合管理
    成果ボード       部署はここだけを介して成果を見せ合う
```

**設計班の会議**は司会主導の 4 段階で進む。

1. **下読み** — 司会が自明な点を自分で決着させ(`resolved_by_chair` に記録)、争点だけを抽出
2. **付議** — 争点ごとに社員名簿から人を指名(理由つき)
3. **検討** — 指名された人が意見と、先行意見への批評を述べる
4. **結論** — 司会が争点ごとの結論・**残った反対意見**・人間への論点を出す

名簿は `config/personas.yaml`(司会 1 名 + メンバー 10 名)。自由に編集でき、起動時に反映される。

### 部署ごとの権限(最小権限)

| 役割 | Web | ファイル書込 | 承認 |
|---|---|---|---|
| 調査 | 可 | 不可 | 不要 |
| 設計班 | 可 | 不可 | 不要 |
| 実装 | 不可 | リポジトリ内のみ | 委任状 |
| デザイン | 可 | 成果物ディレクトリのみ | 委任状 |
| 統合管理 | 不可 | 不可 | — |

「調査は Web を見られるがファイルは書けない」「実装はファイルを書けるが Web は見られない」と
分離してある。変更は `config.yaml` の `roles:` で行い、**人だけが変えられる**
(統合管理エージェントが自分の権限を広げる経路を作らない)。

### 自走(委任状)

`grant_mandate` で目標ごと承認すると、統合管理が各部署に仕事を振り、個別の承認なしに進む。
承認を減らすかわりに、**いつでも捨てられる形**で走らせている。

- 専用の作業ブランチを切る(`main` は触らせない)
- 予算(ジョブ数・コスト)と期限を付け、使い切ると自動で止まる
- **削除・`git push`・履歴の改変・依存の追加は委任の外**。必ず止まって確認する
- ダッシュボードの「停止」ボタン(`revoke_mandate`)で実行中ごと取り消せる

ダッシュボードでは、保留質問キュー・進捗タイムライン・ライブログ・レポート・
セッションのフォークツリー・監査ログが見える。回答は音声でも画面でも同じ経路を通る。

### 秘書のスキル(`skills/`)

毎朝あたらしいチャットを立ち上げる運用のため、秘書には前日の記憶がない。
**立ち上げ手順**はスキルとして `skills/factory-startup/` に置いてある
(状況収集 → 読み上げ順序 → 今日の提案、読み上げ台本つき)。
Claude アプリの設定からアップロードし、チャットの冒頭に **`/factory-startup`**(短縮形 `/cf`)
と打って呼び出す。自然文のトリガー語は置いていない(誤爆と起動漏れを避けるため)。
アプリの `/` サジェストはスキルの `name` から出るので、**名前がそのまま合図**になる。
詳細は `skills/README.md`。

コネクタ側の `SECRETARY_GUIDE`(毎回のリクエストに載る=短く保つ)と、
スキル(必要なときだけ読まれる=手順や台本を置く)で役割を分けている。

### 秘書に効く指示(企画 §検証4)

会話の途中で勝手に依頼を飛ばさないよう、秘書側にこう言っておくとよい:

> 私が「これで依頼して」と言うまで dispatch_to_code は呼ばないで。
> それまでは相談に付き合って、指示文を一緒に整えて。

---

## 5. セキュリティ(制作指示書 §8)

実装済みの防御:

| # | 要件 | 実装 |
|---|---|---|
| 1 | MCP は Bearer トークン必須 | `BearerAuthMiddleware`。未設定なら起動を拒否 |
| 2 | repo_path は allowlist 内の絶対パスのみ | `resolve_repo_path`。`..`・シンボリックリンク脱出も拒否 |
| 3 | 秘書に生シェルを晒さない | MCP のツールは限定インターフェースのみ |
| 4 | 書換/削除/シェル実行は承認ゲート | 計画→承認→実行 + `can_use_tool` + OS サンドボックス |
| 5 | シークレットをコミットしない | `.env` は `.gitignore`、`.env.example` のみ配布 |
| 6 | 全 dispatch・answer を監査ログに記録 | `audit_log` テーブル、ダッシュボードの「履歴」 |
| 7 | レート制限 | MCP・ダッシュボードともトークンバケット |
| 8 | ダッシュボードは認証の背後 | Cookie セッション、または Cloudflare Access |

実 Claude Code で試して分かった要注意点が 2 つあり、対策済み:

- **`can_use_tool` は CLI が自動承認するツールには呼ばれない。** 認可コールバックだけを
  頼りにすると、計画モードでもリポジトリ外に書き込みが起きる。`disallowed_tools` による
  CLI レベルの禁止と、OS サンドボックス(`orchestrator.sandbox`)を重ねてある。
- **`cd` によるディレクトリ脱出はパス検査では止まらない。** Bash コマンド中の絶対パスと
  `..` を `_bash_escapes_workspace` で見ている。
- 対象リポジトリの `.claude/settings.json` は**読み込まない**(`setting_sources=[]`)。
  読み込むとリポジトリが自分の権限を自己承認できてしまう。

---

## 6. 固定公開(M5)

使い捨てトンネルは起動ごとに URL が変わるので、常用するなら名前付きに移す。

```bash
cloudflared tunnel login
cloudflared tunnel create claude-factory
cloudflared tunnel route dns claude-factory mcp.<domain>
cloudflared tunnel route dns claude-factory dash.<domain>
```

`~/.cloudflared/config.yml`:

```yaml
tunnel: claude-factory
credentials-file: /path/to/<tunnel-id>.json
ingress:
  - hostname: mcp.<domain>
    service: http://localhost:8010
  - hostname: dash.<domain>
    service: http://localhost:8787
  - service: http_status:404
```

常時公開は systemd の `cloudflared.service` が担当する(`/etc/cloudflared/config.yml` を読む)。

```bash
systemctl status cloudflared          # 状態確認
sudo systemctl restart cloudflared    # 設定変更の反映
journalctl -u cloudflared -f          # ログ
```

`./scripts/tunnel.sh named claude-factory` でも起動できるが、常駐サービスと
同じトンネルにコネクタが二重に張られるため通常は使わない。手動運転に切り替える
場合は先に `sudo systemctl stop cloudflared` すること。スクリプト側でも常駐を
検知したら警告し、確認を求める。

Route 53 側は `cloudflared tunnel route dns` が CNAME(`<tunnel-id>.cfargotunnel.com`)を
作る。コネクタ登録 URL は `https://mcp.<domain>/mcp`。
ダッシュボードは Cloudflare Access を前段に置き、その場合のみ `dashboard.auth: none` にする。

---

## 7. 開発

```bash
.venv/bin/python -m pytest -q                                  # テスト
cd src/claude_factory/dashboard/web && npm run dev             # フロントの開発サーバー
```

構成は制作指示書 §2 に対応(`src/` 直下ではなく `src/claude_factory/` パッケージにしてある):

```
src/claude_factory/
├─ config.py        設定(config.yaml + .env)
├─ models.py        型・出力規約・その解析
├─ store.py         SQLite DAO
├─ security.py      トークン・allowlist・レート制限
├─ runner.py        claude-agent-sdk ラッパと承認ゲート(役割別の権限)
├─ orchestrator.py  ジョブキュー、計画→承認→実行、自走ループ
├─ sessions.py      セッション一覧/閲覧/分岐(git worktree 隔離)
├─ personas.py      社員名簿と組閣
├─ council.py       設計班の合議エンジン
├─ integrate.py     統合管理(作業計画を出すだけ。実行はしない)
├─ org.py           組織全体の状況
├─ mcp_server.py    秘書向け MCP
└─ dashboard/       FastAPI + React(Vite)
```

ドキュメント: `docs/01_企画書.md`(構想)→ `02_制作指示書.md`(基盤)→
`03_セッション管理.md`(追補)→ `04_組織化設計書.md`(組織化)。

---

## 8. 残っていること

- **M6 音声 E2E**: 実プロジェクト 1 本を音声だけで回す(コネクタ登録後に手動で確認)。
- 判断待ちのプッシュ通知(v2)。
- 分岐 worktree の後片付けルール(マージ後に消すか残すか)。
- 統合管理が同じ仕事を振り続けた場合の検知(いまは予算と期限が唯一の歯止め)。
- 部署間で意見が食い違ったときの調停者を統合管理にするか、人に上げるか。