本文へ移動
cccskills
無料GitHub で公開

codex-refs

Search academic references for a manuscript and export them to a verified refs CSV, then optionally hand off to cec-sheet. Uses Codex CLI as the search driver, but Codex calls a bundled deterministic PubMed tool (scripts/pubmed_search.py) instead of writing its own E-utilities code — bibliography is fetched live from PubMed so PMIDs are never fabricated. Triggers: "論文検索", "文献検索", "文脈検索", "コンテキスト検索", "原稿の引用を探して", "refs.csv", "codex-refs", "/codex-refs".

インストール方法を見る

含まれるファイル(5)

  • SKILL.md12.7 KB
  • .gitignore52 B
  • LICENSE1.0 KB
  • README.md3.9 KB
  • scripts/pubmed_search.py16.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Codex Ref Finder (v2 — bundled PubMed tool)

これは何か

原稿の主張(claim)を裏付ける学術文献を検索し、検証済みの書誌つき CSV (refs.csv) を作るスキル。 検索の駆動は従来どおり Codex CLI に任せるが、Codex には PubMed を叩く Python を書かせない。 代わりにバンドル済みの決定論的ツール scripts/pubmed_search.py を呼ばせる。

v1 からの変更点(なぜ変えたか)

v1 は「PubMed E-utilities を叩く Python を Codex に毎回ゼロから書かせる」設計で、以下の壁に繰り返しぶつかってトークンを浪費していた:

壁v2 での解消
PowerShell の here-string / パイプ実行の構文崩壊スクリプトを呼ぶだけ。引数は単純文字列のみ
パスのスペース・日本語で is not recognized配管をスクリプトに固定。--cd で作業 dir へ
NCBI 429 レート制限・タイムアウトスクリプトが API キー+指数バックオフ+件数事前確認
XML パース失敗・書誌の取りこぼしテスト済みの efetch 解析を流用
PMID 捏造(記憶で書いて別論文を挿入する重大事故)PMID は esearch/efetch の実取得のみ。Codex は書かない
毎回スクリプトを書き直す reasoning コストゼロ(呼ぶだけ)
stdin 無限ハング(Reading additional input from stdin... で固まる)起動コマンドに < /dev/null 必須(モード B 参照)
過剰反復(定番文献で 20+ 回検索し収束せず焼ける)定番はモード A で狙い撃ち/探索は「3〜4 回で収束」と明示
--full-auto 非推奨警告フラグから外す(--sandbox danger-full-access のみ)

核心ルール(3つ):

  1. モードを分ける。 定番・既知文献は Claude が狙い撃ち(モード A)。探索的な問いだけ Codex に agentic に回す(モード B)。定番を agentic に流すと過剰反復で焼ける。
  2. Codex は agentic な検索者のまま(モード B)。 トピックを渡せば Codex 自身がクエリを設計・反復・選別する。ただし「3〜4 回で収束・完璧を追うな」でしつける。
  3. Codex に PubMed E-utilities のコードを書かせない。 検索の"計器"は必ず pubmed_search.py。agentic 反復をしても plumbing で flailing しない。

バンドルツール scripts/pubmed_search.py

絶対パス(Codex に渡すときは必ずダブルクオートで囲む。スペースを含む):

Claude Code がこのスキルを ~/.claude/skills/codex-refs/ にインストールした場合、 パスは自動で解決される。手動で指定する場合は <SKILL_DIR> を実際のパスに置き換える:

"<SKILL_DIR>\scripts\pubmed_search.py"

使い方:

# 候補を覗く(CSV 書込なし。stdout に relevance 上位の一覧)
python "<上記パス>" --query "migraine global burden GBD prevalence disability" --retmax 6 --peek

# 選んだ PMID を確定して一時 CSV に追記(書誌は実取得・捏造ゼロ)
python "<上記パス>" --keep 38493795,28919117 \
    --note "38493795=GBD2021 神経疾患の世界負荷の基準; 28919117=GBD2016 有病率/YLDの定番" \
    --out _tmp_refs_01.csv

# ワンショット(query の relevance 上位 retmax 件をそのまま書込)
python "<上記パス>" --query "..." --out _tmp_refs_01.csv --note "テーマの一言"

