0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1. はじめに

こんにちは。

設計書をはじめとして、議論や意思決定をする際には文章(文書)を書く場面が多くあります。僕の仕事もほぼそれでして、自身の思考や決断、その論拠などを文章にしたためます。

以前は当然自分の手で書いてましたが、最近は AI の発達もあり Claude Code に書かせています。特に僕が Claude Code で行う理由は以下の2点です。

  1. AI に調査を任せられる点
  2. いきなり結論・文章推敲にはいらず、まず一次情報の整理を別ファイルで行える点

ただ、失礼ながら「Claude Code の書く日本語... 下手だなぁ...」と思う場面が多々ありまして、こういうインストラクション を CLAUDE.md に書いたりもしてますが、いまいち改善がありません。

そこで本記事では、こういうインストラクション を Claude Code が文章を書く際に強制させる仕組みをお試しで作ってみた〜記録です。

検証環境:

  • macOS (Windows では動きません。すみません🙏)
  • Claude Code: v2.1.234

現在も試行錯誤をしている段階で、応答性能 (Editの度に待たされる) や状況によって融通が効かない、など多々課題があります。あくまで参考程度になさって下さい。

2. 課題

下手だと感じた箇所を集めて、2種類に分けました。1文だけ見れば分かる「文体の癖」と、文書全体を読まないと分からない「文脈の崩れ」です。

2.1 文体の癖 — 1文の中で完結する違反

文体の癖とは、語尾・語彙・言い回しの選び方に現れる問題です。Claude Code が繰り返し書いてしまうものに、名前をつけて整理します。

癖の名称 修正例
体言止め 保存先は S3。リージョンは東京。 保存先は S3 です。リージョンは東京を選びます。
くだけた語 設定をちょっといじれば動きます。 設定を1箇所変更すれば動きます。
造語・略語 メリデメを整理します。 利点と欠点を整理します。
装飾的な前置き いよいよ本題に入ります。 (この文を削除します)
実況中継調 まず設定画面を開いてみます。すると一覧はこうなっています。 設定画面には接続先の一覧が表示されます。
メタディスコース この章が本記事の最も重要な主題です。 (削除するか、〜を説明します のような直接の宣言に変えます)
読者の反応の決めつけ 意外と知られていませんが、この設定は無効にできます。 この設定は無効にできます。
冗長な言い直し 設定は自動で保存されます。つまり手動の保存操作は不要です。 設定は自動で保存されます。(言い直しの後半を削除します)

1つ1つは小さな癖ですが、文書全体に積もると「AI が書いた読みにくい文章」になります。

2.2 文脈の崩れ — 文書全体を通して現れる違反

既存の文書への追記や改稿で特に起きやすい問題です。

問題の名称 修正例
重複 1章に バックアップは毎日1回、深夜帯に取得します。 と書いたのに、3章で バックアップは1日に1度、夜間に実行します。 と書いています 3章の記述を削除します
接続の乱れ 前述の圧縮方式を使えば、追加の費用は発生しません。 と追記していますが、圧縮方式の説明が文書のどこにもありません 圧縮方式を説明する節を先に置くか、この文から 前述の を外して内容を明示します
構成の崩れ 「保存先の設計」という節に、保存先の選定理由と障害時の連絡フローを並べて書いています 連絡フローを別の節へ移します

文脈の崩れは1文ずつ読めば正しいため、文書全体を読み直さなければ気づけません。修正も1文の書き換えでは済まず、章立ての見直しに及ぶことがあります。

2.3 ルールを CLAUDE.md に書いたが、守られない

そこで、これらの癖を禁止する文章スタイルルールを作り、CLAUDE.md に書きました。全文を示します。

rules.md
## 文章スタイル(ドキュメント・記事・報告すべて)

### 事実性

- 事実情報が足りない段階で、想像で文章や結論を創作しない。執筆に必要な情報が不足している場合は、書き始める前にユーザーに問いかけて確認する。確認できなかった事項は文中で「未確認」と明記する

### 構成(章・セクション)

- ピラミッド原則(Pyramid Principle)で書く。結論・全体像を先に示し、その後に詳細を説明する。「まとめ」セクションを終盤に置いて初めて全体像を示す構成にしない(説明順の逆転)。全体像の図は章・記事の冒頭に置く
- 1つのセクションには1つのトピックだけを書く。余談や蛇足を書かない
- 1つの段落には1つの主張だけを書く。段落の最初の文で主張を示し、続く文はその主張を支える説明・根拠・例だけにする。主張を支えない文が現れたら段落を切る。並列で独立した項目が3つ以上並ぶ場合は、段落ではなく箇条書きで書く

### 文(センテンス)

