Claude Codeで図表番号の整合を取る — 本文参照とキャプションの照合
本文の「図3」「表2」とキャプション番号のずれを、Claude Codeに書かせた小さな照合スクリプトで洗います。欠番・重複・参照先なしの出し方と、Stopフックで直し終えるまで止めない設定をまとめました。
図の挿入や削除を繰り返した原稿では、本文の「図3を参照」とキャプションの「図3」が別物になりがちです。目視の見直しでは取りこぼしが出るため、Claude Codeに照合スクリプトを書かせて機械的に洗う方法を扱います。
照合は文字列の突き合わせで足ります。モデルに「ずれていないか読んで判断して」と頼むより、スクリプトに判定を任せ、Claudeには修正とやり直しを担当させるほうが再現性が出ます。
図表番号のずれは4種類に分けられる
照合で出したいのは、次の4種類です。種類を先に決めておくと、スクリプトの出力がそのままClaudeへの修正指示になります。
| 種類 | 何が起きているか | 典型的な原因 |
|---|---|---|
| 参照先なし | 何が起きているか本文が「図3」を参照するが、キャプションに図3がない | 典型的な原因図を削除した、番号を振り直した |
| 重複 | 何が起きているか同じ番号のキャプションが複数ある | 典型的な原因図をコピーして番号を直し忘れた |
| 欠番 | 何が起きているか図1、図2、図4と飛んでいる | 典型的な原因途中の図を消した |
| 未参照 | 何が起きているかキャプションはあるが本文のどこからも参照されない | 典型的な原因本文の言及を消した |
欠番と未参照は「直すべき誤り」とは限りません。付録の図を意図的に参照しない原稿もあるため、スクリプトは4種類をすべて出し、採否は人間が決める形にします。
Claude Codeに照合スクリプトを書かせる
対象は章ごとに分けたMarkdown原稿です。Wordやpdfからの変換物は、先にテキスト化しておきます。
Claudeへの依頼文は、仕様を番号付きで渡すと迷いが減ります。
docs/ のMarkdown原稿用に check_refs.py を書いてください。
1. 行頭の「図1 」「表2-1:」をキャプションとして集める
2. それ以外の行の「図3」「表2」を本文参照として集める
3. 「図1〜3」は1,2,3に展開する
4. 参照先なし・重複・欠番・未参照を行番号つきで出す
5. 問題があれば終了コード1、なければOKと出して0
標準ライブラリだけで書き、原稿は書き換えないでください。返ってきたコードは、仕様の5点を満たしているかをサンプル原稿で確かめます。例として、わざとずれを仕込んだ原稿を用意しました。
# 第2章 売上の推移
売上は図1のとおり右肩上がりです。地域別の内訳は表1に示し、構成比の変化は図3で確認できます。
詳しくは図1〜2を参照してください。
図1 月別売上の推移
図2 地域別の売上
図2 商品別の売上
表1 地域別の売上
表3 前年との比較仕込んだずれは、図2の重複、図3の参照先なし、表3の未参照、表2の欠番の4つです。
次の check_refs.py は、この依頼文から作った照合スクリプトの一例です。キャプションの書式は「図1題名」形式を前提にしているので、原稿の書式に合わせて正規表現を直してください。
#!/usr/bin/env python3
import re, sys, collections
KIND = r"(図|表)"
NUM = r"([0-9]+(?:[-.][0-9]+)?)"
CAP = re.compile(rf"^\s*(?:\*\*)?{KIND}{NUM}[\s::.. ]")
REF = re.compile(rf"{KIND}{NUM}(?:[〜~]{NUM})?")
def main(path):
caps = collections.defaultdict(list)
refs = collections.defaultdict(list)
for no, line in enumerate(open(path, encoding="utf-8"), 1):
m = CAP.match(line)
if m:
caps[m.group(1) + m.group(2)].append(no)
continue
for r in REF.finditer(line):
k, a, b = r.groups()
if b and a.isdigit() and b.isdigit():
for n in range(int(a), int(b) + 1):
refs[f"{k}{n}"].append(no)
else:
refs[k + a].append(no)
problems = []
for k, lines in sorted(caps.items()):
if len(lines) > 1:
problems.append(f"重複: {k} のキャプションが {lines} 行目に複数あります")
for k, lines in sorted(refs.items()):
if k not in caps:
problems.append(f"参照先なし: {k} を {lines} 行目で参照していますがキャプションがありません")
for k, lines in sorted(caps.items()):
if k not in refs:
problems.append(f"未参照: {k}(キャプション {lines[0]} 行目)を本文が参照していません")
for kind in "図表":
nums = sorted({int(re.match(r"\d+", k[1:]).group()) for k in caps if k[0] == kind})
if nums and nums != list(range(1, nums[-1] + 1)):
gap = sorted(set(range(1, nums[-1] + 1)) - set(nums))
problems.append(f"欠番: {kind}{gap} が飛んでいます")
print("\n".join(problems) if problems else "OK")
return 1 if problems else 0
sys.exit(main(sys.argv[1]))Python 3.12.2で、上のサンプル原稿に対して実行した結果は次のとおりです。
python3 check_refs.py docs/sample.md重複: 図2 のキャプションが [7, 8] 行目に複数あります
参照先なし: 図3 を [3] 行目で参照していますがキャプションがありません
未参照: 表3(キャプション 10 行目)を本文が参照していません
欠番: 表[2] が飛んでいます仕込んだ4つが、行番号つきでそのまま出ています。この出力をClaudeに貼れば「図2の2つ目を図3に直す」といった修正案が返ります。ただし、どちらの図を残すかは原稿の意図に依存します。番号の振り直しは、Claudeに案を出させて人間が決める分担にしてください。
章をまたぐ番号と本文中の言及をどう扱うか
実際の原稿では、サンプルのようにきれいに揃いません。つまずきやすい箇所を挙げます。
「図2-1」型の章別番号
章番号つきの図は、正規表現の NUM が 2-1 や 2.1 を拾う形にしてあります。ただし章ごとにファイルが分かれていると、1ファイルでは「第3章の図3-2」への参照が参照先なしに見えます。ファイルを連結してから渡すか、複数ファイルを一括で集計するようにスクリプトを直させます。
「前掲の図」「次の表」のような番号なし参照
番号を伴わない参照は、正規表現では拾えません。これを検出したい場合は、Claudeに原稿を読ませる使い方が合います。「番号なしで図表を指している箇所を列挙して。直す案は出さなくてよい」と頼むと、番号つきに書き換える候補を一覧で得られます。判定は文脈依存なので、スクリプトには入れません。
本文中の「図に示すように」と「表1のとおり」の混在
「図」と「表」の区別はスクリプトが付けられます。一方で、キャプションの書式が「Figure 3」「Fig.3」と混在した原稿は、KIND に別名を足す必要があります。書式を統一する作業が先に要るか、別名を許すかは原稿ごとに決めてください。
CLAUDE.mdには検証できる形で書く
照合を毎回頼み直す手間を省くため、実行方法をCLAUDE.mdに書いておきます。CLAUDE.mdは強制設定ではなくコンテキストとして扱われます。そのため、守られたかを確認できる具体的な指示にすると効果が出やすくなります。
# 原稿の図表ルール
- 図表を追加・削除・並べ替えたら `python3 check_refs.py <ファイル>` を実行する
- 出力が OK になるまで直す。欠番と未参照は自分で消さず、報告にまとめる
- キャプションは行頭に「図1 題名」「表1 題名」の形で書くメモリのドキュメントは、1ファイル200行未満を目安にし、相反する指示を残さないよう勧めています。図表ルールが増えてきたら、パス指定のルールファイルへ移す手もあります。
ただし、CLAUDE.mdに書いても毎回実行される保証はありません。確実に走らせたいなら、次のフックを使います。
Stopフックで直し終えるまで止めない
Claude Codeのフックは、特定のタイミングで自分のスクリプトを呼ぶ仕組みです。応答を終えるときに走る Stop フックは、decision に "block" を返すとClaudeの停止を止め、reason を理由として伝えて作業を続けさせられます。
PostToolUse の Edit|Write に載せる案もあります。ただし図表の整合は、複数回の編集がすべて終わった後でないと判定できません。1か所を直した時点では参照先がまだ揃わないため、編集のたびに止めると邪魔になります。応答の終わりで1回だけ見る Stop のほうが、この用途に合います。
フックのスクリプトは次のとおりです。標準入力のJSONを jq で読み、git diff に出てきた原稿だけを照合します。
#!/bin/bash
# .claude/hooks/refs-on-stop.sh
input=$(cat)
[ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ] && exit 0
out=""
for f in $(git diff --name-only -- 'docs/*.md'); do
r=$(python3 check_refs.py "$f") || out="$out\n$f\n$r"
done
[ -z "$out" ] && exit 0
printf '{"decision":"block","reason":"図表番号の不整合が残っています。原稿を直してください。%s"}' \
"$(printf "$out" | sed 's/$/\\n/' | tr -d '\n')"1行目の stop_hook_active は、すでにフックの指示で作業を続けている最中かどうかを示す値です。true のときは何もせず終了します。これが無いと、直せない不整合がある限りブロックが続きます。ドキュメントによると、連続した継続には8回の上限もありますが、上限に頼らず自分のスクリプトでも止める作りにしておくと安全です。
設定は .claude/settings.json に置きます。
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": ".claude/hooks/refs-on-stop.sh" }
]
}
]
}
}動作は、git diff に原稿の変更がある状態で、フックに標準入力のJSONを渡して試せます。
echo '{"stop_hook_active": false}' | bash .claude/hooks/refs-on-stop.sh
echo '{"stop_hook_active": true}' | bash .claude/hooks/refs-on-stop.sh; echo "exit=$?"1行目は、先ほどの4件の指摘を含む decision: block のJSONを返しました。2行目は何も出力せず、終了コードは0でした。実際のセッションでの挙動は、不整合のある原稿を編集させて確かめてください。
提出前にコマンドラインで一括照合する
フックは編集中の安全網で、提出前の最終確認は別に持っておくと安心です。非対話モードの claude -p を使うと、スクリプトの結果をClaudeに渡して修正案だけ受け取れます。
python3 check_refs.py docs/ch2.md | claude -p \
"この指摘ごとに、原稿の該当行を見て直し方を1行で提案して。ファイルは変更しない"標準入力に渡せるデータは10MBまでです。原稿が大きい場合は、スクリプトの出力だけを渡し、原稿本体はClaudeに読ませる形にします。
--bare を付けない -p は、プロジェクトの .claude/settings.json のフックも実行します。照合だけを回したいなら、--bare を付けるとフックやCLAUDE.mdの自動読み込みを省けます。ただし、その場合はCLAUDE.mdの図表ルールも読まれない点に注意してください。
複数章の原稿をまとめて照合する
章ごとにファイルが分かれた原稿では、1ファイルずつ照合すると章をまたぐ参照が「参照先なし」に見えます。連結してから渡す方法なら、スクリプトを変えずに済みます。
cat docs/ch*.md > /tmp/all.md && python3 check_refs.py /tmp/all.md連結すると行番号は結合後のものになり、元のファイルと章がすぐには分かりません。指摘を修正に使うなら、スクリプトにファイル名と行番号を持たせる改修をClaudeに頼むのが現実的です。依頼文には「引数で複数ファイルを受け取り、行番号を ファイル名:行 の形で出す」と書きます。
もう1つの落とし穴は、コードブロックの中身です。原稿にコードや引用の例として「図1題名」と書いた行があると、キャプションとして数えられて重複扱いになります。フェンスの内側を読み飛ばす処理を足すよう、依頼文に加えてください。この種の誤検出は、サンプル原稿を作って再現させ、直したあとで同じサンプルを通し直す流れで潰せます。
この方法で拾えないもの
スクリプトが見るのは番号の一致だけです。「図3」の中身が本文の説明と合っているかは判定できません。たとえば本文が「売上は図3のとおり増加」と書いているのに、図3が費用のグラフだった場合は、番号が揃っていてもずれたままです。
内容の突き合わせは、Claudeに該当の段落と図のキャプションを並べて読ませる使い方が向きます。範囲の指定に @ 参照を使うと、依頼文が短くなります。書き方はClaude Codeの@参照でファイル・フォルダを指定する書き方にまとめています。
画像の中の文字や、キャプションが画像側に焼き込まれた図も、テキストの照合では見えません。その場合は図の一覧表を別に作り、番号と題名を表にしておくと、表に対して照合できます。
似た手順の記事との使い分け
文章の表記ゆれや読みにくさの検査は、図表番号ではなく文字列のルールの問題です。そちらはClaude Codeでtextlintを使う — 日本語の校正をhookで自動化の手順が合います。textlintは PostToolUse に載せる構成ですが、本記事の照合は編集が終わってから見る Stop に載せる点が違います。
相互参照の欠落を洗うという考え方は、サイトのhreflang検証にもあります。hreflangをClaude Codeで検証するでは、ページ同士の対応関係を同じように機械で照合しています。「参照元と参照先の集合を作って差を取る」型は、図表番号でもリンク切れでも共通です。
まとめ
図表番号の整合は、読んで判断させるより、集合の差分を取るスクリプトに任せたほうが安定します。Claude Codeには、スクリプトの作成、出力を受けた修正案、Stop フックでの再実行を担当させます。
最初の一歩は、手元の原稿1本で check_refs.py を動かし、出力が実際のずれと合うかを見ることです。正規表現が原稿の書式に合ったら、CLAUDE.mdとフックを足して運用に載せます。欠番と未参照の扱いは、原稿の方針ごとに決めておくと迷いません。