# Gitlab Gatekeeper

> GitLab の MR/イシューのマージ承認「門番」スキル。packet モード=人間が1枚で決めるマージ判断パケットを生成。実際の修正内容（diff）も同じ1枚に段階開示（既定は畳み、操作で展開）で載せ、認知負荷を下げる（紐づく MR に未対応レビューコメントがあれば needs-review へ差し戻して終了）。パケットにはリポジトリ横断で一意・人が入力できる短い ID（例 GK-7F3KQ9）を採番する。decision モード=人間の承認/否認/却下を執行する（対象はパケット ID で指定でき、リポジトリを跨いでも取り違えない）。執行前に最新の issue/MR を再取得してパケットを更新し、未対応レビューコメントが増えていれば packet モードと同様に差し戻す。承認=マージ＋イシュークローズ、否認=needs-review へ差し戻し、却下=却下コメント投稿＋イシュークローズ＋紐づく MR を未マージでクローズ＋ソースブランチ削除。「レビューパケットを作って」「マージ判断パケット」「この MR を1枚に」「GK-7F3KQ9 を承認/否認/却下して」「#42 を承認/否認/却下して」で発動。

- Skill: `ynitto/gitlab-gatekeeper` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add ynitto/gitlab-gatekeeper`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ynitto/gitlab-gatekeeper/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ynitto (https://skillmd.com/u/ynitto)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ynitto/gitlab-gatekeeper

---


# gitlab-gatekeeper — マージ承認の門番

GitLab の変更を、**人間レビュアーが 1 枚見て承認/差し戻しを決められる成果物**に変え、
さらに**人間が下した承認/否認/却下を GitLab へ確実に執行する**門番スキル。散らばった情報
（イシュー本文・コメント・**実際の差分**・CI・受け入れ条件）を読みに行くレビューを、
**キュレーション済みの結論を確認する**レビューに変える。パケットには「どこを・どう直したか」を
**同じ 1 枚に段階開示**で載せ（既定は畳み、操作で展開）、別タブで diff を追わずに済むようにする。

> マージの最終**判断**は人間が下す。このスキルは判断を代行しない（「承認すべき」と書かない）。
> 人間が明示した決定（承認/否認/却下）だけを受けて、ラベル遷移・マージ・クローズ・差し戻しを**執行**する。

## モード

| モード | 起動 | 役割 |
|---|---|---|
| **packet**（既定） | 「レビューパケットを作って」「マージ判断パケット」 | 判断材料＋**実際の修正内容（diff）**を 1 枚に集約（段階開示）。**リポジトリ横断で一意のパケット ID を採番**。**ただし未対応レビューコメントがあれば差し戻して終了**（§Gate A） |
| **decision** | 「GK-7F3KQ9 を承認して」「#42 を否認、理由は〜」「GK-7F3KQ9 を却下、理由は〜」 | 人間の承認/否認/却下を執行（**対象はパケット ID で指定可**）。**執行前に最新状態でパケットを更新し、未対応レビューコメントが増えていれば packet モードと同様に差し戻す**（§decision モード） |

どちらのモードか曖昧なら、対象 IID と「パケット生成か・承認/否認/却下の執行か」を 1 問だけ確認する。
decision で「却下」「reject」「マージしない」等が承認/否認/差し戻しのどれを指すか曖昧なら、
**否認（差し戻し）か却下（クローズ）か**を 1 問だけ確認する（両者は不可逆性が大きく異なる）。

## 設計の前提（重要）

このスキルは**部品**であり、運用ポリシーを内蔵しない。次は **呼び出し側が決める**:

- **対象の限定（ステータス/ラベル/担当）** — スキルは選別ラベルをハードコードしない。
  呼び出し側がセレクタ（gl.py の `--label` / `--state` / `--assignee`、または明示の IID 群）を渡す。
- **間隔・スケジューリング** — このスキルはポーリングもループもしない。定期実行は呼び出し側
  （`/loop`・cron・agent-loop 等）の責務。スキルは「いま渡された対象を 1 回処理する」だけ。
- **出力先**（packet 時） — 成果物をどこへ置くか（Obsidian Vault のパス / `.html` ファイル / Markdown / stdout）。
- **ラベル/マージのポリシー** — 下表の既定を持つが、呼び出し側が上書きできる。

| ポリシー | 既定 | 用途 |
|---|---|---|
| `needs_review_label` | `status:needs-review` | 差し戻し先のステータス（Gate A・否認・マージ不可で付与） |
| `ready_labels` | `status:review-ready` | 差し戻し時に外す「レビュー待ち/承認待ち」ステータス（複数可・カンマ区切り） |
| `merge.squash` / `merge.remove_source_branch` | 共に false | 承認マージ時のオプション |
| `require_ci_success` | true | 承認マージの前提として CI 成功を要求するか |
| `dismiss.delete_source_branch` | true | 却下時に紐づく MR のソースブランチを削除するか（保護ブランチ・既定ブランチは対象外） |
| `packet_id.prefix` / `packet_id.length` | `GK-` / `6` | パケット ID（リポジトリ横断・人が入力できる長さ）の前置詞と本体文字数 |
| `packet_store` | （呼び出し側指定） | パケットの保管場所。decision で ID からパケットを引くときに探す（既定は `output_target` と同じ） |

呼び出し側がこれらを指定しない場合は上の既定を使い、破壊的操作（マージ・**クローズ・ブランチ削除**）の直前に対象を 1 行で要約してから実行する。

## パス解決

- このSKILL.mdのディレクトリを `SKILL_DIR` とする。
- GitLab 操作は `gitlab-idd` スキルの **`scripts/gl.py`** を再利用する（接続情報・トークンも共有）。
  パスは `find` で `.github/skills/gitlab-idd/scripts/gl.py` を探す。`GITLAB_TOKEN` が必要。
  以降 `$GL` は **gl.py を含むディレクトリ**（`.github/skills/gitlab-idd/scripts`）を指し、`python3 $GL/gl.py …` で呼ぶ。
- レビュー観点の詳細は **`agent-reviewer` スキルの `references/`** を再利用する（重複定義しない）。
- 出力の整形は、インストール済みなら下表のドキュメント系スキルに委譲する（自前で凝った整形を書かない）。

### 共通ヘルパ：紐づく MR の解決

イシュー `<iid>` に紐づく MR は **ブランチ命名規約**で引く（`gitlab-idd` の `make-branch-name` と同じ前提）:

```bash
python3 $GL/gl.py list-mrs --source-branch-prefix feature/issue-<iid> --state opened
```

- 0 件 → MR 未作成。packet では「MR なし」と明示、decision では人へ確認（執行できない）。
- 複数件 → opened を優先し、なお複数なら呼び出し側に対象 MR を確認（独断で選ばない）。

### 共通ヘルパ：パケット ID（リポジトリ横断・人が入力できる長さ）

パケットには**リポジトリ横断で一意・人が手入力できる短い ID**を付ける。これにより、別リポジトリの
イシューでも IID が衝突せず、人は ID 一つを伝えるだけで decision を呼べる。

```bash
# 採番（packet 生成時。Crockford base32: I/L/O/U を除外。既定 GK- + 6 文字 ≈ 30bit）
python3 $GL/gl.py gen-packet-id                       # 例: GK-7F3KQ9
python3 $GL/gl.py gen-packet-id --length "$packet_id_length" --prefix "$packet_id_prefix"

