Tsukutta

🔭Claude Codeに毎朝GitHubを巡回させる

― 有用なOSSとスキルを自動採点

Lily2026年6月21日公開
About 25 min read6

前回「Claude Codeを無人で自律改善させる」で朝ブリーフの骨格を作った。今回はそのブリーフ生成ジョブに相乗りさせる形で、GitHubを毎朝巡回して有用なOSSとスキルを自動採点し、要対応だけをデスクトップのブリーフに差し込む github-scout.sh を作った話を書く。

設計のポイントは3つ。クロール(gh、無料)と採点(claude -p、MAX枠)を分けてコストを固定化すること。スキルの安全性をファイル構成で機械判定して自動有効化か要レビュー隔離かに振り分けること。採点失敗時は seen 台帳に刻まず翌朝の自動リトライに委ねること。

困りごと:GitHubを「偶然に依存して」眺めていた

Claude Codeスキルはコミュニティ製のものもGitHubに転がっている。就活トラッカーやChrome拡張の参考OSSも同様だ。ところが「気が向いたとき手動サーチ」では再現性がない。毎回同じ人気リポしか引っかからず、先週見つけたOSSを翌週には忘れる。

有用なスキルを見逃したまま自分で同じものを書き直す、という無駄がどれだけあったか数えられない。朝ブリーフが来る仕組みはすでにある。そこにGitHubの発見を自動で差し込むのが最短の解決だった。

全体設計:4ステージのパイプライン

スクリプトのヘッダコメントにそのまま書いてある。

text
# CRAWL(gh=無料) -> DEDUP(shell) -> ENRICH(gh contents) -> SCORE+ROUTE+INGEST(claude -p=MAX枠) -> LEDGER

コストが発生するのは採点フェーズだけ。候補数に上限を設けることで採点のトークン消費を固定化している。

text
MAX_CANDIDATES=30   # claude へ渡す上位件数(コスト固定化)
MAX_ENRICH=8        # SKILL.md 中身を取りに行く skill 候補の上限
TIMEOUT_SEC=600

出力先は ~/.claude/ 配下のみ。Vault(iCloud同期・TCC保護領域)には直接書かない。これはautopilotと同じ「保護外の自分のツール置き場だけ触る」設計原則で、スクリプトのコメントにも明記されている。

text
# 出力:
#   - ブリーフ差し込み用: ~/.claude/logs/github-scout-latest.md
#   - 安全スキル(md のみ/read系権限): ~/.claude/skills/auto/<name>/ に自動有効化
#   - Bash/script 同梱スキル: ~/.claude/skills/auto/.incoming/<name>/ に隔離 + 要対応フラグ
#   - OSS/トレンド候補: ~/.claude/scout/watchlist.md に追記(clone しない)

1. CRAWL:3レーンで網を張る

gh search repos を用途別3レーンで走らせる。レーンは後段の振り分けにそのまま使う。

text
# A: Claude Code スキル/プラグイン(本丸)
run_search skill "A1 topic:claude-code"   --topic claude-code --sort stars --order desc --limit 25
run_search skill "A2 claude code skills"  "claude code skills" --sort stars --order desc --limit 20
run_search skill "A3 claude agent skill"  "claude agent skill SKILL.md" --sort updated --order desc --limit 20

# B: 自分の開発に直結する OSS(autofill / maps scraper / turso / resume builder / job tracker 等)
run_search project "B1 chrome autofill"     "autofill" --language JavaScript --stars ">100" --sort stars --order desc --limit 12
run_search project "B3 turso"               "turso" --language TypeScript --stars ">50" --sort updated --order desc --limit 12
run_search project "B5 job tracker"         "job tracker" --stars ">30" --sort stars --order desc --limit 12
# ... B2/B4/B6/B7 略

# C: トレンド横断
run_search trend "C1 breakout 90d"  --created ">$D90" --stars ">800"  --sort stars   --order desc --limit 20
run_search trend "C2 hot now"       --updated ">$D3"  --stars ">3000" --sort updated --order desc --limit 20

各 run_search は失敗してもカバレッジに ❌ を記録して続行する。全ソース成功しなくても止まらない。レート制限対策で各検索後に sleep 1 を入れている(GitHub Search API: 30 req/min)。

