kookr-oss-issue-scout
外部リポジトリの最適な貢献機会を見つけることができます。問題を明確さ、規模、受け入れの可能性、競争状況、マッチ度、ローカル再現性といった複数の指標でスコアリングし、最適な貢献対象を提示します。
description の原文を見る
Find the best contribution opportunity in an external repository — scores issues by clarity, size, acceptance likelihood, competition, match, and local reproducibility
SKILL.md 本文
OSS Issue Scout
必要なもの:
~/.claude/{org}-{repo}-recon/recon-report.mdにあるリポジトリごとのリコン([[oss-repo-recon]]で作成)と、オプションで蒸留されたパターンが${KOOKR_PLUGIN_DIR:-$HOME/git/kookr/plugin}/skills/pr-contribution-excellence/repo/{slug}.mdにあること(オプションの OSS 拡張機能の一部 —docs/hooks-setup.mdを参照)。リコンがない場合は停止し、盲目的にスカウトするのではなく、まず[[oss-repo-recon]]を実行してください。パターンファイルのみ欠落している場合は続行しますが、代替案を発明しないでください。
外部リポジトリで貢献するのに最適なイシューを見つけます。単なる「good first issue」ではなく、外部貢献者が現実的に修正してマージできるものの知的なトリアージです。
使用時機
- リコンが完了し、PR パターンが蒸留された後
- 特定のリポジトリで貢献機会を探している場合
- 前回の貢献後に再スカウトする場合
譲歩できないルール
| # | ルール | 違反例 | 正しいパターン |
|---|---|---|---|
| 1 | スカウト前にリコンレポートを読み込む | リポジトリの慣例を推測する | cat ~/.claude/{repoSlug}-recon/recon-report.md |
| 2 | 利用可能な場合は蒸留されたパターンを読み込む | PR 分析の洞察を無視する | cat ${KOOKR_PLUGIN_DIR:-$HOME/git/kookr/plugin}/skills/pr-contribution-excellence/repo/{repoSlug}.md |
| 3 | 各イシューの競合 PR を確認する | 他人が開始した作業を重複させる | リンクされた PR がないか検索した後に選択 |
| 4 | 5 つのすべての側面ですべてのを候補をスコア付けする | 直感で選ぶ | 以下のスコアリングマトリックスを使用 |
| 5 | 1 つだけでなく 3~5 つの候補を提示する | 単一の選択を強制する | 根拠とともにオプションを提供 |
| 6 | スコア付け前に Kookr 全体の OSS 試行ストアと リポジトリごとのリコン contributions.json の両方を確認する | 既に試みられたイシューを再調査する | ~/.kookr/oss-attempts.json + リポジトリごとのファイルで (repo, issueNumber) の二次インデックスをルックアップ |
| 7 | main ブランチでイシューが既に修正されていないか確認する | No-op PR を送信する | 実装前に関連キーワードで マージされた PR を検索 |
| 8 | 実装前にターゲット ファイルの最近のリファクタリング(過去 14 日以内)を確認する | バグが報告される 2 日前にマージされたリファクタリングで「小さな修正」スコープが無効になった perf バグを主張する | git log --since="14 days ago" -- <file> + そのファイルに触れた各 PR を読む |
| 9 | クレームを申し出る前に再現可能性ゲートを通す | ライブ ingress インフラが必要な Telegram webhook バグを主張する;既存のテストが反対を主張するときに「End ノードガード削除」修正を主張する | 再現可能性ゲートを参照 — テスト失敗の再現装置または既存の統合/E2E カバレッジまたは忠実な UI レンダリングまたは権威的な外部ドキュメント |
| 10 | ラベルが他のユーザーがイシューを所有していることを示す候補をハード除外する | n8n #28378 を主張します(status:in-linear + status:team-assigned ラベル付き、n8n チームは Linear GHC-7713 で所有) | 事前スカウト: ラベルベースのハード除外を参照。チーム割り当て / 進行中 / レビュー中 / wip / チーム必要を照合するラベル → スコア付け前にドロップし、コメントしない。in-linear のみはソフトフラグ(n8n 自動トリアージは広く適用 — キャリブレーション注釈を参照) |
パラメータ
- repoFullName:
owner/repo - repoSlug: 状態ディレクトリ用の URL セーフなスラッグ
- contributionFocus:
performance/bugs/any(デフォルト:any)
事前スカウト: レジストリ適格性チェック
スカウトする前に、リポジトリが AI 貢献に適格であるかどうかを確認します:
REPO="{{repoFullName}}"
RESULT=$(~/.claude/hooks/oss-registry-check "$REPO" 2>&1)
RC=$?
echo "$RESULT"
case $RC in
0) ;; # 適格 — スカウトに進む
1) # 不適格(anti-ai またはブロック)
echo "Cannot scout: repo is not eligible for AI contributions."
# ここで停止 — 不適格なリポジトリのイシューをスカウトしない
exit 0
;;
2) # 不明 — レジストリに登録されていない
echo "Repo not in registry. Would you like to run oss-repo-recon first, or proceed anyway?"
# インタラクティブな呼び出し元はオーバーライドできます;自動プレイブックは停止すべき
;;
126|127) # リゾルバー欠落
echo "WARNING: oss-registry-check not found. Proceeding without eligibility check."
;;
esac
事前スカウト: ラベルベースのハード除外
一部のリポジトリではラベルを使用して、チームまたは貢献者が既にイシューを選択したことを示します。スカウトはスコア付け 前に これらをフィルタリング しなければなりません。これらは保守者チームが発する最も強い「これに触れるな」信号だからです。これを見逃すと、このゲートが追加されて防止された正確な障害モードが生成されます:n8n-io/n8n #28378 をスカウトし、status:in-linear + status:team-assigned ラベルが付いていた場合(n8n チームは Linear GHC-7713 で所有)、それを主張してから、ユーザーがラベルを指摘した後に撤回する必要があります。
これらの信号は「割り当てなし、リンクされた PR なし」より信頼性が高いです。多くのチームは GitHub の割り当てやドラフト PR に触れずに Linear / Jira / ClickUp で内部的に作業を追跡するためです。唯一の見えるGitHub サーフェスはラベルです。
ハード除外パターン
各候補について、そのラベルを取得し、すべての ラベル名に(大文字小文字を区別しないで)次の部分文字列が含まれている場合はドロップします:
team-assigned/team_assigned/team assignedin-progress/in_progress/in progressin-review/in_review/in reviewwipassigned(unassignedの一部ではなく、それ自体)needs-team/needs_team/needs teampending-assignment/pending_assignment/pending assignment(n8n 自動トリアージは「チームが次にこれを選択する」に使用)
in-linear / in_linear はこのリストに はありません — ソフトフラグ以下の 2026-04-14 キャリブレーション注釈を参照してください。
パターン マッチは意図的に広いです。ラベル スキームはリポジトリごとに異なるためです(status:team-assigned、team-assigned、Status: Team Assigned など)。疑わしい場合は、候補をドロップして、これらのラベルなしの別の候補を探してください。
ソフトフラグパターン(警告ただし追加の根拠で許可)
これらのラベルは候補を 降格させ ますが、ハード除外ではありません。表面化した場合、候補カードにはフラグ理由を含める 必要があり、スカウトは候補をトップピックとして推奨する前に過去 30 日間の保守者アクティビティがないことを確認 しなければなりません:
in-linear/in_linear— n8n 自動トリアージはこれを ~99% のオープン イシューに「Linear で追跡」をマークするために適用します。単独でこれは「チームが所有」を意味しません;それは Linear チケットがあることを意味するだけです。(2026-04-13 の元のパッチでハード除外)すると n8n の全体のスカウト可能プールが空になりました。現在はソフトフラグです:詳細調査では、イシューがteam-assigned/in-progress/in-review/wip/needs-team—これらのいずれでも また ラベル付けされていないことを確認します — これらのいずれかが上記のルールでハード除外されます。in-linearが単独で表示される場合、候補はフェアゲームです。triage/needs-triagebacklogwaiting/waiting-for-reporter/waiting-for-replystale/inactiveblocked/blocked-by-*
実装
HARD_EXCLUDE=(
"team-assigned" "team_assigned" "team assigned"
"in-progress" "in_progress" "in progress"
"in-review" "in_review" "in review"
"wip"
"needs-team" "needs_team" "needs team"
"pending-assignment" "pending_assignment" "pending assignment"
# in-linear / in_linear は 2026-04-14 で削除されました — n8n イシューの ~99%
# 上記のソフトフラグを参照;team-assigned は依然として元の #28378 ケースを捕捉します
)
label_is_hard_excluded() {
local labels_lower="${1,,}"
for pattern in "${HARD_EXCLUDE[@]}"; do
if [[ "$labels_lower" == *"$pattern"* ]]; then
echo "$pattern"
return 0
fi
done
# `assigned` を全語として、`unassigned` の内部ではなく
if echo "$labels_lower" | grep -Eq '(^|[^a-z])assigned([^a-z]|$)'; then
echo "assigned"
return 0
fi
return 1
}
# 各候補について
ISSUE_NUM=<candidate>
CANDIDATE_LABELS=$(gh api "repos/${REPO}/issues/${ISSUE_NUM}" --jq '[.labels[].name] | join(" ")')
HIT=$(label_is_hard_excluded "$CANDIDATE_LABELS" || true)
if [ -n "$HIT" ]; then
echo "SKIP #${ISSUE_NUM}: label hard-exclude match '${HIT}' in '${CANDIDATE_LABELS}'"
# スキップ済みリスト に理由を記録;スコア付けに進まない
continue
fi
スキップの記録
スキップされた候補は出力形式の「スキップ」セクションにラベル理由が表示された状態で 表示される必要があります。これはオプションではなく、ユーザーがフィルターが余りに積極的か本当の「触るな」信号を正しく捕捉しているかを監査する方法です。
### スキップ済み(ラベルベースのハード除外)
- #28378 — ラベル: `team:nodes, status:in-linear, status:team-assigned` — 理由: team-assigned
また、~/.claude/{slug}-recon/contributions.json でスキップ済みイシューを status: avoid_forever と理由(ラベルを指す)でマークしておくと、将来のスカウト実行で再評価しません:
jq --arg num "$ISSUE_NUM" \
--arg title "$TITLE" \
--arg reason "Label hard-exclude: $HIT ($CANDIDATE_LABELS)" \
--arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'.issues[$num] = {
"number": ($num | tonumber),
"title": $title,
"status": "avoid_forever",
"reason": $reason,
"selected_at": (.issues[$num].selected_at // $now),
"updated_at": $now,
"branch": null,
"pr_number": null
}' ~/.claude/${SLUG}-recon/contributions.json > /tmp/contrib-tmp.json \
&& mv /tmp/contrib-tmp.json ~/.claude/${SLUG}-recon/contributions.json
事前スカウト: 貢献履歴を読み込む
2 つの独立した重複排除ソースはスコア付け前に相談されます。スカウトは 両方を チェック しなければなりません。
1. Kookr 全体の OSS 試行ストア(~/.kookr/oss-attempts.json)
これは Kookr によって追跡されるすべての外部 OSS 試行の権威的なクロスリポ記録です。これは (repo, issueNumber) 二次インデックスを使用するため、履歴 PR レコード(PR 番号でキー付けされ、イシュー番号ではなく)はまだそれらを発信するイシューによってスカウトをブロックします。
REPO="{{repoFullName}}"
OSS_STORE="$HOME/.kookr/oss-attempts.json"
OSS_EXCLUDED="" # (repo, issueNumber) pr_open または merged 状態の対 — 完全にスキップ
OSS_DEMOTED="" # 最新状態は closed — 警告を表示し、サイレントにスキップしない
if [ -f "${OSS_STORE}" ]; then
# このリポジトリのすべてのレコードを抽出;各一意の issueNumber について、決定を計算します。
# allow → 両方のリストに欠落
# exclude → OSS_EXCLUDED にある(state pr_open または merged のすべてのレコード)
# demote → OSS_DEMOTED にある(最新が closed;同じイシューに対して pr_open/merged がない)
OSS_EXCLUDED=$(jq -r --arg repo "$REPO" '
[ .attempts[]
| select(.repo == $repo)
| select(.issueNumber != null)
| select(.state == "pr_open" or .state == "merged")
| .issueNumber
] | unique | .[]?
' "$OSS_STORE" 2>/dev/null || true)
OSS_DEMOTED=$(jq -r --arg repo "$REPO" '
. as $root
| [ .attempts[]
| select(.repo == $repo)
| select(.issueNumber != null)
| .issueNumber
] | unique | .[] as $issue
| select(
[ $root.attempts[]
| select(.repo == $repo and .issueNumber == $issue and (.state == "pr_open" or .state == "merged"))
] | length == 0
)
| select(
[ $root.attempts[]
| select(.repo == $repo and .issueNumber == $issue and .state == "closed")
] | length > 0
)
| "\($issue)"
' "$OSS_STORE" 2>/dev/null || true)
echo "OSS store: excluding $(echo "$OSS_EXCLUDED" | wc -w) issue(s), demoting $(echo "$OSS_DEMOTED" | wc -w) closed attempt(s)"
else
echo "OSS store not found at $OSS_STORE — Kookr-wide dedup unavailable"
fi
スコアリングルール:
- イシュー番号が
OSS_EXCLUDEDに表示されている候補は 削除される必要があります(既に open またはマージされた PR があります)。 - イシュー番号が
OSS_DEMOTEDに表示されている候補は まだ表示可能ですが、候補カードに前の閉鎖理由を含める 必要があります。ストアから閉鎖コメント を逐語的に取得します:jq -r --arg repo "$REPO" --arg issue "$ISSUE_NUM" ' .attempts[] | select(.repo == $repo and .issueNumber == ($issue | tonumber) and .state == "closed") | .closing.closingComment // empty ' "$OSS_STORE" 2>/dev/null
2. リポジトリごとのリコン貢献ログ(~/.claude/{repoSlug}-recon/contributions.json)
レガシー リポジトリごとのファイル単一セッション決定を追跡する(例:「main で既に修正されている PR #X」)。Kookr 全体のストアが捕捉していない理由でそれをコンサルティングし続けます。
SLUG="{{repoSlug}}"
CONTRIB_FILE=~/.claude/${SLUG}-recon/contributions.json
if [ -f "${CONTRIB_FILE}" ]; then
# すべての以前に試みたイシュー(任意のステータス)— 重複排除
EXCLUDED_ISSUES=$(jq -r '.issues | keys[]' "${CONTRIB_FILE}" 2>/dev/null)
# トップレベル never_scout リスト — 「再びスカウトするな」とマークされたイシュー
# 耐久性のある理由を伴う(例:前回実行からのラベルベースのハード除外)。両方のソース は
# ユニオンである **必要があります**。
NEVER_SCOUT=$(jq -r '.never_scout[]?.number // empty' "${CONTRIB_FILE}" 2>/dev/null)
# また、status=avoid_forever を持つものを引き出す — same semantics as never_scout、
# リポジトリごとの記録との後方互換性のために保持。
AVOID_FOREVER=$(jq -r '[.issues[] | select(.status == "avoid_forever") | .number] | .[]' "${CONTRIB_FILE}" 2>/dev/null)
EXCLUDED_ISSUES=$(printf '%s\n' $EXCLUDED_ISSUES $NEVER_SCOUT $AVOID_FOREVER | sort -u | grep -v '^$' || true)
echo "Excluding previously attempted issues: ${EXCLUDED_ISSUES}"
else
EXCLUDED_ISSUES=""
echo "No contribution history found — all issues eligible"
fi
3. 表示した各候補について scouted イベントを発行
スコア付け後、3-5 のショートリストで提示した各候補について、scouted イベントを POST し、将来のスカウト実行がそれらを確認できるようにします:
KOOKR_URL="${KOOKR_API_BASE_URL:-http://localhost:4800}"
curl -fsS -m 2 -X POST \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg repo "$REPO" \
--argjson issueNumber "$ISSUE_NUM" \
--arg issueUrl "https://github.com/${REPO}/issues/${ISSUE_NUM}" \
'{kind: "scouted", repo: $repo, issueNumber: $issueNumber, issueUrl: $issueUrl}'
)" \
"$KOOKR_URL/api/oss-attempts/events" >/dev/null 2>&1 || true
発行失敗はサイレントに無視されます — 重複排除はベストエフォート、ブロックすることはありません。
候補をスコア付けするとき、イシュー番号が EXCLUDED_ISSUES に表示されるすべてのイシューをスキップします。スキップを報告します:
### スキップ済み(以前試行)
- #120918 — status: skipped、理由:既に main で PR #118412 によって修正されています
また、マージされた PR を検索して、main でイシューが既に修正されているか確認します:
gh api "search/issues?q=repo:${REPO}+is:pr+is:merged+{keywords}+created:>YYYY-MM-DD" \
--jq '.items[] | {number, title}'
マージされた PR がイシューを既に修正している場合、理由を含めて contributions.json でスキップしたものとしてマークします。
検索戦略
優先順位順に検索(高い優先度 = マージされる可能性が高い):
ティア 1: 明示的に歓迎される貢献
REPO="{{repoFullName}}"
# Good first issue + help wanted(割り当てなし)
gh api "repos/${REPO}/issues?labels=good+first+issue,help+wanted&state=open&per_page=20&assignee=none" \
--jq '.[] | {number, title, labels: [.labels[].name], created_at, comments}'
# Help wanted のみ(より広い)
gh api "repos/${REPO}/issues?labels=help+wanted&state=open&per_page=30&assignee=none" \
--jq '.[] | {number, title, labels: [.labels[].name], created_at, comments}'
ティア 2: パフォーマンス関連イシュー(contributionFocus = performance の場合)
# パフォーマンスイシューを検索
gh api "search/issues?q=repo:${REPO}+is:issue+is:open+label:performance,optimization,perf,slow,benchmark&per_page=20" \
--jq '.items[] | {number, title, labels: [.labels[].name], created_at, comments}'
# perf キーワード テキスト検索
gh api "search/issues?q=repo:${REPO}+is:issue+is:open+slow+OR+performance+OR+optimize+OR+benchmark&per_page=20" \
--jq '.items[] | {number, title, created_at, comments}'
ティア 3: 再現手順が最近確認されたバグ
# 再現手順を含むバグ
gh api "search/issues?q=repo:${REPO}+is:issue+is:open+label:bug+created:>$(date -d '60 days ago' +%Y-%m-%d 2>/dev/null || date -v-60d +%Y-%m-%d)&sort=reactions-+1&per_page=20" \
--jq '.items[] | {number, title, reactions: .reactions.total_count, comments, created_at}'
ティア 4: ドキュメント およびテスト ギャップ
# ドキュメント イシュー
gh api "search/issues?q=repo:${REPO}+is:issue+is:open+label:documentation,docs&per_page=10" \
--jq '.items[] | {number, title, created_at}'
# テスト カバレッジ リクエスト
gh api "search/issues?q=repo:${REPO}+is:issue+is:open+label:testing,test,coverage&per_page=10" \
--jq '.items[] | {number, title, created_at}'
ティア 5: 放棄された PR(引き継ぎ機会)
# 引き継ぎ可能な古い PR
gh api "search/issues?q=repo:${REPO}+is:pr+is:open+updated:<$(date -d '60 days ago' +%Y-%m-%d 2>/dev/null || date -v-60d +%Y-%m-%d)&per_page=10" \
--jq '.items[] | {number, title, user: .user.login, updated_at}'
スコアリング マトリックス
各候補イシューについて、6 つの側面でスコア付けします(各 1~5)。検証可能性はハード最小値です — 検証可能性 ≤ 2 の候補は、合計スコアに関係なく主張できません。
明確さ(1-5): 要件がどの程度明確か
| スコア | 意味 |
|---|---|
| 5 | 再現するための正確なステップ、期待値と実際値、明確なスコープ |
| 4 | 良い説明、曖昧さはありますが実行可能 |
| 3 | 理解可能ですが、スコープを決定するために調査が必要 |
| 2 | 曖昧で、複数の解釈が可能 |
| 1 | イシューが何であるかさえ不明確 |
規模(1-5): 予想される変更がどの程度小さいか
| スコア | 意味 |
|---|---|
| 5 | 1 つのファイル、<50 行 |
| 4 | 2-3 ファイル、<150 行 |
| 3 | 中程度 — 3-5 ファイル、<300 行 |
| 2 | 大きい — 複数ファイル、重要なロジック |
| 1 | 非常に大きい — アーキテクチャ変更、多くのファイル |
受け入れ(1-5): PR が受け入れられる可能性はどの程度高いか
| スコア | 意味 |
|---|---|
| 5 | 保守者が明示的に修正をリクエスト、イシューは事前承認済み |
| 4 | バグが保守者によって確認、明確な受け入れ基準 |
| 3 | コミュニティが報告、合理的な修正、ロードマップに合致 |
| 2 | 保守者がこの変更を望むかは不明確 |
| 1 | 論争の余地、以前拒否、またはロードマップと矛盾 |
競争(1-5): このイシューはどの程度競争がないか
| スコア | 意味 |
|---|---|
| 5 | 割り当てなし、open PR なし、最近のコメントで主張なし |
| 4 | 誰かが関心をコメント したが PR 開かれていない(>14 日前) |
| 3 | Open PR は存在しますが古い(>30 日アクティビティなし) |
| 2 | 別の貢献者からのアクティブな PR が存在 |
| 1 | 積極的に作業している人に割り当てられている |
マッチ度(1-5): 貢献者の目標にどの程度合致しているか
| スコア | 意味 |
|---|---|
| 5 | 記載されたフォーカスとの完全なマッチ(例:パフォーマンス最適化) |
| 4 | 良好なマッチ、関連するスキルが適用 |
| 3 | 接線的に関連 |
| 2 | コア興味の外ですが実行可能 |
| 1 | 貧弱なマッチ |
検証可能性(1-5): 実現を発明せずにバグを再現するか修正を証明できるか
これは AI slop 貢献をブロックする側面です。再現可能性ゲートを操作パス/失敗基準として参照してください。アンカー:
| スコア | 意味 |
|---|---|
| 5 | 現在のチェックアウト上の単位テストを数秒で再現するバグを書くことができます。修正を実行して再実行することで修正を証明します。外部サービスなし、flaky インフラなし。 |
| 4 | 既存の統合/E2E テストが既にバグのあるコード パスを実行し、実際の動作をカバーします(修正されている依存関係のモックのみではなく)、ローカルで実行できます。または:引用された権威的な外部ドキュメント + 更新できる既存契約テストを持つ狭いバックエンド契約変更。 |
| 3 | 忠実な UI 再現は実際の dev サーバー / Storybook / Playwright 実行で可能 — 前のデザインをシミュレートするモックアップではなく。Before/after スクリーンショットを生成できます。 |
| 2 | 理論的再現のみ(コード読み取り、grep、ドキュメント)— 実行できません。バーの下。主張しないでください。 |
| 1 | 外部インフラが必要(ライブ webhook ingress、認証資格のあるサードパーティ API、特定のハードウェア、構築していないコンテナ スタック)。バーの下。主張しないでください。 |
優先度スコア
priority = clarity + size + acceptance + competition + match + verifiability
| 範囲 | 意味 |
|---|---|
| 24-30 | 優れた機会 — 直ちに追求 |
| 20-23 | 良い機会 — 強い候補(クレーム可能な最小合計) |
| 15-19 | バーの下 — verifiability ≥ 4 で他に利用可能なものがない場合のみ追求 |
| <15 | スキップ — リスク過度または目標から遠い |
ハード最小: verifiability ≥ 3。検証可能性 ≤ 2 の候補は、合計スコアが 25/30 でも主張できません。トレードオフは明示的です:10 個の検証不可能な候補をスキップすることは、保守者の善意を焼失するか — より悪く — 貢献者を LLM 駆動型スパム アカウントとしてフラグを立てられるかもしれない 1 つの AI-slop PR を送信するよりも安いです。
競合チェック
優先実行パス: kookr-oss-issue-scout サブエージェントを使用 (.claude/agents/kookr-oss-issue-scout.md)。これは分離されたコンテキストで完全なスカウト フローを実行し、クレーム可能な(ドラフト クレーム コメント と ワンショット gh api コマンド付き)を返し、競合チェックを忘れたりスキップできないことを保証します。サブエージェント は クレーム自体を投稿しません — 呼び出し元は候補をレビューし、gh api コマンドを逐語的に実行します。~/.claude/hooks/claim-gate.sh の PreToolUse claim-gate フックは呼び出し元の POST で 3 つの競合クエリすべてを再実行し、2 番目の防衛線として機能し、またサブエージェントをバイパスするマニュアル クレームについても発火します。
次の内容はスカウト ワークフローを手動で実行または サブエージェント の動作を監査している誰にとっても権威的なチェックです。
⚠️ 競合チェック に /issues/{N}/timeline エンドポイントを使用しないでください
GitHub タイムライン API は時系列順にイベントをページネーションします。スタックアップされたラベル、コメント、および購読イベントを含む(~すべての古い数日のリポジトリのイシューである)任意のイシューで、後で open された PR の cross-referenced イベントはデフォルト最初ページの ~30 エントリを超えて落ち、サイレントにドロップされます。これは以前スカウトが「競合なし」を返したイシュー #26450 について重複 PR インシデントを引き起こし、PR #28048 はそれに対して 5 日間 open されていました。
以下のエンドポイントは競合目的に対して 禁止 です:
# ❌ 壊れている — サイレントに後期クロス参照イベントをドロップ
gh api "repos/${REPO}/issues/${ISSUE_NUM}/timeline" --jq '.[] | select(.event == "cross-referenced") ...'
✅ 正しい競合チェック(すべて 3 つを使用)
最終選択を確定する前に、すべて 3 つのチェックを実行します。いずれかが空でない結果を返す場合、候補をスキップします。他のスコアリング要因に対して重み付けしないでください。
ISSUE_NUM={number}
# チェック 1: gh pr list --search — イシュー番号を参照するすべての PR を返します
# タイムラインの位置に関係なく。最も信頼できる単一信号。
COMPETING_PRS=$(gh pr list -R "$REPO" --state all --search "$ISSUE_NUM" \
--json number,state,title,createdAt --limit 20 \
--jq '[.[] | select(.state == "OPEN" or (.state == "CLOSED" and (.createdAt | fromdateiso8601) > (now - 2592000)))]')
# チェック 2: GraphQL closedByPullRequestsReferences — 明示的な
# "closes #N" / "fixes #N" リンケージを捕捉し、open されたままの PR を含みます。
LINKED_PRS=$(gh api graphql -f query="query {
repository(owner: \"${REPO%/*}\", name: \"${REPO#*/}\") {
issue(number: ${ISSUE_NUM}) {
closedByPullRequestsReferences(first: 20, includeClosedPrs: true) {
nodes { number state title }
}
}
}
}" --jq '.data.repository.issue.closedByPullRequestsReferences.nodes')
# チェック 3: 割り当て — 別のユーザーに割り当てられたイシューで作業しないでください。
ASSIGNEES=$(gh api "repos/${REPO}/issues/${ISSUE_NUM}" --jq '[.assignees[].login]')
# チェック 4: 最近のコメントの soft-claims(ヒューリスティック、権威的ではない)
gh api "repos/${REPO}/issues/${ISSUE_NUM}/comments" \
--jq '.[] | select(.body | test("I.ll take|working on|I.m on it|assigned to me|taking this"; "i")) | {user: .user.login, date: .created_at, body: (.body[:100])}'
スキップ決定(すべて 1 つ → 候補をドロップ):
COMPETING_PRSが空でない → PR が存在(open または最近 closed)LINKED_PRSは open エントリを含む → closes 参照経由でリンクASSIGNEESが空でなく、jeanibarzを含まない → 他の場所に割り当て- 過去 14 日以内のいずれかの非 OP ユーザーからのソフト クレーム コメント
疑わしい場合はスキップしてください。偽陽性は数分の費用;偽陰性は保守者の善意の費用です。
ファイルレベルの重複チェック(傘イシュー用)
多くの貢献者が異なるファイルに取り組む大きな傘イシューについて、イシューレベルのチェックは不十分です。ブランチを作成する前に、修正する予定のものと同じファイルに触れる open AND closed PR もチェックしてください — 時間カットオフなし。
傘リファクタリングから 2 つのもの が issue-number-anchored クエリで逃げます:
- 「#N に関連」の代わりに「あて先 #N に関連」を使用する PR。 GraphQL
closedByPullRequestsReferencesは閉鎖キーワード(close/closed/closes/fix/fixed/fixes/resolve/resolved/resolves)のみをインデックス化します。本文で「#24494 に関連」と言った包括的な PR はその GraphQL クエリに表示されません。 - 30 日カットオフでフィルターされた古い closed PR。
COMPETING_PRSからの競合チェック は closed PR を 30 日より古いものをドロップします。chore/リファクタリング傘の場合、計画されたファイルをカバーした 6 か月前の包括的な PR はまだ強い「このスライスは話される」信号です — 特に保守者ではなく著者によって自発的に closed された場合。
# (a) イシュー固定チェック時間カットオフなし(古い closed PR をキャッチ)
gh pr list -R ${REPO} --state all --search "${ISSUE_NUM}" --json number,title,state,files,createdAt,closedAt \
--jq '.[] | {number, state, title, files: [.files[].path], createdAt, closedAt}'
# (b) ファイルパス チェック — 計画された修正のファイルごとに、すべての PR 履歴を検索
for FILE in "${PLANNED_FILES[@]}"; do
gh pr list -R ${REPO} --state all --search "${FILE}" --limit 20 \
--json number,title,state,files \
--jq --arg f "$FILE" '.[] | select(.files[]?.path == $f) | {number, state, title}'
done
# (c) パターンキーワード チェック — chore/リファクタリング傘の場合、実際のパターンを検索
# 削除/変更されている、イシュー番号のみではなく。問題のタイトルから 2-3 の特徴的なトークンを選択
# または計画された diff(例:「type ignore」、「SimpleNamespace」、
# 「match case ToolProviderType」)。
gh pr list -R ${REPO} --state all --search "${PATTERN_KEYWORDS}" --limit 30 \
--json number,title,state,body,files \
--jq '.[] | {number, state, title, files: [.files[].path]}'
決定:
- ANY open PR が計画されたファイルに触れる → 別のファイルを選択するか、イシューで調整
- ANY closed PR(年齢に関係なく)は計画されたファイルの >50% と重複し、自発的に closed(拒否ではない) → スライスをドロップし、あなたの作業は冗長です。なぜそれが closed されたかを見てください:「著者が他の作業に焦点を当てる」と「保守者が承認されたが著者が停止」の両方が「誰か既にこれを行った、再度しないでください」信号です。
- Closed PR は計画されたファイルと重複していますが、保守者が パターンを拒否したため closed されました → クリーンアップ全体はオフリミット、スライスのみではなく。候補全体をドロップします。
Chore/リファクタリング傘イシューの特別ルール(chore、refactor などのラベル、またはイシュー タイトル Chore: … / [Chore/Refactor]):
傘クリーンアップ イシューは包括的な「台所の流し」PR を引き付けます。そのような PR が存在する場合 — open または closed — とあなたの計画されたファイルをカバーしている同じパターン、スライスはクレーム。自発的に closed された包括的な PR は作業を解放しません;それは特定の著者が送付しないことを選択したことを意味するだけです。Gemini / Copilot / human レビュアーが これらのファイル間のパターンを承認したという事実は重要な信号です。
2026-04-16 langgenius/dify #24494 への インシデント:スカウトは agent-steven の closed PR #32189(26 ファイル、55 コメント、2026-02-13 に「他の貢献に焦点を当てる」と言って自発的に closed)を見逃しました。GraphQL クエリは閉鎖キーワードのみと一致し、30 日カットオフでそれをフィルター したためです。スカウトは statistic.py の 8/55 コメントを再度提案しました — agent-steven がすでにカバーしていたファイル。
再現可能性ゲート
このゲートは 2026-04-13 に 2 つの並行スカウト実行(langgenius/dify #34827 および n8n-io/n8n #28378)の両方が、元の 5 次元ルーブリックで 22+/25 をスコア付けしたが、実際には検証不可能なイシューを主張したため存在します。dify 修正は既存のテストアサーションと矛盾し、元の著者によって 90 日前に自己復帰していました;n8n 修正は live Telegram webhook インフラが必要であり、スカウトは再現を試みたことはありませんでした。ユーザーは 1 つのクレームを撤回し、他を学習機会として渋々受け入れる必要がありました。
ルールは今:再現可能性の根拠なし、クレームなし。 このゲートは競合チェックとクレーム POST の間に実行されます。システムの前のデザイン、またはあなたが開かれていない framework バージョンまたは外部サービスの未ドキュメント化された動作をシミュレートするモックは、このゲートを満たしません — テストが合格したまま現実について嘘をつきます。
パス条件(いずれか 1 つで十分 — 最初のものが優先)
-
チェックアウト上の失敗テスト再現装置。 単位/統合テストを(または識別)した現在の
upstream/<default>ブランチで実行し、イシューに一致する方法で失敗します。テスト パスを含める + 失敗したアサーションまたはエラー出力を証拠ノートに。 -
バグのあるコード パス上の既存統合/E2E テスト。 実際の統合または E2E テストは、修正が変更するコード パスを既に実行しています。grep 検証済み、テスト本体を読み、シナリオをカバー(ファイルのみではなく)ことを確認しました。バグが入っている依存関係をモック化する汎用単位テストは カウントしません — テストは実際の動作を実行する必要があります。
-
忠実な UI 再現(フロントエンド イシューのみ)。 実際の環境(Storybook、dev サーバー、Playwright)でコンポーネントを構築して実行でき、現在の間違った状態と期待される固定状態の仕様のスクリーンショットを生成できます。前のデザインをシミュレートするモック/モックアップは受け入れられません — ゲートは実際の実行コードまたは十分に忠実なレンダリングを必要とします。スクリーンショット は証拠ノートに移動します。
-
権威的な外部ドキュメント(バックエンド契約の変更のみ)。 修正は外部 API/サービスの狭い契約変更です(例:Todoist 同期 v9 → v1) AND ターゲット契約の存在する、現在の、権威的なドキュメント AND チェックアウトには古いパスを実行する既存テストが含まれます。ドキュメント は WebFetch を使って取得し、証拠ノートで引用される必要があります — training データから再構築されません。「フレームワークは X と仮定」ドキュメント引用なしではありません。
ハード失敗(すべて 1 つ = 候補をドロップ)
- 提案された修正は既存のテストアサーションと矛盾し、反対の動作を強制します。既存のアサーション を書き直すことは、クレーム・アンド・PR ではなく、保守者の署名が必要なセマンティック変更です。修正タッチの識別子のターゲット モジュールをカバーするテスト ファイルを常に grep してください。
- 再現には、あなたが所有していない外部インフラが必要です:ライブ webhook ingress(Telegram、Slack、Stripe)、認証資格のあるサードパーティ API、特定のハードウェア、構築して実行していないコンテナ スタック。
- イシューの前提は、コードの読み取りによって確認できません。例:イシューは「出力ノードは名前変更後に末端制限を保持」と言いますが、リポジトリには
BlockEnum.Outputがありません — 前提は未検証です。 - 同じ修正を試みた前のコミットが過去 90 日以内に復帰し、公開理由は存在しません。復帰された diff を読んでください;復帰は無関係なバンドル であると仮定しないでください。 復帰が修正が触れるガード/アサーション/形を削除する場合、それはセマンティック信号で、候補は終わりです。
- バグ報告は 1 スクリーンショットまたは 1 文で、再現環境がなく、スタック トレースがありません。「Clarity=5」これは楽観的です。証拠ではなく。
- 実装しようとしている、システムの前のデザイン、または開いていない framework バージョンの外部サービスの未ドキュメント化された動作をシミュレートするモックを書きます。停止 — これらのモックはテストに合格し、現実について嘘をつきます。
証拠ノート
/tmp/scout-repro-${SLUG}-${ISSUE_NUM}.md への短いノートを書きます:
# 再現可能性証拠 — {repo} #{issue}
## パス パス
{4 つのパス条件のいずれ:1 / 2 / 3 / 4}
## 証拠
- {失敗テストへのパス、または既存統合テスト パス、またはスクリーンショット パス、またはドキュメント URL + 引用}
- {実行されたコマンドとその出力、OR grep 結果、OR WebFetch 引用}
## 検証可能性スコア
{1-5 正当化付き}
## ハード失敗チェック
- 既存のテストアサーション は修正に矛盾しません:{grep 出力}
- ターゲット上の過去 90 日自己復帰なし:{git log 出力}
- イシュー前提がコードで確認:{grep 出力}
証拠ノートはスカウト戻り値で必須で、オプションではありません。claim-gate フックはクレーム POST を許可する前に存在を確認するように拡張できます。
キャリブレーション質問
すべてのクレーム前に、自分に問いかけてください:
「保守者が「このバグを確認するために実行した正確なコマンドは何か」と返信した場合、実際のコマンド + 実際の出力をペースト できますか?」
正直な答えが「いいえ、私は それについて理由」 — 候補をドロップしてください。
明確なトレードオフ
より多くのイシューをスキップして、1 つのあなたが検証できないことをクレームすることを好みます。 保守者の AI-slop 評判をリスク化することは、適切なイシューなしで 1 日を過すことより悪いです。単一の拒否-as-LLM-spam PR は、貢献者をリポジトリで永遠に ブロックリストできます;適切なイシューを見つけないで 1 日は何ですが時間をかけます。
最近のリファクタリング チェック
イシュー著者は、実行しているバージョンに対するバグ報告を書きます。major リファクタリングが upstream/<default-branch> とそのバージョン間に登録されたとき、ファイル:行ポインター、呼び出しグラフ、そして — 最も危険に — 修正が依存するデータ契約が変更される場合があります。「単に高価な計算をスキップ」提案は、recent リファクタリングが高価な計算の出力を一級保存にしたダウンストリーム消費者が今依存している場合は無効です。
すべてのショートリスト候補をクレーム前に実行このチェック:
LOCAL=$HOME/git/${REPO_NAME}
cd "$LOCAL" && git fetch upstream >/dev/null 2>&1
# イシューから可能性の高いターゲット ファイルを識別(ファイル パス、スタック トレース、エラー メッセージ)
TARGETS=(packages/foo/src/bar.ts packages/foo/src/baz.ts)
for FILE in "${TARGETS[@]}"; do
echo "=== $FILE ==="
git log --since="14 days ago" --oneline upstream/${DEFAULT} -- "$FILE" 2>/dev/null
done
最近のコミット メッセージの赤旗キーワード: refactor、rewrite、restore、replace、migrate、store … as、extract、move、split、unify。過去 14 日間のターゲット ファイルの任意の修正は、修正スコープがサイレントに変更された可能性があることを意味します。
各赤旗コミットについて、PR を読んでください:
PR_NUM=<コミット メッセージから(#NNNN)>
gh pr view $PR_NUM -R ${REPO} --json title,body,files --jq '{title, body: .body[:1500], files: [.files[].path]}'
特に確認してください:
- 永続化されたデータ形状またはストレージ キーの変更(「単に計算をスキップ」修正を中断)
- イシュー著者が削除または遅延にしようとする フィールドの新しいフロントエンド/UI コンシューマー
- 修正がターゲットする関数の署名変更
- 前の作業の復帰または再ランド(churn ゾーン)
スコアリング調整:
- ターゲット ファイル上の recent リファクタリング → 候補サイズを ≤2 に降格させ、受け入れを ≤3 に降格させ、候補カードにリスク を表示します(例:「⚠️ PR #21244 3 日前にマージされた統一パッチを一級ストレージにしました — 修正スコープはイシューが提案するより大きい可能性があります」)。
- イシュー著者の提案された修正と直接矛盾する recent リファクタリング → ショートリストから候補をドロップし、別のものを選択します。候補は既にクレームされた場合は、実装を停止し、イシューの調査をコメント し、破棄します。
Phase 4 でコードの書き込みを開始する前に、実際にタッチする予定のファイルのこのチェックを再実行(イシューが言及するだけではなく)。実装の中程でコントラクト矛盾を検出する場合は、修正を通して強制するのではなく、停止して再評価します。
出力形式
## イシュー スカウト結果:{owner}/{repo}({日付})
### 候補 1:#{number} — {title}
- **スコア**: Clarity={c} Size={s} Acceptance={a} Competition={comp} Match={m} Verifiability={v} → **合計:{sum}/30**
- **ラベル**: {labels}
- **年齢**: {作成以来の日数}
- **コメント**: {数}
- **競合 PR**: {なし / PR #{n} by @{user}(status)}
- **再現可能性**: {パス パス 1/2/3/4 または 失敗理由} — 証拠ノート `/tmp/scout-repro-{slug}-{num}.md`
- **このイシューについて**: {1-2 文の根拠}
- **推定努力**: {small/medium/large}
### 候補 2:...
### 推奨
{追求する候補と理由 — 検証可能性 ≥ 3 が必須}
選択後
イシューが選択されたら:
-
クレーム前のイシュー状態をスナップショット — 現在の
state、updated_atおよびコメント数を記録します:PRE_STATE=$(gh api "repos/${REPO}/issues/${ISSUE_NUM}" --jq '{state, updated_at, comments}') -
イシューにコメント し、割り当てをリクエストします:
COMMENT_URL=$(gh api "repos/${REPO}/issues/${ISSUE_NUM}/comments" -X POST \ -f body="I'd like to work on this. I have a fix approach in mind based on [brief description]. May I be assigned?"
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- kookr-ai
- リポジトリ
- kookr-ai/kookr
- ライセンス
- Apache-2.0
- 最終更新
- 2026/5/12
Source: https://github.com/kookr-ai/kookr / ライセンス: Apache-2.0
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。