# 正規化（decision で人が打った ID を照合用に整える。大文字化・前置詞/空白/ハイフン許容・I/L→1・O→0）
python3 $GL/gl.py normalize-packet-id "<人が入力した文字列>"   # 例: "gk 7f3 kq9" → GK-7F3KQ9
```

- 採番した ID は**パケットのフロントマター `id:`** に書き、ファイル名にも含める（`<id>.md` 等）。
  併せて `host` / `project` / `project_url` / `issue` / `mr` を記録し、ID からリポジトリと対象を一意に解決できるようにする。
- **ID → 対象の解決（decision で使う）**: 入力 ID を `normalize-packet-id` で正規化し、`packet_store`
  （無ければ `output_target`）配下から `id:` が一致するパケット（またはファイル名一致）を 1 件特定する。
  - 0 件 → ID が違う／保管場所が違う。人へ「ID か保管場所」を 1 問だけ確認（独断で対象を選ばない）。
  - 複数件 → ID 衝突（稀）。フロントマターの `project`/`issue` を提示し、人へ対象を確認。
- パケットの `project_url` を `GL_PROJECT_URL` に設定して gl.py を呼べば、**ローカルクローンと別の
  リポジトリでも**そのパケットのプロジェクトに対して執行できる（リポジトリ横断の肝）。

  ```bash
  GL_PROJECT_URL="$(<パケットの project_url>)" python3 $GL/gl.py <command> …
  ```

### 共通ヘルパ：未対応レビューコメントの判定

```bash
python3 $GL/gl.py get-mr-discussions <mr_iid> --unresolved
```

返ってきた**解決可能（resolvable）かつ未解決（unresolved）のスレッドが 1 件以上**あれば「未対応レビューコメントあり」。
ラベル変更等の system note は discussion ではないため対象外（このコマンドは discussion スレッドのみ返す）。

### 共通ヘルパ：差し戻し（needs-review へ戻す）

```bash
python3 $GL/gl.py update-issue <iid> \
  --add-labels "$needs_review_label" --remove-labels "$ready_labels"
