create-einja-app 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +32 -16
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/default/.claude/hooks/einja/plan-mode-skill-loader.sh +23 -0
  6. package/templates/default/.claude/settings.json +15 -1
  7. package/templates/default/.env.personal.example +6 -2
  8. package/templates/default/.envrc +5 -0
  9. package/templates/default/.github/workflows/deploy-pr-preview.yml +23 -24
  10. package/templates/default/.github/workflows/deploy-stable-branches.yml +55 -49
  11. package/templates/default/.mcp.json +2 -12
  12. package/templates/default/.serena/project.yml +7 -0
  13. package/templates/default/CLAUDE.md +28 -4
  14. package/templates/default/README.md +2 -2
  15. package/templates/default/apps/admin/package.json +1 -1
  16. package/templates/default/apps/admin/tsconfig.json +2 -1
  17. package/templates/default/apps/web/package.json +1 -1
  18. package/templates/default/apps/web/tsconfig.json +2 -1
  19. package/templates/default/docs/plans/.gitkeep +0 -0
  20. package/templates/default/docs/plans/ancient-greeting-flamingo-agent-a87e67c.md +221 -0
  21. package/templates/default/docs/plans/ancient-greeting-flamingo-agent-ab73a1c.md +107 -0
  22. package/templates/default/docs/plans/ancient-greeting-flamingo.md +120 -0
  23. package/templates/default/docs/plans/bright-stargazing-dawn.md +87 -0
  24. package/templates/default/docs/plans/calm-stirring-bonbon.md +196 -0
  25. package/templates/default/docs/plans/calm-watching-widget.md +111 -0
  26. package/templates/default/docs/plans/cheerful-wiggling-ullman.md +164 -0
  27. package/templates/default/docs/plans/compiled-humming-cherny.md +94 -0
  28. package/templates/default/docs/plans/dapper-launching-lynx.md +81 -0
  29. package/templates/default/docs/plans/effervescent-munching-kite-agent-ac08baf.md +672 -0
  30. package/templates/default/docs/plans/effervescent-munching-kite-agent-aecc373.md +442 -0
  31. package/templates/default/docs/plans/effervescent-munching-kite.md +263 -0
  32. package/templates/default/docs/plans/fix-orphan-cleaner-review.md +25 -0
  33. package/templates/default/docs/plans/fix-sync-template-variables.md +162 -0
  34. package/templates/default/docs/plans/glimmering-giggling-sedgewick.md +126 -0
  35. package/templates/default/docs/plans/glittery-swimming-bachman.md +78 -0
  36. package/templates/default/docs/plans/happy-watching-toast.md +56 -0
  37. package/templates/default/docs/plans/harmonic-strolling-nebula.md +210 -0
  38. package/templates/default/docs/plans/import-alias-refactor.md +75 -0
  39. package/templates/default/docs/plans/lazy-percolating-sloth-agent-abda679.md +346 -0
  40. package/templates/default/docs/plans/lazy-percolating-sloth.md +151 -0
  41. package/templates/default/docs/plans/linked-greeting-llama-agent-a7a6e5b.md +345 -0
  42. package/templates/default/docs/plans/linked-greeting-llama.md +467 -0
  43. package/templates/default/docs/plans/lovely-bubbling-rose.md +80 -0
  44. package/templates/default/docs/plans/optimized-watching-sprout.md +149 -0
  45. package/templates/default/docs/plans/peaceful-beaming-toast-agent-a292da6.md +288 -0
  46. package/templates/default/docs/plans/peaceful-beaming-toast-agent-a819699.md +366 -0
  47. package/templates/default/docs/plans/peaceful-beaming-toast-agent-ac11de2.md +474 -0
  48. package/templates/default/docs/plans/peaceful-beaming-toast.md +345 -0
  49. package/templates/default/docs/plans/purrfect-spinning-hickey-agent-ae6194c.md +300 -0
  50. package/templates/default/docs/plans/purrfect-spinning-hickey-agent-ae6900e.md +444 -0
  51. package/templates/default/docs/plans/purrfect-spinning-hickey.md +361 -0
  52. package/templates/default/docs/plans/recursive-kindling-lemon-agent-a42199e.md +186 -0
  53. package/templates/default/docs/plans/recursive-kindling-lemon.md +36 -0
  54. package/templates/default/docs/plans/seed-migration-tests.md +47 -0
  55. package/templates/default/docs/plans/sprightly-leaping-manatee.md +224 -0
  56. package/templates/default/docs/plans/stateful-wishing-lerdorf.md +161 -0
  57. package/templates/default/docs/plans/streamed-purring-wreath.md +40 -0
  58. package/templates/default/docs/plans/synthetic-percolating-pearl.md +101 -0
  59. package/templates/default/docs/plans/todo-fix-sync-template-variables.md +21 -0
  60. package/templates/default/docs/plans/todo-phase4-marker-update.md +39 -0
  61. package/templates/default/docs/plans/todo-skill-creator-sync.md +23 -0
  62. package/templates/default/docs/plans/typed-snuggling-parnas-agent-a6f6391.md +476 -0
  63. package/templates/default/docs/plans/typed-snuggling-parnas-agent-adb678b.md +144 -0
  64. package/templates/default/docs/plans/typed-snuggling-parnas.md +84 -0
  65. package/templates/default/docs/plans/warm-hopping-lighthouse-agent-a30aa4f.md +534 -0
  66. package/templates/default/docs/plans/warm-hopping-lighthouse-agent-a57a278.md +508 -0
  67. package/templates/default/docs/plans/warm-hopping-lighthouse-agent-a90b809.md +421 -0
  68. package/templates/default/docs/plans/warm-hopping-lighthouse.md +199 -0
  69. package/templates/default/docs/verification-test.md +2 -0
  70. package/templates/default/gitignore +4 -0
  71. package/templates/default/package.json +2 -2
  72. package/templates/default/packages/admin-ui/package.json +1 -1
  73. package/templates/default/packages/server-core/tsconfig.json +6 -1
  74. package/templates/default/pnpm-lock.yaml +276 -57
  75. package/templates/default/scripts/ensure-serena.sh +75 -0
  76. package/templates/default/scripts/lib/worktree-config.ts +64 -0
  77. package/templates/default/scripts/stop-serena.sh +25 -0
  78. package/templates/default/scripts/worktree/dev.ts +2 -2
  79. /package/templates/default/scripts/{cli-template-update.ts → _cli-template-update.ts} +0 -0
  80. /package/templates/default/scripts/{template-update.ts → _template-update.ts} +0 -0