B系クエリは最初、全語ANDで複数トピックを重ねていたが0件が続出した。コメントに v2: 2026-06-10実測調整 とある通り、コア語+言語+stars下限に緩めた。複合クエリは試して調整するしかない。

run_search 関数はレーンラベルを付けた JSONL を raw.jsonl に追記する構造になっている。

text
run_search() {
  local lane="$1"; shift; local label="$1"; shift
  local json
  json=$("$GH" search repos "$@" --json fullName,description,stargazersCount,url,language,pushedAt,createdAt 2>>"$LOG")
  if [ -n "$json" ] && echo "$json" | "$JQ" -e 'type=="array"' >/dev/null 2>&1; then
    n=$(echo "$json" | "$JQ" 'length')
    echo "$json" | "$JQ" -c --arg lane "$lane" '.[] | {lane:$lane, name:.fullName, ...}' >> "$RAW"
    COVERAGE="${COVERAGE}\n| ${label} | ✅ | ${n} |"
  else
    COVERAGE="${COVERAGE}\n| ${label} | ❌ | 0 |"
  fi
  sleep 1
}

2. DEDUP:seen台帳と既存スキルで除外

~/.claude/scout/seen.tsv(リポfullName・採点日)で過去採点済みを弾き、auto/ 配下の既存スキル名でも弱い重複検出をかける。

text
SEEN_SET="$WORK/seen.set"
cut -f1 "$SEEN" 2>/dev/null | sort -u > "$SEEN_SET"

"$JQ" -s -c 'unique_by(.name) | sort_by(-.stars) | .[]' "$RAW" 2>/dev/null \
  | while IFS= read -r line; do
      nm=$(echo "$line" | "$JQ" -r '.name')
      grep -qxF "$nm" "$SEEN_SET" 2>/dev/null && continue
      echo "$line" >> "$FRESH"
    done

uniqueby(.name) | sortby(-.stars) で重複除去→stars降順に並べてから差分を取る。上位 MAX_CANDIDATES=30 に絞った時点でコスト上限が確定する。

3. ENRICH:skill候補だけSKILL.mdを取りに行く

採点の質を上げるため、lane=skill の上位 MAX_ENRICH=8 件だけルートの SKILL.md 中身とファイル一覧を取得する。それ以外はメタ情報(stars/desc/言語)だけで採点させる。

text
if [ "$lane" = "skill" ] && [ "$enr_count" -lt "$MAX_ENRICH" ]; then
  enr_count=$((enr_count+1))
  skillmd=$("$GH" api "repos/$name/contents/SKILL.md" \
    --jq '.content' 2>/dev/null | base64 -d 2>/dev/null | head -c 6000)
  files=$("$GH" api "repos/$name/contents" \
    --jq '.[].name' 2>/dev/null | tr '\n' ',' | head -c 500)
  if echo "$files" | grep -qiE '\.sh,|\.js,|hooks,|scripts,|install|setup\.'; then
    has_script="true"
  fi
fi

ここで立てた has_script フラグが後段の安全判定に使われる。

4. SCORE + ROUTE:claude -pに採点と実ファイル操作を委ねる

ENRICHedな候補をまとめてプロンプトに渡し、★1-5採点とファイル操作を同時に指示する。振り分けは3経路。

A) スキルの自動取り込み(lane=skill、skillmdあり、★4以上)

プロンプトの安全判定ルールがそのままロジックになっている。

text
安全判定:
  - allowed-tools が無い、または Read/Grep/Glob/WebFetch/WebSearch のみ → 【安全】
  - has_script=true、または allowed-tools に Bash/Write/Edit 等が含まれる → 【要レビュー】
- 【安全】: ~/.claude/skills/auto/<kebab名>/ を作り SKILL.md を書く。
            先頭に provenance を必ず足す:
            <!-- source: github-scout | repo: <name> | url: <url> | adopted: DATE -->
            既存の auto/ に同名/酷似スキルがあればスキップ(重複禁止)。fork/mirror は取り込まない。
- 【要レビュー】: .incoming/<kebab名>/ に SKILL.md を置く(有効化しない)。
                  ブリーフの『要対応』に列挙。
auto/ に書かれたスキルは次回セッション起動時に自動で有効化される。.incoming/ は有効化されない。有効化したければ /scout-promote &lt;name&gt; でレビュー後に昇格させる設計になっている。スクリプト同梱(has_script=true)は必ず人間がレビューする。

