Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "claude-gate",
"owner": { "name": "taniguchi-kyoichi" },
"plugins": [
{
"name": "claude-gate",
"source": "./",
"description": "AI 開発の品質ゲート: ビルドID と出所照合で「できました」に証拠を義務づける。ローカル常駐デーモンへの MCP 接続 + gate-loop スキル + 公式化ガード hook"
}
]
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claude-gate",
"version": "0.7.1",
"version": "0.8.0",
"description": "AI 開発の品質ゲート: ビルドID と出所照合で「できました」に証拠を義務づける。ローカル常駐デーモン(claude-gate)への MCP 接続と使い方スキルを提供",
"skills": "./skills/"
}
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ AI との iOS 開発で、エージェントの「できました」に証拠を

- **証拠の受理**: 観測(スクショ等)は、シミュレータ内の実物とビルドID を照合して一致したときだけ証拠になる
- **判定**: 宣言した全動作が証拠で覆われているかを、ゲートが決定論で判定する(エージェントは自己判定できない)
- **提出**: 合格した報告の、検証されたそのソース(HEAD 一致)だけが `git push` できる
- **提出**: 合格した報告の、検証されたそのソース(HEAD = PR 先頭)の下書きPR だけがレビュー可能にできる。共有(feature ブランチへの push・下書きPR の作成)は自由、取り込み(マージ)は人間だけ

完了報告は1本の状態機械で進む: **下書き → 証拠あり → 合格 / 不合格 / 確認できず → 提出済み**。仕組みの説明書はダッシュボードの「ガイド」画面にある。

Expand All @@ -18,7 +18,7 @@ AI との iOS 開発で、エージェントの「できました」に証拠を
|---|---|---|
| デーモン | `src/` → `dist/` | HTTP MCP サーバ(127.0.0.1:7350・マシンに1プロセス)+ CLI(`claude-gate`) |
| ダッシュボード | `dashboard/` | 人間向けの状態表示 + ガイド。デーモンが `/` で配信(読み取り専用) |
| Claude Code プラグイン | `.claude-plugin/` + `.mcp.json` + `skills/` + `hooks/` | MCP 接続定義 + gate-loop スキル + git push 遮断 hook |
| Claude Code プラグイン | `.claude-plugin/` + `.mcp.json` + `skills/` + `hooks/` | MCP 接続定義 + gate-loop スキル + 公式化ガード hook(ドラフト解除・マージ・デフォルトブランチ直 push の遮断) |

## セットアップ(初回だけ)