python3 $GL/gl.py add-comment <iid> --body-file <生成したコメント.md>
```

### 共通ヘルパ：実際の修正内容（diff）の取得

パケットに「どこを・どう直したか」を載せるため、ファイル単位の unified diff を集める:

```bash
# クローン済みリポジトリがあれば git diff（最も確実）
git fetch origin <target> <source>
git diff origin/<target>...origin/<source>          # 全体
git diff --stat origin/<target>...origin/<source>   # ファイル別 +adds/-dels
git diff origin/<target>...origin/<source> -- <path># ファイル単位の hunk

# クローンが無い／取得できない場合のフォールバック（GitLab API）
python3 $GL/gl.py get-mr-changes <mr_iid>            # [{old_path,new_path,diff}, ...]
```

巨大 diff（自動生成・lockファイル・大量移動）は要約に留め、原文へのリンクを添える（パケットを 1 枚に保つ）。

### 共通ヘルパ：却下（イシュー＋MR をクローズし、ソースブランチを削除）

却下は**不可逆性が高い**ため、実行前に対象（イシュー IID・MR IID・削除予定ブランチ）を 1 行で要約する。
各ステップの戻り値（非 2xx）を確認し、失敗したら「却下した」と報告せず、どこまで実行したかを正直に残す。

```bash
# 1) 却下コメントをイシューへ投稿（MR があればミラーも任意）
python3 $GL/gl.py add-comment <iid> --body-file <却下コメント.md>
python3 $GL/gl.py add-mr-comment <mr_iid> --body-file <却下コメント.md>   # 任意

# 2) イシューをクローズ（ready_labels は外す。needs-review は付けない＝差し戻しではない）
python3 $GL/gl.py update-issue <iid> --state-event close --remove-labels "$ready_labels"

# 3) 紐づく MR を未マージのままクローズ
python3 $GL/gl.py update-mr <mr_iid> --state-event close

