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?

CLAUDE.mdを「事件のたびに条文を足す憲法」として運用する 〜プロンプトで守れないルールはテストへ格上げする〜

0
Posted at

同じ説明を、AIに何度もしていませんか。

日本株のAI銘柄スクリーニング&継続学習端末「ALPHA FORGE(アルファフォージ)」を個人で開発・運用しています(開発には Claude Code を使っています)。Claude Code は会話が変わると前提を忘れるので、このプロジェクトでは CLAUDE.md に「このプロジェクトでしか通用しない約束」だけを書き、事件が起きるたびに条文を足してきました。この記事では、その運用の型と、条文では守り切れなかったルールをテストへ移した実例をまとめます。

【要約】

  1. CLAUDE.md には一般論を書かず、プロジェクト固有の約束だけを書く。9月11日の65行が、16日間・16回の更新で118行になった
  2. 条文の多くはインシデントから生まれた。「今後はこうして」を決めるのは人間、条文案を書くのは AI、という分担にしている
  3. 条文は改正・削除もする。そしてプロンプトの指示(お願い)で守り切れないものは、コードとテスト(約束)へ格上げする

※本記事はnote連載[ALPHA FORGE 開発秘話 #05] の技術的な部分を、エンジニア向けに再構成したものです。

環境

項目 内容
OS Windows 11
開発 Claude Code(CLI)
バックエンド Python 3.11 / FastAPI / Celery(beat + --pool=solo ワーカー)+ Redis
フロントエンド Next.js 16 / TypeScript

CLAUDE.md に書くもの・書かないもの

CLAUDE.md はリポジトリに置いておくと、Claude Code がセッションの最初に読み込むファイルです。冒頭で「一般的なコーディング作法は前提として省略する」と宣言し、次のようなプロジェクト固有の約束だけを書いています(要旨)。

分類 条文の例
ドメイン 時刻は JST、大引けは15:00。上げ=赤/下げ=緑をデザイントークン経由で使う。銘柄コードは4桁で .T を付けない
安全 外部由来のテキストを LLM プロンプトへ注入しない。証券会社への発注機能を持たない。モデルの昇格は提案のみで、承認は人間
Git 同じ作業ツリーを人間も並行編集するので git add -A 禁止(対象ファイルを明示して add)
検証 push 前にローカルCI相当(ruff / black / mypy / bandit / pytest、tsc / jest / eslint / next build)を全部通す
口癖のマクロ 「システム起動して」= frontend / backend / Redis / worker / beat / 検索API を確認し、止まっているものだけ起動する

グローバル側(全プロジェクト共通の ~/.claude/CLAUDE.md)には「日本語で会話・コミット」「可逆な作業は確認なしで進める。コミット・push・削除は確認必須」のような共通ルールを置き、二層に分けています。

インシデント → 条文の年表

git log -- CLAUDE.md をたどると、条文の多くにきっかけがあります。

  1. 9/14:別サービスの検索APIが未起動で、全銘柄でタイムアウトを繰り返した → 起動時に依存サービスまで確認するチェックリストを追加
  2. 9/14:Celery beat の初回起動で溜まっていたスケジュールが一斉発火し、二重実行が起きた → beat を自動起動の対象外に
  3. 9/18:外部ニュースを読んだ LLM の自由記述が、本体の LLM プロンプトへ素通りする経路を発見 → インジェクション境界の条文に経路を追記
  4. 9/22:休場日判定と冪等化で 2. の根本原因が解消したことを確認 → beat を起動対象に戻す(改正)
  5. 9/26:Claude Code のセッションを閉じたら、そこから起動していた Celery worker / beat が巻き添えで停止し、翌朝まで定期実行が止まった → プロセス切り離しの条文を追加(次節)
  6. 9/27:ドメイン定義と実装の食い違いを AI が報告 → 定義を実装に合わせて改正(後述)

条文には「なぜそうするのか」と日付を一緒に書いています。4. のように、あとで「その理由はもう解消したか」を確かめて改正するときに効きます。

実例1:Claude Code から起動したプロセスがセッションごと死ぬ

Claude Code の Bash / PowerShell ツールで run_in_background や Start-Process を使って起動したプロセスは、Claude Code のプロセスツリーの子になります。そのためセッションを閉じると一緒に終了します。常駐させたい Celery worker / beat をこの方法で起動していたため、夕方にセッションを閉じた時点で定期実行が止まっていました。

Windows では、WMI 経由で起動すると親が WmiPrvSE.exe になり、ツリーから切り離せます。CLAUDE.md にはこの手順をそのまま書いています(抜粋)。

CLAUDE.md(抜粋)
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
  CommandLine = 'cmd.exe /c ".venv\Scripts\celery.exe -A backend.celery_app worker --pool=solo --loglevel=info >> logs\celery.out.log 2>> logs\celery.err.log"'
  CurrentDirectory = (Get-Location).Path
}

切り離したプロセスは、セッションを閉じても残り続けます。停止や再起動は、プロセスを特定して明示的に行う必要があります(二重起動にも注意)。

実例2:定義と実装、どちらに合わせるか

CLAUDE.md には「短期=当日〜3営業日、中長期=20 / 60 営業日」と書いていました。ところが、抽出結果の一覧で短期の想定保有期間が「5営業日」と出ていることに AI が気づき、報告してきました。実装を調べると、次のようになっていました(抜粋)。

backend/services/ledger/outcome_resolver.py(抜粋)
HORIZON_SETS: dict[str, tuple[int, ...]] = {
    "short_term": (1, 2, 3),
    "mid_term": (5, 20, 60),
}
backend/services/picks/bracket.py(抜粋)
_MIN_HOLDING_DAYS = 5
_MAX_HOLDING_DAYS = 10
_DEFAULT_HOLDING_DAYS = 7