- 主語・動詞・目的語を省略しない。本文の文を体言止めにしない。「状態保持」のような名詞化された用語は、主語と動詞のある文に書き直す(「サーバーは〜を保存する」)
- 原因や条件を先に、結果を後に置く。「Aが起きます。Bのためです」のように理由を後置した倒置は読者に戻り読みを強いるため、「BのためAが起きます」と1文にまとめる
- 断定調で書く。「〜と思われます」のような検証不能な推測や、「意外と知られていません」のような読者の反応の決めつけを書かない。本当に推測で書く場合のみ「推測」と明記する
- 冗長な表現を書かない。同じ内容の言い直し、図や表の項目を順に読み上げるだけの段落、装飾的な前置き(「いよいよ〜」等)がこれに当たる。ただし図や表の要点・読み方・そこから言えることを言葉で説明する段落は、図表の前後にあっても冗長ではない
- 実況中継調・会話体で書かない。「〜を見てみます」「〜はこうなっています」のような過程をなぞる文は削り、結果を直接書く
- メタディスコース(metadiscourse。文章自身について語る文)は書かない。どうしても必要な場合は、「〜が本章の主題です」のような文章を主語にした遠回しな形ではなく、「〜を説明します」のような直接的な宣言で書く

### 語(単語の選択)

- くだけた語感の語(「いじる」「ちょっと」等)を使わず、文書にふさわしい書き言葉に置き換える。逆に、硬い漢語(「帰結」「作用」等)よりも平易な言い換え(「その結果」「働きかける」等)を選ぶ。文書は丁寧語(です・ます調)で統一する
- カタカナ語は万人に伝わる一般的な用語を用い、造語を使わない
- 新出用語は読者が既に知っている概念で言い換えてから使う

### 箇条書き・ラベル

- 箇条書きは、並列で独立した項目の列挙にだけ使う。項目間に因果や論理の流れがある内容は、箇条書きに分解せず段落の文章で書く
- ラベルや見出しに説明文を添えるときは、なぜその説明がそのラベルに当たるのかまで書き、つながりの補完を読者に任せない。「堅牢性: リトライ処理を実装した」ではなく「堅牢性: 通信が一時的に失敗しても、リトライにより処理が最後まで完了する」のように書く

### 出典

- 外部ドキュメントを参照して書くときは、原文をそのまま持ち込まない。内容を理解してから、自分の文章として書き直し、推敲する

上記 CLAUDE.md を設定しても文章スタイルに従わない生成は多く発生します。文書スタイルが守られない原因は2つです。

原因1: ルールが会話に埋もれる

LLM には「コンテキストの先頭と末尾に置かれた指示が効きやすい」という性質があります。仕様として実装された優先順位ではなく、アーキテクチャと学習から生まれる統計的な傾向です。

  • 先頭が強い理由: 後続のすべてのトークンが先頭を参照できる構造(causal attention)があり、さらに「system prompt は先頭に置かれ、会話全体を通じて従うもの」という分布で訓練されています
  • 末尾が強い理由: 生成点に最も近く、「入力の最後に置かれるのは今従うべき指示」という会話データの慣習を学習しています
  • 中間が弱い理由: 中間は過去の会話履歴が占める位置なので、そこにある文言は「過去のやり取り」として読まれ、指示としては拾われにくくなります

長いコンテキストで中間・遠方の情報が効きにくくなる現象は "lost in the middle" として知られています。

CLAUDE.md の内容は、Claude API(Messages API)の system パラメータではなく、messages 配列の先頭に user メッセージとして毎回挿入されます(公式ドキュメント)。会話の中では最も前の位置ですが、system prompt の一部ではなく会話履歴の一部です。

会話履歴の一部である以上、会話が数万トークンに育つと、CLAUDE.md は「過去のやり取り」の側へ沈んでいきます。末尾には常に直近のタスク(いま書いている文書の内容、直前の指示)があり、モデルの注意はそちらに集中します。冒頭のルールは消えはしませんが、遠く離れた固定文として相対的に薄れ、末尾の「いま」に押し負けます。

原因2: そもそも強制力がない

コンテキスト中のルールは、出力の確率分布を方向づけるだけです。生成された文章をルールと照合する工程は、どこにも存在しません。コンパイラやリンターのような合格・不合格のゲートがないため、守るかどうかは毎回の確率的な結果になります。しかもルールは、タスク内容の正しさや直近の指示と競合する多数の制約の1つにすぎません。

では「書いた直後に、いま書いた文章を見直して直せ」と毎回指示すればよいかというと、これも機能しません。理由は2つあります。