Expand All @@ -29,10 +29,11 @@ npm install && npm run build && npm link # claude-gate コマンドを PATH
claude-gate install # launchd 常駐 + 稼働確認
```

**Claude Code 側**(どこからでも・user スコープ):
**Claude Code 側**(どこからでも・user スコープ)。このリポジトリ自体が marketplace(`.claude-plugin/marketplace.json`)なので:

```bash
claude plugin install claude-gate@taniguchi-kyoichi
claude plugin marketplace add <このリポジトリのクローン先パス or no-problem-dev/claude-gate>
claude plugin install claude-gate@claude-gate
```

これで全ディレクトリ・全セッションで gate ツールと gate-loop スキルが使える。プロジェクト側に置くファイルはゼロ。ゲート運用にするリポジトリだけ、ルートに宣言ファイル `gate.yaml`(env / worksite / checks / passline / cannot_see。全セクション任意)を置く。
Expand All @@ -59,7 +60,7 @@ claude-gate install # 直すのはこれ一発(べき等)
| 変えたもの | 反映コマンド |
|---|---|
| デーモン・ダッシュボード(`src/` `dashboard/`) | `npm run build && claude-gate install` |
| プラグイン(`skills/` `hooks/` `.mcp.json` `.claude-plugin/`) | plugin.json の version を上げて push → `claude plugin update claude-gate@taniguchi-kyoichi` → `/reload-plugins`(ツール一覧は `/mcp reconnect`) |
| プラグイン(`skills/` `hooks/` `.mcp.json` `.claude-plugin/`) | plugin.json の version を上げる → `claude plugin update claude-gate@claude-gate` → `/reload-plugins`(ツール一覧は `/mcp reconnect`) |

プラグインの実体は marketplace キャッシュで、ローカルリポの編集は update を回すまで配布されない。

Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/Diagrams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export function ReportStateDiagram() {
<Box x={360} y={22} w={104} label="合格" tone="good" />
<line x1={464} y1={42} x2={548} y2={42} strokeWidth="1.3" className={line} markerEnd="url(#st)" />
<text x={506} y={32} textAnchor="middle" fontSize="10" className={inkSub}>提出する</text>
<Box x={552} y={22} w={120} h={44} label="提出済み" sub="push / PR 完了" />
<Box x={552} y={22} w={120} h={44} label="提出済み" sub="PR レビュー依頼済み" />

<Box x={360} y={92} w={104} label="不合格" tone="bad" />
<path d="M 412 132 L 412 158 L 228 158 L 228 132" fill="none" strokeWidth="1.3" strokeDasharray="4 3" className={line} markerEnd="url(#st)" />
Expand Down
15 changes: 11 additions & 4 deletions dashboard/src/GuideView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ export function GuideView() {
証拠が複数ビルドやソースにまたがる報告は合格にならない(確認できず)。
</Step>
<Step n={8} name="提出" en="submit">
合格した報告の、<strong>検証されたそのソース(HEAD が判定時の sha と一致)</strong>だけを
git push できる。検証後にコミットが動いていたら拒否 — 「別物を見て OK」の提出版を締める。
共有(feature ブランチへの push・下書きPR の作成)は自由。合格した報告の、
<strong>検証されたそのソース(HEAD = 判定時の sha = PR の先頭)</strong>だけが
下書きPR をレビュー可能にできる。検証後にコミットが動いていたら拒否 —
「別物を見て OK」の提出版を締める。取り込み(マージ)は人間だけの操作。
提出済みの報告は終着(証拠の追加も不可)。
</Step>
</ol>
Expand Down Expand Up @@ -164,6 +166,10 @@ export function GuideView() {
<Word ja="ゲート" en="gate">受理を判断する常駐デーモン(この仕組みの実行実体)</Word>
<Word ja="作業場" en="worksite">worktree + ビルド置き場 + 専用シミュレータの一式</Word>
<Word ja="未コミット変更あり" en="dirty">どのコミットの成果物か確定できない状態</Word>
<Word ja="共有" en="share">feature ブランチへの push・下書きPR の作成。可逆なのでエージェントの自由領域(前提: デフォルトブランチは GitHub 側のブランチ保護で守る)</Word>
<Word ja="下書きPR" en="draft PR">共有の置き場。レビュー依頼は飛ばず、閉じれば戻る</Word>
<Word ja="提出" en="submit">合格した報告の下書きPR をレビュー可能にする(ドラフト解除)。ゲートだけの遷移</Word>
<Word ja="取り込み" en="merge">不可逆の採用。人間だけの操作 — エージェントの語彙に入れない</Word>
</tbody>
</table>
</Card>
Expand Down Expand Up @@ -192,8 +198,9 @@ export function GuideView() {
見えないこと台帳。見えない動作への OK は「確認できず」に変換され、人間に渡る
</StatusRow>
<StatusRow chip={<Chip size="sm" color="success">稼働中</Chip>} name="提出の一本化(スライス3)+ 掃除(2c)">
合格した報告の検証済みソースだけが push できる(submit)。PR 作成は次の実タスクで。
記録の掃除は人間の CLI(claude-gate forget)— エージェントは記録を消せない
共有(push・下書きPR)は自由、合格した報告の検証済みソースだけがレビュー依頼できる(submit =
ドラフト解除)、取り込みは人間だけ。記録の掃除は人間の CLI(claude-gate forget)—
エージェントは記録を消せない
</StatusRow>
</div>
<h3 className="mt-6 mb-2 text-[13px] font-semibold">
Expand Down
19 changes: 16 additions & 3 deletions dashboard/src/ReportsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,22 @@ function ReportCard({

{report.submission !== undefined && (
<p className="mt-3 rounded-xl border border-green-600/30 bg-green-600/8 p-3 text-[13px]">
{report.submission.remote}/{report.submission.branch} へ{" "}
<span className="font-mono">{report.submission.sha.slice(0, 7)}</span> を push 済み(
{formatTime(report.submission.pushedAt)})。検証したソースと同一であることをゲートが照合した上での提出。
{report.submission.prNumber !== undefined ? (
<>
<a href={report.submission.prUrl} target="_blank" rel="noreferrer" className="underline">
PR #{report.submission.prNumber}
</a>
(先頭 <span className="font-mono">{report.submission.sha.slice(0, 7)}</span>)をレビュー可能にした(
{formatTime(report.submission.readiedAt ?? "")})。検証したソース = HEAD = PR
先頭であることをゲートが照合した上での提出。取り込みは人間の操作。
</>
) : (
<>
{report.submission.remote}/{report.submission.branch} へ{" "}
<span className="font-mono">{report.submission.sha.slice(0, 7)}</span> を push 済み(
{formatTime(report.submission.pushedAt ?? "")})。旧形式(提出 = push)の記録。
</>
)}
</p>
)}

Expand Down
11 changes: 9 additions & 2 deletions dashboard/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface GateEvent {
verdict?: string;
sha?: string;
branch?: string;
prNumber?: number;
alreadyRegistered?: boolean;
alreadyAttached?: boolean;
alreadyOpened?: boolean;
Expand Down Expand Up @@ -92,7 +93,10 @@ export interface Submission {
sha: string;
branch: string;
remote: string;
pushedAt: string;
prNumber?: number; // レビュー可能にした PR。旧形式(提出 = push)の記録には無い
prUrl?: string;
readiedAt?: string;
pushedAt?: string; // 旧形式(提出 = push)の記録のみ
}

export interface Report {
Expand Down Expand Up @@ -282,7 +286,10 @@ export function eventSentence(event: GateEvent): string {
}
if (event.tool === "submit") {
if (event.result !== "ok") return "提出を拒否";
return event.alreadySubmitted ? "提出(既提出の返却)" : `提出した — ${event.branch ?? ""} を push`;
if (event.alreadySubmitted) return "提出(既提出の返却)";
return event.prNumber !== undefined
? `提出した — PR #${event.prNumber} をレビュー可能にした`
: `提出した — ${event.branch ?? ""} を push`;
}
if (event.tool === "forget") {
return "記録を掃除(人間の操作)";
Expand Down
9 changes: 6 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
```
Claude Code セッション(対話 / 並列ワーカー / cron)… N 個
├── XcodeBuildMCP ──── ビルド・テスト・シミュレータ操作(ゲートは関与しない)
├── claude-gate プラグイン ── MCP 接続 + gate-loop スキル + PreToolUse hook(git push 遮断)
├── claude-gate プラグイン ── MCP 接続 + gate-loop スキル + PreToolUse hook(公式化ガード)
└── claude-gate デーモン(HTTP MCP・127.0.0.1:7350・マシンに1プロセス・launchd 常駐)
├─ tools: ping / open_report / register_build / attach_evidence / run_check / judge / submit
├─ kernel: store(状態)+ audit(監査)+ api(ダッシュボード読み取りモデル)
Expand All @@ -21,6 +21,7 @@ Claude Code セッション(対話 / 並列ワーカー / cron)… N 個
- **申告を信用しない**: ビルドID はゲートが .app の中身から計算する。証拠の受理時はシミュレータ内の実物から計算し直して照合する。テスト系の確かめはエージェントの自己申告ではなく、ゲート自身が gate.yaml 宣言のコマンドを実行する(run_check)
- **実機は Mach-O UUID で照合する**: 実機からは .app を取り出せない(コンテナが取れない)ので中身ハッシュでは照合できない。代わりに register_build 時に .app 内の**全 Mach-O**(メイン実行ファイル + `<App>.debug.dylib` + Frameworks 内バイナリ)の LC_UUID を集合で記録し、実機で走ったアプリが自分の LC_UUID を print したセルフレポート(device_report・`buildUUID=` 行)を集合と照合する。keychain 復元・課金・通知配信・上書き更新 E2E など実機でしか検証できない動作の正式な証拠経路。※ Xcode 16+ Debug は実コードを `.debug.dylib` に置きメイン実行ファイルは不変スタブになるので、アプリ側は `#dsohandle`(自分のコードが載っている image)の UUID を報告する — メイン実行ファイルだけ見ると内容の違うビルドが同じ UUID になり古いビルドを受理してしまう(実測)
- **判定は決定論**: judge は pure function(報告 + 証拠 + builds + gate.yaml + 見えないこと台帳 → 判定)。LLM なし。ゴールデンテストで固定
- **共有は自由、公式化はゲート、取り込みは人間**: feature ブランチへの push と下書きPR の作成は縛らない。ドラフト解除(提出)は submit が sourceSha = HEAD = PR 先頭の三点照合を通してから行い、マージはエージェントの語彙に入れない(forget と同格)。hook は入口の誘導であって壁ではない(パターン照合は破れる)— 破れない壁は GitHub 側のデフォルトブランチ保護に置く。この境界線は事故由来ではなく人間の設計判断(2026-07、下書きPR 運用 + ブランチ保護を前提に feature ブランチへの push はリスク極小と判断)
- **全操作べき等**: ID は乱数ではなく中身・対象から決める。同じ呼び出しを2回しても状態は1つ。再実行・リトライ・スケジュール実行が安全
- **セッション状態を持たない**: 全ツールが対象(worksitePath 等)を明示引数で受ける。並列で何セッション繋がっても混線しない
- **silent fallback 禁止**: 失敗は必ず rejected(reason + fix)として表面化する
Expand Down Expand Up @@ -51,7 +52,8 @@ src/
tools/ # MCP ツールの本体(open_report / register_build / attach_evidence /
# run_check / judge / submit)
dashboard/ # React ダッシュボード(HeroUI v3 + Tailwind v4。設計 = docs/dashboard-design.md)
hooks/ # PreToolUse hook(guard-push.sh: gate.yaml のあるリポでエージェントの git push を遮断)
hooks/ # PreToolUse hook(guard-official.sh: gate.yaml のあるリポで公式化操作 —
# ドラフト解除・マージ・非ドラフト PR 作成・デフォルトブランチ直 push — を遮断)
skills/gate-loop/ # 使い方スキル(いつ・どの順でツールを呼ぶか)
.claude-plugin/ # Claude Code プラグイン定義(バージョンはデーモンと独立)
.mcp.json # プラグインの MCP 接続定義(→ http://127.0.0.1:7350/mcp)
Expand Down Expand Up @@ -97,4 +99,5 @@ rejected は「何がダメか(reason)」と「どうすれば通るか(fix)」

- 中心は **A1 再現テスト**(attach_evidence: 古いビルドのスクショは拒否される)と **judge のゴールデンテスト**(入力組合せ → 期待判定。「覆いは動作ごとに最新の適合証拠1件」— 証拠の積み上がりが同一ソース要件を永久に破る回帰を含む)
- **実機レポート**は照合ロジックを分けてテスト: dwarfdump 出力・セルフレポートの UUID パース(`macho_uuid.test.ts`・実バイナリ非依存)と、attach_evidence(device_report)の受理/拒否(`device_report.test.ts`・register_build に UUID を注入)
- submit はローカル bare リモートへの実 push で検証(全拒否経路 + べき等)
- submit はローカル bare リモートへの実 push + PATH に注入した偽 gh(pr view / pr ready を状態ファイルで再現)で検証(全拒否経路 + べき等)
- 公式化ガード hook は bash をそのまま起動し、JSON 入力 → 終了コードのテーブルで検証(`guard_official.test.ts`)
Loading