# 4) ソースブランチを削除（dismiss.delete_source_branch=true のとき）
#    削除前に source_branch を MR オブジェクトから取得し、target/既定/保護ブランチでないことを確認する
src=$(python3 $GL/gl.py list-mrs --source-branch-prefix feature/issue-<iid> --state all --get 0.source_branch)
python3 $GL/gl.py delete-branch "$src"
```

> ブランチ削除のガード: `source_branch` が**既定ブランチ（`get-default-branch`）と一致する場合や、`main`/`master`/`develop`
> 等の保護されたブランチの場合は削除しない**。命名規約（`feature/issue-<iid>-…`）に一致する MR のソースブランチだけを削除する。

---

## packet モードの実行プロトコル

### Step 1 — SELECT（呼び出し側のセレクタで対象を取得。status は絞らない）

呼び出し側が渡したセレクタをそのまま gl.py に流す。例:

```bash
# 例: 「label=ready の opened イシュー」
python3 $GL/gl.py list-issues --label ready --state opened
# 例: 明示の IID 群が渡されたら list は飛ばしてそれを使う
```

セレクタが無ければ **絞らずに opened を対象**とし、件数が多ければ呼び出し側に範囲を確認する。
スキル側の独断でラベル/ステータスを足したり引いたりしない。

### Step 2 — Gate A（未対応レビューコメントの差し戻し）★必須・パケット生成より先

各対象イシューについて、判断材料を集める**前に**次を実行する:

1. 共通ヘルパで紐づく MR を解決する。MR が無ければ Gate A はスキップ（Step 3 へ）。
2. 共通ヘルパで未対応レビューコメントを判定する。
3. **未対応コメントが 1 件以上ある場合**:
   - 共通ヘルパで `needs_review_label` へ差し戻す（`ready_labels` を外す）。
   - 差し戻しコメントを生成・投稿する。本文は「未対応のレビューコメントがあるため `needs-review` に戻しました」
     と明記し、**未対応スレッドの要点を箇条書き**（各スレッドの該当ファイル:行・コメント要旨）で列挙する。
     憶測を足さず、スレッド本文に書かれた指摘だけを写す。
   - この対象は**これで終了**（パケットは作らない）。複数対象なら次の対象へ進み、最後に
     「差し戻し N 件 / パケット生成 M 件」を要約する。
4. 未対応コメントが無ければ Step 3 へ進む。

> Gate A の意図: 未対応のレビュー指摘が残っている変更にマージ判断パケットを作るのは無駄であり、
> 人間に「もう見て良い」と誤信させる。門番として、まず実装者（ワーカー）へ突き返す。

### Step 3 — GATHER（1 対象ぶんの判断材料を集める）

各対象について gl.py と git で次を集める（無いものは「なし」と明示、捏造しない）:

| 材料 | 取得 |
|---|---|
| イシュー本文・ラベル・受け入れ条件 | `get-issue` / 本文から受け入れ条件（チェックリスト）を抽出 |
| 議論の経緯 | `get-comments` / `get-mr-discussions <mr> --unresolved` |
| 紐づく MR | 共通ヘルパ |
| 差分の概況 | `git diff --stat origin/<target>...origin/<source>`（ファイル別 +adds/-dels） |
| **実際の修正内容（diff 本文）** | 共通ヘルパ「実際の修正内容（diff）の取得」（`git diff …` / `get-mr-changes`）。パケットに段階開示で載せる |
| CI 結果 | `get-mr-pipeline <mr_iid>` |

差分は**自分でファイルパス・内容からリスク分類**する（high/medium/low）。
例: 認証・DB・API・入力処理・権限・秘密情報・データ移行・後方互換に触れるものは high 寄り。
**実際の diff 本文も必ず集める**（パケットの「📝 変更内容」ブロックに使う）。リスク順に並べ、巨大 diff は要約＋リンクに留める。

### Step 4 — REVIEW（agent-reviewer の観点で評価）

`agent-reviewer` の観点選択に従い perspective を選ぶ。変更が大きい/重要なら `agent-reviewer` を
**サブエージェントとして起動**し、その集約結果をこのスキルのパケット形式に**圧縮**する。
小さな変更は本インスタンスで直接評価してよい。

| 対象 | 観点 |
|---|---|
| high リスク差分（認証/DB/API/入力処理） | functional, ai-antipattern, security |
| 一般プロダクションコード | functional, ai-antipattern, architecture |
| テスト中心 | test |
| ドキュメント・仕様 | document |

詳細手順は `agent-reviewer/references/<perspective>.md` を参照。

### Step 5 — RENDER（出力先に合わせ、見やすさに全振り）

**パケットの中身は固定**（下記「パケットの構成」）。**整形と出力先は呼び出し側指定に従い、
インストール済みのドキュメント系スキルへ委譲**する。スキルの存在は `.github/skills/<name>/SKILL.md`
の有無で判定する。

| 出力先 / 要求 | 委譲先スキル | 成果物 |
|---|---|---|
| Obsidian Vault のパス | **obsidian-use** | 1 枚ノート（callout＋ウィキリンク、必要なら Canvas で関係を図示） |
| `.html` ／「HTML で」 | **spec-to-readable-html** | 要約・図表・ソーストレーサビリティ付きの読みやすい HTML |
| 図解が要る関係（依存・フロー・状態） | **mermaid-diagrammer** | 上記成果物に Mermaid 図を埋め込み |
| README/ガイド調の長文 | **technical-writer** | 整形済みドキュメント |
| 委譲先が未インストール / stdout 指定 | （フォールバック）素の Markdown | callout 構成の Markdown をそのまま出力先へ |

委譲時は「このパケット本文を、人間が 1 枚で読み切れるよう整形して <出力先> に書き出して」と
明示的に指示する。**HTML を選ぶ場合は折りたたみ（details）・色付き callout・目次で段階開示**を効かせる。

**段階開示の徹底（認知負荷を下げる中心方針）**: パケットは「上から読むほど深くなる」構成にし、
**詳細（とりわけ diff 本文）は既定で畳み、人間の操作で初めて開く**。
- HTML / Markdown: `<details><summary>…</summary> … </details>` で各ファイルの diff を畳む。
- Obsidian: 折りたたみ callout（`[!example]-` の「-」で既定折りたたみ）に diff を入れる。
- 最初に見えるのは「急所 3 点」と「変更ファイルの一覧（リスク色＋±行数）」だけ。
  個別の diff 本文・観点別所見・低リスク差分は**クリックするまで隠す**。これにより「1 枚に全部あるが、最初の視界は軽い」を実現する。

パケットには、人間が後で decision モードを呼べるよう**フロントマターに識別情報と決定欄を用意**する。
共通ヘルパで**パケット ID を採番**し（`gen-packet-id`）、ファイル名にも含める。

```yaml
---
id: GK-7F3KQ9            # リポジトリ横断で一意・人が入力できる短い ID（gen-packet-id）
host: gitlab.com         # 対象の GitLab ホスト（project-info から）
project: team/app        # 対象プロジェクトのパス
project_url: https://gitlab.com/team/app   # GL_PROJECT_URL に渡して横断執行する
issue: 42                # 対象イシュー IID
mr: 17                   # 紐づく MR IID（無ければ空）
confirmed_by:            # 承認/否認/却下した人（人間が記入）
decision:                # approve | reject | dismiss（人間が記入。dismiss＝却下）
---
```

人間は ID 一つ（`GK-7F3KQ9`）と決定（approve|reject|dismiss）を伝えれば decision を呼べる。
`host`/`project`/`project_url`/`issue`/`mr` は ID から対象を一意に解決するための記録で、IID が
リポジトリ間で衝突しても ID で取り違えない。

---

## decision モードの実行プロトコル（人間の承認/否認/却下の執行）

**入力**: **対象（パケット ID `GK-XXXXXX`、または イシュー `<iid>`）**、`decision`（`approve` | `reject` | `dismiss`）、
`reject`/`dismiss` 時は**ユーザーの自然文コメント**（差し戻し／却下の理由）。
これらは呼び出し時の引数、または packet 成果物のフロントマター（`decision:` / `confirmed_by:`）から読む。
**人間の明示決定が無い限り、このモードは何も実行しない**（憶測で approve/reject/dismiss しない）。

- 対象の指定は**パケット ID を推奨**（リポジトリ横断で一意・人が入力しやすい）。IID 直指定も可だが、
  複数リポジトリを跨ぐ運用では IID が衝突するため、どのリポジトリかが曖昧なら ID を使う／確認する。
- `approve`（承認）= マージ＋イシュークローズ。
- `reject`（否認）= **差し戻し**。needs-review へ戻し、実装者が直して再申請する（イシュー/MR は開いたまま）。
- `dismiss`（却下）= **取り下げ**。却下コメントを投稿し、イシューをクローズ、紐づく MR を**未マージでクローズ**、ソースブランチを削除する（再申請を意図しない終了）。

### Step D0 — REFRESH & RE-GATE（対象解決 → 最新状態でパケット更新 → 必要なら差し戻し）★執行より先・必須

パケット生成後に状況が動いている可能性がある。執行の**前に**必ず:

0. **対象の解決**: 入力がパケット ID なら、共通ヘルパ「パケット ID」で正規化（`normalize-packet-id`）し、
   `packet_store`（無ければ `output_target`）から該当パケットを 1 件特定する。フロントマターの
   `host`/`project`/`project_url`/`issue`/`mr` を読み、以降の gl.py 呼び出しは `GL_PROJECT_URL` に
   `project_url` を設定して**そのパケットのリポジトリに対して**実行する。0 件/複数件は人へ 1 問だけ確認。
   入力が IID 直指定なら現在のリポジトリを対象とする（曖昧なら ID を確認）。
1. **最新状態の再取得**: 共通ヘルパで紐づく MR を解決し、`get-issue` / `get-comments` /
   `get-mr-discussions <mr> --unresolved` / `get-mr-pipeline <mr>` / diff を取り直す。
2. **パケットの更新**: packet 成果物がある（`output_target` が分かる）なら、最新状態で**パケットを再生成・上書き**する
   （構成は §パケットの構成。これにより人間が見ている 1 枚と GitLab の実状態が一致する）。
   **フロントマターの `id` と識別情報（host/project/project_url/issue/mr）は変えない**（同じ ID で追跡できるよう保つ）。
3. **RE-GATE（未対応レビューコメントの再判定）**: 共通ヘルパで未対応（resolvable & unresolved）レビューコメントを判定する。
   **パケット作成後に未対応レビューコメントが増えている／残っている場合は、packet モードの Gate A と同じ挙動**を取る:
   - `approve` の場合 → **マージしない**。共通ヘルパで `needs_review_label` へ差し戻し、未対応スレッドの要点を
     箇条書きにした差し戻しコメントを投稿し、更新したパケットを残して**終了**（古い承認で未対応指摘を踏み越えない）。
   - `reject` の場合 → 行き先は同じ needs-review。reject を執行し、差し戻しコメントに
     **ユーザーの指摘＋検出した未対応スレッドの両方**を含める。
   - `dismiss` の場合 → 却下は意図的な終了操作なので Gate A では止めない。ただし却下コメントに
     「クローズ時点で未対応のレビュースレッドが N 件あった」ことを明記する（情報を捨てない）。
4. 未対応コメントが無ければ、各 decision の執行（下記）へ進む。

> RE-GATE の意図: decision は packet という「写真」を見て下されるが、写真は古びる。執行直前に現像し直し、
> 新しい未対応指摘が写っていれば（特に approve では）マージを止めて突き返す。

### approve（承認 → マージ＋クローズ）

> 前提: Step D0 を通過済み（未対応レビューコメントがあればここに来る前に差し戻して終了している）。

1. 共通ヘルパで紐づく MR を解決（無ければ執行不能 → 人へ確認して終了）。
2. **マージ可否を事前確認**（ここで弾けるものは API を叩く前に弾く。D0 で見た未対応コメントは再確認の保険）:
   - 未対応レビューコメント（共通ヘルパ）が**無い**こと。
   - MR がドラフトでない・コンフリクトが無いこと（`list-mrs` の MR オブジェクトの
     `detailed_merge_status` / `merge_status` を見る。`mergeable`/`can_be_merged` 以外は不可寄り）。
   - `require_ci_success` が true なら `get-mr-pipeline <mr>` が `success` であること。
3. **可**なら `merge-mr <mr_iid>`（`merge.squash` / `merge.remove_source_branch` をポリシーに従い付与）。
   - マージ成功 → `update-issue <iid> --state-event close` でイシューをクローズし、
     `add-comment <iid>` に「承認により !<mr_iid> をマージし、本イシューをクローズしました（承認者: <confirmed_by>）」を残す。
4. **不可**（Step 2 で弾けた、または `merge-mr` が非 2xx で失敗した）→ **マージしない**。
   - 共通ヘルパで `needs_review_label` へ差し戻す。
   - `add-comment <iid>` に「承認を受けたがマージできなかったため `needs-review` に戻しました」と、
     **不可理由を具体的に**（例: コンフリクト / CI 失敗 / ドラフト / 未対応レビューコメント / API エラー本文）記す。
   - 実装者が次に何をすればよいか（リベース・CI 修正・コメント対応）を 1〜3 点で示す。

> `merge-mr` はレース等で実行時にも失敗しうる。**非 2xx は必ず「不可」として差し戻し経路へ**回し、
> 「マージした」と誤って報告しない。

### reject（否認 → 差し戻し＋コメント生成）

1. 共通ヘルパで `needs_review_label` へ差し戻す（`ready_labels` を外す）。MR があれば任意で MR にもミラー（`add-mr-comment`）。
2. **ユーザーの自然文コメントを解釈**し、実装者（ワーカー）が直せる**差し戻しコメント**を生成して `add-comment <iid>` で投稿する。
   生成コメントの要件:
   - ユーザーの指摘を**具体的・実行可能**な是正項目に翻訳する（「ここを直す → 受け入れ条件/該当ファイルとの対応」）。
   - ユーザーが触れていない要求を**足さない**（門番が新しい仕様を作らない）。出典はユーザーコメント。
   - 該当箇所が分かるなら `path:line` や受け入れ条件番号を添える。
   - 末尾に「対応後、`ready_labels` を付け直して再申請してください」と再入場の導線を 1 行で示す。
3. ユーザーコメントが**空/曖昧で是正項目に翻訳できない**場合は、差し戻しを実行する前に
   「何を直せばよいか」を 1 問だけ確認する（憶測で差し戻しコメントを捏造しない）。

### dismiss（却下 → 却下コメント＋イシュークローズ＋MR クローズ＋ブランチ削除）

却下は**不可逆性が高い終了操作**（差し戻しと違い再申請を前提にしない）。実行前に対象を 1 行で要約してから進む。

1. **却下コメントの生成**: ユーザーの自然文コメントを解釈し、**なぜ却下するか**を実装者・関係者に分かる形でまとめる。
   - 出典はユーザーコメント。**ユーザーが述べていない理由を足さない**（門番が判断を作らない）。
   - 「差し戻しではなく却下である（このイシュー/MR はクローズし、再申請は想定しない）」ことを明記する。
   - 関連があれば代替・後続（別イシューで仕切り直す等）への導線を 1 行で添える（ユーザーが言及した範囲で）。
   - ユーザーコメントが**空/曖昧で却下理由を書けない**場合は、却下を実行する前に「却下の理由」を 1 問だけ確認する。
2. **執行**（共通ヘルパ「却下」の順序で。各ステップの非 2xx を確認し、失敗したら正直に途中経過を残す）:
   - 却下コメントをイシューへ投稿（MR があれば任意でミラー）。
   - イシューを `--state-event close`（`ready_labels` を外す。`needs_review_label` は付けない＝差し戻しではない）。
   - 紐づく MR があれば `update-mr <mr> --state-event close`（**未マージのままクローズ**）。
   - `dismiss.delete_source_branch` が true なら、ガード（既定/保護ブランチでない・命名規約に一致）を確認のうえ
     `delete-branch <source_branch>` でソースブランチを削除する。
3. **報告**: 実施した操作（投稿・イシュークローズ・MR クローズ・ブランチ削除の各成否）を要約する。
   一部が失敗したら「却下完了」とは書かず、どこまで実行し何が残っているかを示す。

> MR が無い／既にマージ済み等で「未マージクローズ」が成立しない場合は、ブランチ削除を勝手に進めず人へ確認する
> （マージ済みブランチの削除は別判断）。MR が無くイシューだけ却下するケースは、イシュークローズ＋コメントのみ行う。

---

## パケットの構成（packet モードの出力の中身）

どの出力先でも、人間が**上から読むほど深くなる**よう段階開示する。
**冒頭にパケット ID と対象を 1 行で明示**し（フロントマターが描画されない出力先でも人が ID を読めるように）、
続けて最低限この 5 ブロック:

```markdown
# 🪪 GK-7F3KQ9 — team/app !17 (issue #42)  ← この ID で decision を呼ぶ（approve|reject|dismiss）