1. 検品するのが「書いた本人」と同一のコンテキストだからです。 違反文は、ルールが読める状態で「これで正しい」と判断されて生成されたものです。同じモデル・同じコンテキストで読み直しても、同じ判断が再生されて「問題なし」に至ります。外部からのフィードバックなしの自己修正は改善をもたらさないという報告(Huang et al., ICLR 2024)とも整合します。

2. 「確認せよ」という指示に合否の判定者がいないからです。 最小の労力で応じる方法は「確認した、問題ない」と先へ進むことです。検品の品質を保証するものが何もありません。

つまり、ルールをどこに書いても、何度再提示しても、「お願い」の域を出ません。守らせるには、書いた本人とコンテキストを共有しない独立した検証者と、違反時に作業を先へ進ませないゲートを、生成の外側に追加する必要があります。

3. 解決策

2章の原因それぞれに対策を当てる plugin を作りました。

第三者が公開している Claude Code plugin を利用することは一般的にセキュリティ上の危険がありあまり推奨されないかと思います。

このリポジトリはあくまで実現方法のサンプルとして公開しているものです。

仕組みは次の3層です。

実装 動作 解決する原因
① セッション開始時 SessionStart hook セッション開始・再開・/clear・compact のたびにルール全文を注入し直す 原因1(埋もれ)
② 書き込む直前 PreToolUse hook
(検証エージェント×2)
文章を書いた Claude とは別のサブエージェント2つが、書き込み予定の内容を並列で検査し、違反があれば書き込みを拒否する 原因2(強制力の欠如)
③ 文書の完成時 skill
/style-review
ルール1項目につき1つのサブエージェントを起動し、文書の全文を並列で検査する

層②のゲートは、次の順で働きます。拒否から修正までは同一ターンの中で完結するため、ユーザーが操作する必要はありません。

動作の実例です。Claude が違反を含む文章を Write しようとすると、ツール呼び出し自体がエラーになり、拒否理由が Claude に返ります。

PreToolUse:Write hook error: Agent hook condition was not met: 文章スタイルルール違反が3件あります。

1. 原文:「いよいよ保存先の話。」
   違反ルール: 装飾的な前置き(「いよいよ〜」等)の禁止、および本文の体言止めの禁止。
   修正案: この文を削除するか、「保存先を説明します。」に置き換える。
(中略)
【編集した本体エージェントへ: この書き込みは実行されていません。指摘を反映した内容で
再度 Write/Edit を実行してください。同じ編集が3回連続で拒否された場合は、作業を止めて
ユーザーに相談してください。】

Write が実行されていないので、ファイルは編集前のままです。拒否されたターンでは、Claude は指摘を反映した内容で書き込みをやり直します。ルールが「お願い」ではなくゲートとして働きます。

4. 仕組み

4.1 層①: SessionStart hook によるルール注入

SessionStart hook は、セッションの開始・再開・/clear・compact 実行のたびに発火し、標準出力の JSON でコンテキストへテキストを注入できます。ここでルール全文を注入すれば、CLAUDE.md に書くのと同等の事前提示を、plugin として配布できる形で実現できます。compact でコンテキストが要約された直後にも再注入されるため、そのままの CLAUDE.md より埋もれに強くなります。

そもそも、Claude Code Plugin 形式では CLAUDE.md は配布できなかった、というのが正直な理由ではありますが。

hooks.json(抜粋)
"SessionStart": [
  {
    "matcher": "startup|resume|clear|compact",
    "hooks": [
      {
        "type": "command",
        "command": "${CLAUDE_PLUGIN_ROOT}/hooks/bin/inject-rules.sh",
        "timeout": 10
      }
    ]
  }
]

inject-rules.sh が行うのは、rules.md を読んで、その全文を含む指示文を JSON で標準出力に書き出すことだけです。Claude Code はこの JSON の hookSpecificOutput.additionalContext の値をコンテキストへ差し込みます。実際に差し込まれるのは次の文です(末尾に rules.md の全文が続きます)。

このセッションで文章(記事・ドキュメント・報告)を書くときは、次の文章スタイルに従ってください。
文章を追加・修正するときは、書く前に対象箇所の前後と関連セクションを読み直し、既出の内容との
重複がないか・前後と論理的に接続するかを確認してから書いてください。文書全体の執筆・改稿が
一段落したターンでは、style-review スキルによる全文検証の実行をユーザーに提案してください。
.md および .txt ファイルの編集は、必ず Write ツールまたは Edit ツールで行ってください。
Bash 経由の書き込み(sed、ヒアドキュメント、リダイレクト等)では文章スタイル検証が働かない
ため使用しないでください。(中略)

## 文章スタイル(ドキュメント・記事・報告すべて)
### 事実性
- 事実情報が足りない段階で、想像で文章や結論を創作しない。...

... (ルール本文が続く) ...

...