B) OSS/プロジェクト候補(lane=project または skill で skillmd空、★4以上)

~/.claude/scout/watchlist.md に追記するだけ。形式は - [name](url) ★N — 30字以内の理由(DATE)。clone はしない。

C) トレンド(lane=trend、★3以上)

ブリーフに1行乗せるだけ。ファイル操作なし。

ブリーフに差し込まれるセクションはこの構成になる。

text
## 🔭 GitHub Scout (2026-06-17)
### ⚡ 自動で入れたスキル
- foo-skill ★4 — 何ができるか1行(有効化済み)
### ⚠️ 要対応(あなたの確認待ち)
- bar-skill ★5 — 何ができるか / なぜ要レビューか / 有効化するなら: /scout-promote bar-skill
### 💡 採用候補(OSS) — watchlist追記済み
- baz-repo ★4 — 刺さりどころ1行
### 📈 トレンド横断
- trending-repo ★3 — 何が新しいか1行

5. 朝ブリーフへの相乗り

scoutは独立したlaunchdジョブにしていない。vault-auto-ingest.sh のブリーフ生成(step 2.5)直後のstep 2.55として呼ばれる。

text
# vault-auto-ingest.sh 抜粋 (step 2.55)
# 2.55 GitHub Scout 巡回 → 発見/要対応をブリーフに差し込む。scout 本体は ~/.claude(保護外)のみ書く。
run_to 900 bash "$HOME/.claude/scripts/github-scout.sh" >> "$LOG" 2>&1 \
  || echo "[$(ts)] github-scout 失敗(継続)" >> "$LOG"

SCOUT_SEC="$HOME/.claude/logs/github-scout-latest.md"
BRIEF_SRC="$VAULT/wiki/today-brief.md"
if [ -s "$SCOUT_SEC" ] && [ "$SCOUT_SEC" -nt "$START_STAMP" ] && \
   [ -s "$BRIEF_SRC" ] && [ "$BRIEF_SRC" -nt "$START_STAMP" ]; then
  { echo ""; echo "---"; echo ""; cat "$SCOUT_SEC"; } >> "$BRIEF_SRC"
fi

失敗しても || echo "...(継続)" で次のステップに進む。ブリーフ生成全体を止めない。

鮮度チェック(-nt "$STARTSTAMP")で古いファイルが誤追記されるのを防いでいる。vault-auto-ingest.sh は 4:55 / 8:15 / 10:15 / 12:15 の複数スロットで発火するが、成功後は DONEMARKER で以降のスロットを即returnするため、scout含めブリーフ全体が一度成功すれば重複実行されない。

6. 失敗耐性:seen台帳に刻まないことがリトライの鍵

採点フェーズで何が起きるかを制御しているのが SCOREOK フラグと SESSIONLIMIT 検出。

text
# Claude MAX 枠切れ(session limit)を専用検出
SESSION_LIMIT=0
if [ "$rc" -ne 0 ] && grep -qi "session limit" "$RUN_OUT" 2>/dev/null; then
  SESSION_LIMIT=1
  # リセットまで <=8 分なら待って1回リトライ
  if [ "$wait_s" -ge 0 ] && [ "$wait_s" -le 480 ]; then
    log "session limit — ${wait_s}s 待って1回リトライ (reset=${RESET_STR:-?})"
    sleep "$wait_s"
    run_claude; rc=$?
  else
    log "session limit — reset 遠い/不明(${wait_s}s)。待たず seen 非commit で次回巡回に委ねる"
  fi
fi

採点結果の中身が薄い(- 行が1件未満)場合は SCORE_OK=0 にして3段フォールバックに入る。

text
CONTENT_LINES=$(grep -cE '^- ' "$OUT" 2>/dev/null); CONTENT_LINES=${CONTENT_LINES:-0}
# ※ grep -c は0件でも「0」を出力し exit 1 → `|| echo 0` は 0\n0 を生んで整数比較を壊す footgun。付けない。
if [ "${CONTENT_LINES:-0}" -lt 1 ]; then
  SCORE_OK=0
  # 1段目: 今回クロールの TOP 候補(生データ)を jq で整形
  FB_LIST=$(head -n 8 "$TOP" | "$JQ" -r \
    '"- [\(.name // "?")](\(.url // "")) \((.stars // 0))⭐ [\(.lane // "?")] — \((.desc // "")[0:60])"')
  # 2段目: TOP が空なら直近 watchlist の ★4/★5 を引く
  # 3段目: それでも空なら明示的な失敗行(無言の空欄にしない)