# 接続・APIキーの自己診断
python "<上記パス>" --selftest
  • 終了コード: 0=成功 / 3=ヒット0件(→検索語を広げる)/ 4=接続・キー異常 / 1=その他。
  • CSV 列: PubMed_ID, Author, Year, Title, Journal, Volume, Issue, Pages, doi, abstract, whats_interesting1..5。
  • DOI/PMID 重複は自動排除。既存 PMID には次の空き whats_interesting 列へ理由を追記。

API キー(任意・強く推奨)

.secrets/ncbi_api_key.txt にキー1行を置くと 3→~9 req/s になり 429 がほぼ消える(.secrets/ は .gitignore 済み、共有・公開しても漏れない)。無くても動作する。 環境変数 NCBI_API_KEY でも可(こちらが優先)。取得は無料・即時(NCBI account → API Key Management)。


ワークフロー

0. まずモードを見極める(最重要)

文献を 2 種類に分け、取りに行き方を変える。実測で、定番文献を agentic 探索させると codex が「完璧な言い回し」を追って 20 回以上反復し収束せず大量トークンを焼く一方、狙い撃ちは一発で当たる。

種類例モード担当
定番・既知(欲しい物が分かっている)GBD 頭痛負荷 / Felitti オリジナル ACE / Hughes メタ / HIT-6・K6・ACE 尺度の妥当性 / Hazumi 2025A. 狙い撃ちClaude 本体が計器を直接叩く(codex 不要・低トークン)
探索的(どの論文かは未知)「X と Y の関連を縦断で見た論文」「この知見の最新の観察研究」B. agentic 探索Codex に自由に検索させる

モード A: 狙い撃ち取得(Claude が計器を直接叩く)

定番文献は反復不要。Claude が pubmed_search.py を直接呼ぶ:

# 候補を見る(abstract つき)
python "<SKILL_DIR>\scripts\pubmed_search.py" --query "<狙ったクエリ>" --retmax 6 --peek
# 確定
python "<SKILL_DIR>\scripts\pubmed_search.py" --keep <PMID,...> --note "PMID=理由" --out refs_YYYYMMDD.csv

これは codex を使わないので最速・最小トークン。定番はこれで十分。

モード B: agentic 探索(Codex に委ねる)

探索的な問いだけ Codex に回す。手順は縛らないが、収束はしつける(さもないと過剰反復で焼ける)。

0-1. Codex 認証プリフライト(起動前に必ず。v1 は失効に気づかず全滅した):

codex exec --model gpt-5.4-mini --sandbox danger-full-access --skip-git-repo-check --cd "<project>" "PRINT_OK とだけ出力して終了して。" < /dev/null

401 / token_invalidated / refresh_token_reused が出たら ! codex login を依頼し、成功後に起動。

0-2. Codex 起動コマンドの定型(★ 過去に踏んだ罠を回避):

timeout 220 codex exec --model gpt-5.4-mini --sandbox danger-full-access --skip-git-repo-check \
  --cd "<project>" "<下のプロンプト>" < /dev/null 2>&1
  • < /dev/null 必須: 付けないと codex が「Reading additional input from stdin...」で無限ハングする(特に run_in_background 時)。
  • --full-auto は使わない(非推奨。--sandbox danger-full-access だけでよい)。
  • | tail でパイプしない: 終了まで出力が見えずライブ監視不能になる。出力はファイルへ。
  • timeout を付ける: 過剰反復の保険。
  • 1 トピック = 1 Codex、各自 _tmp_refs_NN.csv のみに書く(共有 CSV 並列書込はデータ消失)。

0-3. プロンプト雛形(agentic な裁量 + 収束のしつけ):

あなたは PubMed 検索エージェントです。下のトピックに最適な引用文献を見つけて確定してください。

## トピック(裏付けたい主張)
「<自然文の主張・トピック>」

## 使える唯一の検索計器(PubMed を叩くコードは自分で書かない)
  python "<SKILL_DIR>\scripts\pubmed_search.py" --query "<任意>" --peek
  python "<SKILL_DIR>\scripts\pubmed_search.py" --keep <PMID,...> --note "PMID=理由" --out _tmp_refs_<NN>.csv

## 進め方(裁量で反復するが、必ず収束させる)
- まず広めのクエリで --peek。abstract を読んで relevance を判断。
- ずれていれば同義語・MeSH・AND/OR・年指定を変えて再 --peek。
- ★ 収束ルール: **検索は多くても 3〜4 回まで**。完璧な言い回しを追わない。
  代表的で妥当な 1〜数件が見つかったら、それ以上探さず即 --keep で確定する。