ルール本文の前に置いた指示が、ルールの使い方を決めています。「書く前に前後を読み直す」は文脈の崩れ(2.2)を予防するためで、「Write / Edit で編集する」は層②の検証が Write と Edit にしか働かないためです。

AI は校正チェックが厳しすぎると、チェックが入る WRITE|EDIT tool を避け、Bash tool に逃げる傾向があります😭

4.2 層②: PreToolUse hook による書き込み前の検証と拒否

hooks.json の PreToolUse イベントに type: "agent" の hook を登録すると、ツール実行の直前にサブエージェントが起動します。このエージェントは書き込み予定の内容(tool_input)を受け取って検査し、違反があれば deny を返してツール実行を止めます。

if フィールドには権限ルール構文(Write(**/*.md) 等)を書けます。検証を .md/.txt への書き込みだけに絞ることで、コードを書く作業では検証エージェントが起動せず(実測82ms)、執筆以外の用途を妨げません。

hooks.json(抜粋)
"PreToolUse": [
  {
    "matcher": "Write|Edit",
    "hooks": [
      {
        "type": "agent",
        "if": "Write(**/*.md)",
        "prompt": "(検証手順と文章スタイルルールの全文)",
        "model": "claude-sonnet-5",
        "timeout": 150
      }
    ]
  }
]

検証エージェントには、ユーザーと対話している側の Claude(claude-fable-5)より下位の claude-sonnet-5 を使っています。文章を書いた Claude とコンテキストを共有しないため、自分の書いた文章を自分で「正しい」と判定してしまう偏り(2.3)がかからず、下位のモデルでも本体が見逃した違反を検出できます。

文体の癖(2.1)は書き込み予定の内容だけで判定できるのに対し、文脈の崩れ(2.2)は編集前の文書と照合しなければ判定できません。必要な入力が違うため、検証を2つに分けて同じ書き込みに対して並列に起動しています。

検証エージェント 何を検査するか 実測
文体検証 書き込み予定の内容をルール全文と照合します。書き込む部分だけで合否を判定できるので、前後の文章もファイルも読みません 3.93秒
文脈検証 書き込み予定の内容が文書の他の箇所と重複しないか、前後の段落とつながるかを検査します。判定には文書全体が必要なので、編集前のファイルを読みます 12.97秒

文脈検証はファイルを読む往復が1回増えるので、文体検証の3倍以上かかります。2本は並列に動くため、待ち時間は2本の合計ではなく遅い方だけになります。

2本のエージェントが Claude API とやり取りする順序は次のとおりです。

4.3 層③: skill + subagent による完成時の全文検証

層②は書き込むたびに、そのとき書く部分だけを検査します。文書を書き終えたら /style-review <ファイルパス> を実行し、文書全体をすべてのルールで検査し直します。

skill は、ルール1項目につき1つのサブエージェントを並列で起動し、違反箇所をルール別に報告します。1つのエージェントに16項目すべてを渡すと個々のルールの検査が甘くなるため、1ルール1エージェントに分けています。

⏺ 記事全文を対象に、ルール16項目それぞれへ専任エージェントを1つずつ、並列で起動します。

⏺ 16 background agents launched (↓ to manage)
  ┣ 事実性ルールの検査
  ┣ ピラミッド原則の検査
  ┣ 1セクション1トピックの検査
  ┣ 1段落1主張の検査
  ┣ SVO・体言止めの検査
  ┣ 原因先置きの検査
  ┣ 断定調の検査
  ┣ 冗長表現の検査
  ┣ 実況中継調の検査
  ┣ メタディスコースの検査
  ┣ 語感(くだけた語・硬い漢語)の検査
  ┣ カタカナ語・造語の検査
  ┣ 新出用語の導入の検査
  ┣ 箇条書きの適切さの検査
  ┣ ラベルと説明の因果の検査
  ┗ 出典の書き直しの検査

⏺ ルール16項目それぞれに専任の校閲エージェントを起動しました(全16本を並列実行中)。各エージェントには対象ファイルのパスと担当ルール1項目だけを渡しており、他のルールは見ていません。

5. まとめ

本記事では「CLAUDE.md に書いた文章スタイルルールを守らせる」ことをどう実現できるか実験を試みました。実際には、表題の目的を達成するために、Claude ではなく日本語文章生成に強いモデルを利用するなど、他にも方法はあるかと思います。

開発の現場では、実装(ソースコード生成)においてはコンパイラやリンター、ユニットテストなどを用いて生成物が正しいことへのガードレールをひくことが有効であると分かっており、モデルとそれを律するハーネスが重要です。

品質確認をするガードレールが静的な仕組みであると安定しますが、今回のように生成も、その承認も AI Agent で行うことに難しさを感じました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?