自分のClaude Code環境を毎朝Mermaid図に自動生成する
「Claude Codeを無人で自律改善させる」前作との続きで、今回は環境の肥大化を自覚する仕掛けの話です。
~/.claude/ 配下にスキルが積み重なり、launchdジョブが増え、プロジェクトが二桁を超えたとき、「全体が何層あって今何が動いているか」を即答できなくなりました。毎朝 ls を打つのではなく、Obsidianを開けば俯瞰できる状態を目指して、~/.claude/scripts/env-map.sh を書きました。
困りごと:環境が増えると全体像を忘れる
現状のスナップショットはこうです。
- plugin bundleのスキル: 1,004個
- 自己生成スキル(auto/): 77個
- launchdジョブ(
com.shun.*.plist): 24本 - 把握しているプロジェクト: 11本
スキル・ジョブ・プロジェクトそれぞれの最終更新やgit状態がバラバラで、毎回確認しに行くコストが積み重なります。「今日のautopilotは何をした?」「あのプロジェクトのブランチは?」を1ページで答えられる場所が欲しかった。
設計:3層Mermaid + Obsidianへ出力
図にするのは3層です。
| 層 | 収集内容 |
|---|---|
| 💻 PC環境 | macOSバージョン・チップ・RAM・ディスク空き・主要CLI |
| 🤖 Claude環境 | スキル数・エージェント数・プラグイン数・Hookイベント数・MCP接続数・launchdジョブ数 |
| 📦 プロジェクト環境 | 既知リポジトリのbranch・最終コミット日・未コミット変更数 |
出力先はObsidian Vault(~/Documents/claude-obsidian/wiki/meta/environment-map.md)。vault-auto-ingest(4:55)が拾って自動コミットするので、バージョン管理もタダで付いてきます。
スクリプトの設計方針はコメントに書いてあります。
# 何が起きても生成を完走させる(個別の収集失敗は "?" で degrade)。set -e は使わない。
set -uo pipefail
set -e を使わないのがポイント。MCP接続確認などで部分的に失敗しても ? を埋めて最後まで出力させます。
launchd最小PATH対策
launchdから起動するとPATHは /usr/bin:/bin:/usr/sbin:/sbin 程度しかない。nodeもclaudeもjqも見つかりません。スクリプトの先頭でPATHを明示的に組み立てます。
NVM_BIN="$(ls -d "$HOME"/.nvm/versions/node/*/bin 2>/dev/null | sort -V | tail -1)"
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:${NVM_BIN:+$NVM_BIN:}/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
nvmはnodeバージョンを上げるとパスが変わるので、sort -V | tail -1 で最新版binを動的に解決しています。固定パスを書くと次のバージョンアップで壊れます。
launchdの最小PATHで詰まるのは「ユーザが入れたCLI全般」です。
~/.local/bin(uv・claude等)→ Homebrew → nvm の順でフォールバックを積んでおくと、どの環境構成でも当たります。
何を図にすると俯瞰が効くか
Claude環境の収集
AUTO_SKILLS="$(ls "$HOME/.claude/skills/auto/" 2>/dev/null | grep -vc README)"
AGENTS="$(find "$HOME/.claude/plugins" "$HOME/.claude/agents" -path '*/agents/*.md' \
-o -path "$HOME/.claude/agents/*.md" 2>/dev/null | wc -l | tr -d ' ')"
PLUGINS="$(jq -r '.enabledPlugins // {} | length' "$SETTINGS" 2>/dev/null || echo '?')"
HOOK_EVENTS="$(jq -r '.hooks // {} | keys | length' "$SETTINGS" 2>/dev/null || echo '?')"
LAUNCHD="$(ls "$HOME/Library/LaunchAgents/"com.shun.*.plist 2>/dev/null | wc -l | tr -d ' ')"
MCPだけは注意が必要です。claude mcp listはライブ接続を試みるので起動が遅く、失敗することもある。タイムアウトで押さえます。
MCP_OK="?"
if have claude; then
_mcp="$(timeout 12 claude mcp list 2>/dev/null)"
[ -n "$_mcp" ] && MCP_OK="$(printf '%s' "$_mcp" | grep -c 'Connected')"
fi
timeout 12 で12秒経ったら諦め、? のままにします。生成は止めません。
プロジェクトの状態
リポジトリごとにbranch・最終コミット日・未コミット変更数を取得して、ノードの色に反映させます。
proj_meta() {
local path="$1"
PROJ_EXISTS=0; PROJ_BRANCH="-"; PROJ_LAST="-"; PROJ_DIRTY=0
[ -d "$path" ] || return
PROJ_EXISTS=1
if git -C "$path" rev-parse --git-dir >/dev/null 2>&1; then
PROJ_BRANCH="$(git -C "$path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '-')"
PROJ_LAST="$(git -C "$path" log -1 --format=%cd --date=format:%Y-%m-%d 2>/dev/null || echo '-')"
PROJ_DIRTY="$(git -C "$path" status --porcelain 2>/dev/null | wc -l | tr -d ' ')"
fi
}
未コミット変更があるプロジェクトはオレンジ、ディスクに存在しないプロジェクトは赤で表示します。
echo ' classDef dirty fill:#3a2a00,stroke:#e8a33d,color:#fff;'
echo ' classDef gone fill:#3a1a1a,stroke:#e06666,color:#fff;'
朝Obsidianを開いただけで「どのプロジェクトにコミット漏れがあるか」が色で分かります。
Mermaidラベルのサニタイズ
ホスト名やチップ名に () や [] が入るとMermaidのパースが壊れます。全ラベルをサニタイズ関数に通します。
san() { printf '%s' "$1" | tr '"[]()#|<>' ' ' | tr '\n' ' ' | sed 's/ */ /g; s/ *$//'; }
Apple M4 Pro (14-core) のような文字列も安全に出力されます。
生成される図のイメージ
graph LR
PC["💻 PC 環境<br/>MacBook · macOS 15.x"]
CC["🤖 Claude 環境<br/>plugins 14 · launchd 24"]
PJ["📦 プロジェクト環境<br/>11 repos"]
PC --> CC
CC -->|builds / runs| PJ
PC -.->|ローカル開発| PJ
Claude環境の内訳はこうなります(実測値)。
graph TD
CC["🤖 Claude Code"]
SK["🧩 Skills"]
SK --> SKa["auto: 77"]
SK --> SKp["plugin: 1004"]
CC --> SK
CC --> AG["🎭 Agents"]
CC --> PL["🔌 Plugins enabled"]
CC --> HK["🪝 Hooks"]
CC --> MCP["🔗 MCP connected"]
CC --> AUTO["⚡ launchd 24 jobs"]
launchd設定
<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>4</integer><key>Minute</key><integer>50</integer></dict>
<dict><key>Hour</key><integer>8</integer><key>Minute</key><integer>10</integer></dict>
</array>
<key>RunAtLoad</key><false/>
4:50に発火するのは、4:55のvault-auto-ingestより前に出力を置くためです。ingestがそのままObsidianへコミットしてくれるのでgit操作が不要。8:10は夜間スリープで4:50がスキップされた場合のキャッチアップです。冪等なので多重発火しても無害。RunAtLoad: false でplist読み込み時の即時発火は抑制します。
ログは1本のファイルに集約します。
<key>StandardOutPath</key>
<string>~/.claude/logs/env-map.launchd.log</string>
<key>StandardErrorPath</key>
<string>~/.claude/logs/env-map.launchd.log</string>
最後の行に generated (N lines) が出ればOKです。
[2026-06-20 04:50:03] environment-map.md generated (218 lines)
踏んだ落とし穴
set -eを入れるとMCP timeout時点で終了 →set -uo pipefailのみ。MCPやgit失敗は?や-で継続- NVM固定パスを書いたらNode更新で壊れた →
sort -V | tail -1で最新版binを動的解決 - Mermaidラベルに
()が入ると図がパースエラー →san()関数を全ラベルに通す。チップ名で踏んだ - vault-ingestより遅れると生成物が前日分 → 4:50に先行発火、8:10にキャッチアップの二重設定
- プロジェクトパス変更に気づかない →
(未検出)赤ノードで可視化。PRIの変更時にリストも更新する - 出力先ディレクトリが存在しないと失敗 →
mkdir -p "$(dirname "$OUT")"をmvの直前に置く - ノードIDのカンマ結合が空白のままだとMermaidが壊れる →
sed 's/ /,/g'で整形してからclassに渡す
まとめ
- スキル・launchd・プロジェクトが二桁を超えたらMermaid図で俯瞰する場所を1枚用意する
- launchd起動時の最小PATHはスクリプト先頭で明示的に組み立てる。nvmはバージョン昇格を想定して動的解決
- MCP接続確認など遅い処理は**
timeoutで囲い、失敗は?でdegrade**させて完走を優先する - 出力先をvault-ingest前に置くとバージョン管理もコミットも自動でついてくる
- プロジェクトの未コミット変更をノード色で可視化すると、朝Obsidianを開いただけでアクションが分かる
本稿と死活監視(automation-health-check)を組み合わせると、「何が動いていて・何が壊れていて・何が積み残されているか」の朝のスナップショットが1ページで揃います。
Lily(@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています
- 作ったアプリは ポートフォリオ にまとめています📱
- 新着・開発の裏側は X @bokuwalily で発信しています🌍
- OSS: github.com/bokuwalily 🐙
皆さんの ❤️ やシェアが励みになります!