- 良い候補が無い/0件(exit 3)が続くなら、最善の候補を確定して理由に「代表的」と記す。
保存先 _tmp_refs_<NN>.csv のみ。確定したら PMID と理由を 1 行で報告。

設計の要点: 探索戦略は Codex に委ねて間口を保ちつつ、「3〜4 回で収束・完璧を追うな」で過剰反復を防ぐ。 定番をここに流さない(モード A で狙い撃つ)。

3. マージ(Claude 本体が順次実行)

全バッチ完了後、_tmp_refs_*.csv をメイン refs_YYYYMMDD.csv に DOI 重複チェックしながら統合し、一時ファイルを削除する。下のテンプレート参照。

4. PMID 空欄チェック(★ハルシネーション最終防壁)

マージ後、メイン CSV の PubMed_ID 列に空欄が無いか検査。空欄があれば検索/マージ漏れなので補完してから次工程へ。 (pubmed_search.py 経由なら PMID は必ず実在するが、手動編集が混じった場合に備える。)


文献選定の品質ルール

pubmed_search.py が書誌の正確性を保証するので、Codex/Claude は 意味的な選別に集中する:

  • 件数を指定しない。「代表的・定番」で品質を指定する(枠埋めの粗悪論文を避ける)。
  • エビデンスレベルは文脈から判断: 広く認知された事実→メタアナリシス/システマティックレビュー、治療効果→RCT、稀な知見→観察研究も可。
  • abstract のメインテーマが主張と合致するかを必ず確認。キーワードがかすっているだけの論文は除外。
  • whats_interesting1 に「なぜこの論文を選んだか」を必ず書かせる(選定理由=後の検証材料)。

マージ処理テンプレート(Claude 本体が実行)

import csv, glob, os
MAIN_CSV = 'refs_YYYYMMDD.csv'
TMP_PATTERN = '_tmp_refs_*.csv'
FIELDNAMES = ['PubMed_ID','Author','Year','Title','Journal','Volume','Issue','Pages','doi','abstract',
              'whats_interesting1','whats_interesting2','whats_interesting3','whats_interesting4','whats_interesting5']
WI = FIELDNAMES[10:]
rows, doi_index, pmid_index = [], {}, {}
if os.path.exists(MAIN_CSV):
    with open(MAIN_CSV, encoding='utf-8-sig') as f:
        for r in csv.DictReader(f):
            rows.append(r)
            if r.get('doi'): doi_index[r['doi'].lower()] = len(rows)-1
            if r.get('PubMed_ID'): pmid_index[r['PubMed_ID']] = len(rows)-1
for tmp in sorted(glob.glob(TMP_PATTERN)):
    with open(tmp, encoding='utf-8-sig') as f:
        for nr in csv.DictReader(f):
            doi = (nr.get('doi') or '').lower(); pmid = nr.get('PubMed_ID','')
            idx = doi_index.get(doi) if doi else None
            if idx is None and pmid: idx = pmid_index.get(pmid)
            if idx is not None:
                ex = rows[idx]
                for c in WI:
                    if not ex.get(c):
                        ex[c] = nr.get('whats_interesting1',''); break
            else:
                rows.append(nr)
                if doi: doi_index[doi] = len(rows)-1
                if pmid: pmid_index[pmid] = len(rows)-1
with open(MAIN_CSV, 'w', encoding='utf-8', newline='') as f:
    w = csv.DictWriter(f, fieldnames=FIELDNAMES); w.writeheader()
    for r in rows: w.writerow({k: r.get(k,'') for k in FIELDNAMES})
for tmp in glob.glob(TMP_PATTERN): os.remove(tmp)
empty = [r for r in rows if not (r.get('PubMed_ID') or '').strip()]
print(f'Total {len(rows)} entries. Empty PMID: {len(empty)}')

CEC 表が欲しいとき

本スキルの責務は refs.csv の作成まで。主張↔根拠↔引用の対応表は cec-sheet スキルに渡す (PubMed efetch から書誌を再取得して HTML 表を生成し、捏造を防ぐ)。


直書き禁止の徹底

  • API キーを SKILL.md・スクリプト・CSV・ドキュメントに直書きしない。必ず .secrets/(gitignore 済)か環境変数。
  • PMID を記憶・推測で書かない。必ず pubmed_search.py の出力から取る。

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

このスキルの問題を報告する