@@ -0,0 +1,442 @@
1
+ # Serena MCP バックグラウンドプロセス自動停止機能の調査・設計
2
+
3
+ **作成日**: 2026-02-27
4
+ **ステータス**: 調査完了
5
+ **優先度**: Medium
6
+
7
+ ---
8
+
9
+ ## 背景
10
+
11
+ ### 問題
12
+
13
+ - `ensure-serena.sh` が `.envrc` から source されてSerenaをバックグラウンド起動(`nohup ... &`)
14
+ - ユーザーがターミナルを全部閉じても Serena プロセスは残り続ける
15
+ - メモリ消費(LSPサーバー + インデックス)が数百MB規模で長期間残る
16
+
17
+ ### 現状の環境
18
+
19
+ - 現在の `.envrc` には Serena 起動スクリプトは**未実装**(マネージドセクションと seed セクションのみ)
20
+ - `.claude/hooks/einja/ensure-serena.sh` ファイルは**存在しない**
21
+ - これはテンプレート配布前の設計段階であることを示唆
22
+
23
+ ---
24
+
25
+ ## 調査結果
26
+
27
+ ### 1. Serena の idle timeout 機能
28
+
29
+ #### CLI オプション調査
30
+
31
+ - `serena start-mcp-server --help` で確認可能なオプション:
32
+ - `--port`: ポート番号指定
33
+ - `--context`: コンテキスト指定(ide, claude-code, desktop-app)
34
+ - `--project`: プロジェクトパス指定
35
+ - `--transport`: トランスポート指定(stdio, sse)
36
+ - `--mode`: モード指定(複数指定可能)
37
+
38
+ **結論**: **Serena 自体には idle timeout や自動シャットダウンのCLIオプションは存在しない**
39
+
40
+ #### ダッシュボード機能
41
+
42
+ - HTTP/SSEモードでは `http://localhost:24282/dashboard/index.html` でダッシュボードが起動
43
+ - ダッシュボードから手動でシャットダウン可能
44
+ - ただし**自動停止機能はない**
45
+
46
+ **参考**:
47
+ - [Serena Configuration](https://oraios.github.io/serena/02-usage/050_configuration.html)
48
+ - [Serena Client Connection](https://oraios.github.io/serena/02-usage/030_clients.html)
49
+
50
+ ---
51
+
52
+ ### 2. MCP プロトコル仕様の session timeout 機能
53
+
54
+ #### タイムアウトの種類
55
+
56
+ MCP仕様では以下のタイムアウトが定義されている:
57
+
58
+ | タイムアウト種別 | 対象 | 必須レベル | 用途 |
59
+ |-----------------|------|-----------|------|
60
+ | Request Timeout | 個別リクエスト | SHOULD | 個別リクエストの応答待ちタイムアウト |
61
+ | Session Timeout | セッション全体 | 任意(hint) | アイドルセッションの自動クローズ |
62
+
63
+ #### Request Timeout
64
+
65
+ > Implementations **SHOULD** establish timeouts for all sent requests, to prevent hung connections and resource exhaustion.
66
+
67
+ - **クライアント側**が設定する(サーバー側ではない)
68
+ - 個別リクエスト単位での応答待ちタイムアウト
69
+ - **セッション全体の idle timeout とは別物**
70
+
71
+ #### Session Timeout(HTTP transport)
72
+
73
+ > Session management in MCP is evolving. The protocol includes provisions for a `sessionTimeout` parameter (an idle timeout hint in seconds) that can be included in the `InitializeResult`.
74
+
75
+ - `InitializeResult` に `sessionTimeout` を含めることができる
76
+ - ただし**hint(ヒント)**であり、強制ではない
77
+ - HTTP実装では `IdleTrackingBackgroundService` でセッション監視可能
78
+
79
+ **重要**: これは MCP プロトコルレベルの仕様であり、Serena が実装しているかは不明
80
+
81
+ **参考**:
82
+ - [MCP Lifecycle Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle)
83
+ - [State, and long-lived vs. short-lived connections Discussion](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/102)
84
+
85
+ ---
86
+
87
+ ### 3. 対策案の評価
88
+
89
+ #### A. ラッパースクリプトでの idle 監視 ⭐ **推奨**
90
+
91
+ **実現方法**:
92
+ ```bash
93
+ # ensure-serena.sh
94
+ nohup bash -c '
95
+ serena start-mcp-server --transport sse --port 24282 &
96
+ SERVER_PID=$!
97
+
98
+ while true; do
99
+ sleep 300 # 5分ごとにチェック
100
+
101
+ # 最後のアクセスから N 分経過していたら kill
102
+ LAST_ACCESS=$(lsof -ti:24282 -sTCP:ESTABLISHED 2>/dev/null | wc -l)
103
+ if [ "$LAST_ACCESS" -eq 0 ]; then
104
+ IDLE_COUNT=$((IDLE_COUNT + 1))
105
+ if [ "$IDLE_COUNT" -ge 6 ]; then # 30分 idle
106
+ kill $SERVER_PID
107
+ break
108
+ fi
109
+ else
110
+ IDLE_COUNT=0
111
+ fi
112
+ done
113
+ ' > ~/.serena/serena-watchdog.log 2>&1 &
114
+ ```
115
+
116
+ **評価**:
117
+
118
+ | 項目 | 評価 | 詳細 |
119
+ |------|------|------|
120
+ | 実装の複雑さ | 🟡 中 | 60行程度のシェルスクリプト |
121
+ | 信頼性 | 🟢 高 | `lsof` でTCP接続を監視するため確実 |
122
+ | macOS/Linux対応 | 🟢 可 | `lsof` は両OS標準コマンド |
123
+ | ユーザー体験 | 🟢 良 | 透過的に動作。ログで確認可能 |
124
+
125
+ **メリット**:
126
+ - 完全に自動化できる
127
+ - アイドル時間を柔軟に調整可能
128
+ - 既存のSerenaに変更不要
129
+
130
+ **デメリット**:
131
+ - スクリプトがやや複雑
132
+ - バックグラウンドプロセスが2つになる(Server + Watchdog)
133
+
134
+ ---
135
+
136
+ #### B. direnv の deactivate 機能
137
+
138
+ **調査結果**:
139
+ - direnv には**公式の on_exit フックは存在しない**
140
+ - ディレクトリを離れたときに**自動的に環境変数を unload するのみ**
141
+ - Issue #129 で議論されているが、未実装
142
+
143
+ **参考**:
144
+ - [Unload hook · Issue #129 · direnv/direnv](https://github.com/direnv/direnv/issues/129)
145
+
146
+ **評価**:
147
+
148
+ | 項目 | 評価 |
149
+ |------|------|
150
+ | 実現可能性 | ❌ 不可 |
151
+ | 理由 | direnv に cleanup hook が存在しない |
152
+
153
+ ---
154
+
155
+ #### C. macOS launchd / Linux systemd でのプロセス管理
156
+
157
+ **実現方法**:
158
+ - ユーザーレベルの service として Serena を登録
159
+ - systemd の `TimeoutStopSec` や `KillMode` で制御
160
+
161
+ **評価**:
162
+
163
+ | 項目 | 評価 | 詳細 |
164
+ |------|------|------|
165
+ | 実装の複雑さ | 🔴 高 | macOS/Linux で別実装が必要 |
166
+ | 信頼性 | 🟢 高 | OS標準機能を使用 |
167
+ | macOS/Linux対応 | 🟡 要工夫 | launchd/systemd で構文が異なる |
168
+ | ユーザー体験 | 🔴 悪 | セットアップ手順が複雑化 |
169
+
170
+ **デメリット**:
171
+ - テンプレート配布に不向き(ユーザーが手動で service 登録が必要)
172
+ - プロジェクトごとに service を作るのは現実的でない
173
+
174
+ ---
175
+
176
+ #### D. `ensure-serena.sh` 実行時に古いプロセスをクリーンアップ ⭐ **シンプル案**
177
+
178
+ **実現方法**:
179
+ ```bash
180
+ # ensure-serena.sh
181
+ # 既存プロセスの確認
182
+ SERENA_PID=$(pgrep -f "serena start-mcp-server.*24282")
183
+ if [ -n "$SERENA_PID" ]; then
184
+ # プロセスの起動時刻を確認
185
+ if ps -p "$SERENA_PID" -o etime= | grep -E '(days|[0-9]{2}:[0-9]{2}:[0-9]{2})'; then
186
+ # 1時間以上起動中なら kill して再起動
187
+ kill "$SERENA_PID"
188
+ sleep 2
189
+ else
190
+ # まだ新しいので何もしない
191
+ return
192
+ fi
193
+ fi
194
+
195
+ # 新規起動
196
+ nohup serena start-mcp-server --transport sse --port 24282 > ~/.serena/serena.log 2>&1 &
197
+ ```
198
+
199
+ **評価**:
200
+
201
+ | 項目 | 評価 | 詳細 |
202
+ |------|------|------|
203
+ | 実装の複雑さ | 🟢 低 | 20行程度のシェルスクリプト |
204
+ | 信頼性 | 🟡 中 | 起動タイミングに依存(cd しない限りクリーンアップされない) |
205
+ | macOS/Linux対応 | 🟢 可 | `pgrep`, `ps` は両OS標準 |
206
+ | ユーザー体験 | 🟢 良 | 透過的に動作 |
207
+
208
+ **メリット**:
209
+ - 実装が非常にシンプル
210
+ - 既存プロセスとの衝突を防げる
211
+ - 長時間放置されたプロセスを自動クリーンアップ
212
+
213
+ **デメリット**:
214
+ - **cd しない限りクリーンアップされない**(完全なidle監視ではない)
215
+ - 複数プロジェクトで同じポートを使う場合に競合する可能性
216
+
217
+ ---
218
+
219
+ #### E. 何もしない(ドキュメントで `stop-serena.sh` の使用を案内)
220
+
221
+ **実現方法**:
222
+ - `docs/einja/instructions/` にSerenaプロセス管理のドキュメントを追加
223
+ - `stop-serena.sh` スクリプトを提供
224
+
225
+ **評価**:
226
+
227
+ | 項目 | 評価 | 詳細 |
228
+ |------|------|------|
229
+ | 実装の複雑さ | 🟢 最低 | ドキュメントのみ |
230
+ | 信頼性 | 🟡 中 | ユーザーの意識に依存 |
231
+ | ユーザー体験 | 🔴 悪 | 「驚きの原則」に反する(停止を忘れるとメモリが圧迫される) |
232
+
233
+ **デメリット**:
234
+ - プロセスの放置が常態化する可能性
235
+ - 「なぜメモリが圧迫されているのか」の原因追跡が困難
236
+
237
+ ---
238
+
239
+ ## 推奨案
240
+
241
+ ### 第1案(推奨): **D + 軽量版A のハイブリッド** ⭐⭐⭐
242
+
243
+ ```bash
244
+ #!/usr/bin/env bash
245
+ # .claude/hooks/einja/ensure-serena.sh
246
+
247
+ set -euo pipefail
248
+
249
+ SERENA_PORT=24282
250
+ SERENA_PID_FILE="$HOME/.serena/serena.pid"
251
+ SERENA_LOG_FILE="$HOME/.serena/serena.log"
252
+ IDLE_THRESHOLD_HOURS=2 # 2時間 idle で自動停止
253
+
254
+ mkdir -p "$(dirname "$SERENA_PID_FILE")"
255
+
256
+ # 既存プロセスの確認とクリーンアップ
257
+ cleanup_old_process() {
258
+ if [ -f "$SERENA_PID_FILE" ]; then
259
+ OLD_PID=$(cat "$SERENA_PID_FILE")
260
+ if ps -p "$OLD_PID" > /dev/null 2>&1; then
261
+ # プロセスの起動時刻を確認
262
+ RUNTIME=$(ps -p "$OLD_PID" -o etime= | tr -d ' ')
263
+
264
+ # N時間以上起動中なら kill
265
+ if echo "$RUNTIME" | grep -qE "^([0-9]+-|[0-9]{2}:)"; then
266
+ echo "$(date): Killing old Serena process (PID: $OLD_PID, runtime: $RUNTIME)" >> "$SERENA_LOG_FILE"
267
+ kill "$OLD_PID" 2>/dev/null || true
268
+ sleep 2
269
+ rm -f "$SERENA_PID_FILE"
270
+ else
271
+ # まだ新しいので何もしない
272
+ return 0
273
+ fi
274
+ else
275
+ # プロセスが存在しないので PID ファイルを削除
276
+ rm -f "$SERENA_PID_FILE"
277
+ fi
278
+ fi
279
+ }
280
+
281
+ # Serena 起動
282
+ start_serena() {
283
+ nohup bash -c "
284
+ serena start-mcp-server --transport sse --port $SERENA_PORT --context ide 2>&1 | tee -a $SERENA_LOG_FILE &
285
+ echo \$! > $SERENA_PID_FILE
286
+
287
+ # 簡易 idle 監視(バックグラウンド)
288
+ while sleep 1800; do # 30分ごとにチェック
289
+ if ! lsof -ti:$SERENA_PORT -sTCP:ESTABLISHED > /dev/null 2>&1; then
290
+ if ps -p \$(cat $SERENA_PID_FILE) -o etime= | grep -qE '^0?$IDLE_THRESHOLD_HOURS:'; then
291
+ echo \"\$(date): Serena idle for $IDLE_THRESHOLD_HOURS hours, shutting down\" >> $SERENA_LOG_FILE
292
+ kill \$(cat $SERENA_PID_FILE) 2>/dev/null || true
293
+ rm -f $SERENA_PID_FILE
294
+ break
295
+ fi
296
+ fi
297
+ done
298
+ " >> "$SERENA_LOG_FILE" 2>&1 &
299
+
300
+ disown # シェルから切り離し
301
+ }
302
+
303
+ # メイン処理
304
+ main() {
305
+ cleanup_old_process
306
+
307
+ # プロセスが存在しない、または古いプロセスを kill した場合のみ起動
308
+ if [ ! -f "$SERENA_PID_FILE" ]; then
309
+ echo "$(date): Starting Serena MCP server on port $SERENA_PORT" >> "$SERENA_LOG_FILE"
310
+ start_serena
311
+ fi
312
+ }
313
+
314
+ main
315
+ ```
316
+
317
+ **特徴**:
318
+ - **起動時クリーンアップ(D案)**: 既存プロセスが長時間起動中なら再起動
319
+ - **軽量idle監視(A案簡易版)**: 2時間完全アイドルで自動停止
320
+ - **PIDファイル管理**: プロセス追跡を確実に
321
+
322
+ **メリット**:
323
+ - シンプルで理解しやすい(80行程度)
324
+ - 自動停止とクリーンアップの両立
325
+ - macOS/Linux両対応
326
+ - ログで動作確認可能
327
+
328
+ ---
329
+
330
+ ### 第2案: **D案のみ(最もシンプル)** ⭐⭐
331
+
332
+ ```bash
333
+ #!/usr/bin/env bash
334
+ # .claude/hooks/einja/ensure-serena.sh(簡易版)
335
+
336
+ set -euo pipefail
337
+
338
+ SERENA_PORT=24282
339
+ SERENA_LOG_FILE="$HOME/.serena/serena.log"
340
+
341
+ mkdir -p "$(dirname "$SERENA_LOG_FILE")"
342
+
343
+ # 既存プロセスの確認
344
+ SERENA_PID=$(lsof -ti:$SERENA_PORT 2>/dev/null || true)
345
+
346
+ if [ -n "$SERENA_PID" ]; then
347
+ # プロセスの起動時刻を確認(1時間以上起動中なら再起動)
348
+ RUNTIME=$(ps -p "$SERENA_PID" -o etime= | tr -d ' ')
349
+ if echo "$RUNTIME" | grep -qE '^([0-9]+-|0?[1-9]:)'; then
350
+ echo "$(date): Restarting old Serena (PID: $SERENA_PID, runtime: $RUNTIME)" >> "$SERENA_LOG_FILE"
351
+ kill "$SERENA_PID" 2>/dev/null || true
352
+ sleep 2
353
+ else
354
+ # 起動して間もないので何もしない
355
+ exit 0
356
+ fi
357
+ fi
358
+
359
+ # Serena 起動
360
+ echo "$(date): Starting Serena MCP server" >> "$SERENA_LOG_FILE"
361
+ nohup serena start-mcp-server --transport sse --port $SERENA_PORT --context ide >> "$SERENA_LOG_FILE" 2>&1 &
362
+ disown
363
+ ```
364
+
365
+ **特徴**:
366
+ - 最小限の実装(30行程度)
367
+ - cd するたびに古いプロセスをチェック
368
+ - 完全なidle監視はなし
369
+
370
+ **メリット**:
371
+ - 非常にシンプル
372
+ - 依存が少ない(`lsof`, `ps` のみ)
373
+
374
+ **デメリット**:
375
+ - cd しない限りクリーンアップされない
376
+
377
+ ---
378
+
379
+ ### 第3案: **E案 + stop-serena.sh 提供**
380
+
381
+ **最もシンプルだが、ユーザー体験は悪い**
382
+
383
+ ```bash
384
+ #!/usr/bin/env bash
385
+ # .claude/hooks/einja/stop-serena.sh
386
+
387
+ SERENA_PID=$(lsof -ti:24282 2>/dev/null || true)
388
+ if [ -n "$SERENA_PID" ]; then
389
+ echo "Stopping Serena (PID: $SERENA_PID)"
390
+ kill "$SERENA_PID"
391
+ else
392
+ echo "Serena is not running"
393
+ fi
394
+ ```
395
+
396
+ **ドキュメント**: `docs/einja/instructions/serena-process-management.md` を追加
397
+
398
+ ---
399
+
400
+ ## 最終推奨
401
+
402
+ ### **第1案(D + 軽量版A)を推奨** ⭐⭐⭐
403
+
404
+ **理由**:
405
+ 1. **シンプルさと信頼性のバランス**: 80行程度で実装可能、かつ自動停止機能を持つ
406
+ 2. **テンプレート配布に最適**: `.envrc` に source するだけで動作
407
+ 3. **ユーザー体験**: 透過的に動作し、「驚きの原則」に反しない
408
+ 4. **macOS/Linux両対応**: 標準コマンドのみ使用
409
+
410
+ **補足対応**:
411
+ - `docs/einja/instructions/serena-troubleshooting.md` にログ確認方法を記載
412
+ - `.gitignore` に `~/.serena/` を追加(リポジトリには含めない)
413
+
414
+ ---
415
+
416
+ ## 参考資料
417
+
418
+ ### Serena 関連
419
+ - [Serena GitHub Repository](https://github.com/oraios/serena)
420
+ - [Serena Configuration](https://oraios.github.io/serena/02-usage/050_configuration.html)
421
+ - [Serena Client Connection](https://oraios.github.io/serena/02-usage/030_clients.html)
422
+ - [Serena MCP Error Issues](https://github.com/oraios/serena/issues/898)
423
+ - [Dashboard Timeout Issues](https://github.com/oraios/serena/issues/648)
424
+
425
+ ### MCP Protocol
426
+ - [MCP Lifecycle Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle)
427
+ - [SEP-1359: Protocol-Level Sessions](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1359)
428
+ - [State and Connection Discussion](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/102)
429
+
430
+ ### direnv
431
+ - [Unload hook · Issue #129 · direnv/direnv](https://github.com/direnv/direnv/issues/129)
432
+ - [direnv Manual](https://direnv.net/man/direnv.1.html)
433
+
434
+ ---
435
+
436
+ ## 次のステップ
437
+
438
+ 1. ユーザーに推奨案(第1案)を提示
439
+ 2. 承認後、`ensure-serena.sh` 実装
440
+ 3. `.envrc` への統合
441
+ 4. `docs/einja/instructions/serena-troubleshooting.md` 作成
442
+ 5. テンプレート配布用の `presets/default/` に反映
@@ -0,0 +1,263 @@
1
+ # Serena MCP サーバー共有化(1インスタンス化)
2
+
3
+ ## Context
4
+
5
+ 現在の `.mcp.json` では Serena が `type: "stdio"` で設定されており、Claude Code を起動するたびに個別のSerenaプロセスが生成される。同一プロジェクトで複数のClaude Codeを並行利用する場合、メモリ・CPUを無駄に消費する。
6
+
7
+ 目標:
8
+ 1. 1プロジェクト1インスタンスで共有
9
+ 2. ポート衝突時は自動解決
10
+ 3. 起動忘れの自動カバー
11
+ 4. テンプレートとして社内複数リポジトリに配布
12
+ 5. 別プロジェクトのSerenaインスタンスとの誤接続を防止
13
+
14
+ 方式C(Claude Code hook)はMCP接続がセッション開始時にeagerに行われるため技術的に不可。
15
+ 方式A(direnv)+ D(手動フォールバック)のハイブリッドを採用。
16
+
17
+ ## 仕組み
18
+
19
+ ```
20
+ cd プロジェクト
21
+ ↓ direnv が .envrc を実行
22
+ source scripts/ensure-serena.sh
23
+
24
+ ├─ .serena-port あり → PID生存チェック
25
+ │ ├─ PID生存 → SERENA_PORT export(即完了)
26
+ │ └─ PID死亡 → .serena-port 削除 → 新規起動へ
27
+ └─ .serena-port なし → 空きポート検出 → バックグラウンド起動 → PID+ポート保存
28
+
29
+ claude コマンド起動(Serena は既に稼働中)
30
+
31
+ .mcp.json: "url": "http://127.0.0.1:${SERENA_PORT:-9850}/mcp"
32
+ ```
33
+
34
+ ### 設計判断: PIDベースの所有権管理
35
+
36
+ `.serena-port` に `PORT PID` を保存し、PIDの生存確認(`kill -0`)で判別する。
37
+
38
+ | シナリオ | PIDファイル | 判定 | 動作 |
39
+ |---------|-----------|------|------|
40
+ | 自プロジェクトのSerena起動中 | あり、PID生存 | 自分のもの | 再利用 |
41
+ | 自プロジェクトのSerena異常終了 | あり、PID死亡 | 終了済み | ファイル削除→新規起動 |
42
+ | 別プロジェクトのSerenaが同ポート | なし or PID不一致 | 他人のもの | ポートスキップ |
43
+ | ポート9850が非Serenaに使用中 | なし | 無関係 | ポートスキップ |
44
+
45
+ **curlを使わない理由**: MCP streamable-httpの `/mcp` はPOST専用のため、GETでの `curl -sf` は405を返し「未起動」と誤判定する。PIDチェックならプロトコル非依存で確実。
46
+
47
+ ## 変更内容
48
+
49
+ ### 1. `scripts/ensure-serena.sh` 新規作成
50
+
51
+ Serenaの冪等起動スクリプト。`.envrc` から source される。
52
+
53
+ **ファイル形式**: `.serena-port` は `PORT PID` のスペース区切り(例: `9850 12345`)
54
+
55
+ ```bash
56
+ #!/bin/bash
57
+ # Serena MCP サーバーの冪等起動
58
+ # .envrc から source して使用
59
+
60
+ # メインワークツリーをベースにする(worktree 間で共有)
61
+ _SERENA_BASE="${1:-$(pwd)}"
62
+ _SERENA_PORT_FILE="$_SERENA_BASE/.serena-port"
63
+ _SERENA_DEFAULT_PORT="${SERENA_PORT:-9850}"
64
+
65
+ # --- 既存インスタンスチェック(PIDベース) ---
66
+ if [ -f "$_SERENA_PORT_FILE" ]; then
67
+ read -r _saved_port _saved_pid < "$_SERENA_PORT_FILE"
68
+ if [ -n "$_saved_pid" ] && kill -0 "$_saved_pid" 2>/dev/null; then
69
+ # PIDが生存 → 自プロジェクトのSerena
70
+ export SERENA_PORT="$_saved_port"
71
+ return 0 2>/dev/null || true
72
+ fi
73
+ # PID死亡 → クリーンアップ
74
+ rm -f "$_SERENA_PORT_FILE"
75
+ fi
76
+
77
+ # --- uvx 確認 ---
78
+ if ! command -v uvx &> /dev/null; then
79
+ echo "[ensure-serena] Warning: uvx not found. Serena will not auto-start." >&2
80
+ echo "[ensure-serena] Install uv first: curl -LsSf https://astral.sh/uv/install.sh | sh" >&2
81
+ return 0 2>/dev/null || true
82
+ fi
83
+
84
+ # --- 空きポート検出 ---
85
+ _port="$_SERENA_DEFAULT_PORT"
86
+ _port_found=false
87
+ for _i in $(seq 1 10); do
88
+ if ! lsof -i ":$_port" -sTCP:LISTEN > /dev/null 2>&1; then
89
+ _port_found=true
90
+ break
91
+ fi
92
+ _port=$((_port + 1))
93
+ done
94
+
95
+ if [ "$_port_found" = false ]; then
96
+ echo "[ensure-serena] Error: No available port found (tried $_SERENA_DEFAULT_PORT-$_port)" >&2
97
+ return 0 2>/dev/null || true
98
+ fi
99
+
100
+ # --- バックグラウンド起動 ---
101
+ echo "[ensure-serena] Starting Serena on port $_port..."
102
+ uvx --from git+https://github.com/oraios/serena \
103
+ serena start-mcp-server \
104
+ --transport streamable-http \
105
+ --host 127.0.0.1 \
106
+ --port "$_port" \
107
+ --context claude-code \
108
+ --project "$_SERENA_BASE" \
109
+ > /dev/null 2>&1 &
110
+ disown
111
+ _serena_pid=$!
112
+
113
+ # --- 起動待機(PID生存 + ポートLISTEN、最大30秒) ---
114
+ for _i in $(seq 1 60); do
115
+ if ! kill -0 "$_serena_pid" 2>/dev/null; then
116
+ echo "[ensure-serena] Warning: Serena process exited unexpectedly" >&2
117
+ return 0 2>/dev/null || true
118
+ fi
119
+ if lsof -p "$_serena_pid" -i ":$_port" -sTCP:LISTEN > /dev/null 2>&1; then
120
+ echo "$_port $_serena_pid" > "$_SERENA_PORT_FILE"
121
+ export SERENA_PORT="$_port"
122
+ echo "[ensure-serena] Serena ready on port $_port (PID: $_serena_pid)"
123
+ return 0 2>/dev/null || true
124
+ fi
125
+ sleep 0.5
126
+ done
127
+
128
+ # タイムアウト(起動失敗してもdirenvはブロックしない)
129
+ echo "[ensure-serena] Warning: Serena failed to start within 30s" >&2
130
+ return 0 2>/dev/null || true
131
+ ```
132
+
133
+ ### 2. `scripts/stop-serena.sh` 新規作成
134
+
135
+ 手動停止用フォールバック。PIDベースで確実に停止。
136
+
137
+ ```bash
138
+ #!/bin/bash
139
+ PORT_FILE=".serena-port"
140
+ if [ -f "$PORT_FILE" ]; then
141
+ read -r PORT PID < "$PORT_FILE"
142
+ if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
143
+ kill "$PID"
144
+ # SIGTERM後、最大5秒待機
145
+ for _i in $(seq 1 10); do
146
+ if ! kill -0 "$PID" 2>/dev/null; then
147
+ echo "Serena stopped (PID: $PID, port: $PORT)"
148
+ rm -f "$PORT_FILE"
149
+ exit 0
150
+ fi
151
+ sleep 0.5
152
+ done
153
+ # 応答なし → 強制終了
154
+ kill -9 "$PID" 2>/dev/null
155
+ echo "Serena force-killed (PID: $PID, port: $PORT)"
156
+ else
157
+ echo "Serena process not running (PID: $PID)"
158
+ fi
159
+ rm -f "$PORT_FILE"
160
+ else
161
+ echo "Serena not running (.serena-port not found)"
162
+ fi
163
+ ```
164
+
165
+ ### 3. `.envrc` managed セクションに追記
166
+
167
+ **ファイル**: `.envrc`(L25-26 の間に追加)
168
+
169
+ ```diff
170
+ if [ -n "$MAIN_WORKTREE" ] && [ -f "$MAIN_WORKTREE/.env.personal" ]; then
171
+ dotenv_if_exists "$MAIN_WORKTREE/.env.personal"
172
+ fi
173
+ +
174
+ + # Serena MCP サーバー自動起動
175
+ + if [ -n "$MAIN_WORKTREE" ] && [ -f "$MAIN_WORKTREE/scripts/ensure-serena.sh" ]; then
176
+ + source "$MAIN_WORKTREE/scripts/ensure-serena.sh" "$MAIN_WORKTREE"
177
+ + fi
178
+ # @einja:managed:end
179
+ ```
180
+
181
+ ### 4. `.mcp.json` のSerena設定を変更
182
+
183
+ **ファイル**: `.mcp.json`(L34-47)
184
+
185
+ ```diff
186
+ "serena": {
187
+ - "type": "stdio",
188
+ - "command": "uvx",
189
+ - "args": [
190
+ - "--from",
191
+ - "git+https://github.com/oraios/serena",
192
+ - "serena",
193
+ - "start-mcp-server",
194
+ - "--context",
195
+ - "claude-code",
196
+ - "--open-web-dashboard",
197
+ - "false"
198
+ - ]
199
+ + "type": "http",
200
+ + "url": "http://127.0.0.1:${SERENA_PORT:-9850}/mcp"
201
+ }
202
+ ```
203
+
204
+ ### 5. `.env.personal.example` に追記
205
+
206
+ ```diff
207
+ + # Serena MCPサーバーのポート番号(ポート衝突時のみ変更。通常は自動解決)
208
+ + # SERENA_PORT=9851
209
+ ```
210
+
211
+ ### 6. `.gitignore` に追加
212
+
213
+ ```diff
214
+ # Serena設定(個人用)
215
+ **/.serena/
216
+ + .serena-port
217
+ ```
218
+
219
+ ### 7. `docs/einja/instructions/local-server-environment-and-worktree.md` に追記
220
+
221
+ ※ このリポジトリは `docs/einja/` の原本(Single Source of Truth)。`presets/default/` へはビルド時にコピーされる。
222
+
223
+ `@einja:managed:end` の直前(L629手前)に「MCP Server (Serena)」セクションを追加。PostgreSQLセクションと同パターン。
224
+
225
+ 記載内容:
226
+ - **概要**: Serena MCP サーバーの共有アーキテクチャ(1プロジェクト1インスタンス、worktree間共有)
227
+ - **自動起動の仕組み**: direnv → `ensure-serena.sh` → PIDベース所有権管理
228
+ - **`.serena-port` ファイル仕様**: `PORT PID` のスペース区切りフォーマット
229
+ - **PIDベース所有権管理**: なぜcurlではなくPIDを使うか(MCP streamable-httpはPOST専用、GETでは405)
230
+ - **ポート自動解決**: デフォルト9850、衝突時+1で10回まで試行
231
+ - **手動操作**: `./scripts/stop-serena.sh`、`direnv reload`(再起動)、`cat .serena-port`(状態確認)
232
+ - **トラブルシューティング**: Serena接続エラー、ポート衝突、ゾンビプロセス対処
233
+ - **`.env.personal` でのオーバーライド**: `SERENA_PORT` でデフォルトポート変更
234
+
235
+ ## 対象ファイル一覧
236
+
237
+ | ファイル | 操作 |
238
+ |---------|------|
239
+ | `scripts/ensure-serena.sh` | **新規作成** |
240
+ | `scripts/stop-serena.sh` | **新規作成** |
241
+ | `.envrc` | 編集(managed セクションに source 追加) |
242
+ | `.mcp.json` | 編集(serena を stdio → http) |
243
+ | `.env.personal.example` | 編集(SERENA_PORT 追記) |
244
+ | `.gitignore` | 編集(.serena-port 追加) |
245
+ | `docs/einja/instructions/local-server-environment-and-worktree.md` | 編集(MCP Serverセクション追加) |
246
+
247
+ ## テンプレート配布の考慮
248
+
249
+ - `.envrc` は `@einja:managed` セクション → `einja sync` で配布
250
+ - `.mcp.json` は sync 対象
251
+ - `scripts/ensure-serena.sh`, `scripts/stop-serena.sh` → 配布方法を要検討(sync 対象に含めるか `einja init` でコピーか)
252
+
253
+ ## 検証方法
254
+
255
+ 1. `direnv reload` → `echo $SERENA_PORT` でポート設定を確認
256
+ 2. `cat .serena-port` → `PORT PID` 形式で保存されていることを確認
257
+ 3. `kill -0 <PID>` → プロセス生存を確認
258
+ 4. `claude` 起動 → Serena ツール(`find_symbol` 等)が使えることを確認
259
+ 5. 別ターミナルで `claude` → 同じく使えることを確認(同ポート再利用)
260
+ 6. `ps aux | grep serena` でプロセスが1つだけであることを確認
261
+ 7. ポート衝突テスト: `python3 -m http.server 9850 &` → `direnv reload` → `SERENA_PORT` が 9851 になることを確認
262
+ 8. Serena 停止テスト: `./scripts/stop-serena.sh` → `direnv reload` → 再起動されることを確認
263
+ 9. 重複起動テスト: `direnv reload` を複数回実行 → `.serena-port` のPIDが変わらないことを確認