投稿前の文体チェックやアイキャッチ画像の下書き生成には、Claude Codeと自前のPythonスクリプトを使っています。そのチェック観点や画像生成のお作法を CLAUDE.md や context/ 配下にどんどん書き足していくのが日々の運用です。「アイキャッチに前掛け(エプロン)を出さない」「育児日記以外では市区町村名を書かない」「投稿APIでは status を必ず明示する」といった具合です。
ところが、ある日アイキャッチ画像を見直していて気づきました。ドキュメントには書いてあるのに、画像生成スクリプト側で全くそのルールが効いていなかったのです。書いたつもりで満足していて、実装に落ちていなかったわけです。
これは投稿前チェックを仕組み化するうえで結構怖い問題でした。ルールを増やすほどに「書いただけで実装に反映されていないルール」が積み上がっていくからです。最終的に、ドキュメントと実装の両方にルールのキーワードが揃っているかを照合する rule_consistency_check.py を組んで、月次チェックに統合しました。この記事ではその仕組みと、組んでみてわかった運用上のコツをまとめます。
この記事でわかること
- 投稿前チェックのルールを増やしたときに発生する「書いたのに実装で効いていない」問題の正体
- ドキュメントと実装の整合性をPythonで自動検知する仕組みの組み方
- 月次チェックに統合して、ルールのドリフトを最悪1ヶ月で検出する運用
背景:ドキュメント駆動でルールを増やすほど、実装と乖離する
このブログでは、投稿前の文体チェックや画像生成の補助をClaude Codeと自前スクリプトに任せています。チェックや補助の挙動を整えるために「こういう表現は弾いてほしい」「こういう画像は出さないでほしい」を CLAUDE.md と context/ 配下に少しずつ書き足していきました。
ルールを増やすにあたって、私は次のような暗黙のフローを踏んでいました。
- 何か気になる出力が出る(例:アイキャッチに毎回エプロン姿の母親が出てくる)
context/workflow-images.mdに「エプロンは出さない」と追記する- 次の記事で同じ問題が出る ←!?
3で気づいたわけです。ドキュメントに書いただけでは、画像生成スクリプト generate_eyecatch.py が組み立てるプロンプトには反映されません。スクリプトを実際に直さないと意味がありません。
考えてみれば当たり前なのですが、ドキュメントを書いた瞬間に「対応した気持ち」になってしまうのが落とし穴でした。書いたルールと実装のあいだに断絶があり、その断絶を検知する仕組みがなかったのが本当の問題です。
解決の方針:ドキュメントと実装の両方にマーカー文字列が揃っているかを照合する
完璧な静的解析を目指すと大変なので、もっと泥臭い方法に倒しました。「このルールはドキュメントにこの語があり、実装にもこの語がある」という対応表を1つ作って、両方とも存在するかを照合するだけの仕組みです。
たとえばアイキャッチのステレオタイプ回避ルールであれば、こう定義します。
- ドキュメント側(
context/workflow-images.md)にapronとstereotypesの語があること - 実装側(
_meta/generate_eyecatch.py)にもapronとstereotypesの語があること
両方そろっていれば「ルールが書かれていて、かつ実装でも触れられている」と見なします。完全な意味解析ではないので「実装の中身が正しいか」までは保証できませんが、「ドキュメントにだけ存在して、コードに痕跡すらない」状態は確実に検出できます。私の用途ではこれで十分でした。
完成版はこちら
長い手順を読む前に、最終的に動いているスクリプトを置いておきます。_meta/rule_consistency_check.py として配置し、python3 _meta/rule_consistency_check.py で実行します。
#!/usr/bin/env python3
"""
rule_consistency_check.py — CLAUDE.md / context/ のルールと、スクリプト実装の整合性チェック
"""
import sys
from pathlib import Path
REPO = Path(__file__).parent.parent
# (ルール名, docパス, docで必須の語, 実装パス, 実装で必須の語)
RULES = [
("eyecatch_no_apron",
"context/workflow-images.md", ["apron", "stereotypes"],
"_meta/generate_eyecatch.py", ["apron", "stereotypes"]),
("style_jimini_limit",
"context/style-guide.md", ["地味に", "1回"],
"_meta/article_check.py", ["地味に", "LIMITED_WORDS"]),
("wp_status_required",
"CLAUDE.md", ["status", "明示"],
"_meta/wp_post.py", ['"status": status']),
# ……必要なルールぶんだけ追加していく
]
def check():
issues = []
for name, doc_path, doc_needs, code_path, code_needs in RULES:
doc = REPO / doc_path
code = REPO / code_path
if not doc.exists():
issues.append((name, f"doc missing: {doc_path}"))
continue
if not code.exists():
issues.append((name, f"code missing: {code_path}"))
continue
doc_text = doc.read_text(errors="ignore")
code_text = code.read_text(errors="ignore")
missing_doc = [w for w in doc_needs if w not in doc_text]
missing_code = [w for w in code_needs if w not in code_text]
if missing_doc:
issues.append((name, f"{doc_path} に未記載: {missing_doc}"))
if missing_code:
issues.append((name, f"{code_path} に未実装: {missing_code}"))
return issues
def main():
issues = check()
print("=" * 60)
print("ルール整合性チェック (ドキュメント ↔ 実装)")
print("=" * 60)
if not issues:
print(f"\n✅ {len(RULES)}ルール すべて整合")
return 0
print(f"\n⚠️ {len(issues)} 件の不整合\n")
for name, msg in issues:
print(f" ❗ [{name}] {msg}")
return 1
if __name__ == "__main__":
sys.exit(main())
実行するとこのような出力が出ます。
============================================================
ルール整合性チェック (ドキュメント ↔ 実装)
============================================================
✅ 12ルール すべて整合
不整合があれば、該当ルール名・どちらのファイルに何が足りないかが表示されます。
手順1:ルールの「ドキュメント」と「実装」を1対1で対応づける
組むうえで一番の勘所はここです。1つのルールについて、次の4点を必ずセットで決めます。
| 項目 | 例 |
|---|---|
| ルール名(識別子) | eyecatch_no_apron |
| ドキュメント側のファイル | context/workflow-images.md |
| ドキュメント側に存在すべき語 | apron stereotypes |
| 実装側のファイル | _meta/generate_eyecatch.py |
| 実装側に存在すべき語 | apron stereotypes |
語の選び方にはコツがあります。「そのルールが守られている実装ならば自然に登場するはずの単語」を選ぶことです。たとえば「エプロン禁止」のルールなら、実装側のプロンプトに no apron のような形で apron という語が出るはずです。逆にコメントとしてだけ # エプロンは出さない と書いても、それは検査対象として弱いです。
最初は欲張らず、3〜5個くらいから始めるのをおすすめします。私のリポジトリでは現在12ルールほどに増えていますが、最初は4ルールから出発しました。
手順2:ルール定義をリストにまとめる
RULES リストにタプルで並べます。1行1ルールで書いたほうが、後から「どのルールが落ちたか」を読みやすいです。
ジャンルでコメント区切りを入れると、ドキュメント側の章立てとも対応が取りやすくなります。
RULES = [
# アイキャッチのステレオタイプ回避
("eyecatch_no_apron", ...),
("eyecatch_character_appearance", ...),
# スタイルガイド: 頻度制限語
("style_jimini_limit", ...),
("style_honmatsu_limit", ...),
# ワークフロー
("wp_status_required", ...),
]
このようにジャンルごとにブロックを区切っておくと、ルールが10個を超えてきたあたりから一気に読みやすくなります。
手順3:月次チェックに統合する
単体で動くだけだと走らせるのを忘れるので、私は月1で回している blog_check.py(ブログ全体の健全性チェック)にこのスクリプトの呼び出しを足しました。月次でインデックス申請も自動化しているので(Google Indexing APIで再クロールを促す手順)、そのフローと一緒に走らせる形にしています。
# blog_check.py の末尾あたり
import rule_consistency_check
print("\n--- ルール整合性チェック ---")
rule_consistency_check.main()
これで月1で必ず走るので、ドキュメントだけ更新して実装し忘れた状態は最悪1ヶ月で検知できます。月次レポートに ⚠️ が混ざっていたら、その月のうちに直すという運用です。
詰まったこと・気づいたこと
詰まり1:マーカー語が一般的すぎると常に通ってしまう
最初に組んだとき、status という語をマーカーにしたルールで「実装側に status がある=OK」としてしまっていました。ところが wp_post.py には別の文脈の status 変数があふれているので、ルールを実装し忘れても素通りしてしまいました。
そこで、より具体的な文字列(たとえば '"status": status' のようにコード片そのもの)をマーカーにする方針に変えました。マーカーは「そのルールに固有な語」を選ぶのがコツです。
詰まり2:ドキュメント側はあえて日本語、実装側は英語のことが多い
context/style-guide.md のような文体ルールは日本語で書かれていますが、article_check.py の実装内では LIMITED_WORDS のような変数名で扱われます。両方を一致させようとすると無理があるので、ドキュメント側のマーカーは「ルールを表す日本語」、実装側のマーカーは「変数名や定数名」と割り切りました。
実例として「地味に」を1記事1回までに制限するルールはこう書いています。
("style_jimini_limit",
"context/style-guide.md", ["地味に", "1回"],
"_meta/article_check.py", ["地味に", "LIMITED_WORDS"]),
ドキュメント側で「地味に」と「1回」が同居していること、実装側で「地味に」が LIMITED_WORDS という形で参照されていること、をそれぞれ確認するイメージです。
詰まり3:ルールを足すこと自体を忘れる
これが最後まで残った課題です。新しいルールをドキュメントに足したのに、rule_consistency_check.py の RULES リストへ追加するのを忘れていれば、せっかくの仕組みが効きません。
完全な解決策は思いついていませんが、運用ルールとして「ドキュメントに新ルールを追記したら、同時に RULES への1行追加と実装変更をワンセットで行う」と決めて、ADR(context/decisions/)にも明記しました。気休めですが、習慣化されてからは取りこぼしが減りました。
まとめ
- 投稿前チェックのルールを書き足していくほど「ドキュメントには書いたが実装に反映されていない」状態が静かに増える
- ドキュメントと実装の両方にマーカー語が揃っているかを照合するだけで、最低限のドリフト検知はできる
- 月次のブログ全体チェックに統合しておくと、最悪1ヶ月でズレに気づける
- マーカー語は「そのルールに固有な語」を選ぶのがコツ。日本語と英語が混ざっていても割り切ってよい
投稿前チェックをスクリプト化していく以上、ルールが実装に反映されているかを人間が毎回目視で追いかけるのは現実的ではなくなります。完璧な解析ではなく「最低限ドキュメントとコードが連動しているか」だけでも自動でチェックする仕組みを早めに入れておくと、じわじわ効いてきますので、似た悩みを持つ方は試してみてください。

コメント