この資料は、Claude Code を仕事の道具として使うための「足場」の作り方を、章の順に1つずつ説明する。コマンドが打てれば進められる。インストールは短く、「なぜこの仕掛けが要るのか」を図で見せることに時間をかけている。各章は「絵 → なぜ要るか → 3行でいうと → 作り方 → テンプレ → できたかの確認」の順で、その章だけで完結する。Windows と Mac は上の「表示」で切り替えると、手順・テンプレ・指示文が自分の OS のものだけになる。各章の末尾の「自分の環境で数えてみる」は、自分の環境の規模を測るコマンドで、読まなくても先へ進める。
読み方は4つだけ。
C:\Users\自分の名前Finder の家のアイコン(ターミナルでは ~)mkdir .claude\rules のように作る(エクスプローラーで作るなら、名前の最後にもドットを付けて .claude. と入力すると .claude になる)ターミナルで mkdir -p .claude/rules のように作る(Finder では先頭ドットのフォルダは隠れる。Command+Shift+. で表示).txt が付くテキストエディットは既定で書式付き(rtf)で保存する。保存は VS Code などのエディタで行うか、Claude Code に「このファイルをこの場所に作って」と貼って頼むこの資料全体の地図。下の「積み木」は足場の層で、各段の( )は対応する章。下から順に積む。その下の「困りごとと仕掛け」は、なぜその段が要るのかを1行で対応させたもの。
irm https://claude.ai/install.ps1 | iex を実行する(winget install Anthropic.ClaudeCode でもよい)ターミナルを開き、curl -fsSL https://claude.ai/install.sh | bash を実行する(brew install --cask claude-code でもよい)。公式手順: code.claude.com/docs/en/setup。VS Code 派は拡張機能で「Claude Code」を追加git --version を打つと、無ければ入れるか聞かれる(Xcode コマンドラインツール)。第1章でも使うclaude を一度実行し、開いたブラウザで Claude のアカウントにログインする(有料プランか、組織から配られたアカウントが要る)cd して claude を起動し、「このフォルダの中身を説明して」と頼んで返答を確かめる。「/」だけ打つとコマンド一覧が出て、Esc で止められる(今は覚えなくてよい)このフォルダの中身を一覧して、何のためのフォルダに見えるか一言で教えてください。ファイルは変更しないでください。claude で会話できる。powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"ターミナルで curl -fsSL https://chatgpt.com/codex/install.sh | sh。codex を実行し「Sign in with ChatGPT」で自分の ChatGPT アカウントにログインする。以降の章の「作業フォルダで起動」は codex と読み替える。git --version で版が出る。出なければ第0章の手順2に戻るターミナルで git --version を打つ。無ければ入れるか聞かれるので入れるcd し、git init を1回だけ実行する(フォルダの中に隠しフォルダ .git ができる)git config --global user.name "自分の名前"/git config --global user.email "自分のメール"git add . のあと git commit -m "最初の記録"。以降は区切りごとに同じ2コマンド(Claude Code に「コミットして」と頼んでもよい)この作業フォルダを git で管理したいです。git init が済んでいるか確かめ、済んでいなければ実行してください。その後、今あるファイルを「最初の記録」というメッセージでコミットしてください。GitHub への push はしないでください。git log --oneline で「最初の記録」が1行出る。git add . は「フォルダの全部」を記録する。認証ファイル(secret_*.json・.env)が入っていると履歴に残ってしまう。第7章の秘匿スキャンを入れるまでは、認証ファイルを作業フォルダに置かない。CLAUDE.md を 作業フォルダ の直下に置き、<仕事の名前> と役割の1行を自分の言葉に書き換える01-must-follow.md を 作業フォルダ ▸ .claude ▸ rules に置く(フォルダの作り方は「はじめに」の「テンプレの置き方」)CLAUDE.md# CLAUDE.md — <仕事の名前> の Claude Code 設定(目次だけ)
## 役割
<仕事の名前>の専属アシスタント。<主な仕事を1行で。例: 議事録・週次レポート・問い合わせ対応の下書き>
## 絶対ルール
本体は `.claude/rules/01-must-follow.md`。ここには再掲しない。
## ルールファイル(`.claude/rules/` は毎セッション自動で読まれる)
- `01-must-follow.md` — 絶対ルール5つ
## 保存先
新しいファイルの置き場は `context/README.md` が正本。迷ったら `inbox/` に置く。
## 記憶
覚えておく事実は Claude Code のメモリ(索引 MEMORY.md + 1ファイル1事実)に書く。経緯や日記は書かない。
.claude/rules/01-must-follow.md# 絶対守るルール(5つ)
毎ターン確認する。文脈が長くなっても有効。
## 1. 数値は実データだけ
件数・金額・割合は、必ず実際のデータやコマンドの結果から取る。根拠がない数値は「不明」と書く。
「送りました」「登録しました」の報告は、戻り値ではなく実体(メッセージID・ログ・保存ファイル)で確かめてから書く。
## 2. 作業前に一言確認
ファイルの上書き・コードの変更・外部への送信は、方針を一言確認してから。読むだけ・軽微な修正は確認不要。
頼まれた範囲を超える追加・整理はしない。
## 3. 秘密に触らない
`secret_*.json`・`credentials*.json`・`.env` は読まない・書かない・コミットしない。
自動実行の定義(Windows のタスクスケジューラの登録、Mac の LaunchAgents の plist)は変更前に必ず確認する。
## 4. 相談と依頼を区別する
「どう思う?」「なぜ?」は分析・所見を返すだけ。修正・送信・登録は、はっきり頼まれてから。
## 5. 日次サマリーを書く
意味のある作業が終わったら、その日の日次サマリー(1日1ファイル)に1件追記する。
会社名・顧客名・個人が特定できる情報は書かない。
今の絶対ルールを、.claude/rules/01-must-follow.md に書いてあるとおりに5つ挙げてください。ファイルは変更しないでください。.claude/rules/ の中身は Claude Code が起動時に自動で読む。同じ内容を hook でも流すと二重に読まれて無駄なので、方針は rules に1回だけ書く。特定のファイルを触るときだけ読ませたいルールは、ファイルの先頭に paths: で対象を書ける(上級者向け。最初は使わなくてよい)。CLAUDE.md の代わりに作業フォルダ直下の AGENTS.md を読む(付録D のテンプレ codex/AGENTS.md.sample)。.claude/rules/ の自動読み込みは無いので、絶対ルールも AGENTS.md に書く。指示書の本数と大きさ(毎セッション読まれる量)を、自分の環境で数えてみる(作業フォルダで実行する)。
Get-ChildItem CLAUDE.md, .claude\rules\*.md | Select-Object Name, Lengthwc -c CLAUDE.md .claude/rules/*.mdcontext/README.md を 作業フォルダ ▸ context に置く。台帳のファイルは必要になったときに1つずつ作る2026-01-01.sample.json を見本として置くcontext/README.md# context/ — 人が更新する台帳の置き場
Claude Code が仕事の前に読む「前提」を置く。経緯や日記ではなく、今の事実だけを書く。
| ファイル | 何を書くか | 更新する人・頻度 |
|---|---|---|
| `team.md` | チームの役割・呼び方(人名の表記ルール)。個人情報は書かない | 人・異動のたび |
| `projects.md` | 進行中の案件と今の状態(1案件3行) | 人・週1 |
| `output-map.md` | 「どの種類の成果物をどこに保存するか」の正本 | 人・保存先を新設したとき |
| `ai-org.md` | 係の名簿(課・係・呼び出し・実体・仕事) | 係を足したとき |
## 約束
- 1ファイル200行以内。超えたら分ける
- 数値・日付はここに書かない(すぐ古くなる)。書くなら「いつ時点か」を添える
- 顧客名・個人名を書く場合は、社外に出ない前提を守る(このフォルダを共有しない)
diary/2026-01-01.sample.json{
"date": "2026-01-01",
"accomplishments": [
"作業フォルダに CLAUDE.md と絶対ルールを置き、新しいセッションで「今の絶対ルールは?」と聞いて中身が返ることを確かめた。",
"日次サマリーの置き場を diary/ に決め、この1件目を書いた。"
],
"tips_discovered": [
"指示書を目次だけにしたら、ルールを直す場所が1か所になり迷わなくなった"
],
"summary": "指示書と日次サマリーの置き場を決めた"
}
メモリを整えてください。MEMORY.md は「- [題名](file.md) — 要点」の1行だけの索引にし、本文・経緯・最終セッションの要約は書かない。
1ファイル1事実(feedback=私の指示・訂正、project=進行中の確定判断、reference=外部リンク)。索引にない/存在しないファイルの食い違いをゼロにし、
1行を100文字以内に。古い経緯は memory/archive/YYYY-MM.md へ。
意味のある作業が終わるたびに diary/YYYY-MM-DD.json(作業フォルダの diary/) の accomplishments に1件追記する運用にしてください
(会社名・顧客名・個人名は書かない)。コンテキストが70%に達したら 引き継ぎメモ handoffs/NEXT_SESSION_<題名>.md(次のセッションの最初に読ませるファイル。handoffs フォルダが無ければ作る)に「実測で確定した事実/罠/
触ってはいけないもの/残タスク+完了条件/次セッションに貼るプロンプト」を書いてから止めてください。.codex/config.toml に features.memories = true で有効)がある。context/日次サマリーは同じ形で残す。記憶の索引 MEMORY.md の行数を、自分の環境で数えてみる(作業フォルダで実行する)。
Get-ChildItem "$HOME\.claude\projects" -Recurse -Filter MEMORY.md | ForEach-Object { "$($_.FullName): $((Get-Content $_.FullName).Count) 行" }wc -l ~/.claude/projects/*/memory/MEMORY.mdallow/deny/ask に書く「ツール名」が、どんな操作を指すかの一覧。
| ツール名 | どんな操作か | 書き方の目安 |
|---|---|---|
| Read | ファイルの中身を読む | Read(**/secret_*.json) のようにパターンで絞れる |
| Glob | ファイル名のパターンで探す(例: *.md) | 読むだけ。許可してよい |
| Grep | ファイルの中の文字列を探す | 読むだけ。許可してよい |
| Edit | 既存ファイルの一部を書き換える | 作業フォルダの中に限られる |
| Write | ファイルを新規作成・全文上書きする | 同上。deny に Edit(**/secret_*.json) を書くと Write も止まる(Edit のルールは Write にも効く) |
| Bash(…) | コマンドを実行する(Git for Windows の bash 経由標準のシェル経由) | Bash(git status:*) のようにコマンド単位で許可する。Bash 単独は全許可なので書かない |
| PowerShell(…) | PowerShell のコマンドを実行するWindows だけのツール。Mac では使わない | 同上。PowerShell(Remove-Item:*) は ask にMac の settings には書かなくてよい |
| WebFetch/WebSearch | Web ページを読む・検索する | 使うなら allow に。社内資料だけなら不要 |
| mcp__名前__* | MCP でつないだ外部サービスの操作(第5章) | サーバーごとに mcp__google-workspace__* のように許可する |
settings.jsonsettings.mac.json を settings.json に改名して 作業フォルダ ▸ .claude に置く(hooks の登録も入っている。中身は第6章で置く)Bash や PowerShell を丸ごと許可しない(下の一覧で、どの操作を許すか決める)secret_*.json 等).claude/settings.json{
"permissions": {
"allow": [
"Read",
"Glob",
"Grep",
"Edit",
"Write",
"Bash(ls:*)",
"Bash(cat:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"PowerShell(Get-ChildItem:*)",
"PowerShell(Get-Content:*)",
"PowerShell(git status:*)",
"PowerShell(git diff:*)",
"PowerShell(git log:*)"
],
"deny": [
"Read(**/secret_*.json)",
"Read(**/credentials*.json)",
"Read(**/.env)",
"Read(**/.env.*)",
"Edit(**/secret_*.json)",
"Edit(**/credentials*.json)",
"Edit(**/.env)",
"Edit(**/.env.*)"
],
"ask": [
"Bash(rm:*)",
"Bash(git push:*)",
"PowerShell(Remove-Item:*)",
"PowerShell(git push:*)",
"PowerShell(schtasks:*)"
]
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/inject-core-rules.ps1"
],
"timeout": 5
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|PowerShell|Write|Edit",
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-sensitive-files.ps1"
],
"timeout": 5
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/session-close.ps1"
],
"timeout": 10
}
]
}
]
}
}
.claude/settings.mac.json{
"permissions": {
"allow": [
"Read",
"Glob",
"Grep",
"Edit",
"Write",
"Bash(ls:*)",
"Bash(cat:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(python3:*)"
],
"deny": [
"Read(**/secret_*.json)",
"Read(**/credentials*.json)",
"Read(**/.env)",
"Read(**/.env.*)",
"Edit(**/secret_*.json)",
"Edit(**/credentials*.json)",
"Edit(**/.env)",
"Edit(**/.env.*)"
],
"ask": [
"Bash(rm:*)",
"Bash(git push:*)",
"Bash(launchctl:*)"
]
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/inject-core-rules.sh",
"timeout": 5
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard-sensitive-files.py",
"timeout": 5
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-close.sh",
"timeout": 10
}
]
}
]
}
}
作業フォルダの .claude/settings.json の permissions を設計してください(自分のホームの settings.json は触らない)。allow は Read・Glob・Grep・Edit・Write と、よく使う git コマンド(status/diff/log)だけ。deny に secret_*.json・credentials*.json・.env の Read と Edit。ask に削除(rm・Remove-Item)と git push。Bash や PowerShell を丸ごと許可しないでください。変更前に今の内容を表示し、差分を見せてから適用してください。作業フォルダの .claude/settings.json の permissions を設計してください(自分のホームの settings.json は触らない)。allow は Read・Glob・Grep・Edit・Write と、よく使う git コマンド(status/diff/log)だけ。deny に secret_*.json・credentials*.json・.env の Read と Edit。ask に削除(rm)と git push。Bash を丸ごと許可しないでください。変更前に今の内容を表示し、差分を見せてから適用してください。allow/deny/ask の代わりに、自分のホームの .codex/config.toml で approval_policy(いつ確認するか)と sandbox_mode(どこまで書けるか)を決める。最初は approval_policy = "untrusted"・sandbox_mode = "workspace-write"(作業フォルダの中だけ書ける)が安全。本体のモデルと effortを、自分の環境で数えてみる(作業フォルダで実行する)。
Select-String -Path "$HOME\.claude\settings.json" -Pattern '"model"|"effortLevel"'grep -E '"model"|"effortLevel"' ~/.claude/settings.json/plugin で見られる)。つなぐほど常駐コストと事故の面が増えるので、使うものだけにする。mcp-setup.md の手順どおりに、Google Cloud で OAuth クライアントを作り、IDとシークレットを OS のユーザー環境変数に登録する(.mcp.json の ${変数名} はそこから埋まる。ファイルに値を書かない)。先に uv(Python の道具を1コマンドで動かす小さなツール。MCP サーバーの起動に使う)を入れる: PowerShell で powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"ターミナルで curl -LsSf https://astral.sh/uv/install.sh | sh.mcp.json.sample を 作業フォルダ ▸ .mcp.json にコピーする(値は変数名のまま)/mcp を開き、自分のアカウントで同意する。「Drive のファイルを5件一覧して」と頼み、一覧が返ることを確かめるmcp-setup.md# MCP を1本つなぐ手順(例: Google Workspace)
MCP は「外部システムを Claude Code の道具として渡す共通の口」。最初の1本は Drive・Gmail・カレンダーがまとめて使える Google Workspace が向く。
## 前提(1回だけ)
- `uv`(Python の道具を1コマンドで動かすツール)を入れる: `curl -LsSf https://astral.sh/uv/install.sh | sh`
- Google Cloud のプロジェクトを1つ作る(会社の Google アカウントで https://console.cloud.google.com を開く。管理者に権限が要ることがある)
## 手順
1. 何にアクセスするか(Drive/Gmail/カレンダー)と認証方式(Google の OAuth)を決める
2. Google Cloud で OAuth クライアントを作る: 「APIとサービス」→「認証情報」→「認証情報を作成」→「OAuth クライアント ID」→ 種類は「デスクトップ アプリ」。同じ画面の「ライブラリ」で Drive・Gmail・Calendar の API を有効にする。表示されたクライアントIDとシークレットを控える。**ファイルには書かない**
3. 控えた値を **OS のユーザー環境変数** に登録する(Claude Code は `.mcp.json` の `${変数名}` を OS の環境変数から埋める。`.env` は読まない)
- Windows(PowerShell): `[Environment]::SetEnvironmentVariable("GOOGLE_OAUTH_CLIENT_ID", "<控えたID>", "User")` を ID・シークレット・メール(`USER_GOOGLE_EMAIL`)の3つ分。登録後は PowerShell を開き直す
- Mac: 自分のホームの `.zshrc` に `export GOOGLE_OAUTH_CLIENT_ID="<控えたID>"` を3つ分書き、ターミナルを開き直す
4. `.mcp.json.sample` を `.mcp.json` にコピーする(値は `${変数名}` のまま。起動時に環境変数から埋まる。ファイルに値を直書きしない)
5. 新しいセッションを開き、`/mcp` で接続状態を見る。「要認証」ならブラウザで会社アカウントを選んで同意する
## できたかの確認
「Drive のファイルを5件一覧して」と頼み、一覧が返る。
## つまずき
- 個人アカウントと会社アカウントを取り違えやすい。書き込みの前に「どちらのアカウントか」を確かめる約束を rules に足す
- 使わない MCP を残すと、毎セッション「要認証」の指示文が常駐してコストになる。外す
- サーバー名や起動コマンドは MCP ごとに違う。配布元の README の値を使う(`.mcp.json.sample` の `uvx workspace-mcp` は一例)
.mcp.json.sample{
"mcpServers": {
"google-workspace": {
"command": "uvx",
"args": ["workspace-mcp"],
"env": {
"GOOGLE_OAUTH_CLIENT_ID": "${GOOGLE_OAUTH_CLIENT_ID}",
"GOOGLE_OAUTH_CLIENT_SECRET": "${GOOGLE_OAUTH_CLIENT_SECRET}",
"USER_GOOGLE_EMAIL": "${USER_GOOGLE_EMAIL}"
}
}
}
}
このプロジェクトに MCP サーバー「<name>」を追加してください。共有用なので .mcp.json に stdio 形式(command/args/env)で書き、
秘密は OS の環境変数を ${VAR} で参照する形にし、ファイルに直書きしないでください。追加後、新セッションでの接続確認手順と、
作業フォルダの .claude/settings.json の permissions.allow に足す項目(mcp__<name>__*)を提示してください。既存の MCP と機能が重複する場合は追加せず理由を報告してください。/mcp で接続済みになっている。.codex/config.toml の [mcp_servers.名前] に書く(または codex mcp add 名前 -- コマンド)。同じサーバーを Claude Code と Codex で二重に持たなくてよい。つながっている MCP サーバーの一覧と数を、自分の環境で数えてみる(作業フォルダで実行する)。
claude mcp listclaude mcp list.claude/hooks/ の .ps1 3本.sh・.py 3本 を 作業フォルダ ▸ .claude ▸ hooks に置く(第4章の settings.json がこの3本を呼ぶ)。ターミナルで chmod +x .claude/hooks/*.sh を実行するsession-close.ps1 の $diaryDirsession-close.sh の DIARY_DIR を第3章で決めた置き場に合わせるpowershell -ExecutionPolicy Bypass -File .claude\hooks\inject-core-rules.ps1ターミナルで bash .claude/hooks/inject-core-rules.sh を実行し、2行だけ出ることを確かめるcat で読ませようとすると止まることを確かめる.claude/hooks/inject-core-rules.ps1# UserPromptSubmit フック(Windows): 毎ターン、最小限だけ注入する。
# 方針の本文は .claude/rules/ に書く(自動で読まれる)。ここで再掲すると二重になるので、
# 出すのは「今の日時」と「rules への指差し1行」だけ。出力は300バイト以下を目安にする。
$now = Get-Date -Format "yyyy-MM-dd (ddd) HH:mm"
Write-Output "【現在時刻】$now"
Write-Output "【毎ターン確認】数値は実データのみ/作業前に一言確認/秘密に触らない(詳細は .claude/rules/01-must-follow.md)"
.claude/hooks/guard-sensitive-files.ps1# PreToolUse フック(Windows): 危ないファイル操作を手前で止める。
# - 認証ファイル(secret_*.json / credentials*.json / .env)は、読む・書く・消す・コピーのどれも止める
# - 自動実行の定義(タスクスケジューラ: schtasks の /Create /Delete /Change、Mac の LaunchAgents)を書き換える操作を止める
# 標準入力に JSON(tool_name / tool_input)が来る。止めたい時は終了コード 2 と理由(標準エラー)。
$raw = [Console]::In.ReadToEnd()
if (-not $raw) { exit 0 }
try { $call = $raw | ConvertFrom-Json } catch { exit 0 }
$tool = "$($call.tool_name)"
$secret = 'secret_[^\s''"/\\]*\.json|credentials[^\s''"/\\]*\.json|(^|[\s/\\''"])\.env(\.[^\s''"/\\]*)?($|[\s''"])'
$sched = 'schtasks(\.exe)?\s+.*\/(create|delete|change)|Library[/\\]LaunchAgents[/\\][^\s''"]*\.plist'
function Stop-Hook($msg) { [Console]::Error.WriteLine($msg); exit 2 } # ブロックは終了コード 2(1 は警告扱いで通ってしまう)
if ($tool -eq "Bash" -or $tool -eq "PowerShell") {
$cmd = "$($call.tool_input.command)"
if ($cmd -match $secret) { Stop-Hook "認証ファイル(secret_*.json / credentials*.json / .env)に触るコマンドです。止めました。必要なら人が手で行ってください。" }
if ($cmd -match $sched) { Stop-Hook "自動実行の定義(タスクスケジューラ/LaunchAgents)を書き換える操作です。止めました。変えるときは人が手で行ってください。" }
}
if ($tool -eq "Write" -or $tool -eq "Edit") {
$path = "$($call.tool_input.file_path)"
if ($path -match $secret) { Stop-Hook "認証ファイルに直接書き込もうとしています。止めました。" }
if ($path -match 'Library[/\\]LaunchAgents[/\\][^\s]*\.plist$') { Stop-Hook "自動実行の定義に書き込もうとしています。止めました。" }
}
exit 0
.claude/hooks/session-close.ps1# Stop フック(Windows): セッション終了時に「今日の日次サマリーがあるか」だけ確かめる。
# 無ければ systemMessage で知らせる(止めはしない)。置き場は $diaryDir を自分の場所に書き換える。
$projectDir = if ($env:CLAUDE_PROJECT_DIR) { $env:CLAUDE_PROJECT_DIR } else { (Get-Location).Path }
$diaryDir = Join-Path $projectDir "diary" # <自分の置き場に変える>
$today = Get-Date -Format "yyyy-MM-dd"
$file = Join-Path $diaryDir "$today.json"
if (-not (Test-Path $file)) {
$msg = "今日の日次サマリー($file)がまだありません。意味のある作業をしたなら日次サマリーに1件追記してください。"
$obj = @{ systemMessage = $msg }
Write-Output ($obj | ConvertTo-Json -Compress)
}
exit 0
.claude/hooks/inject-core-rules.sh#!/bin/bash
# UserPromptSubmit フック: 毎ターン、最小限だけ注入する。
# 方針の本文は .claude/rules/ に書く(自動で読まれる)。ここで再掲すると二重になるので、
# 出すのは「今の日時」と「rules への指差し1行」だけ。出力は300バイト以下を目安にする。
NOW_DISP=$(date '+%Y-%m-%d (%a) %H:%M')
printf '【現在時刻】%s\n【毎ターン確認】数値は実データのみ/作業前に一言確認/秘密に触らない(詳細は .claude/rules/01-must-follow.md)\n' "$NOW_DISP"
.claude/hooks/guard-sensitive-files.py#!/usr/bin/env python3
"""
PreToolUse フック: 危ないファイル操作を手前で止める。
- 認証ファイル(secret_*.json / credentials*.json / .env)は、読む・書く・消す・コピーのどれも止める(Bash でも Write/Edit でも)
- 自動実行の定義(自分のホームの Library/LaunchAgents/*.plist)への書き込み・削除・コピーを止める(変えるときは人が手で行う)
- launchd を止める操作(launchctl unload / bootout / remove)を止める
標準入力に {"tool_name": ..., "tool_input": {...}} が来る。止めたい時は終了コード 2 と理由(標準エラー)。
"""
import json
import re
import sys
SECRET = r"(secret_[^\s'\"/]*\.json|credentials[^\s'\"/]*\.json|(?:^|[\s/'\"])\.env(?:\.[^\s'\"/]*)?(?=$|[\s'\"]))"
SECRET_RE = re.compile(SECRET)
PLIST_RE = re.compile(r"Library/LaunchAgents/[^\s'\"]*\.plist")
WRITE_VERBS = re.compile(r"\b(rm|mv|cp|tee|sed\s+-i|truncate|chmod|chown|>|>>)\b|>{1,2}\s*\S*Library/LaunchAgents")
def stop(msg: str) -> None:
print(msg, file=sys.stderr)
sys.exit(2) # ブロックは終了コード 2(1 は警告扱いで通ってしまう)
def main() -> None:
try:
data = json.load(sys.stdin)
except Exception:
sys.exit(0)
tool = data.get("tool_name", "")
arg = data.get("tool_input", {}) or {}
if tool == "Bash":
cmd = arg.get("command", "")
if SECRET_RE.search(cmd):
stop("認証ファイル(secret_*.json / credentials*.json / .env)に触るコマンドです。止めました。必要なら人が手で行ってください。")
if PLIST_RE.search(cmd) and (WRITE_VERBS.search(cmd) or re.search(r"\b(rm|mv|cp|tee)\b", cmd)):
stop("自動実行の定義(LaunchAgents の plist)を書き換える操作です。止めました。変えるときは人が手で行ってください。")
if "launchctl" in cmd and re.search(r"\b(unload|bootout|remove)\b", cmd):
stop("自動実行(launchd)を止める操作です。止めました。意図した操作なら人が手で行ってください。")
if tool in ("Write", "Edit"):
path = arg.get("file_path", "")
if SECRET_RE.search(path):
stop("認証ファイルに直接書き込もうとしています。止めました。")
if PLIST_RE.search(path):
stop("自動実行の定義(LaunchAgents の plist)に書き込もうとしています。止めました。変えるときは人が手で行ってください。")
sys.exit(0)
if __name__ == "__main__":
main()
.claude/hooks/session-close.sh#!/bin/bash
# Stop フック: セッション終了時に「今日の日次サマリーがあるか」だけ確かめる。
# 無ければ systemMessage で知らせる(止めはしない)。
# 日次サマリーの置き場は DIARY_DIR を自分の場所に書き換える。
set -uo pipefail
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
DIARY_DIR="${DIARY_DIR:-$PROJECT_DIR/diary}" # <自分の置き場に変える>
TODAY=$(date +%Y-%m-%d)
if [ ! -f "$DIARY_DIR/$TODAY.json" ]; then
MSG="今日の日次サマリー($DIARY_DIR/$TODAY.json)がまだありません。意味のある作業をしたなら日次サマリーに1件追記してください。"
printf '{"systemMessage": "%s"}\n' "$MSG"
fi
exit 0
.claude/settings.json に hooks を3本登録してください。
1) UserPromptSubmit: 現在日時1行と「rules/01を守る」の1行だけを出す PowerShell スクリプト(出力300バイト以下)
2) PreToolUse(Bash|PowerShell|Write|Edit): secret_*.json・credentials*.json・.env に触るコマンド(読む・書く・消す)と、タスクスケジューラの登録変更(schtasks /Create /Delete /Change) を exit 2 で止める PowerShell スクリプト
3) Stop: 当日の日次サマリーJSONが無ければ systemMessage で知らせる PowerShell スクリプト
各hookを単体で実行してバイト数と止まることを確かめて報告してください。方針の本文は hooks に書かず .claude/rules/ に置いてください。.claude/settings.json に hooks を3本登録してください。
1) UserPromptSubmit: 現在日時1行と「rules/01を守る」の1行だけを出す bash(出力300バイト以下)
2) PreToolUse(Bash|Write|Edit): secret_*.json・credentials*.json・.env に触るコマンド(読む・書く・消す)と、Library/LaunchAgents の plist への書き込み・コピー・削除、launchctl unload/bootout/remove を exit 2 で止める python3
3) Stop: 当日の日次サマリーJSONが無ければ systemMessage で知らせる bash
各hookを単体で実行してバイト数と止まることを確かめて報告してください。方針の本文は hooks に書かず .claude/rules/ に置いてください。${CLAUDE_PROJECT_DIR} から組み立てる)。hook に方針文を書くと rules を打ち消す。config.toml に features.hooks = true、内容は hooks.json か [hooks]。PreToolUse など Claude Code と同名のイベント)。書き方は Codex 公式の Hooks ガイド(developers.openai.com/codex)。承認と sandbox(approval_policy/sandbox_mode)も併用する。hook の本数と、allow・deny・ask の項目数を、自分の環境で数えてみる(作業フォルダで実行する)。
$j = Get-Content .claude\settings.json -Raw | ConvertFrom-Json; $j.hooks.PSObject.Properties.Name; $j.permissions.allow.Count; $j.permissions.deny.Count; $j.permissions.ask.Countpython3 -c "import json;j=json.load(open('.claude/settings.json'));print(list(j.get('hooks',{})),{k:len(v) for k,v in j.get('permissions',{}).items() if isinstance(v,list)})".githooks/ を 作業フォルダ ▸ .githooks に置く(Python 3 が要る。無ければ winget install Python.Python.3.12Mac は標準で入っている)bash .githooks/setup_githooks.sh --apply を実行する(git に「フックはここ」と設定し、自己テストが通る)denylist-paths.txt に、リポジトリに入れてはいけないフォルダ名を足す.githooks/pre-commit#!/bin/bash
# pre-commit — ステージ済み差分の秘密情報スキャン
# ステージ済み差分を scan_secrets.py で検査する
# 有効化: bash .githooks/setup_githooks.sh --apply (git config core.hooksPath .githooks)
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
PY="$(command -v python3 || command -v python)" # Windows では python3 が無いことがある
"$PY" "$REPO_ROOT/.githooks/scan_secrets.py" --staged
.githooks/pre-push#!/bin/bash
# pre-push — push対象コミット範囲の秘密情報再スキャン
# --no-verify での pre-commit 突破に対する最終防衛
# 有効化: bash .githooks/setup_githooks.sh --apply (git config core.hooksPath .githooks)
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
PY="$(command -v python3 || command -v python)" # Windows では python3 が無いことがある
ZERO="0000000000000000000000000000000000000000"
STATUS=0
while read -r local_ref local_sha remote_ref remote_sha; do
[ "$local_sha" = "$ZERO" ] && continue
if [ "$remote_sha" = "$ZERO" ]; then
BASE=$(git rev-list --max-parents=0 "$local_sha" | tail -1)
else
BASE="$remote_sha"
fi
if ! "$PY" "$REPO_ROOT/.githooks/scan_secrets.py" --range "$BASE" "$local_sha"; then
STATUS=1
fi
done
exit $STATUS
.githooks/scan_secrets.py#!/usr/bin/env python3
"""秘匿情報スキャナ本体(.githooks/pre-commit・pre-push 共通)
Layer A : 禁止パス(denylist-paths.txt)
Layer A': 一括投入(新規50ファイル以上)— 警告のみ
Layer B : APIトークン形式
Layer C : 平文パスワード(キーワード近接 + 値の非自明性スコアリング)
Layer D : 要配慮個人情報キーワード — 警告のみ
コミットだけでなく push の直前も、同じ検査を通す。
"""
import argparse
import hashlib
import math
import os
import re
import subprocess
import sys
from collections import Counter, defaultdict
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
DENYLIST_PATH = REPO_ROOT / ".githooks" / "denylist-paths.txt"
ALLOWLIST_PATH = REPO_ROOT / ".githooks" / "allowlist.txt"
LOG_DIR = Path.home() / ".harness-starter" / "logs"
SCAN_LOG = LOG_DIR / "git-secret-scan.log"
BYPASS_LOG = LOG_DIR / "git-secret-bypass.log"
BULK_ADD_THRESHOLD = 50
API_TOKEN_PATTERNS = [
r"GOCSPX-[A-Za-z0-9_-]{10,}",
r"AIza[0-9A-Za-z_-]{30,}",
r"token=[A-Za-z0-9_%-]{20,}",
r"BEGIN (RSA |EC |OPENSSH |PGP )?PRIVATE KEY",
r"xox[baprs]-[0-9A-Za-z-]{10,}",
r"ghp_[A-Za-z0-9]{20,}",
r"gho_[A-Za-z0-9]{20,}",
r"ghu_[A-Za-z0-9]{20,}",
r"ghs_[A-Za-z0-9]{20,}",
r"ghr_[A-Za-z0-9]{20,}",
r"AKIA[0-9A-Z]{16}",
r"sk-[A-Za-z0-9_-]{20,}",
r"eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}",
r'"refresh_token"\s*:\s*"[A-Za-z0-9]',
r'"client_secret"\s*:\s*"[A-Za-z0-9]',
]
API_TOKEN_RE = re.compile("|".join(API_TOKEN_PATTERNS))
PII_KEYWORDS_RE = re.compile(
r"(病名|通院|休職|うつ|診断書|退職(予定|意向)|傷病|障害者手帳|マイナンバー|給与額|年収)"
)
KEY_STRONG = r"(?:pass(?:word|wd)?|pwd|pw|パスワード|パス)"
KEY_WEAK = r"(?:secret|api[_-]?key|access[_-]?key|auth[_-]?key|認証キー|暗証)"
CRED_ASSIGN_RE = re.compile(
r"(?i)(?P<key>" + KEY_STRONG + "|" + KEY_WEAK + r")\s*(?:[:=:=]|=>|->|:=)\s*"
r'(?P<q>["\'`「]?)(?P<val>[^\s"\'`「」,;、。]{4,80})(?P=q)?'
)
PLACEHOLDER_WORDS = {
"xxx", "***", "redacted", "masked", "dummy", "sample", "example",
"changeme", "change_me", "your_password", "todo", "tbd", "null", "none",
"nil", "true", "false", "n/a", "未設定", "非公開", "省略", "ここに入力", "-", "—",
}
TYPE_WORDS = {
"str", "string", "text", "varchar", "char", "int", "bool",
"optional[str]", "string!",
}
def shannon_entropy(s):
if not s:
return 0.0
counts = Counter(s)
length = len(s)
return -sum((c / length) * math.log2(c / length) for c in counts.values())
def is_function_call_or_subscript(val):
return bool(re.match(r"^[A-Za-z_][\w.]*\s*[(\[]", val))
def is_placeholder_expr(val):
return bool(re.search(r"\$\{|\{\{|%s|%\(|[<>]", val))
def is_bare_var_reference(quote, val):
"""引用符なし・アンダースコア区切り・大文字小文字が統一された識別子のみ変数参照とみなす
(例: user_password, PASSWORD_CONST)。ランダムな大小混在文字列(実パスワード)は除外しない"""
if quote:
return False
if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", val):
return False
if "_" not in val:
return False
return val.islower() or val.isupper()
def score_credential(key, val):
low = val.lower().strip(".,;:")
if low in PLACEHOLDER_WORDS or low in TYPE_WORDS:
return None
if re.match(r"^[{\[]", val):
return None
score = 0
if len(val) >= 12:
score += 2
elif len(val) >= 8:
score += 1
if re.search(r"\d", val):
score += 1
if re.search(r"[a-z]", val) and re.search(r"[A-Z]", val):
score += 1
if re.search(r"[^A-Za-z0-9]", val):
score += 1
if shannon_entropy(val) >= 2.3:
score += 1
if re.match(KEY_STRONG, key, re.IGNORECASE):
score += 1
if re.match(r"^[A-Za-z]+$", val) and len(val) <= 12:
score -= 3
if re.search(r"[ぁ-んァ-ヶ一-龠ー]", val):
score -= 3
if re.match(r"^\d+$", val) and len(val) <= 6:
score -= 1
return score
def find_plaintext_passwords(line):
"""1行から (score, matched_text, val) の候補一覧を返す。呼び出し側で score>=閾値 を判定する"""
findings = []
for m in CRED_ASSIGN_RE.finditer(line):
val = m.group("val")
quote = m.group("q")
key = m.group("key")
if is_function_call_or_subscript(val):
continue
if is_placeholder_expr(val):
continue
if is_bare_var_reference(quote, val):
continue
score = score_credential(key, val)
if score is None:
continue
findings.append((score, m.group(0), val))
return findings
def mask(val):
if len(val) <= 4:
return "*" * len(val)
return val[:2] + "*" * (len(val) - 4) + val[-2:]
def fingerprint(text):
return hashlib.sha256(text.encode("utf-8", "ignore")).hexdigest()[:12]
def load_lines(path):
if not path.exists():
return []
out = []
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
out.append(line)
return out
def load_allowlist_fingerprints():
fps = set()
for line in load_lines(ALLOWLIST_PATH):
parts = line.split()
if parts:
fps.add(parts[0])
return fps
def run_git(args):
return subprocess.run(
["git"] + args, cwd=REPO_ROOT, capture_output=True, text=True, check=False,
errors="replace", # 非UTF-8バイトを含む差分(バイナリ/.gz)で落ちないようにする
)
def get_changed_files(mode, rev_range):
if mode == "staged":
res = run_git(["diff", "--cached", "--name-status", "-z"])
else:
res = run_git(["diff", f"{rev_range[0]}..{rev_range[1]}", "--name-status", "-z"])
entries = res.stdout.split("\0")
files = []
i = 0
while i < len(entries):
status = entries[i]
if not status:
i += 1
continue
if status.startswith("R") or status.startswith("C"):
new_path = entries[i + 2]
files.append((status[0], new_path))
i += 3
else:
path = entries[i + 1]
files.append((status[0], path))
i += 2
return files
def get_added_lines(mode, rev_range):
if mode == "staged":
res = run_git(["diff", "--cached", "-U0", "--no-color"])
else:
res = run_git(["diff", f"{rev_range[0]}..{rev_range[1]}", "-U0", "--no-color"])
diff_text = res.stdout
current_file = None
added = defaultdict(list)
line_no = 0
for raw in diff_text.splitlines():
if raw.startswith("+++ "):
path = raw[4:]
if path.startswith("b/"):
path = path[2:]
current_file = path
continue
if raw.startswith("@@"):
m = re.search(r"\+(\d+)", raw)
line_no = int(m.group(1)) if m else 0
continue
if raw.startswith("+") and not raw.startswith("+++"):
if current_file:
added[current_file].append((line_no, raw[1:]))
line_no += 1
return added
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--staged", action="store_true")
parser.add_argument("--range", nargs=2, metavar=("BASE", "HEAD"))
args = parser.parse_args()
mode = "staged" if args.staged else "range"
rev_range = args.range if args.range else None
denylist_patterns = [re.compile(p) for p in load_lines(DENYLIST_PATH)]
allowlist_fps = load_allowlist_fingerprints()
changed = get_changed_files(mode, rev_range)
added_paths = [p for status, p in changed if status == "A"]
path_blocks = []
content_blocks = []
warns = []
# Layer A: 禁止パス
for status, path in changed:
for pat in denylist_patterns:
if pat.search(path):
path_blocks.append({"label": "Layer A: 禁止パス", "loc": path, "shown": path, "pattern": pat.pattern})
break
# Layer A': 一括投入警告
if len(added_paths) >= BULK_ADD_THRESHOLD:
top_dirs = Counter(p.split("/")[0] for p in added_paths)
summary = ", ".join(f"{d}({n})" for d, n in top_dirs.most_common(5))
warns.append({"label": "Layer A': 一括投入", "loc": f"{len(added_paths)}件新規追加", "shown": summary})
added_lines = get_added_lines(mode, rev_range)
for path, lines in added_lines.items():
for lineno, text in lines:
if "REDACTED" in text or "MASKED" in text:
continue
m = API_TOKEN_RE.search(text)
if m:
matched = m.group(0)
fp = fingerprint(matched)
if fp not in allowlist_fps:
content_blocks.append({"label": "Layer B: APIトークン形式", "loc": f"{path}:{lineno}", "shown": mask(matched), "fp": fp})
continue
for score, matched, val in find_plaintext_passwords(text):
fp = fingerprint(matched)
if fp in allowlist_fps:
continue
label = f"Layer C: 平文パスワード疑い score={score}"
if score >= 3:
content_blocks.append({"label": label, "loc": f"{path}:{lineno}", "shown": mask(val), "fp": fp})
elif score >= 2:
warns.append({"label": label, "loc": f"{path}:{lineno}", "shown": mask(val)})
if PII_KEYWORDS_RE.search(text):
warns.append({"label": "Layer D: 要配慮個人情報キーワード", "loc": f"{path}:{lineno}", "shown": text.strip()[:60]})
if warns:
LOG_DIR.mkdir(parents=True, exist_ok=True)
with open(SCAN_LOG, "a", encoding="utf-8") as f:
for w in warns:
f.write(f'{w["label"]} | {w["loc"]} | {w["shown"]}\n')
print("⚠️ WARN(コミットは通します):", file=sys.stderr)
for w in warns:
print(f' [{w["label"]}] {w["loc"]}: {w["shown"]}', file=sys.stderr)
blocks = path_blocks + content_blocks
if not blocks:
return 0
secret_ok = os.environ.get("SECRET_OK", "").strip()
denypath_ok = os.environ.get("DENYPATH_OK", "").strip()
secret_ok_valid = len(secret_ok) >= 10
can_bypass_content = secret_ok_valid
can_bypass_path = secret_ok_valid and denypath_ok == "1"
fully_bypassable = (not content_blocks or can_bypass_content) and (not path_blocks or can_bypass_path)
if fully_bypassable:
LOG_DIR.mkdir(parents=True, exist_ok=True)
with open(BYPASS_LOG, "a", encoding="utf-8") as f:
for b in blocks:
f.write(f'BYPASS reason="{secret_ok}" denypath_ok={denypath_ok} | {b["label"]} | {b["loc"]}\n')
print(f"⚠️ 秘匿情報検出をバイパスしました(理由: {secret_ok})。ログ: {BYPASS_LOG}", file=sys.stderr)
return 0
print("❌ 秘匿情報らしき文字列/禁止パスが検出されたため、コミットを中止しました。", file=sys.stderr)
for b in blocks:
print(f' [{b["label"]}] {b["loc"]}: {b["shown"]}', file=sys.stderr)
print("", file=sys.stderr)
print("対処:", file=sys.stderr)
print(" 1) 値を環境変数へ移して、ファイルには書かない(参照だけにする)", file=sys.stderr)
print(" 2) 該当ファイルを .gitignore に追加する", file=sys.stderr)
if content_blocks:
print(" 3) 誤検知の場合: .githooks/allowlist.txt に以下を追記(フィンガープリント 日付 理由)", file=sys.stderr)
for b in content_blocks:
print(f' {b["fp"]} {b["loc"]} 理由: <ここに理由を記入>', file=sys.stderr)
print("", file=sys.stderr)
print("正当な例外投入の場合のみ(記録が残ります):", file=sys.stderr)
print(' SECRET_OK="具体的な理由(10文字以上)" git commit ...', file=sys.stderr)
if path_blocks:
print(" ※ 禁止パスの例外投入は DENYPATH_OK=1 も併せて必要です", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
.githooks/setup_githooks.sh#!/bin/bash
# .githooks(秘匿情報スキャン)の有効化+自己診断
# 使い方:
# bash .githooks/setup_githooks.sh # 現状診断のみ(既定・安全)
# bash .githooks/setup_githooks.sh --apply # core.hooksPath設定+自己テスト実行
#
# core.hooksPath はリポジトリローカル設定のため、clone・.git再作成で黙って消える。
# 定期的に diff なしで実行して現状を確認すること。
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
APPLY=0
[ "${1:-}" = "--apply" ] && APPLY=1
echo "=== git秘匿情報スキャンhook 診断 ==="
CURRENT=$(git config core.hooksPath 2>/dev/null || echo "(未設定)")
echo "現在の core.hooksPath: $CURRENT"
if [ "$APPLY" = "1" ]; then
git config core.hooksPath .githooks
chmod +x .githooks/pre-commit .githooks/pre-push
echo "→ core.hooksPath を .githooks に設定しました"
else
if [ "$CURRENT" != ".githooks" ]; then
echo "⚠️ core.hooksPath が .githooks に設定されていません。--apply を付けて再実行してください。"
fi
fi
PY="$(command -v python3 || command -v python || true)"
if [ -z "$PY" ]; then
echo "❌ python3 / python が見つかりません(Windows は winget install Python.Python.3.12)" >&2
exit 1
fi
echo "✅ python: $($PY --version)"
if [ "$APPLY" = "1" ]; then
echo ""
echo "=== 自己テスト(ダミー平文パスワードでBLOCK判定を確認) ==="
RESULT=$("$PY" -c "
import sys
sys.path.insert(0, '.githooks')
from scan_secrets import find_plaintext_passwords
hits = find_plaintext_passwords('パスワード: Xk8vQ2mfLp01')
blocked = [h for h in hits if h[0] >= 3]
print('BLOCK' if blocked else 'PASS')
")
if [ "$RESULT" = "BLOCK" ]; then
echo "✅ 自己テスト成功: ダミー平文パスワードがBLOCK判定されました"
else
echo "❌ 自己テスト失敗: ダミー平文パスワードがBLOCKされませんでした(要調査)" >&2
exit 1
fi
fi
echo ""
echo "完了。"
.githooks/denylist-paths.txt# 禁止パスパターン(Python正規表現・部分一致)。ここに合うパスは追加投入を自動でブロックする。
# 例外投入は SECRET_OK="理由" + DENYPATH_OK=1 の両方が必要(scan_secrets.py 参照)
# 例: 個人のメモ置き場・人物名簿・生データのアーカイブなど、リポジトリに入れてはいけない場所を書く
(^|/)private-notes(/|$)
(^|/)people(/|$)
(^|/)raw-archive(/|$)
(^|/)\.env(\.[^/]+)?$
.githooks/allowlist.txt# 誤検知の恒久除外リスト(1行1件: フィンガープリント 日付 理由)
# フィンガープリントは scan_secrets.py の BLOCK 出力にある12桁の値をそのままコピーする
# 例: a1b2c3d4e5f6 2026-01-01 テストコードのダミー値のため誤検知と確認済み
.githooks/pre-commit と pre-push を作り、git config core.hooksPath .githooks で有効化してください。追加行に対して
APIキー形式・平文パスワード・禁止パス(private-notes/ 等)を検出してブロック。誤検知はフィンガープリント付き allowlist、
例外投入は理由付き環境変数でバイパスしログに残す。差分に非UTF-8が混ざっても落ちないよう errors="replace" で読む。.claude/skills/daily-summary/ と .claude/commands/日誌.md を、作業フォルダの同じ位置に置く(日次サマリーを /日誌 の一言で追記するスキル)/日誌 と打ち、日次サマリーが追記されることを確かめる<作業名> と <id> を書き換えると、その仕事の SKILL.md と日本語の入口ができる。形は tools/skill_lint.py で検査できる.claude/skills/daily-summary/SKILL.md---
name: daily-summary
description: 日次サマリーを作成・追記。「日次サマリー作って」「今日のまとめ」「/日誌」で起動。
---
# daily-summary — 日次サマリー
## 目的
意味のある作業が終わるたびに、その日の記録を1ファイルに残す。翌日の自分と、週報・月報の材料になる。
## 出力先
`diary/YYYY-MM-DD.json`(作業フォルダの `diary/`。置き場を変えるなら `.claude/hooks/session-close`(Windows は .ps1・Mac は .sh)の置き場も合わせる)
## 手順
1. 当日ファイル `diary/YYYY-MM-DD.json` があるか確かめる(日付は推測せず、システムの日付で実測する。Mac は `date +%F`、Windows は `Get-Date -Format yyyy-MM-dd`)
2. あれば `accomplishments` に1件追記、なければ `reference/format.md` の形で新規作成する
3. `summary` は既存の文を消さず、末尾に「 / 新しい要点」を足す
4. 会社名・顧客名・個人が特定できる情報は書かない
## 書き方
- `accomplishments`: 何をして、どうなったか(1件1〜2文)
- `tips_discovered`: 「〔場面〕で〔工夫〕したら〔結果〕だった」の形。ファイル名・技術用語は書かない
- 書式の正本は `reference/format.md`(必要な時だけ読む)
## 受け入れ
```sh
python3 -c "import json,datetime; json.load(open('diary/%s.json' % datetime.date.today()))" # Mac
python -c "import json,datetime; json.load(open('diary/%s.json' % datetime.date.today()))" # Windows
```
当日ファイルが JSON として読めれば合格。
## 検品の観点
- 当日の日付のファイルに追記されている(別の日付に書いていない)
- 会社名・顧客名・個人が特定できる情報が入っていない
- 既存の項目が消えていない(追記であって上書きでない)
.claude/skills/daily-summary/reference/format.md# daily-summary の JSON 書式
```json
{
"date": "YYYY-MM-DD",
"accomplishments": ["本日の作業(何をして、どうなったか)"],
"tips_discovered": ["再利用できる仕事の気づき"],
"summary": "一行サマリー"
}
```
- `date`: 対象日
- `accomplishments`: その日の作業。1件1〜2文
- `tips_discovered`: 「〔場面〕で〔工夫〕したら〔結果〕だった」
- `summary`: 要点を一行。追記する時は既存の文を残して「 / 」でつなぐ
既存 JSON を更新する時は、配列の既存項目を残したまま追加する。
.claude/commands/日誌.md---
description: 日誌係 — /daily-summary の日本語エイリアス(名簿: context/ai-org.md)
argument-hint: "なし"
---
これは日誌係(/daily-summary)の日本語呼び出しです。
`.claude/skills/daily-summary/SKILL.md` を読み、そこに書かれた手順に従ってそのまま実行してください。
引数: $ARGUMENTS
tools/skill_lint.py#!/usr/bin/env python3
"""Claude Codeスキルの形式(説明文・本文の量・参照の実在・受け入れコマンド)を検査する。"""
from __future__ import annotations
import re
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple
MAX_BODY_BYTES = 6000
SAMPLE_EXEMPTIONS = {"daily-summary"}
HISTORY_PATTERN = re.compile(
r"(20\d{2}-\d{2}-\d{2}\s+(?:追加|更新|確立|指摘))"
)
REFERENCE_PATTERN = re.compile(
r"(?<![A-Za-z0-9_./-])((?:automation|tools|reference)/[A-Za-z0-9_./-]+)"
)
COMMAND_PATTERN = re.compile(
r"^(?:python3?|bash|sh|zsh|node|npm|npx|pytest|make|go|ruby|php|git|curl|uv)(?:\s|$)"
)
TRIGGER_WORDS = (
"起動",
"使用",
"依頼",
"作成",
"作って",
"確認",
"生成",
"変換",
"整理",
"分析",
"登録",
"レビュー",
"チェック",
)
def parse_frontmatter(text: str) -> Tuple[Dict[str, str], str, List[str]]:
"""先頭の簡易YAML frontmatterと本文を分離する。"""
lines = text.splitlines(keepends=True)
errors: List[str] = []
if not lines or lines[0].strip() != "---":
return {}, "", ["先頭に frontmatter の開始記号 `---` がありません"]
end_index: Optional[int] = None
for index in range(1, len(lines)):
if lines[index].strip() == "---":
end_index = index
break
if end_index is None:
return {}, "", ["frontmatter の終了記号 `---` がありません"]
fields: Dict[str, str] = {}
front_lines = lines[1:end_index]
index = 0
while index < len(front_lines):
raw_line = front_lines[index].rstrip("\r\n")
if not raw_line.strip():
index += 1
continue
match = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", raw_line)
if not match:
errors.append(f"frontmatter の解釈できない行: {raw_line}")
index += 1
continue
key, value = match.groups()
if value in {"|", ">"}:
continuation: List[str] = []
index += 1
while index < len(front_lines):
continuation_line = front_lines[index].rstrip("\r\n")
if continuation_line and not continuation_line[0].isspace():
break
continuation.append(continuation_line.strip())
index += 1
separator = "\n" if value == "|" else " "
fields[key] = separator.join(continuation).strip()
continue
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"}:
value = value[1:-1]
fields[key] = value
index += 1
body = "".join(lines[end_index + 1 :])
return fields, body, errors
def section_body(body: str, heading: str) -> Optional[str]:
"""指定されたH2節の本文を返す。"""
heading_pattern = re.compile(r"^##\s+" + re.escape(heading) + r"\s*$", re.MULTILINE)
match = heading_pattern.search(body)
if not match:
return None
next_heading = re.search(r"^##\s+", body[match.end() :], re.MULTILINE)
end = match.end() + next_heading.start() if next_heading else len(body)
return body[match.end() : end]
def has_executable_command(section: str) -> bool:
"""節内のコードブロックまたはインラインコードからコマンドを探す。"""
fenced_blocks = re.findall(r"```[^\n]*\n(.*?)```", section, re.DOTALL)
candidates = fenced_blocks + re.findall(r"`([^`\n]+)`", section)
for block in candidates:
for line in block.splitlines():
command = line.strip().lstrip("$> ")
if COMMAND_PATTERN.match(command):
return True
return False
def referenced_paths(body: str) -> List[str]:
"""検査対象にしている相対パスを重複なしで返す。"""
paths: List[str] = []
for match in REFERENCE_PATTERN.finditer(body):
path = match.group(1).rstrip(".,;:。、")
if path not in paths:
paths.append(path)
return paths
def lint_skill(skill_dir: Path) -> List[str]:
errors: List[str] = []
skill_file = skill_dir / "SKILL.md"
if not skill_file.is_file():
return [f"SKILL.md がありません: {skill_file}"]
try:
text = skill_file.read_text(encoding="utf-8")
except UnicodeDecodeError as exc:
return [f"SKILL.md がUTF-8ではありません: {exc}"]
fields, body, frontmatter_errors = parse_frontmatter(text)
errors.extend(frontmatter_errors)
name = fields.get("name", "").strip()
description = re.sub(r"\s+", " ", fields.get("description", "")).strip()
if not name:
errors.append("frontmatter に name がありません")
if not description:
errors.append("frontmatter に description がありません")
elif len(description) > 60:
errors.append(f"description が60文字を超えています: {len(description)}文字")
elif not (
any(word in description for word in TRIGGER_WORDS)
or (name and name in description)
or (name and f"/{name}" in description)
):
errors.append("description にトリガー語がありません")
body_bytes = len(body.encode("utf-8"))
if body_bytes > MAX_BODY_BYTES:
errors.append(f"SKILL.md 本文が6000バイトを超えています: {body_bytes}バイト")
if HISTORY_PATTERN.search(body):
errors.append("本文に日付付き変更履歴があります")
repo_root = Path(__file__).resolve().parent.parent
for relative_path in referenced_paths(body):
if relative_path.startswith("reference/"):
target = skill_dir / relative_path
else:
target = repo_root / relative_path
if not target.exists():
errors.append(f"参照先が存在しません: {relative_path}")
skill_id = name or skill_dir.name
if skill_id not in SAMPLE_EXEMPTIONS and not (skill_dir / "reference" / "sample.md").is_file():
errors.append("reference/sample.md がありません")
acceptance = section_body(body, "受け入れ")
if acceptance is None:
errors.append("`## 受け入れ` 節がありません")
elif not has_executable_command(acceptance):
errors.append("`## 受け入れ` 節に実行可能なコマンドがありません")
inspection = section_body(body, "検品の観点")
if inspection is None:
errors.append("`## 検品の観点` 節がありません")
else:
inspection_lines = [line for line in inspection.splitlines() if line.strip()]
if len(inspection_lines) < 3:
errors.append("`## 検品の観点` 節が3行未満です")
return errors
def main(argv: List[str]) -> int:
if len(argv) != 2:
print("使い方: python3 tools/skill_lint.py .claude/skills/<id>", file=sys.stderr)
return 2
skill_dir = Path(argv[1]).resolve()
if not skill_dir.is_dir():
print(f"不合格: スキルディレクトリがありません: {skill_dir}", file=sys.stderr)
return 1
errors = lint_skill(skill_dir)
if errors:
print(f"不合格: {skill_dir}", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
return 1
print(f"合格: {skill_dir}")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))
「<作業名>」を .claude/skills/<id>/SKILL.md として作ってください。frontmatter は name と description(60文字以内・
トリガー語を含む)。本文は目的3行・入力表・番号付き手順・受け入れコマンド・出力先・検品の観点(3行以上)で6KB以下。
書式テンプレ reference/format.md と、理想の完成品1本 reference/sample.md(skill_lint が必須とするファイル名)を reference/ に置き、手順から「必要な時に読む」と参照する。日付付きの変更履歴は書かない。
完了条件は python3 tools/skill_lint.py .claude/skills/<id> の終了コード0。
.claude/commands/議事録.md を作り、description を「議事録係 — /minutes の日本語エイリアス」(20文字程度)、本文は
「.claude/skills/minutes/SKILL.md を読み手順どおり実行。引数: $ARGUMENTS」だけにしてください。/日誌 の一言で日次サマリーが追記され、新セッションのスキル一覧に説明文が出る。/skills で一覧、$名前 で呼ぶ。作るなら $skill-creator)。参考サンプルの SKILL.md はそこへコピーすれば使える。日本語の入口(.claude/commands)は Claude Code だけの仕組み。スキルの本数と入口コマンドの本数を、自分の環境で数えてみる(作業フォルダで実行する)。
(Get-ChildItem .claude\skills -Directory).Count; (Get-ChildItem .claude\commands -Filter *.md).Countls -d .claude/skills/*/ | wc -l; ls .claude/commands/*.md | wc -l.claude/agents/kenpin.md を 作業フォルダ ▸ .claude ▸ agents にコピーするcheckup/kenpin-paste-prompt.md を新しいチャットに貼る.claude/agents/kenpin.md---
name: kenpin
description: 検品係 — レポート・議事録・文面・登録データなど納品物の最終チェック専門。納品前にまっさらな文脈で合否を判定する。修正はしない。
tools: Read, Grep, Glob, Bash
---
# 検品タスク
## タスク
納品物(レポート・議事録・スライド・メール文面・登録データ)を、作った側とは独立した目でチェックし、**合否と指摘だけ**を返す。
## 鉄則
- **修正しない。実装案・書き直し案も出さない**(作成者の文脈に引き戻されるため)
- 褒めない・励まさない。仕事は「通すか、止めるか」の判定だけ
- 判定できない項目は「未検証」と正直に書く(勝手に合格にしない)
## チェック観点(自分の環境のルールに合わせて書き換える)
1. **数値の根拠**: 件数・金額・割合・目標値に出典(ファイル・コマンドの結果)があるか。出典と食い違う数値は high。**出典が見つからない数値は medium の不合格**にし、「未検証」に逃がさない
2. **表記ルール**: フッター・敬称・言語が自分の環境の決まりどおりか(例: 「<会社名> | CONFIDENTIAL」・人名は「さん」付け)。人名は名簿(例: `context/team.md`)と照合し、名簿にない名前は変換ミスを疑って不合格にする
3. **禁止事項**: 秘密(認証ファイル・個人情報)が含まれていないか。社内データを外部に置くことを促していないか
4. **宛先**: 送る先・書き込む先(アカウント・フォルダ)が正しいか
5. **完了条件との照合**: 頼まれた要件をすべて満たすか。頼まれていない追加や整理をしていないか
6. **成果物の実在**: 報告されたファイル・URL・登録が実在するか(パスを Read/Bash で実際に確かめる)
7. **日本語の読みやすさ(文章のとき)**: 次に当たる所がないか。重さは原則 low
- 出来事の日付と書いた順が逆
- 並べた事柄の形がそろっていない
- 述語のない語句を読点だけで3つ以上並べている
- 独立した2つの事実を読点だけでつないでいる
- 同じ略語・用語に違う訳語や表記を使っている(例: 同じ指標を「一次解決率」と「初回解決率」)
- 意味の取りにくい縮め語
## 合格の条件
- high・medium の指摘が0件のときだけ合格(`passes: true`)。low だけなら合格のまま指摘を返す
- 3段の判定 `verdict`: 指摘なし=「合格」/low だけ=「要改善」/high か medium がある=「不合格」
## 返答形式(これ以外を出力しない)
```json
{
"passes": true または false,
"verdict": "合格" または "要改善" または "不合格",
"failures": [
{"item": "対象箇所", "reason": "何が問題か → 放置するとどうなるか", "severity": "high|medium|low"}
],
"unverified": ["検証できなかった項目とその理由"]
}
```
## 呼ぶ側の決まり(作った側の Claude Code が守る)
- `Agent(subagent_type="kenpin", model="<使うモデル。例: opus>")` で、まっさらな文脈で呼ぶ
- 渡すのは**成果物と評価基準(完了条件・出典の場所)だけ**。作った経緯・苦労・「ここは直した」などの説明は渡さない(採点が甘くなる)
- 同じ成果物の検品は**最大3回**(最初の1回を含む)。3回目も不合格なら直すのをやめ、成果物ではなく基準か指示を疑って人が見る
- この定義を書き換えたら、わざと壊した成果物(数字の出典が無い・用語が揺れている等)を1〜2本作って通し、落とせるかを確かめる
checkup/kenpin-paste-prompt.md# 貼るだけで使える検品の指示文(ChatGPT・Gemini・Claude 共通)
作った AI とは**別の新しいチャット**に貼る。同じチャットで「チェックして」と頼むと、自分の成果物に甘くなる。
社外秘の文章を貼るときは、会社で使ってよいサービスか先に確かめ、取引先名・個人名は伏せ字(〇〇)にする。
```
下の成果物を、下の評価基準だけで判定してください。
直さないでください。書き直し案も出さないでください。褒めないでください。
# 評価基準(番号順に判定する)
1. 数値: 出典(下に貼った資料)と一致するか。出典と食い違う数値は「重」、出典が見つからない数値は「中」
2. 表記: (自分の決まりを書く。例: フッターは「〇〇株式会社 | CONFIDENTIAL」のみ・人名は「さん」付け)
3. 完了条件: (頼んだことを書く。例: 今週の件数と前週比が入っている)
4. 読みやすさ(重さは「軽」): 日付と書いた順が逆/並べた形がそろわない/同じ用語に違う呼び方
# 合格の条件
「重」と「中」が0件のときだけ合格。「軽」だけなら「要改善」。
判定: 合格/要改善/不合格 のどれか1つ。
# 出力
- 判定(1語)
- 指摘の表: 場所(原文をそのまま引用)/何が問題か/放置するとどうなるか/重さ(重・中・軽)
- 確かめられなかったこと(あれば)
# 成果物
(ここに貼る)
# 出典
(ここに貼る。無ければ「なし」と書く)
```
## 使い方の決まり
- 渡すのは**成果物と評価基準(と出典)だけ**。作った経緯や「ここは直した」という説明は渡さない(採点が甘くなる)
- 検品は同じ成果物で**最大3回**(最初の1回を含む)。3回目も不合格なら、成果物ではなく評価基準か最初の指示を疑う
- 評価基準は「分かりやすいか」のような抽象語で書かない。「前週比が入っているか」のように、はい・いいえで答えられる形にする
出どころ: 作る役と採点する役を分ける設計(Anthropic 公式の evaluator-optimizer パターンを日本語で解説した公開記事)と、合格の条件を「すべての基準を満たしたときだけ合格」と先に決める書き方。
いま作った成果物(<ファイル名>)を検品係にかけてください。Agent ツールで subagent_type="kenpin"、model="<使うモデル名。例: opus>" を指定して、まっさらな文脈で呼んでください。渡すのは成果物の場所と評価基準(頼んだ完了条件・数字の出典の場所)だけにし、作った経緯や直した点の説明は渡さないでください。返ってきた判定(合格・要改善・不合格)と指摘をそのまま私に報告し、勝手に直さないでください。config.toml)。無い場合は、成果物を新しい Codex セッションに渡して「合否と指摘だけ返して。修正はしないで」と頼めば同じ効果になる。サブエージェントの本数を、自分の環境で数えてみる(作業フォルダで実行する)。
(Get-ChildItem .claude\agents -Filter *.md).Countls .claude/agents/*.md | wc -lcontext/ai-org.md を 作業フォルダ ▸ context に置くcontext/ai-org.md# 係の名簿(サンプル: 3課+夜勤チーム・係4つから始める)
運用: スキル・自動化を新設したら、この名簿に1行足す。人名は使わず係名だけ。呼び出しは日本語コマンドか自然文のどちらでも。
## 組織図
```
🏢 <仕事の名前> の係(担当: <あなた> / 現場統括: Claude Code 本体)
│
├─ 📋 秘書室 …………… 毎日の記録
├─ 📝 会議事務局 ……… 会議の前後
└─ 🛡 品質管理室 ……… 納品前の検品
```
## 名簿
### 📋 秘書室
| 係名 | 呼び出し | 実体 | 仕事 |
|---|---|---|---|
| 日誌係 | **/日誌**(=/daily-summary) | daily-summary | 日次サマリーを1日1ファイルに追記する |
### 📝 会議事務局
| 係名 | 呼び出し | 実体 | 仕事 |
|---|---|---|---|
| 議事録係 | **/議事録**(=/minutes) | minutes(第8章の指示文で作る例) | 文字起こしから議事録を作り、決定事項と宿題を出す |
### 🛡 品質管理室
| 係名 | 呼び出し | 実体 | 仕事 |
|---|---|---|---|
| 検品係 | `Agent(subagent_type="kenpin", model="<使うモデル。例: opus>")` | .claude/agents/kenpin.md | 納品物の合否だけを返す(修正はしない。まっさらな文脈で呼ぶ) |
### 🌙 夜勤チーム(無人で動くもの。時刻と実体を書く)
| 係名 | 出勤時刻 | 実体 | 仕事 |
|---|---|---|---|
| 日誌配達員 | 毎朝 07:30 | automation/windows/run_daily_job.ps1(Mac は automation/mac/run_daily_job.sh) | 前日の日次サマリーを保管フォルダへ写す |
## 増やし方
1. 仕事を1文で決める(入力→出力)。1文にならなければ係を分ける
2. 実体を作る(スキルなら SKILL.md、無人ならスクリプト+plist)
3. 日本語の入口を1つ作る(.claude/commands/<係名>.md)
4. ここに1行足す
5. 3か月使わなかった係は退避する
context/ai-org.md(係の名簿)に、新しい係を1行追加してください。列は「課|係名|呼び出し|実体|仕事」で(課は既存の見出しのどれかに入れる)、係名は人名を使わず係名だけ、呼び出しは日本語コマンド(例: /議事録)と実体の id、仕事は1文(何を入力に何を出すか)。追加後、名簿の表と .claude/skills/・.claude/commands/ の実体が食い違っていないか(名簿にあって実体が無い/実体があって名簿に無い)を一覧で報告してください。名簿以外のファイルは変更しないでください。/係名 で呼ぶと、その手順が動く。$名前(Codex のスキル呼び出し)に読み替える。名簿の表の行数(見出しの行を引く)を、自分の環境で数えてみる(作業フォルダで実行する)。
(Select-String -Path context\ai-org.md -Pattern '^\|').Countgrep -c '^|' context/ai-org.mdautomation/daily_job.py と automation/windows/ を 作業フォルダ ▸ automation にコピーする(毎朝 07:30 に前日の日次サマリーを写す無害なジョブ)powershell -ExecutionPolicy Bypass -File automation\windows\register_task.ps1 を実行して登録内容を見てから、-Apply を付けて登録するschtasks /Query /TN "my-harness\daily-job" で登録が見える。翌朝、automation\windows\daily_job.log が増えていることを見るautomation/stop-conditions.md を 作業フォルダ ▸ automation にコピーし、中の指示文で「止まる条件」(試行の上限・制限時間・枠が切れたら・失敗に誰が気づくか)を点検するautomation/daily_job.py と automation/mac/ を 作業フォルダ ▸ automation にコピーする(毎朝 07:30 に前日の日次サマリーを写す無害なジョブ)bash automation/mac/deploy.sh を実行して何が配布されるか見てから、--apply で実行場所(自分のホーム ▸ Library ▸ Scripts)へコピーし launchd に登録するlaunchctl list | grep com.example に載ることを確かめる。翌朝、ログ(自分のホーム ▸ Library ▸ Logs)が増えていることを見るautomation/daily_job.py#!/usr/bin/env python3
"""daily_job.py — 前日の日次サマリー(diary/YYYY-MM-DD.json)を保管フォルダへ写す。
最初の無人ジョブに向く「失敗しても被害がない」仕事。環境変数で場所を変える:
DIARY_DIR 日次サマリーの置き場(既定: <作業フォルダ>/diary)
ARCHIVE_DIR 保管先(既定: ~/Documents/diary-archive)
終了コード: 0=写した/0=前日分が無い(何もしない)/1=失敗
"""
import datetime as dt
import os
import shutil
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
DIARY_DIR = Path(os.environ.get("DIARY_DIR", HERE.parent / "diary"))
ARCHIVE_DIR = Path(os.environ.get("ARCHIVE_DIR", Path.home() / "Documents" / "diary-archive"))
def main() -> int:
day = (dt.date.today() - dt.timedelta(days=1)).isoformat()
src = DIARY_DIR / f"{day}.json"
if not src.exists():
print(f"skip: {src} が無い")
return 0
try:
ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, ARCHIVE_DIR / src.name)
print(f"copied: {src} -> {ARCHIVE_DIR / src.name}")
return 0
except OSError as e:
print(f"error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
automation/windows/run_daily_job.ps1# run_daily_job.ps1 — 毎朝1回の無害な無人ジョブ(Windows タスクスケジューラから起動)
# 前日の日次サマリーを保管フォルダへ写す。失敗は終了コードで返す(0以外=失敗。タスクの「前回の実行結果」に残る)
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$log = Join-Path $here "daily_job.log"
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
& python (Join-Path $here "..\daily_job.py") 2>&1 | Tee-Object -FilePath $log -Append
$rc = $LASTEXITCODE
Add-Content -Path $log -Value "[$stamp] run_daily_job: done rc=$rc"
exit $rc
automation/windows/register_task.ps1# register_task.ps1 — タスクスケジューラに「毎朝 07:30 に run_daily_job.ps1 を実行」を登録する(Windows)
# 使い方(PowerShell で。管理者権限は不要):
# powershell -ExecutionPolicy Bypass -File automation\windows\register_task.ps1 # 登録内容を表示だけ
# powershell -ExecutionPolicy Bypass -File automation\windows\register_task.ps1 -Apply # 実際に登録
param([switch]$Apply)
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$script = Join-Path $here "run_daily_job.ps1"
$taskName = "my-harness\daily-job" # <自分の名前に変える>
$action = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$script`""
Write-Output "タスク名: $taskName"
Write-Output "実行内容: $action"
Write-Output "時刻: 毎日 07:30"
if ($Apply) {
schtasks /Create /F /SC DAILY /ST 07:30 /TN $taskName /TR $action | Out-Null
Write-Output "登録しました。確認: schtasks /Query /TN `"$taskName`" /V /FO LIST"
} else {
Write-Output "(-Apply を付けると登録します)"
}
automation/mac/run_daily_job.sh#!/usr/bin/env bash
# run_daily_job.sh — 毎朝1回の無害な無人ジョブ(launchd: com.example.daily-job)
# 前日の日次サマリーを保管フォルダへ写す。失敗は終了コードで返す(0以外=失敗。launchctl list の status に残る)
set +e
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[ -f "$SCRIPTS_DIR/.env" ] && { set -a; source "$SCRIPTS_DIR/.env"; set +a; }
log(){ echo "[$(date '+%F %T')] run_daily_job: $*"; }
python3 "$SCRIPTS_DIR/daily_job.py" # deploy.sh が daily_job.py を同じ場所に配布する
rc=$?
log "done rc=$rc"
exit "$rc"
automation/mac/deploy.sh#!/bin/bash
# deploy.sh — 作業フォルダの automation/ を「実行場所」へ配布し、plist を LaunchAgents に置く
#
# なぜ分けるか: クラウド同期フォルダ(Google Drive 等)の中のファイルは launchd から実行できないことがある。
# 実行実体は自分のホームの Library/Scripts/<名前>/ に置き、ここから一方向にコピーする。
#
# 使い方:
# bash automation/mac/deploy.sh # 何が変わるか表示だけ(既定・安全)
# bash automation/mac/deploy.sh --apply # 実際にコピー+plist 配置+launchd 登録
set -euo pipefail
NAME="${DEPLOY_NAME:-my-harness}" # <自分の名前に変える>
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # automation/mac
SRC_ROOT="$(cd "$SRC/.." && pwd)" # automation(daily_job.py はここ)
DEST="$HOME/Library/Scripts/$NAME"
LOG_DIR="$HOME/Library/Logs/$NAME"
AGENTS="$HOME/Library/LaunchAgents"
MODE="${1:---dry-run}"
echo "=== ${SRC} → ${DEST}(${MODE})==="
if [ "$MODE" = "--apply" ]; then
mkdir -p "$DEST" "$LOG_DIR"
rsync -av --update --exclude '.env*' --exclude 'secret_*.json' --exclude 'credentials*.json' --exclude '*.plist' "$SRC/" "$SRC_ROOT/daily_job.py" "$DEST/"
for p in "$SRC"/*.plist; do
[ -f "$p" ] || continue
out="$AGENTS/$(basename "$p")"
sed -e "s#__SCRIPTS_DIR__#$DEST#g" -e "s#__LOG_DIR__#$LOG_DIR#g" "$p" > "$out"
plutil -lint "$out" >/dev/null
launchctl unload "$out" 2>/dev/null || true
launchctl load -w "$out"
echo "登録: $(basename "$p")"
done
echo "確認: launchctl list | grep com.example"
else
rsync -avn --update --exclude '.env*' --exclude 'secret_*.json' --exclude 'credentials*.json' --exclude '*.plist' "$SRC/" "$SRC_ROOT/daily_job.py" "$DEST/" | grep -v '/$' || true
echo "(--apply で実行。plist は __SCRIPTS_DIR__=${DEST}, __LOG_DIR__=${LOG_DIR} に置換して ${AGENTS} に置く)"
fi
automation/mac/com.example.daily-job.plist<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.daily-job</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>__SCRIPTS_DIR__/run_daily_job.sh</string>
</array>
<!-- 毎朝 07:30。deploy.sh --apply が __SCRIPTS_DIR__ を実際の場所に置き換えて LaunchAgents に置く -->
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>7</integer>
<key>Minute</key>
<integer>30</integer>
</dict>
<key>StandardOutPath</key>
<string>__LOG_DIR__/daily_job.log</string>
<key>StandardErrorPath</key>
<string>__LOG_DIR__/daily_job_err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>LANG</key>
<string>ja_JP.UTF-8</string>
</dict>
<key>RunAtLoad</key>
<false/>
</dict>
</plist>
automation/stop-conditions.md# AIを無人で動かすスクリプトの「止まる条件」点検表
夜間や朝に、人が見ていないところで AI(Claude Code の `claude -p` など)を動かすスクリプトは、止まる条件を決めておかないと、失敗しても気づけなかったり、使用上限(枠)を使い切るまで回ったりする。
無人ジョブが2本以上になったら、この表で1本ずつ点検する。月1回の健診(第12章)でも見直す。
## 表の型(自分の作業フォルダの `automation/` に置いて埋める)
| スクリプト | 起動のされ方 | 完成の条件 | 試行の上限 | 制限時間 | 枠が切れたら | 使うモデル | 失敗したら誰が気づくか |
|---|---|---|---|---|---|---|---|
| (例)daily_job.py | 毎朝 07:30(タスクスケジューラ/launchd) | 保管フォルダに前日分のファイルがある | 1回 | なし | 対象外(AI を使わない) | 使わない | 翌朝ログを見る |
各欄の意味:
- **完成の条件**: 何ができたら終わりか(ファイルがある・検品が合格した など)。「動いた」ではなく「できた」で書く
- **試行の上限**: AI を呼ぶ回数とやり直しの上限。「上限なし」は危ない
- **制限時間**: 1回あたり何秒で打ち切るか。無いと固まったまま朝を迎える
- **枠が切れたら**: 使用上限(枠)のエラーを見分けて「待つ/止める/知らせる」のどれにするか。見分けないと、ただの失敗として記録される
- **失敗したら誰が気づくか**: 通知・ログ・翌朝の点検など。「誰も気づかない」が一番危ない
## 貼るだけの指示文(Claude Code に貼る)
```
作業フォルダの automation/ の中で、AI を画面なしで動かすスクリプト(claude -p や --print を呼ぶもの)を全部探してください。
1本ずつコードを読み、automation/stop-conditions.md の表の型の8つの欄を埋めた表を作ってください。
- コードに書いていないことは「なし」と書く。推測で埋めない
- 各欄に根拠の場所(ファイル名:行)を付ける
- 最後に「試行の上限がない」「制限時間がない」「枠が切れても見分けない」「失敗に誰も気づかない」ものを一覧にする
スクリプトは直さないでください。直すかどうかは表を見て私が決めます。
```
## 直すときの目安
- 試行の上限・制限時間が無いもの → 先に足す(いちばん効く)
- 枠切れを見分けないもの → エラー文に「limit」などが出たら、戻る時刻まで待って最大3回やり直す形にする
- 失敗に誰も気づかないもの → 失敗したときだけ自分あてに1通知らせる
出どころ: Claude Code 公式のループの整理(目標型のループは「成功条件+最大試行回数」を明示する)と、このガイドの作者の環境で24本を点検した結果(2026-09)。
毎朝1回動く無害な無人ジョブを1本作ってください。automation/daily_job.py(前日の diary/YYYY-MM-DD.json を保管フォルダへコピー、無ければ何もしない、失敗は終了コード1)と、それを呼ぶ automation/windows/run_daily_job.ps1、タスクスケジューラに毎日 07:30 で登録する register_task.ps1(-Apply を付けた時だけ登録)を作ってください。通知は出さないでください。登録は私が手で実行します。毎朝1回動く無害な無人ジョブを1本作ってください。automation/daily_job.py(前日の diary/YYYY-MM-DD.json を保管フォルダへコピー、無ければ何もしない、失敗は終了コード1)と、それを呼ぶ automation/mac/run_daily_job.sh、launchd の定義 automation/mac/com.example.daily-job.plist(毎日 07:30・ログは自分のホームの Library/Logs)と、実行場所へ配布して登録する deploy.sh(--apply を付けた時だけ実行)を作ってください。通知は出さないでください。登録は私が手で実行します。codex exec "指示" を .ps1.sh から呼ぶ。登録されている定時ジョブの数を、自分の環境で数えてみる(作業フォルダで実行する)。
schtasks /Query /FO LIST | Select-String 'TaskName' | Measure-Objectlaunchctl list | wc -lcheckup/ を 作業フォルダ ▸ checkup にコピーするbash checkup/measure.sh を実行し、instruction-checkup.md の表に今月の値を書くcheckup/instruction-checkup.md# 指示書の健診(月1回・チェックリスト)
指示書(CLAUDE.md・rules・settings.json・スキルの説明文)は書いた後に腐る。月初に次を数えて、前月と比べる。**直すのは番号を選んでから1件ずつ**。
## 数える(`bash checkup/measure.sh` で一度に出る)
| 項目 | 目標 | 前月 | 今月 |
|---|---|---|---|
| `.claude/rules/*.md`+`CLAUDE.md` の合計バイト | 20KB 以下 | | |
| MEMORY.md のバイト数 | 20KB 以下・1行1メモリ | | |
| 毎ターン注入(inject-core-rules.sh の出力) | 300B 以下 | | |
| スキル説明文(description)の合計文字数 | 増えていないか | | |
| permissions.allow の項目数 | 増えていないか | | |
## 見る(4観点・番号を付けて書き出す)
1. **参照切れ**: 指示書の中の実在しないパス・ファイル名
2. **矛盾**: 同じことが2か所に別の値で書かれている
3. **肥大化**: 1ファイル200行超・コードを読めば分かる説明・古い日付の断定
4. **危険**: APIキー・トークンの直書き、広すぎる allow
## 書く
`improvements/instruction-checkup-YYYY-MM.md` に、上の表と番号付きの指摘を保存する。修正はしない。
checkup/measure.sh#!/bin/bash
# measure.sh — 常駐コストを数える(月1の健診で前月と比べる)。作業フォルダで実行する。
set -u
cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
echo "計測日: $(date '+%Y-%m-%d %H:%M')"
printf 'rules+CLAUDE.md : %s B\n' "$(cat .claude/rules/*.md CLAUDE.md 2>/dev/null | wc -c | tr -d ' ')"
MEM="$HOME/.claude/projects/$(pwd | sed 's#/#-#g')/memory/MEMORY.md"
[ -f "$MEM" ] && printf 'MEMORY.md : %s B / %s 行\n' "$(wc -c < "$MEM" | tr -d ' ')" "$(wc -l < "$MEM" | tr -d ' ')" || echo "MEMORY.md : まだ無い(Claude Code が最初の記憶を書くと作られる)"
if [ -f .claude/hooks/inject-core-rules.ps1 ] && command -v powershell.exe >/dev/null 2>&1; then printf '毎ターン注入 : %s B\n' "$(powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude/hooks/inject-core-rules.ps1 | wc -c | tr -d ' ')"; elif [ -f .claude/hooks/inject-core-rules.sh ]; then printf '毎ターン注入 : %s B\n' "$(bash .claude/hooks/inject-core-rules.sh | wc -c | tr -d ' ')"; else echo "毎ターン注入 : hook が無い"; fi
printf 'スキル説明文の合計 : %s 字\n' "$( (grep -h '^description:' .claude/skills/*/SKILL.md .claude/commands/*.md 2>/dev/null || true) | sed 's/^description: *//' | tr -d '\n' | wc -m | tr -d ' ')"
PY="$(command -v python3 || command -v python)"
[ -f .claude/settings.json ] && printf 'permissions.allow : %s 項目\n' "$("$PY" -c "import json;print(len(json.load(open('.claude/settings.json')).get('permissions',{}).get('allow',[])))")"
月初の点検をしてください。まず bash checkup/measure.sh を実行し、出た値(rules+CLAUDE.md の容量・MEMORY.md の容量と行数・毎ターン注入のバイト数・スキル説明文の合計文字数・permissions.allow の項目数)を checkup/instruction-checkup.md の表の「今月」列に書いてください。次に CLAUDE.md・.claude/rules/・.claude/skills/*/SKILL.md・.claude/settings.json を読み、参照切れ(実在しないパス)・矛盾(同じことが別の値で2か所)・肥大化(200行超・古い日付の断定)・危険(鍵の直書き・広すぎる allow)を番号付きで指摘してください。修正はしないでください。45日以上使っていないスキルがあれば「退避候補」として挙げるだけにし、移動もしないでください。measure.sh の rules の行を AGENTS.md に読み替える。| 項目 | 数え方 | 今月 | 前月 |
|---|---|---|---|
| 指示書(rules+CLAUDE.md)の合計容量 | 第2章 | ||
| MEMORY.md の行数 | 第3章 | ||
| allow・deny・ask の項目数 | 第4章 | ||
| MCP サーバーの数 | 第5章 | ||
| スキルの本数 | 第8章 | ||
| サブエージェントの本数 | 第9章 | ||
| 定時ジョブの本数 | 第11章 |
ゼロから作る人の順番の目安。★の区分は章に対応する(★=第0〜4章、★★=第5〜10章、★★★=第11〜12章)。
| 部品 | 理由 |
|---|---|
| 指示書(CLAUDE.md+rules 1本) | 絶対ルール5項目。最初の1本 |
| 権限(allow/deny/ask) | 認証ファイルを読ませない・消す前に聞く |
| hook 3本 | 日付とルールを毎回1行差し込む。手前で止める。終了時に確認する。数百バイトで効く |
| 日次サマリー | その日やったことを1日1ファイル |
| 部品 | 理由 |
|---|---|
| MCP 1本(まず Drive・メール・カレンダー) | 仕事の入口と出口がつながる |
| git hook(秘匿スキャン) | ターミナルからの素通りを止める |
| スキル 3〜5本(日誌・議事録・週報など) | いつもの仕事を一言で呼ぶ |
| 検品係 1本 | 作った本人とは別の目。最初のサブエージェント |
| 係の名簿 1枚 | 係が増えたときの座席表 |
| 部品 | 理由 |
|---|---|
| 無人ジョブ 1本(失敗しても被害のないもの) | 日次サマリーの転送など。最初の1本に向く |
| 指示書健診(月1回) | 参照切れ・矛盾・肥大化を数えて前月と比べる |
| Codex 連携(任意) | 実装・反復の別枠。付録Dと付録Eを見る |
| 夜間の編纂ジョブ(任意) | 記憶の整理など。無人化に慣れてから |
| 部品 | 理由 |
|---|---|
| 仕事専用のスキル・ジョブ・サブエージェント | 仕事ごとに違うもの。真似せず、自分の仕事で同じ型を作る |
章の順に並べてある。1語=ひとこと・日常のたとえ・どこにあるか。
| 章 | 用語 | ひとこと | 日常のたとえ | どこにあるか |
|---|---|---|---|---|
| 第0章 | Claude Code | フォルダの中で動くAIアシスタント。ファイルを読み書きし、コマンドも実行する | 机の隣に座る新人。指示書を渡せば同じ仕事を繰り返せる | ターミナル(claude コマンド)か VS Code 拡張。入れ方は 公式セットアップ |
| 第0章 | PowerShell/ターミナル | 文字でコマンドを打つ画面。Windows は PowerShell(スタートで「PowerShell」と検索)、Mac は「ターミナル」 | 受付の窓口。決まった言い方で頼むと動く | スタートメニューで「PowerShell」と検索アプリケーション ▸ ユーティリティ ▸ ターミナル |
| 第0章 | セッション | 会話の1区切り。閉じると文脈(それまでの会話の中身)は消え、記憶に書いたものだけ残る | 1日の勤務。翌日に持ち越すには日報が要る | — |
| 第0章 | スラッシュコマンド | 「/」で始まる呼び出し。スキルや設定の入口 | 内線番号。押せば決まった係につながる | .claude/commands/・.claude/skills/ |
| 第0章 | ターン | 指示を1回出して返答が1回返るまでの往復 | 会話のひと往復 | — |
| 第0章 | 本体モデル | 会話を受け付けて判断しているAIモデル(例: Opus)。設定ファイルで決まる | その日の担当者。設計判断は担当者本人がする | 自分のホーム ▸ .claude ▸ settings.json の model |
| 第1章 | git/リポジトリ | ファイルの変更履歴を残す道具(git)と、それで管理するフォルダ(リポジトリ)。作業フォルダで一度 git init すればよい | 戻せる台帳付きの書庫 | 作業フォルダ直下の .git/(見えないフォルダ) |
| 第1章 | コミット | 変更をひと区切りとして履歴に記録する操作 | 台帳に日付と要約を書いて綴じる | git commit |
| 第1章 | GitHub | git の履歴を置く共有場所(クラウド)。1人で使う間は不要。使う時は所属する組織のルールに従う | 共有の書庫 | https://github.com |
| 第2章 | ハーネス | AIを仕事に合わせて縛る道具立て全体(指示書・記憶・安全装置・スキル・接続・自動化) | 馬具。馬(モデル)を替えても同じ荷が運べるようにする | この資料の全部 |
| 第2章 | CLAUDE.md | 起動時に必ず読まれる目次。役割と「どのファイルを読むか」だけ書く | 机の上の1枚の案内板 | CLAUDE.md(リポジトリ直下) |
| 第2章 | rules | 絶対ルールのファイル群。毎セッション自動で読まれる | 就業規則。短く、正本は1か所 | .claude/rules/*.md |
| 第2章 | 文脈 | そのセッションで AI が今読んでいる中身の全部(指示・ファイル・返答)。多いほど遅く高くなる | 机の上に広げた書類の山 | — |
| 第2章 | 常駐コスト | 毎ターン必ず文脈に入るもの(指示書・記憶の索引・スキルの説明文)の量。小さく保つ | 毎回持ち歩くカバンの重さ | 第12章の記録表で毎月数える |
| 第3章 | MEMORY.md | セッションをまたいで覚えておく事実の索引(1行1件)。事実の本文は1ファイル1事実で同じフォルダに置く | 引き継ぎノートの目次 | 自分のホーム ▸ .claude ▸ projects ▸ (作業フォルダの場所を「-」区切りにした名前) ▸ memory ▸ MEMORY.md(Claude Code が自動で作る。手で作らない) |
| 第3章 | context/ | チーム・顧客・案件・保存先の台帳。人が更新する | 部署の名簿と棚の配置図 | context/*.md |
| 第3章 | 日次サマリー | その日の実績を1ファイルに残す。翌日と月報の材料 | 日報 | 作業フォルダ ▸ diary ▸ YYYY-MM-DD.json(テンプレの置き場。自分で1か所決める) |
| 第4章 | settings.json | Claude Code の設定。権限・hooks・モデルが入る | 鍵束と入退室ルール | 作業フォルダ ▸ .claude ▸ settings.json(この作業フォルダ用)/自分のホーム ▸ .claude ▸ settings.json(自分の全体用) |
| 第4章 | allow / deny / ask | ツール操作の「許可・禁止・毎回確認」の3区分 | 自由に入れる部屋・立入禁止・受付で聞く部屋 | settings.json の permissions |
| 第4章 | トークン(認証) | 外部サービスに入るための鍵ファイル。読ませない・コミットしない | 金庫の鍵。新人に持たせない | secret_*.json など(作業フォルダの外に置く) |
| 第5章 | MCP | 外部システムを「道具」としてAIに渡す共通の口(Model Context Protocol) | 電源のコンセント規格。合えば何でも挿せる | 作業フォルダ ▸ .mcp.json(そのフォルダで共有)/自分のホーム ▸ .claude.json(自分だけ) |
| 第5章 | コネクタ | MCP でつながった先(Drive・Gmail・カレンダーなど) | コンセントに挿した機器 | 第5章(MCP の説明) |
| 第5章 | プラグイン | スキル・コマンド・MCP をまとめて配布する包み | 拡張パック | 自分のホーム ▸ .claude ▸ plugins |
| 第5章 | uv | Python の道具を、入れる手間なく1コマンドで動かす小さなツール。MCP サーバー(Google Workspace など)の起動に使う | 道具箱ごと持ってきてくれる係 | PowerShellターミナルで uv --version が出れば入っている |
| 第6章 | hook | 決まった瞬間(開始・指示のたび・ツールの手前・終了)に自動で走る小さなスクリプト | ドアに付けた鈴。通るたびに鳴る | 作業フォルダ ▸ .claude ▸ hooks(Windows は .ps1、Mac は .sh/.py)。登録は settings.json。瞬間の名前は4つ: SessionStart(開始)・UserPromptSubmit(指示のたび)・PreToolUse(ツールの手前)・Stop(終了) |
| 第6章 | 終了コード | コマンドが終わる時に返す数。0 が成功、それ以外が失敗。無人ジョブの失敗はこれで拾う | 仕上がりの判子(OK/NG) | echo $? で見える |
| 第7章 | git hook(秘匿スキャン) | コミット・プッシュの直前に秘密の混入を機械的に検査して止める | 出荷前の金属探知機 | .githooks/pre-commit・pre-push |
| 第8章 | スキル | 繰り返し作業の手順書。一言で呼ぶと同じ手順が動く | 作業マニュアルの1冊 | .claude/skills/<id>/ |
| 第8章 | SKILL.md | スキルの本体ファイル。目的・入力・手順・確認方法を書く | マニュアルの本文 | .claude/skills/<id>/SKILL.md |
| 第8章 | description(説明文) | スキルの先頭に書く1〜2文。これだけが常に読まれ、本文は呼ばれた時に読む | マニュアルの背表紙 | SKILL.md の先頭の frontmatter(--- で囲んだ名前と説明文の欄) |
| 第8章 | reference/ | スキルの詳細資料置き場。必要な時だけ読まれる | マニュアルの付録 | .claude/skills/<id>/reference/ |
| 第8章 | 日本語エイリアス | 英語の id に付けた日本語の呼び名(例: /議事録) | 内線の名前表示 | .claude/commands/<日本語名>.md |
| 第8章 | frontmatter | Markdown の先頭を --- で囲んだ欄。名前・説明文・使う道具を書く | 書類の表紙欄 | SKILL.md・エージェント定義・コマンド定義の先頭 |
| 第8章 | 標準形式スキル | .claude/skills/<id>/SKILL.md+reference/ の形。説明文だけ常駐し、本文は呼ばれた時に読む | 背表紙だけ棚に出ている本 | .claude/skills/(旧形式は skills/<id>.md) |
| 第8章 | 受け入れ検査(skill_lint) | スキルの形が約束どおりか(説明文の長さ・本文の量・参照の実在)を機械で検査する | 提出前のチェックリスト | テンプレの tools/skill_lint.py(作業フォルダ ▸ tools) |
| 第9章 | サブエージェント | まっさらな文脈で1つの仕事だけをする別のAI。結果だけ本体に返す | 別室で作業する担当者。本体の机は汚れない | .claude/agents/*.md |
| 第9章 | 検品係 | 納品物の合否だけ返すサブエージェント。修正はしない。まっさらな文脈で呼び、成果物と評価基準だけを渡して「別の目」にする | 出荷検査員。作った人とは別の人 | .claude/agents/kenpin.md |
| 第9章 | モデル名(haiku/sonnet/opus など) | AIモデルの大きさの系列。小さいほど速く安く、大きいほど判断が要る仕事向き。「固定」は定義ファイルに書いた指定、「呼ぶ時に指定」はその都度渡す指定 | 軽自動車・普通車・大型車。荷物で選ぶ | エージェント定義の model:、または呼び出し時の model= |
| 第9章 | プランモード | 手を動かす前に方針だけ出して合意する動き方 | 着工前の設計図の確認 | 本体の EnterPlanMode |
| 第10章 | 係の名簿 | 係名・呼び出し・実体・仕事を1行ずつ並べた組織図 | 部署の座席表 | 作業フォルダ ▸ context ▸ ai-org.md |
| 第11章 | タスクスケジューラ/launchd | 決めた時刻にプログラムを動かす OS の仕組み。Windows はタスクスケジューラ(schtasks)、Mac は launchd(plist) | 目覚まし時計付きの当番表 | Windows: タスクスケジューラ(schtasks /Query)/Mac: 自分のホーム ▸ Library ▸ LaunchAgents |
| 第11章 | deploy | スクリプトを実行場所へ配布すること(Mac の参考サンプル)。Windows は作業フォルダから直接タスク登録するので不要 | 倉庫から現場へ道具を運ぶ | テンプレの automation ▸ mac ▸ deploy.sh |
| 第12章 | 指示書健診 | 指示書の参照切れ・矛盾・肥大化・危険を月1回数えて指摘する | 定期健康診断 | テンプレの checkup/instruction-checkup.md と measure.sh(作業フォルダ ▸ checkup) |
| 第12章 | スナップショット | 毎月の実測値を1ファイルに残し、前月と比べる材料にする | 毎月の体重記録 | 毎月の計測結果を残すフォルダ(自分で決める) |
| 第12章 | 第二の脳(ノート置き場) | 会議録・日次サマリーなどを整理して置く知識ベース(任意) | 整理された自分用の百科事典 | 自分のホームの中の好きな場所 |
| — | Codex CLI | OpenAI の AI コーディング道具。Claude Code と同じ足場が作れる(対応表は付録D)。どちらか1つで足りる | 外注の実装チーム。仕様書と受け入れ検査で付き合う | codex・設定は 自分のホーム ▸ .codex ▸ config.toml・指示書は AGENTS.md |
| — | 引き継ぎ書/受け入れコマンド | Codex に渡す仕様書と、できたかを機械で判定する1コマンド。自己申告でなく終了コードで完了を決める | 発注書と検収の合格基準 | 付録D のテンプレ(CODEX_指示_テンプレ.md) |
つまずきやすい失敗と、その防ぎ方。どれも「あとで気づいて直すのが大変」なものばかり。
git add . のように全部をまとめて記録する。関係ないファイルや認証ファイルまで履歴に入る。変更したファイル名を指定して add し、続けて commit する。${VAR} 参照にする)。章を全部終えた後に、全体がどうつながっているかを見る3枚。
運用サイクルの図のうち、「毎セッションの SessionStart hook」と「毎週の週次メンテナンス」は発展で、この資料では作らない。第12章で作るのは月1回の健診。1ターンの図の Stop hook は、第6章で作る「日次サマリーがあるかの確認」だけ。
| 何を | どこで | どう |
|---|---|---|
| 認証ファイルの読み書き | settings.json の deny+手前で止める hook | secret_*.json・credentials*.json・.env は読ませない。コマンド経由の操作も止める |
| 削除コマンド | settings の ask | rm 系は毎回確認 |
| 定時ジョブの停止・書き換え | hook | launchd/タスクスケジューラの操作は警告して止める |
| 秘密のコミット | .githooks/pre-commit(scan_secrets.py) | APIキー形式・平文パスワード・禁止フォルダを機械でスキャン。例外は理由付きで記録 |
| 数値の捏造 | rules+検品係 | 件数・数字は実データだけ。検品で出典の実在を確かめる |
| 外部サービスへのアップロード | rules | 仕事のデータを、許可されていない外部サービスに上げない |
| 通知・課金・本番操作 | rules | 実行前に毎回確認(外への送信・課金が出る API・本番環境) |
| うっかり全部をコミット | 運用ルール | git add <ファイル名> でファイルを指定し、続けて commit する。フォルダ丸ごとの add はしない |
同じ足場を Codex(OpenAI)で作る人のための対応表。どちらか1つのサブスクリプションで足りる。両方を連携させる必要はない。
| 項目 | Claude Code | Codex |
|---|---|---|
| 入れる(第0章) | irm https://claude.ai/install.ps1 | iex | powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"。codex で起動し ChatGPT アカウントでログイン |
| 指示書(第2章) | CLAUDE.md+.claude/rules/ | 作業フォルダ直下の AGENTS.md 1枚(下のテンプレ codex/AGENTS.md.sample) |
| 記憶(第3章) | 自動メモリ(MEMORY.md)+context/日次サマリー | Memories(既定オフ・config.toml の features.memories = true)+context/日次サマリー |
| 権限(第4章) | settings.json の allow/deny/ask | 自分のホーム ▸ .codex ▸ config.toml の approval_policy/sandbox_mode |
| MCP(第5章) | .mcp.json | config.toml の [mcp_servers.名前]、または codex mcp add |
| hooks(第6章) | settings.json の hooks | hooks あり(features.hooks = true+hooks.json。同名イベント)。approval/sandbox も併用 |
| 秘匿スキャン(第7章) | git hook | 同じ(git の仕組み) |
| スキル(第8章) | .claude/skills/<id>/SKILL.md+日本語入口 | SKILL.md は同形式。置き場は .agents/skills(作業フォルダ)か 自分のホーム ▸ .agents ▸ skills。/skills で一覧、$名前 で呼ぶ。日本語入口は無い |
| 検品係(第9章) | .claude/agents/ のサブエージェント | サブエージェント(config.toml)か、別セッションに渡して判定だけ頼む |
| 無人化(第11章) | タスクスケジューラ/launchd から claude -p | 同じ仕組みから codex exec |
| 発注(参考) | — | 下のテンプレ codex/CODEX_指示_テンプレ.md(触ってよいファイル・受け入れコマンド・禁止) |
codex/AGENTS.md.sample# AGENTS.md — <仕事の名前> の Codex 設定(Claude Code の CLAUDE.md に相当・目次だけ)
## 役割
<仕事の名前>の専属アシスタント。<主な仕事を1行で>
## 絶対ルール(5つ)
1. 数値は実データだけ。根拠がない数値は「不明」と書く
2. ファイルの上書き・外部への送信は、方針を一言確認してから
3. `secret_*.json`・`credentials*.json`・`.env` は読まない・書かない・コミットしない
4. 「どう思う?」は分析だけ返す。修正・送信は頼まれてから
5. 意味のある作業が終わったら `diary/YYYY-MM-DD.json` に1件追記する
## 保存先
新しいファイルの置き場は `context/README.md` が正本。迷ったら `inbox/` に置く。
## 補足
Codex の hooks(`features.hooks`)と Memories(`features.memories`)は既定では使わない前提で、ルールは全部この AGENTS.md に書き、記憶は `context/` と日次サマリーで持つ。使うなら config.toml で有効にする。
codex/CODEX_指示_テンプレ.md# Codex への指示: <作るもの1行>
作成: <YYYY-MM-DD> / 依頼者: Claude Code 本体 / 形態: `codex exec`(非対話)
## 触ってよいファイル(これ以外は読むだけ。編集・作成禁止)
- <新規作成 or 編集するファイルを1つずつ書く>
- git 操作(add/commit/push)はしない
## 目的
<何のために、何ができればよいかを3行で>
## 仕様
- <入力と出力>
- <守るべき形式・制約>
- <実測済みの罠。想像で足さない>
## 完了条件(受け入れコマンド)
```sh
<1コマンド。終了コード0で合格。例: python3 tools/xxx.py --selftest>
```
- 依頼側(Claude)がこのコマンドを実行して判定する。**自己申告では完了にならない**
- 落ちたら失敗内容をファイルに書いて再発注。上限2回、超えたら人へ
## 禁止
- 外部ネットワーク・パッケージの追加インストール
- 数値・日付・人名・顧客名をサンプルに入れる
- 他ファイルの編集
## 発注コマンド(Claude 側で実行・長時間なら nohup で切り離す)
```sh
nohup codex exec -s workspace-write -C <作業フォルダ> "<このファイルのパス> を全文読んでから、そこに書かれたファイルだけを作成してください。完了条件は受け入れコマンドの終了コード0です。最後に実行結果と作成したファイルを出力してください。" > <ログファイル> 2>&1 &
```
出典: 各章の「Codex の場合」注釈と、Codex 公式ドキュメント(developers.openai.com/codex)
2026年10月時点の一つの運用例。本体は Opus(5.5)、方針が決まった実装は Sonnet(5.5)のサブエージェント、Codex は利用枠が別なので並行して使う(非セキュリティの実装・反復・大量処理の主力)。モデル名は変わるので、数字よりも「仕事の種類で担当を分ける」考え方を持ち帰る。
| 仕事の種類 | 担当 | 呼び方の例 |
|---|---|---|
| 方針・設計・最終チェック | 本体(Opus)。判断が重い仕事だけ本体が自分で動く | プランモードで方針を決めてから実行に移る |
| 普通の実装・バグ修正・資料や文面の作成 | Sonnet のサブエージェント | Agent(model="sonnet") |
| 判断が要らない大量の処理(要約・整文・分類・抽出) | Sonnet のサブエージェント(本体の文脈を汚さない) | Agent(model="sonnet") |
| 非セキュリティの実装・設計・反復・大量処理 | Codex Luna(GPT-5.6・考える深さは最大)。Claude とは利用枠が別なので主力にできる | codex exec -m gpt-5.6-luna -c model_reasoning_effort="max" |
| 判断を伴う実装(仕様の曖昧さが残る・既存コードへの影響が広い) | Codex Terra(GPT-5.6) | codex exec -m <Terra のモデル名> |
| セキュリティ判断(認証・権限・秘密情報)・原因不明の障害の第二の目 | Codex Sol(GPT-6.1・high 以上) | codex exec -m gpt-6.1-sol -c model_reasoning_effort="high" |
| 最終検品 | まっさらな文脈のサブエージェント(Opus) | Agent(subagent_type="kenpin", model="opus") |
/plugin から入れる)で、検索・投稿の道具とスキルが一式入る。送信は必ず人が内容を確かめてから。