決着の記録と評価は HORIZON_SETS で回っており、想定保有期間はホライズンに関係なく 5〜10 営業日に丸めていました。つまり、定義のほうが古かったわけです。

直し方は「コードを定義に合わせる」「定義をコードに合わせる」の2通りがあります。これはプロダクトの方針そのものなので、AI には選ばせず、人間が「定義を実装に合わせる」と決めました。AI が条文と計画書・決定ログを書き直し、実装は変えていません。

AI が読むドキュメントが実装と食い違っていると、AI は古い前提のまま「正しく間違える」ことがあります。食い違いを見つけたら、どちらを正にするかを人間が決め、ドキュメントを単一の情報源に戻しておくと安全です。

実例3:プロンプトの「お願い」をテストの「約束」に格上げする

アプリには LLM に文章を書かせる機能があり、プロンプトで「強気・弱気のような売買を連想させる語は使わない」と指示しています。そのうえで生成後に、NG 表現を検証用の言い回しへ機械置換しています。

それでも、ある朝の出力に「一部シグナルは強気方向を示す」という一文が残っていました。「強気」「弱気」は、置換表に入れずにプロンプトの指示だけで防いでいたためです。

そこで置換表に追加しました。ここで注意したのが複合語です。「強気」だけを置換すると「正のトレンド相関(検証用)方向」と不自然な文になるので、「強気方向」を先に置換させます。置換は NG 表現の長さの降順で適用しているので、複合語をエントリとして持つだけで済みます(抜粋・コメントは省略)。

compliance_terms.py(抜粋)
BULLISH_TERM = "正のトレンド相関(検証用)"
BEARISH_TERM = "負のトレンド相関(検証用)"

TERM_REPLACEMENTS: tuple[tuple[str, str], ...] = (
    # ...既存のエントリ...
    ("強気方向", BULLISH_TERM),
    ("弱気方向", BEARISH_TERM),
    ("強気", BULLISH_TERM),
    ("弱気", BEARISH_TERM),
)

# 長い語から先に置換しないと、部分一致で文が崩れる
_ORDERED_REPLACEMENTS = sorted(TERM_REPLACEMENTS, key=lambda pair: len(pair[0]), reverse=True)

実際にすり抜けた文をそのまま回帰テストにしています(抜粋・コメントは省略)。

test_compliance_terms.py(抜粋)
def test_apply_compliance_terms_replaces_bullish_direction_phrase_naturally() -> None:
    result = ct.apply_compliance_terms("一部シグナルは強気方向を示す一方、弱気方向の指標もある")

    assert result == "一部シグナルは正のトレンド相関(検証用)を示す一方、負のトレンド相関(検証用)の指標もある"

「強気」のような語を機械置換に入れる前に、一般語を壊さないか確認してください。このプロジェクトでは「買い」「売り」単体は「買い物」「売上」を壊すため置換対象にせず、プロンプトの指示とラベルの設計で担保しています。

ルールの置き場所を使い分ける

最終的に、守りたいルールを次の4か所に振り分けるようにしています。

置き場所 向いているもの 例
CLAUDE.md 毎セッション全員が読むべき約束 JST・上げ赤/下げ緑・git add -A 禁止
スキル(手順書) 毎回同じ手順 定型作業の手順と、過去にはまった罠の回避策
メモリ このユーザー・プロジェクトの判断基準 課金を伴う検証は実行前に一度確認する
テスト コードで機械的に守れる約束 NG 表現の置換、定期実行の時刻と順番

条文を書いたら、「これはテストにできないか」を毎回考えます。できるものは、お願いから約束へ格上げしていきます。

まとめ

  1. CLAUDE.md はプロジェクト固有の約束だけを書き、条文には理由と日付を添える
  2. インシデントの直後が、いちばん具体的な条文を書けるタイミング
  3. 条文は改正・削除する。定義と実装のどちらを正にするかは人間が決める
  4. プロンプトの指示で守り切れないものは、コードとテストへ格上げする

気づいて報告したのは AI、方針を決めて線を引いたのは人間、という分担でした。

次回は、日曜日に定期実行が勝手に動き出した「休場日ガード」の話を書きます。

☕ note.comで開発秘話と毎朝の検証ログを連載中

note.comでは、ALPHA FORGE の開発秘話と、毎朝のアルゴリズム検証ログを公開しています。

👉 元記事(note連載第5話):
【ALPHA FORGE 開発秘話 #05】CLAUDE.md はプロジェクトの憲法 ― 事件が起きるたびに条文が増えた


免責事項
本記事は個人開発およびAIエージェントを活用したシステム開発の技術記録です。特定の銘柄の売買推奨や投資助言を行うものではありません。本ツールは証券会社等への発注機能を持ちません。記事中のコードは抜粋であり、パスや環境固有の値は省略しています。

⚠️ 【システム検証記録に関する免責事項・注意事項】

  • 本記事は、独自開発アルゴリズム「ALPHA FORGE」の動作検証およびデータ分析過程を公開する技術・運用の個人的な記録ログです。
  • 掲載されているすべてのデータ(各スコア、基準観測値、統計的変動上限、シナリオ無効化水準など)は、過去の市場データに基づき数式(ATR等)により機械的に算出されたバックテスト・シミュレーション用のパラメータであり、特定の有価証券の売買勧誘、取引の推奨、投資助言・代理行為を目的としたものではありません。
  • また、将来の株価変動や運用成果を保証するものではありません。実際の投資判断および最終決定は、必ずご自身の責任と判断において行っていただけますようお願いいたします。
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?