> [!danger]+ 🔴 必ず確認すべき3点（人間が目視すべき急所）
> 1. <ファイル:行> <なぜ危険か。1 行で>
> 2. ...
> 3. ...

> [!question]- 🤔 受け入れ条件の達成評価（トレーサビリティ）
> - <条件text> → ✅根拠 `path:line` / ⚠️未達・証跡なし の別を明示
>   （diff・テスト・コメントから証跡を示す。憶測は「証跡なし」と書く。黙って✅にしない）

> [!info]- 🧪 観点別レビュー所見（信頼度つき）
> - functional: <所見> (信頼度 0.0–1.0)
> - security: ...
> - （対象に応じ agent-reviewer の観点を選ぶ。所見ゼロの観点は省略可）

> [!example]- 📝 変更内容（実際の修正・1枚で段階開示）★必須
> この MR が実際に変えた箇所。**最初に見えるのはファイル一覧（リスク色＋±行数）だけ**、
> 各ファイルの `details` を開くと unified diff が出る（既定は畳む＝認知負荷を下げる）。
> - 🔴 high: `path/to/auth.py` (+40/-3)
>   <details><summary>diff を表示</summary>
>
>   ```diff
>   @@ -10,3 +10,4 @@ def authorize(req):
>   -    return True
>   +    if not verify(req.token):
>   +        raise Forbidden()
>   +    return True
>   ```
>   </details>
> - 🟡 medium: `path/to/util.py` (+5/-1) … <details>…</details>（既定で畳む）
> - ⚪ low: テスト/ドキュメント等は 1 つの details にまとめて畳む
> - 巨大 diff（自動生成・lock・大量移動）は要約＋原文リンクに留める（1 枚を保つ）