fi

最後のLEDGERステップで、採点成功時だけ seen.tsv に刻む。

text
if [ "${SCORE_OK:-1}" -eq 1 ]; then
  while IFS= read -r line; do
    nm=$(echo "$line" | "$JQ" -r '.name')
    printf '%s\t%s\n' "$nm" "$TODAY" >> "$SEEN"
  done < "$TOP"
else
  log "採点未成立(SCORE_OK=0) — seen 非commit。次回巡回で再採点させる。"
fi

SESSIONLIMITでリセットが遠い場合も SCOREOK=0 と同じく非commit。翌朝の巡回で同じ候補が自動的に再浮上する。

カバレッジ表:漏れを明記する

ブリーフ末尾の &lt;details&gt; にソース別のカバレッジ表が付く。

text
<details><summary>scout カバレッジ</summary>

| ソース | 状態 | 件数 |
|---|---|---|
| A1 topic:claude-code | ✅ | 25 |
| B1 chrome autofill | ✅ | 12 |
| C1 breakout 90d | ❌ | 0 |
...

_raw 234 → fresh 41 → 採点 30。seen累計 187。_
</details>

CLAUDE.mdの「Multi-source Coverage」方針に従い、失敗したソースを隠さず ❌ で出す。「採点 30」が MAX_CANDIDATES のcapが効いている証拠で、コストが固定されていることが読み取れる。

踏んだ落とし穴

  • 全語AND検索でヒット0件 → コア語+言語+stars下限に緩める。B系クエリは実測で調整した(コメントに v2: 2026-06-10実測調整 とある)
  • launchdの最小PATHで gh/jq が見つからない → autopilotと同じ対策: nvmのnode・Homebrew・localのbinを全部エクスポート
  • gh search に --created/--updated をクエリ文字列に混ぜると無視される → gh search repos のネイティブフラグ --created "&gt;$D90" として渡す(C系はこれが必要)
  • claude OUTが35バイトのヘッダのみで -s チェックを通り抜ける → grep -cE '^- ' で候補行数を判定するようにした(2026-06-11の明け方ソケット死後に実装)
  • grep -c が0件で exit 1 を返す → || echo 0 を付けると 0\n0 になって整数比較が壊れる。スクリプトのコメントに "footgun。付けない" と明記してある
  • スクリプト同梱スキルをフロントマターだけ見ると安全に見える → ファイル一覧の has_script フラグで補完
  • SESSION_LIMITをエラーと同扱いにすると🚨が誤報知される → 専用フラグで分岐し「枠待ち」と「採点バグ」を別メッセージにした

まとめ

  • クロール(gh、無料)と採点(claude -p、MAX枠)を分離し、MAX_CANDIDATES=30 でコストを固定化する
  • 3レーン(Aスキル/BOSS/Cトレンド)×複数クエリで網を張り、seen.tsv で翌日の再浮上を防ぐ
  • スクリプト同梱スキルは .incoming/ に隔離、Read系権限のみのものだけ auto/ に即有効化する
  • launchdのジョブとして独立させず、朝ブリーフ生成の step 2.55として相乗り させる
  • 採点失敗時は3段フォールバック+seen非コミットで翌朝に自動リトライ
  • カバレッジ表で漏れを隠さない

このシリーズで作ってきたパーツ(4層記憶 / 自己増殖スキル / コンテキスト削減 / launchd / autopilot / 長期記憶)がここで一つながりになった。GitHubの発見がブリーフに乗り、良いスキルは翌日から自動で使えるようになる。環境が「読んで育つ」ループが一本つながっている。

次回は、この毎朝積み上がるスキルとブリーフが増えるにつれてコンテキスト注入が肥大化した話 ―― **228KBから48KBに削ったコンテキスト監査**を書いた。

この記事が良かったら

「チップをリクエスト」で著者にチップの受け取り設定をお願いできます

シェア