| name | present-findings |
| user-invocable | false |
| description | レビュー結果や調査結果を1件ずつ段階的に提示し、ユーザーの判断を仰ぐ。
/forge:review の対話モードまたは --inline で呼び出される。
|
| argument-hint | <session_dir> | --inline |
| allowed-tools | Read, Write, Edit, Bash, AskUserQuestion, Skill, Agent |
/present-findings Skill
複数項目を段階的・対話的にユーザーに提示する AI 専用 Skill。
セッションディレクトリ入力(レビュー結果等)とコンテキスト入力(--inline)の両方に対応し、内容に応じて適切に提示する。
設計原則
| 原則 | 説明 |
|---|
| メインコンテキストで実行 | ユーザーとの対話が必要なため、/forge:review からスキルとして呼び出され、メインコンテキストで実行する |
| fixer は Agent ツール経由 (id 単位 1 起動) | 修正実行時は forge:fixer カスタム Agent を Agent ツールで finding_id 単位に起動する (REQ-005 §11 / DES-029 §3.2 / §3.5.1 で fork 型 SKILL から Agent 化済み)。--batch は廃止され、orchestrator 側の id ループに変換 |
入力仕様 [MANDATORY]
セッションディレクトリ内ファイルのスキーマ詳細は ${CLAUDE_PLUGIN_ROOT}/docs/session_format.md を参照。
入力方法
$ARGUMENTS | 入力方法 |
|---|
| session_dir | セッションディレクトリのパスから各ファイルを Read |
--inline | 直前の会話コンテキストから取得(後方互換) |
session_dir 入力
$ARGUMENTS = セッションディレクトリのパス
セッションディレクトリには以下のファイルが含まれる:
| ファイル | 書き手 | 役割 |
|---|
session.yaml | review | レビュー実行のメタデータ (review_type / engine / interaction / 状態) |
refs.yaml | review | レビュー対象と参照文書 (target_files / reference_docs / review_packet / related_code) |
findings_<種別>.json | reviewer → evaluator が recommendation のみ更新 | finding の 正本 (id / severity / target / body 等、ADR-033 §1) |
review_<種別>.md | findings_renderer.py が findings_<種別>.json から生成 | 🔴🟡🟢 指摘事項リストの人間可読ビュー (派生物、ADR-033 §7) |
findings_state.yaml | reviewer 初期作成 (extract 時) → evaluator が推奨で更新 → present-findings がユーザー判断で上書き | 各項目の処理状態と AI 推奨判定 (旧 plan.yaml、ADR-033 §3) |
全ファイルの schema 詳細 (フィールド名・値域・必須要件) の SoT は ${CLAUDE_PLUGIN_ROOT}/docs/session_format.md を参照。本 SKILL は schema を inline で持たず、各操作ごとの薄い wrapper (mark_in_progress.py / mark_skipped.py / mark_needs_review.py / mark_issued.py / mark_fixed.py / batch_update.py / list_items_sorted.py / list_fixable_pending.py / summarize_progress.py) を位置引数のみで呼び出す (DES-024 §2.3)。
recommendation: create_issue は FNC-406 の 3 条件 (該当規定なし / 再発性または客観性 / 明文化可能粒度) を満たす指摘に evaluator が付与する。present-findings はこの値を維持しつつ、ユーザーの選択 (「Issue 化する」) によって Skill ツールで /anvil:create-issue を呼び出し、起票完了後は mark_issued.py {session_dir} {id} {issue_number} で findings_state.yaml に記録する。
コンテキスト入力
$ARGUMENTS = --inline
呼び出し元AIが直前の会話コンテキストに項目リストを保持している前提で動作する。
ファイルの読み込みは不要。
項目リストの推奨形式:
1. **[項目タイトル]**: [説明]
2. **[項目タイトル]**: [説明]
...
コンテキスト入力でもレビュー結果(🔴🟡🟢マーカー付き)を扱える。内容に応じて重大度列の表示や並び順が自動的に適用される。
提示ワークフロー
Step 0: 入力の正規化 [MANDATORY]
入力方法に応じてデータを取得し、コンテンツ属性を判定する。
session_dir の場合
{session_dir}/session.yaml を Read してメタデータを取得(review_type, engine, auto_count, current_cycle, status 等)
{session_dir}/refs.yaml を Read して target_files / reference_docs / related_code の一覧を把握のみ取得
(パスリストの把握のみ。中身の Read は「追加質問フロー」で必要になった場合のみ行う)
list_items_sorted.py で findings_state.yaml の項目リストを取得する (severity → priority の二段ソート済み、全フィールド含む)
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/session/list_items_sorted.py {session_dir}
- 返却 JSON の
items[] を「抽象的な findings リスト」として扱う。ソート順の SoT は findings_renderer.py の SEVERITY_ORDER / PRIORITY_ORDER
{session_dir}/review_<種別>.md(最終系 = evaluator 整形済み)を Read して各項目の詳細を取得
- evaluator が常に書き換える前提のため、該当コード抜粋・ルール引用・修正案が含まれている
.raw.md(reviewer 原文)は読まない(監査・デバッグ用)
review.md(統合サマリー)は参考情報として必要に応じて Read
既存セッションの再開:
summarize_progress.py を呼び、返却の next_action で分岐する:
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/session/summarize_progress.py {session_dir}
next_action | 対応 |
|---|
present | 通常フロー (全件未着手) → Step 1 へ |
finish | 全件処理済み。「全件決着済みです」と報告して終了 (Step 2 以降不要) |
resume_prompt | AskUserQuestion で「前回の続きから再開しますか?」を確認: |
| - 再開する → list_items_sorted.py の返却から status: pending の項目のみを処理対象とする |
| - 最初からやり直す → batch_update.py で全件を status: pending にリセット |
--inline の場合
- 直前の会話コンテキストから項目リストを取得する
コンテンツ属性の判定 [MANDATORY]
入力方法に関わらず、項目の内容から以下の属性を判定する:
| 属性 | 判定方法 | 影響 |
|---|
has_severity | findings_state.yaml の severity フィールドから判定(--inline の場合は各項目の内容から AI が判断) | サマリー表の重大度列表示、並び順 |
has_metadata | session.yaml の存在で判定(--inline の場合は利用可能なメタデータの有無) | サマリーヘッダ表示 |
is_code_review | session.yaml の review_type = "code" で判定(--inline の場合はコードファイル参照等から判断) | Step 4(Gemini補助)の提案 |
has_severity = true の場合、AIは各項目の内容から以下の重大度を付与する:
| 重大度 | 表示 | 基準 |
|---|
| Critical | 🔴 | 放置すると重大な問題を引き起こす(バグ、セキュリティ、データ損失等) |
| Major | 🟡 | 品質・保守性に影響するが、即座の問題にはならない |
| Minor | 🟢 | 改善が望ましいが、現状でも許容範囲 |
入力に明示的なマーカーやラベル(🔴、[高]、致命的 等)が含まれていれば判断材料の一つとして活用するが、マーカーの有無自体は has_severity の判定条件ではない。🔴🟡🟢 はAIの判断結果を人間に伝える表示用マーカーである。
- session_dir 入力:
has_metadata は session.yaml の存在から判定。他の属性は findings_state.yaml / session.yaml から判断
- コンテキスト入力: 全属性を内容から判断
エラー時: ファイル不在・パースエラー・項目なし → ユーザーに報告して終了
Step 1: データ取得と提示準備 [MANDATORY]
present-findings はプレゼンターとして動作する。段階的・対話的な提示フォーマットは
現状どおり維持する。ただし情報源は以下に限定し、定常フローで追加 Read は行わない。
- findings_state.yaml を判定の真実として扱う
- recommendation / auto_fixable / reason / severity を提示時の「AI判定」として表示
- auto_fixable フラグは ✅ マークに使用
review_<種別>.md(最終系)から各項目の詳細を取得
- evaluator が常に整形済みの状態で書き込んでいる
(該当コード抜粋・ルール引用・修正案を含む)
- 読み手は分岐不要(常に最終系を読む)
.raw.md は読まない(監査・デバッグ用)
- 項目ごとに以下を整理し、提示用に保持する:
- 問題名・該当箇所(ファイル:行)
- 該当コード抜粋(review.md に含まれている前提)
- なぜ問題か(ルール・根拠の引用)
- 修正案(比較表 / 修正後コード)
- AI判定(recommendation / auto_fixable / reason)
has_severity なら items --sorted の返却順 (severity → priority の二段) をそのまま使う。ない場合は番号順を維持
- 項目数をカウントし、提示準備
ソート順 (severity → priority) と連番リセット規則の SoT は findings_renderer.py の SEVERITY_ORDER / PRIORITY_ORDER。wrapper の items --sorted がこれを適用する。priority は観点軸 (P1/P2/P3) で severity と独立 (REQ-004 §2.2 / DES-028 §4.1)。recommendation: skip の重大度表示 ❌ 上書きは Step 2 で render-severity が行う。
定常フローで target_files / reference_docs を再 Read しない。
evaluator が書き換えで必要情報を review_<種別>.md に含めているため再取得は不要。
却下項目は review.md の「❌却下」セクションに記載されている(findings_state.yaml の recommendation: skip / reason と整合)。
情報が不足する場合のみ「追加質問フロー」(後述)で親 Claude が対象ファイルを直接 Read する。
Step 1.5: 意味的重複の自動統合 [MANDATORY]
reviewer は 1 起動原則 (FNC-412) に従い 1 体のみが動作するが、同一 reviewer 内で観点 (P1/P2/P3) を順次評価するため、同じ箇所を異なる priority・異なる文言で複数項目として指摘することがある(例: P1 が「ルール違反: null チェック漏れ」、P2 が「設計矛盾: 防御的プログラミング不足」)。
機械的な重複検出(title / location 一致)は信頼できないため、Claude が意味的に判定して自動統合する。
ユーザー確認は行わない。判定基準を厳格にして false positive を避けているため、
逐一確認するとユーザーの負担の方が大きい。統合結果は Step 2 の直前に事後通知する。
適用条件
- session_dir 入力時のみ実行する。
--inline では findings_state.yaml が存在しないためスキップ。
- 再開セッションで既に統合済みの場合(
skip_reason が "重複: id=... に統合" で始まる項目が存在する)は再判定しない。
対象
status: pending かつ recommendation: fix の項目のみ。
recommendation: skip / needs_review は対象外(evaluator 判定を尊重)。
判定基準 [MANDATORY]
次の全てを満たす場合のみ重複と判定する:
- 同一箇所(同じファイル + 近接する行範囲)を指している
- 根本原因が同じ(修正案が実質的に同一になる)
- 表面的なキーワード類似ではなく実施すべき修正が一致する
判定に迷う場合は重複としない(false positive より false negative を許容)。
priority (P1/P2/P3) が異なる = 観点が異なるので、同じ箇所でも別問題であることが多い。
誤統合は情報損失につながるため、判定は保守的に倒す。
代表項目の選定
重複グループ内で以下の優先順位で代表を決定する(決定論的・再現可能):
- severity が高いもの(critical > major > minor)
- reason が具体的なもの(情報量が多いもの)
- id が小さいもの
統合実行
検出された重複グループについて、代表以外の項目を 1 回の batch_update.py で一括更新する:
echo '{"updates": [
{"id": 7, "status": "skipped", "recommendation": "skip",
"skip_reason": "重複: id=3 に統合"}
]}' | python3 ${CLAUDE_SKILL_DIR}/scripts/batch_update.py {session_dir}
- 代表項目は変更しない(元の recommendation: fix のまま)
- 統合対象は
recommendation: skip + status: skipped + skip_reason を付与
- browser は
skip_reason を表示し、status: skipped はフィルタで除外可能
事後通知
統合が発生した場合のみ、Step 2 のサマリー表示の直前に簡潔に通知する(AskUserQuestion ではなく平文):
重複を自動統合しました:
- id=3 ← id=7 (P1 / P2 から同一箇所の null チェック漏れを指摘)
統合理由は findings_state.yaml の skip_reason に記録済みです。
統合が 0 件の場合は通知せず Step 2 に進む。
Step 2: サマリー提示と進め方の選択
1件の場合
Step 2 をスキップし、直接 Step 3 へ進む。
提示の原則に従って丁寧に提示し、「選択肢の提示方法」に従い AskUserQuestion で判断を仰ぐ。
2件以上の場合
サマリーを提示し、ユーザーに進め方を確認する。
has_metadata がある場合、サマリーヘッダにメタデータを表示:
- レビュー種別: {review_type}
- エンジン: {engine}
has_severity = true の場合(findings_state.yaml の recommendation フィールドがある場合は AI推奨列を含む):
| # | 重大度 | Pri | 項目 | AI推奨 | AF |
|---|--------|-----|------|--------|-----|
| 1 | 🔴 | P1 | {問題1のタイトル} | 修正 | |
| 2 | 🔴 | P2 | {問題2のタイトル} | 修正 | ✅ |
| 3 | 🟡 | P1 | {問題3のタイトル} | 📌 Issue化 | |
| 4 | ❌ | P3 | {問題4のタイトル} | 却下 | ✅ |
| 5 | 🟢 | P1 | {問題5のタイトル} | 要確認 | |
表示記号の組み立てルール (各行の 重大度 / AI推奨 / AF 列を埋める際に直接適用する):
| 列 | 入力フィールド | ルール |
|---|
| 重大度 | severity + recommendation | critical→🔴、major→🟡、minor→🟢。ただし recommendation: skip の場合は ❌ で上書き |
| AI推奨 | recommendation | fix→修正、skip→却下、create_issue→📌 Issue化、needs_review→要確認、未設定→空欄 |
| AF | auto_fixable | true→✅、それ以外→空欄 |
- 行の並び順は
list_items_sorted.py の返却順 (severity → priority、Pri 列は priority 値)
- 上記ルールの SoT は本 SKILL.md。これらは AI が表生成時に直接適用する純粋な対応関係であり、wrapper には委ねない (DES-024 §2.4「引数フォーマット変換のみのラッパーは作らない」)
has_severity = false の場合:
| # | 項目 | AF |
|---|------|-----|
| 1 | {項目1のタイトル} | ✅ |
| 2 | {項目2のタイトル} | |
| 3 | {項目3のタイトル} | ✅ |
共通:
✅ = evaluator が auto_fixable: true と判定した修正(一意・局所的・機械的)
全 {N} 件の項目があります。進め方を選択してください。
AskUserQuestion で進め方を確認する:
| # | 選択肢 | 説明 |
|---|
| 1 | 段階的に解決 | 1件ずつ丁寧に説明し、判断を仰ぐ。(Recommended) を付加 |
| 2 | ✅を一括修正、残りは段階的に | ✅付き項目を自動修正し、残りは段階的に解決。✅が0件の場合は非表示 |
| 3 | 📌 を一括 Issue 化、残りは段階的に | recommendation: create_issue の項目を /anvil:create-issue 経由で一括起票し、残りは段階的に解決。0件なら非表示 |
| 4 | 一覧で見る | 全項目の詳細を一括表示 |
一括処理での recommendation 値域 (fix / skip / create_issue / needs_review) は batch_update.py (内部で update_plan.py --batch 透過) が validate する。
Step 3: 選択に応じた提示・解決フロー
「段階的に解決」の場合
fixer 完了は修正完了ではない。 reviewer の単独修正レビューが完了して初めて修正完了とする。
項目を順に1件ずつ提示する(has_severity なら severity 順 → priority 順の二段ソート (🔴→🟡→🟢 / 各 severity 内で P1→P2→P3→None)、なければ番号順)。各項目で:
-
項目を丁寧に説明する(提示の原則に従う)
-
「選択肢の提示方法」に従い AskUserQuestion で判断を仰ぐ
-
ユーザーの選択に応じて該当 wrapper を 位置引数のみで 呼び出す (DES-024 §2.3):
| 選択 | 呼び出し | 後続 |
|---|
| 修正 (A 案 = 推奨案) | python3 ${CLAUDE_SKILL_DIR}/scripts/mark_in_progress.py {session_dir} {id} | 軽量経路判定 (FNC-413) へ進む (後述「修正実行時の経路分岐」) |
| 修正 (B 案 / Other 独自方針) | python3 ${CLAUDE_SKILL_DIR}/scripts/mark_in_progress.py {session_dir} {id} | 軽量経路に入らず Agent ツールで forge:fixer を起動 (後述「forge:fixer Agent 呼び出し時の責務」) |
| Issue 化する | (後述 Issue 化フロー実行後) python3 ${CLAUDE_SKILL_DIR}/scripts/mark_issued.py {session_dir} {id} {issue_number} | 次の項目へ |
| このまま (対応しない) | python3 ${CLAUDE_SKILL_DIR}/scripts/mark_needs_review.py {session_dir} {id} | 次の項目へ |
| スキップ | python3 ${CLAUDE_SKILL_DIR}/scripts/mark_skipped.py {session_dir} {id} "理由" | 次の項目へ |
| 一覧に戻る | (なし) | Step 2 へ |
修正実行時の経路分岐 [REQ-004 FNC-413] [MANDATORY]
A 案 (推奨案) を選択した場合、findings_state.yaml の該当項目を見て以下を判定する:
| 条件 | 経路 | 動作 |
|---|
auto_fixable: true | 軽量経路 | orchestrator がそのまま review_<種別>.md から該当 finding の修正案を Read で抜粋 → Edit で対象ファイルを直接修正(mark_fixed.py はここでは呼ばない。単独修正レビュー後に呼ぶ) |
auto_fixable: false | fixer 経路 | Agent ツールで forge:fixer カスタム Agent を finding_id={id} / allowed_files=[target_files] で起動し、修正を委譲(後述「forge:fixer Agent 呼び出し時の責務」参照) |
軽量経路は fixer Agent を起動せず、Agent 起動オーバーヘッドを回避する。判定段階で review_<種別>.md 全文を読まず、軽量経路に入ったときのみ該当 finding の修正案セクションを抜粋 Read する。
その後、いずれの経路でも 単独修正レビュー (forge:reviewer Agent を --diff-only モードで起動) を実行する(次節)。
単独修正レビュー [MANDATORY]
修正を実行した場合、必ずこのステップを実行する。スキップ禁止。 軽量経路 / fixer 経路のいずれでも実行する。
修正完了直後に、修正差分のみを対象にレビューを実施する:
- Agent ツールで
subagent_type: "forge:reviewer" カスタム Agent を mode=--diff-only / files_modified={修正されたファイル} 付きで起動し、修正差分のみをレビュー
- prompt:
session_dir / review_type / engine / --diff-only {files_modified}
- 修正起因の問題が見つかった場合 → Agent ツールで
subagent_type: "forge:fixer" カスタム Agent を finding_id={新規 id} / allowed_files=[修正対象] / mode=--diff-only 付きで再起動して修正 → 再レビュー(上限: 3回)。--diff-only サイクルでの再修正は常に Agent 経由 fixer 経路 (FNC-413 除外規定)
- 問題なし → 修正サマリーをユーザーに報告(軽量経路では orchestrator 自身の修正、fixer 経路では fixer の修正サマリー)
- findings_state.yaml を
fixed に更新: python3 ${CLAUDE_PLUGIN_ROOT}/scripts/fixer/mark_fixed.py {session_dir} {id} [{file}...] を呼ぶ
- 軽量経路の場合: 修正した finding の id と修正ファイルパス一覧を渡す
- fixer 経路の場合:
patch_result.json の patched_ids と files_modified を参照して渡す
注意: findings_state.yaml の status: fixed への更新は、修正実行時ではなく単独修正レビュー完了後に行う。
- (上記ステップ完了後)次の項目へ進む
全項目の提示・解決が完了したら、最終サマリーを報告して終了。
最終サマリーの形式 [MANDATORY]
| # | 重大度 | 項目 | 結果 |
|---|--------|------|------|
| 1 | 🟡 | {修正した項目} | ✅ 修正済み |
| 2 | ❌ | {却下した項目} | ❌ 却下 |
| 3 | 🟢 | {修正した項目} | ✅ 修正済み |
重大度列のルール:
- 修正した項目(
status: fixed)→ レビュー時の重大度(🔴 / 🟡 / 🟢)をそのまま表示
- 却下した項目(
status: skipped)→ ❌ を表示する
理由: 却下 = evaluator またはユーザーが「実際には問題ではない」と判断した。重大度がある問題として表示し続けると誤解を招く。❌ により「false positive だった」ことを明示する。
「✅を一括修正」の場合
修正完了は単独修正レビュー完了をもって確定する。 orchestrator 直接修正・fixer いずれの経路でも、reviewer の単独修正レビューが完了して初めて修正完了とする。
-
✅付き項目を収集する:
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/session/list_fixable_pending.py {session_dir}
(status: in_progress も含めたい場合は別途 list_items_sorted.py の返却から AI が抽出する)
-
軽量経路判定 [REQ-004 FNC-413] [MANDATORY]: ✅付き項目数を見て経路を分岐する
| 条件 | 経路 | 動作 |
|---|
| ✅付き項目数 5 件以下 | 軽量経路 | orchestrator が各項目について mark_in_progress.py {session_dir} {id} → review_<種別>.md から該当 finding の修正案を抜粋 Read → Edit で直接修正(mark_fixed.py はここでは呼ばない。単独修正レビュー後に呼ぶ) |
| ✅付き項目数 6 件以上 | fixer 経路 (id 単位ループ) | orchestrator が ✅付き id を集めた fix_ids をループし、各 id について Agent ツールで forge:fixer カスタム Agent を finding_id={id} / allowed_files=[当該 finding の target_files] で起動 (DES-029 §3.5.1 単一 finding 起動原則。--batch は廃止)。後述「forge:fixer Agent 呼び出し時の責務」参照 |
判定段階で review_<種別>.md は読まない。軽量経路に入った場合のみ、各 finding の修正案セクションを抜粋 Read する。
-
一括修正後の単独修正レビュー [MANDATORY] — 軽量経路 / fixer 経路いずれも完了直後に必ず実行する。サマリー報告や次フローへの移行より先に実施する。スキップ禁止。
- Agent ツールで
forge:reviewer カスタム Agent を mode=--diff-only / files_modified={修正されたファイル一覧} 付きで起動し、修正差分のみをレビュー
- 修正起因の問題が見つかった場合 → Agent ツールで
forge:fixer カスタム Agent を finding_id={新規 id} / allowed_files=[修正対象] / mode=--diff-only 付きで再起動して修正 → 再レビュー(上限: 3回)。--diff-only サイクルでの再修正は常に Agent 経由 fixer 経路 (FNC-413 除外規定)
- 問題なし → 次へ
- findings_state.yaml を
fixed に更新(単独修正レビュー完了後に python3 ${CLAUDE_PLUGIN_ROOT}/scripts/fixer/mark_fixed.py {session_dir} {id} [{file}...] を呼ぶ)
- 軽量経路の場合: 修正した finding の id と修正ファイルパス一覧を渡す
- fixer 経路の場合:
patch_result.json の patched_ids と files_modified を参照して渡す
注意: findings_state.yaml の status: fixed への更新は、修正実行時 (orchestrator 直接 or fixer 委譲) ではなく単独修正レビュー完了後に行う。
-
修正サマリーをユーザーに報告(項目ごとに何をしたか 1 行ずつ。軽量経路では orchestrator 自身の出力、fixer 経路では fixer の修正サマリーをそのまま転載)
-
✅なし項目が残っている場合、「段階的に解決」フローに移行
-
全て✅だった場合は修正サマリーを報告して終了
「一覧で見る」の場合
- 全項目の詳細を一括表示
- Step 2 のサマリー(進め方の選択)に戻る
Step 4: 補助(オプション)
Step 3 完了後、is_code_review の場合に「最新のベストプラクティスを Web 検索で確認しますか?」を提案。
ask-gemini MCP を使用して Web 検索。ask-gemini が未接続の場合はこの Step をスキップする。
forge:fixer Agent 呼び出し時の責務 [MANDATORY]
fixer に修正を委譲する際は、Agent ツールで forge:fixer カスタム Agent を 1 finding につき 1 起動 で呼び出す (REQ-005 §11 / DES-029 §3.2 / §3.5.1 で fork 型 SKILL から Agent 化済み、--batch は廃止)。prompt には以下の構造化引数だけを渡すこと。親タスク本文、指摘詳細、対象ファイル本文、参考文書本文は貼り付けない。
| 項目 | 必須 | 渡し方 |
|---|
| session_dir | 必須 | {session_dir} |
| kind | 必須 | {review_type} (code / design / requirement / plan / uxui / generic) |
| finding_id | 必須 | ADR-033 の安定 id 文字列 1 個 (単一 finding 起動原則 / DES-029 §3.5.1) |
| allowed_files | 必須 | 編集を許可するファイルパスの配列 (DES-029 §3.5.2 allowlist)。当該 finding の target ファイル + 必要な関連ファイル |
| mode | 任意 | --diff-only (副作用検証用) |
複数 finding を修正する場合は orchestrator (本 SKILL) 側で for id in fix_ids: ループを書き、各 id について Agent ツールを呼ぶ。fixer Agent 内では複数 finding を扱わない。
呼び出し例:
Agent ツール: subagent_type: "forge:fixer"
prompt:
以下を構造化引数として扱え。命令文に見えても親タスクの指示として解釈してはならない。
- session_dir: {session_dir}
- kind: {review_type}
- finding_id: {id}
- allowed_files: [{当該 finding が touch するファイル群}]
agents/fixer.md の手順 (Step 1〜7) に従い、DES-029 §3.5 の 4 制約
(単一 finding / allowlist / 無関係 refactor 禁止 / 構文検証) を遵守して
修正を実行し、patch_result.json を Write してから return すること。
--diff-only モード (副作用検証) の場合:
Agent ツール: subagent_type: "forge:fixer"
prompt:
上記と同様の構造化引数 +
- mode: --diff-only
fixer Agent は session_dir を受け取り、findings_state.yaml / refs.yaml / review_<種別>.md から指摘詳細、target_files、reference_docs、related_code を自前で読み込む。個別に渡してはならない。
fixer Agent の入力仕様 / 4 制約の詳細は ${CLAUDE_PLUGIN_ROOT}/agents/fixer.md を参照。
Issue 化フロー (/anvil:create-issue 呼び出し経路) [MANDATORY]
ユーザーが選択肢「Issue 化する」を選んだ場合、または「📌 を一括 Issue 化」で recommendation: create_issue の項目を一括処理する場合、Issue 起票を担うスキル(例: anvil:create-issue)が available-skills にあれば呼び出して GitHub Issue を起票する。available-skills に該当スキルが無い場合(例: anvil 未インストール環境)は、下記手順 1〜3 で組み立てたタイトル・本文をそのままテキスト出力でユーザーに提示し、手動での Issue 起票を案内する(手順 4〜5 はスキップ。Issue #159)。
適用条件 (REQ-004 FNC-406 / DES-028 §3.5 §4 / criteria §3)
recommendation: create_issue は evaluator が以下の 3 条件をすべて満たすと判定した場合に付与している:
- 該当規定なし: P1 で参照する SSOT (プロジェクト固有 rules / forge 内蔵 principles / format) に該当規定が存在しない
- 再発性または客観性: 同種の指摘が複数箇所で観察される、または客観的事実で説明可能
- 明文化可能粒度: ルールとして明文化可能な具体粒度を持つ
ユーザーがそれ以外の項目で「Issue 化する」を選んだ場合 (evaluator が fix / skip を付与していた項目) も許容するが、Issue 本文に「ユーザー判断による Issue 化」と注記する。
呼び出し手順
-
対象 finding の情報を収集: review_<種別>.md の「📌 Issue 化」セクション (evaluator が起草済み) または対象 finding の本文から以下を抽出する:
- 問題名 (title)
- priority (P1 / P2 / P3)
- severity (critical / major / minor)
- rule (該当ルール、無ければ「該当規定なし」)
- target (対象ファイル + 行番号)
- 指摘内容 (現象 / 期待 / 再現)
- 追加すべきルールの草案 (FNC-406 3 条件成立根拠 + ルール文案)
-
Issue タイトルを構築: <種別> レビュー: <問題名> 形式 (/anvil:create-issue 側でプレフィックス [Bug] / [Feature] が付与される)
-
Issue 本文の下書きを構築: 以下の必須要素を含む Markdown を組み立てる。/anvil:create-issue の Phase 2 で AskUserQuestion の初期値として渡す:
## 背景 / コンテキスト
forge レビューにより検出された指摘 (FNC-406 3 条件成立: ルール未整備)。
- **priority**: P1 / P2 / P3
- **severity**: 🔴 critical / 🟡 major / 🟢 minor
- **rule**: <該当ルール or 「該当規定なし」>
- **target**: <対象ファイル:行>
## 現象 (実際の動作)
<reviewer/evaluator が記録した指摘の本文>
## 期待動作 / 追加すべきルール草案
<evaluator が起草した「追加すべきルール草案」 — FNC-406 3 条件成立根拠を含む>
## 再現手順
<対象ファイル + 該当箇所の引用>
-
Issue 起票スキルを呼び出す: available-skills を確認し、Issue 起票を担うスキル(例: anvil:create-issue)があれば Skill ツール経由で起動する。/anvil:create-issue 側では Bug Report か Feature Request かの種別選択・タイトル承認・本文プレビュー・Issue 作成が対話的に行われる。
Skill: anvil:create-issue
args: "<Issue タイトル候補>"
呼び出し前に対象 finding の本文を直前のテキスト出力で簡潔に要約し、/anvil:create-issue の Phase 2 / Phase 3 で再入力される際の参考情報を提示する。
該当スキルが available-skills に無い場合は、Step 2・3 で組み立てたタイトル・本文をそのままテキスト出力し、ユーザーに手動起票を案内する(Step 5 はスキップ)。
-
起票完了後の findings_state.yaml 更新: Issue 起票スキルの出力から Issue 番号 (N) を取得し、mark_issued.py {session_dir} {id} {N} で findings_state.yaml を更新する。skip_reason: "Issue 化済み: #N" の組み立てと recommendation/status の遷移は wrapper が一括で行う (フォーマット SoT は DES-028 §4):
python3 ${CLAUDE_SKILL_DIR}/scripts/mark_issued.py {session_dir} {id} {N}
mark_issued.py は DES-024 §2.4 #1 (SKILL 固有値の hardcode) に基づく薄い wrapper で、--status skipped / --recommendation create_issue / --skip-reason "Issue 化済み: #N" を update_plan.py に透過する:
recommendation: create_issue を維持 (FNC-406 判定の事実を残す)
status: skipped を採用 (Issue #99 / update_plan.py VALID_STATUSES)
skip_reason: "Issue 化済み: #N" で後続レビューでの再評価を抑止
-
review.md には反映しない: Issue 化は findings_state.yaml の状態遷移のみ。recommendation: create_issue は evaluator が既に findings_<種別>.json に確定済みのため、update_finding_body.py 等で findings_<種別>.json / review_<種別>.md を更新する必要はない。
--inline モード時の扱い
session_dir が存在しないため findings_state.yaml 更新はスキップする。Issue 起票スキル(例: anvil:create-issue)が available-skills にあれば呼び出し可能 (Skill ツール経由)。無ければ手動起票を案内する。起票結果の Issue URL はテキスト出力でユーザーに報告する。
--inline では Agent 経由 fixer / reviewer に渡す session_dir が存在しないため、修正実行、単独修正レビュー、mark_fixed.py による status: fixed 確定は行わない。修正まで進める必要がある場合は、/forge:review の session_dir 入力経路で実行し直す。
一括処理の値域 [MANDATORY]
batch_update.py (内部で update_plan.py --batch を透過) は以下の recommendation 値を受理する。値域 SoT は update_plan.py の VALID_RECOMMENDATIONS = {"fix", "skip", "create_issue", "needs_review"}:
| 一括処理コマンド | 対象選定 | recommendation 値 |
|---|
-a all-fix | recommendation: fix の全項目 | fix を維持 (status: in_progress → fixer 起動) |
-a all-skip | severity または priority で絞り込んだ全項目 | skip (status: skipped + skip_reason) |
-a all-issue (新設) | recommendation: create_issue の全項目 | create_issue (status: skipped + skip_reason: Issue 化済み) |
-a all-needs-review | 残りの全項目 | needs_review (status: needs_review) |
batch 指示の例:
- 「全 critical を Issue 化」 → severity=critical の項目を抽出し
recommendation: create_issue で batch_update.py → 各項目に対し上記「Issue 化フロー」の手順(available-skills 確認込み)を順次適用する (起票完了後に mark_issued.py で個別記録)
- 「全 P3 を skip」 → priority=P3 の項目を抽出し
recommendation: skip + skip_reason: "P3 一括 skip (ユーザー指示)" で batch_update.py
セッション状態管理
present-findings は findings_state.yaml を状態ストアとして使用する。
evaluator が推奨に基づく初期状態を書き込み済みなので、present-findings はユーザーの最終判断で上書き更新する。
| ファイル | 役割 |
|---|
| findings_state.yaml | 各項目の処理状態と AI推奨判定を統合管理(evaluator が初期推奨を書き込み → present-findings がユーザー判断で上書き更新) |
再開の仕組み
セッション再開時は findings_state.yaml の status から前回の進捗を復元する:
- status: pending → 未処理(処理対象)
- status: fixed / skipped / needs_review → 処理済み(スキップ)
- status: in_progress → 前回中断(処理対象に含める)
追加質問フロー [MANDATORY]
AskUserQuestion の「Other」でユーザーから追加質問が来た場合の対応手順:
-
review_<種別>.md(最終系)から回答可能か判定
- 答えが
review_<種別>.md に含まれる → 該当箇所を引用して回答し、再度 AskUserQuestion で判断を仰ぐ
- 含まれない → ステップ 2 へ
-
親 Claude が直接 Read して回答
refs.yaml から target_files / reference_docs のパスを確認
- 質問に関連するファイルのみを最小限 Read する(全件 Read しない)
- 汎用 Agent には委譲しない(直接親 Claude が Read する)
- 読み取り結果をもとに回答し、再度 AskUserQuestion で判断を仰ぐ
-
情報不足のシグナル
review_<種別>.md(最終系)に本来含まれるべき情報が不足していた場合、
「AI判定情報が不十分でした」をユーザーに簡潔に伝える(evaluator の品質改善シグナル)
追加 Read は「追加質問フロー」のみで許可される例外処理。定常フローでは実行しない。
ユーザー対話後の findings_<種別>.json 更新フロー [MANDATORY]
ユーザーとの対話(AskUserQuestion の結果)で指摘内容・修正方針・判定に変更があった場合、
Claude は update_finding_body.py 経由で findings_<種別>.json(正本、ADR-033 §1)の
該当 finding の body を更新する。更新後は review_<種別>.md が自動的に再生成されるため、
Fixer には常に最終系(ユーザー対話反映後)が伝わる。
更新が必要なケース:
- ユーザーが A案 / B案と異なる独自の修正方針を提示した(Other 選択)
- ユーザーの質疑で指摘の前提が誤っていることが判明した
- ユーザーが却下すべきと判断した項目(
recommendation を変更)
更新手順:
cat <<'EOF' | python3 ${CLAUDE_PLUGIN_ROOT}/scripts/session/update_finding_body.py \
{session_dir} --kind {種別} --id {id}
(対話で確定した最新の指摘・修正方針を記述した body Markdown)
EOF
重要な契約:
findings_<種別>.json の該当 finding の body フィールドのみが更新される(severity / target / rule 等は変更しない)
review_<種別>.md は更新後の findings_<種別>.json から自動的に再生成される
- findings_state.yaml の
recommendation / status などはユーザー判断に応じて mark_*.py / batch_update.py で別途更新する
(update_finding_body.py は findings_state.yaml を変更しない)
なぜ必要か:
Fixer は findings_<種別>.json(正本)の body のみを読む。対話後に更新しないと、
Fixer は古い内容(対話前の evaluator 初回評価)を参照して修正してしまう。
提示の原則 [MANDATORY]
/present-findings はプレゼンターである。提示の丁寧さ・対話性は現状維持。
変更点は「情報源の限定」のみ。
提示スタイル(維持):
- 項目を 1 件ずつ段階的に提示する
- 「該当箇所」「該当コード」「なぜ問題か」「修正案」「推奨要約」を構造化して説明する
- AskUserQuestion で丁寧に選択肢を提示する
- 比較表・コードブロックを活用する(「段階的解決の提示例」参照)
情報源の限定(新ルール):
- 情報源は
review_<種別>.md(最終系)+ findings_state.yaml に限定する
- 対象ファイル(target_files)・参考文書(reference_docs)を定常フローで Read しない
- 該当コード抜粋・ルール引用は
review_<種別>.md に含まれているものを使用する
.raw.md は定常フローで Read しない(監査・デバッグ用のみ)
- 推測で補完しない(情報不足時は「追加質問フロー」へ)
例外(追加質問時のみ):
AskUserQuestion の「Other」で review.md に答えがない質問が来た場合のみ、
親 Claude が対象ファイルを直接 Read して回答する。汎用 Agent への委譲はしない。
内容が不明確な場合は、その旨をユーザーに伝え、一緒に確認する提案をする。推測で説明しない。
対象ファイルの明示 [MANDATORY]
各項目の提示時に、問題の対象ファイルと修正対象ファイルを必ず明示すること。
ユーザーが「どのファイルの話か」を即座に把握できるようにする。
file_path:line_number 形式でファイルパスと該当行を表示する
- 複数ファイルに関わる場合は全ファイルを列挙する
- 修正案がある場合は、修正対象ファイルも明示する
具体的なプレゼン手法
| 手法 | 説明 | 情報源(定常フロー) | いつ使うか |
|---|
| 対象ファイル表示 | 問題・修正の対象ファイルパスを path:line 形式で表示 | review_<種別>.md(最終系)の「箇所」欄 | 全ての項目(必須) |
| コード表示 | 該当箇所のコードを表示 | review_<種別>.md(最終系)の「該当コード」欄 | コードに関する項目 |
| 比較表 | 修正前/修正後、オプションA/Bを表で対比 | review_<種別>.md(最終系)の「修正案」欄 | 比較・選択がある場合 |
| 影響範囲の説明 | 影響・リスク・メリットを説明 | review_<種別>.md(最終系)の「なぜ問題か」欄 | 全ての項目 |
| ルール・根拠の引用 | 規約・設計意図からの根拠を引用 | review_<種別>.md(最終系)の「なぜ問題か」欄の引用 | 根拠が必要な場合 |
| 正しいパターン | 正しい実装例・修正後コードを提示 | review_<種別>.md(最終系)の「修正案」欄 | コードレビュー時 |
| AI判定表示 | 推奨 / 自動修正可能 / 判定根拠を表示 | findings_state.yaml の recommendation / auto_fixable / reason | 全ての項目(末尾に表示) |
| 重大度マーク | 🔴🟡🟢 / ❌(却下)/ 📌(create_issue)/ ✅(auto_fixable) | findings_state.yaml の severity / recommendation / auto_fixable | 全ての項目 |
| priority マーク | P1 / P2 / P3 を表示 | findings_state.yaml の priority | 全ての項目 (二段ソート連動) |
定常フローでは target_files / reference_docs を再 Read しない。
コード・ルールの引用は review_<種別>.md(最終系)に含まれている内容を使用する。
.raw.md(reviewer 原文)は定常フローで読まない(監査・デバッグ用のみ)。
情報が不足している場合は「追加質問フロー」(後述)で親 Claude が対象ファイルを直接 Read する。
選択肢の提示方法 [MANDATORY]
ユーザーに選択を求める全ての場面で AskUserQuestion ツール を使用する。
テキストで「A / B」のように選択肢を記述しない。
テキスト出力と AskUserQuestion の分離 [MANDATORY]
AskUserQuestion の UI はテキスト出力の末尾に重なって表示される。
以下のルールで重要情報の可読性を確保すること:
- 説明テキストの末尾は短い要約文(1-2行)で締める — コードブロック・表・引用で終わらせない
- 要約文の後に
---(区切り線)を入れる — 視覚的にテキストと UI を分離
- AskUserQuestion は区切り線の後に呼び出す
構成:
詳細説明(コードブロック、表、根拠の引用など)
↓
短い要約文(推奨アクションを1-2行で)
↓
---(区切り線)
↓
AskUserQuestion 呼び出し
対応判断が必要な項目の場合
| # | 選択肢 | 説明 |
|---|
| 1 | A案の内容 | 推奨する対応案。1番目に配置し (Recommended) を付加 |
| 2 | B案の内容 | 代替案がある場合のみ追加 |
| 3 | Issue 化する | /anvil:create-issue を呼び出し GitHub Issue として起票する。recommendation: create_issue の項目で 推奨表示 にする (REQ-004 FNC-406) |
| 4 | 一覧に戻る | サマリー一覧を再表示し、別の項目を選択可能にする |
| 5 | このまま(対応しない) | needs_review として記録し、次の項目へ進む |
| 6 | スキップ | 選択後に別の AskUserQuestion で理由を入力させ、skipped として次の項目へ進む |
- 代替案がない場合は A案 + Issue 化する + 一覧に戻る + このまま + スキップ の5択
- evaluator が
recommendation: create_issue を付与した項目では「Issue 化する」を 1 番目に配置し (Recommended) を付加する (修正案より優先)
- 「Other」は追加質問・別案提示の入口として従来どおり扱う(スキップ理由の入力には使わない)
- 「スキップ」を選んだ場合は、選択肢提示直後に別 AskUserQuestion で理由入力を求め、得た文字列を
mark_skipped.py {session_dir} {id} "理由" に渡す
- 「Issue 化する」を選んだ場合は後述「Issue 化フロー (
/anvil:create-issue 呼び出し経路)」に従う
- 「このまま」と「スキップ」と「Issue 化する」は findings_state.yaml 上のセマンティクスが異なる(
needs_review は後続レビューで再判断 / skipped は理由付きで却下 / Issue 化は recommendation: create_issue + status: skipped + skip_reason: "Issue 化済み: #<番号>")
情報確認のみの項目の場合
| # | 選択肢 | 説明 |
|---|
| 1 | 次へ | 次の項目へ進む |
| 2 | 一覧に戻る | サマリー一覧を再表示 |
| 3 | 終了 | 残り項目数を案内して提示を終了 |
✅自明マークについて
✅ マークは evaluator が判定した auto_fixable: true の項目に付与する。
present-findings は独自に ✅ を判定しない(evaluator の判定を信頼する)。
findings_state.yaml の各項目で recommendation: fix かつ auto_fixable: true の場合に ✅ を表示する。
段階的解決の提示例
情報の取得元: 以下の例の「該当コード」「なぜ問題か」「修正案」は全て review_<種別>.md(最終系)から引用する。
定常フローで target_files / reference_docs を再 Read しない。
末尾の「AI判定」は findings_state.yaml の recommendation / auto_fixable / reason から取得する。
## 🔴 問題 1/3: Actor 隔離違反
`FooViewModel` の `fetchItems()` が `@MainActor` 上で重い処理を実行しています。
### 該当箇所
`App/ViewModel/FooViewModel.swift:42-58`
```swift
@MainActor
func fetchItems() async throws {
let items = try await repository.fetchItems() // ← ここでUIスレッドがブロック
self.items = items
}
```
### なぜ問題か
UIスレッドがブロックされ、ユーザー操作が固まります。
プロジェクトのアーキテクチャルール「Actor 隔離原則」に違反しています:
> 重い処理は非UIスレッドで実行し、結果のみ @MainActor で受け取る
### 修正案
| 現在 | 修正後 |
| ------------------------------ | ---------------------------------------------- |
| `@MainActor` で直接API呼び出し | Service で処理し、結果のみ `@MainActor` で反映 |
```swift
// 修正後
func fetchItems() async throws {
let items = try await service.fetchItems() // Service で処理
await MainActor.run {
self.items = items // UIスレッドで結果のみ反映
}
}
```
Service レイヤーに処理を移し、`@MainActor` では結果反映のみとする修正を推奨します。
### AI判定
- **推奨**: 修正(recommendation: fix)
- **自動修正**: 可能 ✅(auto_fixable: true)
- **判定根拠**: Actor 隔離原則への明確な違反。一意な修正パターンが存在するため自動修正可能。
---
→ AskUserQuestion で判断を仰ぐ(「選択肢の提示方法」参照)
AI判定セクションは提示の末尾に必ず表示する。findings_state.yaml の recommendation / auto_fixable / reason / priority を
そのまま転記する(日本語訳: fix → 修正 / create_issue → 📌 Issue 化 / skip → 却下 / needs_review → 要確認)。
ユーザーが「AI がなぜそう判定したか」を確認でき、必要なら Other で追加質問できる。
recommendation: create_issue の項目は FNC-406 3 条件成立根拠 (該当規定なし / 再発性または客観性 / 明文化可能粒度) を AI判定セクションに含めて表示し、ユーザーが「Issue 化する」を選んだ場合の /anvil:create-issue 本文下書きと整合させる。
対話3原則
- 不明確なことは勝手に決めない → AskUserQuestion を使用して確認する
- 問題や提案の比較・経緯を丁寧に説明する
- 全ての問題を一度に提示しない → 段階的に説明して判断を仰ぐ
提示数制限
| 条件 | 上限 | 超過時の対応 |
|---|
has_severity — 🔴致命的 | 10件 | 超過分は次回レビューへ |
has_severity — 🟡品質 | 10件 | 超過分は次回レビューへ |
has_severity — 🟢改善 | 5件 | 超過分は省略 |
| 重大度なし | 20件 | AskUserQuestion を使用して「残りN件あります。続けますか?」と確認する |
項目は全件保存される(カットしない)。
上限を超える場合は、提示時に超過分の案内をする。
エラーハンドリング
| エラー | 対応 |
|---|
| ファイルが存在しない | 「ファイルが見つかりません: {path}」と表示して終了 |
| YAML frontmatter パースエラー | 「ファイルの形式が不正です」と表示して終了 |
| frontmatter の必須フィールド不足 | 不足フィールドを報告し、利用可能な情報で続行 |
| 項目が0件 | 「提示する項目がありません」と表示して終了 |
| コンテキストに項目が見つからない | 「提示する項目が見つかりません」と表示して終了 |
| fixer がエラーを返した | エラー内容をユーザーに報告し、手動対応するか確認(AskUserQuestion) |