> [!example]- 📂 リスク段階開示（自動チェック＋差分マップ）
> - CI: <status>  /  lint・SAST: <あれば>
> - high: <path> (+adds/-dels) … medium/low は畳む
```

> Obsidian は `[!example]-`（末尾 `-` で既定折りたたみ）、HTML / Markdown は `<details>` を使い、
> **diff 本文・低リスク差分・観点別所見は既定で隠す**。最初の視界は「急所 3 点＋変更ファイル一覧」に絞る。

### 信頼度の付与
各所見に **0.0–1.0 の信頼度**を付ける。人間の精読配分を決めるシグナル。
- 確証がある指摘のみ高信頼度。**自信のない指摘を高信頼度で出さない**（過検出は人間の時間を奪う）。
- 全体が「低リスク × 高信頼度 × 受け入れ条件充足」なら末尾に
  `> [!tip] 🟢 軽量承認候補: 急所に問題なし。スポットチェックで承認可と思われる（最終判断は人間）`。

---

## やってはいけないこと

- ❌ ステータス/ラベルをスキル側で**選別**条件として勝手に限定・追加する（対象選別は呼び出し側の責務）。
  ※ Gate A・否認・却下・マージ不可での `needs-review` 差し戻しやクローズは、人間の決定/規約に基づく**執行**であり別。
- ❌ ポーリング・sleep・定期ループをスキル内で回す（間隔は呼び出し側の責務）。
- ❌ 人間の明示決定が無いのに承認/否認/却下・マージ・クローズ・ブランチ削除する（判断は人間、執行のみ）。
- ❌ `merge-mr` が失敗したのに「マージした」と報告する（非 2xx は必ず差し戻し経路へ）。同様に、却下の各操作が
  非 2xx で失敗したのに「却下完了」と報告する（途中経過を正直に残す）。
- ❌ 否認・却下のコメントに、ユーザーが述べていない要求・理由を足す（門番が仕様/判断を作らない）。
- ❌ 否認（差し戻し）と却下（クローズ）を取り違える。曖昧なら執行前に 1 問だけ確認する（不可逆性が大きく違う）。
- ❌ 既定/保護ブランチや、命名規約に一致しないブランチを却下で削除する（必ずガードを通す）。
- ❌ decision モードで最新状態を確認せず古いパケットのまま執行する（D0 の REFRESH & RE-GATE を必ず通す）。
- ❌ パケット ID から対象を解決できない／複数一致するのに、独断でリポジトリ・イシューを選んで執行する（人へ 1 問確認）。
- ❌ パケットの `project_url` を無視し、ローカルクローンのリポジトリに対して誤って執行する（横断時は GL_PROJECT_URL を必ず設定）。
- ❌ パケットに diff 本文を最初から全展開で並べる（段階開示で畳み、操作で開く＝認知負荷を下げる）。
- ❌ 自信のない指摘を断定・高信頼度で出す（過検出は害）。
- ❌ raw データに無い事実を捏造する（特に受け入れ条件の達成判定）。

## 関連スキル

- `gitlab-idd` — `gl.py` の提供元（接続・トークン・MR/イシュー操作・`make-branch-name`）。
- `agent-reviewer` — 観点別レビューの本体（本スキルが再利用）。
- `obsidian-use` / `spec-to-readable-html` / `mermaid-diagrammer` / `technical-writer` — 出力整形の委譲先。
- `self-checking` — ワーカーが事前自己評価し、ここに来る前に指摘を減らす。

