Repository navigation
Expand file tree
/
Copy pathoperation.html
More file actions
343 lines (318 loc) · 24.8 KB
/
Copy pathoperation.html
File metadata and controls
343 lines (318 loc) · 24.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
<!DOCTYPE html>
<html lang="ja" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>私のLoop Engineering運用ガイド — 具体手順</title>
<meta name="description" content="自分のClaude Code環境でLoop Engineering(Daily Triage メタループ)を毎日どう運用するかの具体手順。cron・承認ゲート・state.json・トラブル対応まで。">
<meta property="og:title" content="私のLoop Engineering運用ガイド — 具体手順">
<meta property="og:description" content="Daily Triage メタループの毎日の運用手順・アーキテクチャ・state.json・トラブル対応">
<meta property="og:type" content="article">
<meta property="og:url" content="https://fukukei23.github.io/loop-engineering-guide/operation.html">
<meta name="twitter:card" content="summary">
<link rel="stylesheet" href="assets/style.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔧</text></svg>">
<style>
.textbook { max-width: 900px; margin: 0 auto; padding: calc(var(--header-h, 60px) + 1.5rem) 1rem 4rem; overflow-wrap: anywhere; word-break: break-word; }
.concept-box {
background: linear-gradient(135deg, var(--accent-bg, rgba(124,58,237,.08)), rgba(124,58,237,.02));
border: 1px solid var(--accent, #7c3aed);
border-radius: 12px; padding: 1.2rem 1.4rem; margin-bottom: 1.5rem;
}
.concept-box h2 { margin-top: 0; color: var(--accent, #7c3aed); }
.acc-item {
border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 10px;
margin-bottom: .8rem; overflow: hidden; background: var(--bg-card, #fff);
}
.acc-item.open > .acc-content { display: block; }
.acc-content { display: none; padding: 1.2rem 1.4rem; border-top: 1px solid var(--border, rgba(0,0,0,.08)); }
.acc-content p, .acc-content li { font-size: .92rem; line-height: 1.7; }
.acc-content h4 { margin: 1rem 0 .3rem; color: var(--accent, #7c3aed); font-size: .95rem; }
.acc-content h4:first-child { margin-top: 0; }
.acc-header {
width: 100%; text-align: left; border: none; background: transparent;
padding: 1rem 1.4rem; font-size: 1.1rem; font-weight: 700; cursor: pointer;
color: var(--text, #1e293b); display: flex; justify-content: space-between; align-items: center;
}
.acc-header:hover { background: rgba(124,58,237,.05); }
.acc-header .caret { transition: transform .2s; font-size: .9rem; opacity: .6; }
.acc-item.open > .acc-header .caret { transform: rotate(180deg); }
.part-h2 { margin: 2rem 0 1rem; padding-bottom: .4rem; border-bottom: 2px solid var(--accent, #7c3aed); color: var(--accent, #7c3aed); font-size: 1.3rem; }
.acc-content table, .ref-table { width: 100%; max-width: 100%; border-collapse: collapse; margin: .5rem 0; display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.acc-content th, .ref-table th { background: rgba(124,58,237,.1); padding: .5rem; text-align: left; font-size: .82rem; border: 1px solid var(--border, rgba(0,0,0,.1)); }
.acc-content td, .ref-table td { padding: .5rem; font-size: .85rem; border: 1px solid var(--border, rgba(0,0,0,.1)); vertical-align: top; }
.acc-content td a, .ref-table td a { color: var(--accent, #7c3aed); }
.src { font-size: .8rem; color: var(--accent, #7c3aed); text-decoration: none; display: inline-block; margin-top: .4rem; }
.src:hover { text-decoration: underline; }
.top-links { display:flex; gap:.5rem; flex-wrap:wrap; margin-bottom:1.2rem; font-size:.85rem; }
.top-links a { color: var(--accent, #7c3aed); }
blockquote { border-left: 3px solid var(--accent, #7c3aed); margin: .6rem 0; padding: .4rem 1rem; background: rgba(124,58,237,.04); font-style: italic; }
.acc-content pre { background: rgba(124,58,237,.06); border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 8px; padding: .8rem 1rem; overflow-x: auto; max-width: 100%; font-size: .82rem; line-height: 1.5; -webkit-overflow-scrolling: touch; }
.acc-content code { background: rgba(124,58,237,.08); padding: .1rem .35rem; border-radius: 4px; font-size: .85rem; }
.acc-content pre code { background: transparent; padding: 0; }
.check-ok { color: #16a34a; font-weight: 700; }
.check-ng { color: #dc2626; font-weight: 700; }
</style>
</head>
<body>
<header class="site-header">
<button class="menu-toggle" aria-label="メニュー" id="menuToggle"><span></span><span></span><span></span></button>
<a href="https://fukukei23.github.io/loop-engineering-guide/" class="site-title">🔄 ループエンジニアリング</a>
<button class="theme-toggle" id="themeToggle" aria-label="テーマ切替"><span class="icon-light">☀️</span><span class="icon-dark">🌙</span></button>
</header>
<!-- NAV_START -->
<nav class="site-nav" aria-label="サイト内移動">
<a href="start.html">🔧 やさしい入口</a>
<a href="index.html">📖 概念教科書</a>
<a href="operation.html">⚙️ 運用ガイド</a>
</nav>
<!-- NAV_END -->
<main class="textbook">
<h1>🔧 私のLoop Engineering運用ガイド</h1>
<aside class="tldr">
<ol>
<li>概念ではなく、Claude Code環境で<strong>毎日どう運用するか</strong>の具体手順を整理した私用ガイド</li>
<li>Daily Triage(朝にAIが当日タスク候補を自動生成)を起点としたメタループ</li>
<li>朝の自動発火 → 人間承認 → 自律実行連鎖 → 停止 までを時系列で追う</li>
</ol>
</aside>
<div class="firsttime-box" role="note">
はじめての方は、まず <a href="start.html">「やさしい入口」(start.html)</a> から読むのがおすすめです(3-5分で概要を掴めます)。
</div>
<div class="concept-box">
<h2>概念を学ぶ — ではなく、毎日どう回すか</h2>
<p>姉妹ページ『<a href="index.html">ループエンジニアリング教科書</a>』が「ループエンジニアリングとは何か」を学ぶ概念中心の教科書なのに対し、本ページは<strong>自分のClaude Code環境で実際に毎日どう運用するかの具体手順</strong>を整理した私用ガイド。<span class="term">Daily Triage<span class="term-popup">朝6:07にAIがバックログ等から当日タスク候補を自動生成する仕組み</span></span> メタループ(Loop Engineering・Phase1〜3.1 完了済の現行システム)の、朝の自動発火から人間承認・自律実行連鎖・停止までを時系列で追う。</p>
<p style="font-size:.82rem; margin-bottom:0; opacity:.7;">原本: SSOT <code>00_SYSTEM/共通ルール/自律開発ループ.md</code>(139行)/ Phase4(OSS Issue自律ループ復活)完了後の2026-06-29整理</p>
</div>
<div class="top-links">
<a href="index.html">📖 概念教科書(ループエンジニアリングとは)</a>
<a href="https://fukukei23.github.io/claude-code-guide/">⚡ Claude Code Guide(機能詳細)</a>
<a href="task-logs.html">📋 タスク経緯ログ(auto-loop の実行記録)</a>
<a href="https://fukukei23.github.io/guides/">📚 ガイド集</a>
</div>
<div class="concept-box">
<h2>全体像</h2>
<p>毎朝 <strong>Daily Triage</strong> がバックログ・<span class="term">handoff<span class="term-popup">前回セッションの文脈を次に渡す引き継ぎメモ</span></span>・🟢進行中タスクからその日のタスク候補を自動生成し、人間が承認すると、実装→検証の連鎖が自律的に回る仕組み。</p>
<pre><code>Daily Triage(朝6:07自動)→ today-tasks.md 生成
↓
人間承認(approve.py)→ state.json 登録・最初のタスク起動
↓
自律実行連鎖: run-task.sh(実装+検証)→ next_issue.py(Stop hook・次タスク起動)
↓
全完了 or blocked で停止通知</code></pre>
<div class="flow-diagram" aria-label="1日の流れフロー">
<div class="flow-node">Daily Triage(朝6:07自動)<small>バックログ+手付タスクをAIが集めてリスト化</small></div>
<div class="flow-arrow">↓</div>
<div class="flow-node">人間承認(approve.py)<small>どれをやるか人間が選ぶ・唯一の関与ポイント</small></div>
<div class="flow-arrow">↓</div>
<div class="flow-node">自律実行連鎖<small><span class="term">run-task.sh<span class="term-popup">実装と検証を別プロセスで回す実行スクリプト</span></span>(実装+検証)→ <span class="term">next_issue.py<span class="term-popup">1タスク終了後に次タスクへ自動遷移させる制御</span></span> が OKで次へ/NGで停止</small></div>
</div>
</div>
<div class="why-box">
<strong>なぜこのループを組む?</strong>
人間が毎朝バックログを読み直しタスクを選ぶのは時間がかかる。AIに「候補選び」と「実装・検証の連鎖」を任せ、人間は「どれをやるか」の承認だけを出せばよくなる。これがLoop Engineeringの核心。
</div>
<h2 class="part-h2">第1部 1日の流れ</h2>
<div class="acc-item" data-key="op1">
<button class="acc-header" onclick="toggle(this)">① 朝6:07(自動): Daily Triage 発火 <span class="caret">▼</span></button>
<div class="acc-content">
<p><code>daily-triage.sh --notify-discord</code> が cron(<span class="term"><code>renew-crons.sh</code><span class="term-popup">cronジョブを再登録して永続化する(durableジョブは7日で期限切れするため定期更新)</span></span> 経由で永続化)で発火する。人間は何もしなくてよい。</p>
<h4>何が起きるか(4ステップで順に)</h4>
<ol>
<li><strong>材料集め</strong>: <span class="term">バックログ<span class="term-popup">未完了タスクの一覧表。SSOTの「バックログ.md」が正典</span></span>(タスク一覧)+ handoff(前回の引き継ぎ)+ <span class="term">🟢進行中タスク<span class="term-popup">active-sessions.mdで他セッションが占有中のタスク。重複着手を防ぐため候補から除外</span></span>(他セッション作業中)を集める</li>
<li><strong>優先度判定</strong>: Claude(GLM)が「今日やるべき順」を判定し、<code>~/.claude/state/today-tasks.md</code> に書き出す</li>
<li><strong>自動/手動の振り分け</strong>: 各タスクに <span class="term">marker<span class="term-popup">today-tasks.mdで自動実行可か手動かを区別する印</span></span>(印)を付ける − <code>(repo: 名前)</code> なら自動実行可・<code>(手動)</code> なら人間対応</li>
<li><strong>通知</strong>: <span class="term">Discord webhook<span class="term-popup">Discordの特定チャンネルにメッセージを自動送信する仕組み。通知先のURL</span></span> で結果を送る(未設定なら警告だけ)</li>
</ol>
</div>
</div>
<div class="acc-item" data-key="op2">
<button class="acc-header" onclick="toggle(this)">② セッション開始(自動): 候補表示 <span class="caret">▼</span></button>
<div class="acc-content">
<p><span class="term">SessionStart hook<span class="term-popup">セッション開始時に発火する仕掛け。handoff読込等を自動化</span></span>(<span class="term"><code>load-obsidian-log.sh</code><span class="term-popup">SessionStart hook本体。セッション開始時にhandoffとtoday-tasks.mdを読み込む</span></span>)が <span class="term"><code>today-tasks.md</code><span class="term-popup">当日のタスク候補リスト(優先度順・marker付き)。daily-triage.sh が生成</span></span> を表示する。人間が候補を確認するだけ。</p>
</div>
</div>
<div class="acc-item" data-key="op3">
<button class="acc-header" onclick="toggle(this)">③ 人間承認(手動): approve.py <span class="caret">▼</span></button>
<div class="acc-content">
<p><code>python3 approve.py</code> で実行するタスクを番号選択する。ここが<strong>唯一の人間の関与ポイント</strong>(<span class="term">承認ゲート<span class="term-popup">AIが勝手に実行しないよう人間がGOを出す唯一の関所</span></span>)。</p>
<h4>承認ゲートの挙動</h4>
<ul>
<li><code>(手動)</code> marker 付き・repo が実在しないタスクは自動除外</li>
<li>実在 repo のみキューへ(Phase3.1: <span class="term">per-task repo<span class="term-popup">タスクごとに対象リポジトリを紐付ける方式。多repo混在キューに対応</span></span> で多 repo 混在キューに対応)</li>
<li><span class="term"><code>state.json</code><span class="term-popup">ループの進行状態(active/current/pending/completed/blocked)を記録するファイル。停止再開の要</span></span> に pending / current を登録</li>
<li>最初のタスクが <span class="term"><code>run-task.sh</code><span class="term-popup">実装+検証を別プロセスで回す実行スクリプト。current.repoを読んで対象repoで実行</span></span> でバックグラウンド起動</li>
</ul>
</div>
</div>
<div class="acc-item" data-key="op4">
<button class="acc-header" onclick="toggle(this)">④ 自律実行連鎖(自動): run-task.sh → next_issue.py <span class="caret">▼</span></button>
<div class="acc-content">
<p>人間が承認した後は、完全に自律的に回る。</p>
<h4>run-task.sh(実装 + 検証)</h4>
<p>実装 claude(git commit まで)→ 検証 claude(別プロセス・ch8 分離)。検証結果を <span class="term"><code>verify-result.txt</code><span class="term-popup">検証プロセスが書き出す OK/NG の判定結果ファイル。next_issue.py が読んで次タスク起動を制御</span></span> に <code>OK</code> / <code>NG</code> で書き出す。</p>
<h4><span class="term">Stop hook<span class="term-popup">Claude終了時に発火する仕掛け。ここで次タスクを起動する</span></span> → next_issue.py(次タスク起動)</h4>
<p><span class="term"><code>verify-result.txt</code><span class="term-popup">検証プロセスが書き出す OK/NG の判定結果ファイル。next_issue.py が読んで次タスク起動を制御</span></span> を判定し、次タスクへ遷移させる。</p>
<table>
<tr><th>検証結果</th><th>遷移</th></tr>
<tr><td><code>OK</code></td><td><code>completed</code> に記録・pending から次タスクを current に昇格して起動</td></tr>
<tr><td><code>NG</code></td><td><code>blocked</code> に記録・停止(Phase3 では即停止・Phase4-B でリトライ厳密化予定だが実測0失敗で YAGNI 後回し)</td></tr>
</table>
</div>
</div>
<div class="acc-item" data-key="op5">
<button class="acc-header" onclick="toggle(this)">⑤ 終了(自動): 停止通知 <span class="caret">▼</span></button>
<div class="acc-content">
<ul>
<li>全タスク完了 → Windows トースト「dev-cycle 完了」</li>
<li>検証 NG 等 → <code>blocked</code>・<code>active: false</code> で停止通知(人間エスカレーション)</li>
</ul>
</div>
</div>
<h2 class="part-h2">第2部 アーキテクチャ・コンポーネント</h2>
<div class="why-box">
<strong>なぜ状態を <span class="term">state.json<span class="term-popup">ループの進行状態(実行中/待機/完了/blockers)を記録するファイル。停止再開の要</span></span> に記録する?</strong>
ループは複数プロセス(実装・検証・次タスク起動)にまたがる。メモリだけでなくファイルに「今どのタスク・待機群・完了/blockers」を書き込んでおくことで、停止しても再開でき、次タスク制御が状態を読むだけで済む。
</div>
<div class="acc-item" data-key="op6">
<button class="acc-header" onclick="toggle(this)">コンポーネント一覧 <span class="caret">▼</span></button>
<div class="acc-content">
<p>全コンポーネントは <code>~/projects/claude-config/scripts/auto-dev/</code> 配下。</p>
<table>
<tr><th>コンポーネント</th><th>役割</th></tr>
<tr><td><code>daily-triage.sh</code></td><td>Triage 発火エンジン(Claude判定呼出・today-tasks.md 生成・Discord通知)</td></tr>
<tr><td><code>daily_triage.py</code></td><td>バックログ/handoff収集・LLM判定・repo付与(<code>validate_repo()</code> 実在チェック二重防御)</td></tr>
<tr><td><code>approve.py</code></td><td>人間承認ゲート・<code>select_queueable()</code> で manual除外・state.json 登録</td></tr>
<tr><td><code>run-task.sh</code></td><td>実装 + 検証(別プロセス)・<code>current.repo</code> を読んで対象 repo で実行</td></tr>
<tr><td><code>next_issue.py</code></td><td>Stop hook 発火・<code>verify-result.txt</code> 判定・completed/blocked 遷移・次タスク起動</td></tr>
</table>
</div>
</div>
<div class="acc-item" data-key="op7">
<button class="acc-header" onclick="toggle(this)">state.json スキーマ(現行・Phase3.1) <span class="caret">▼</span></button>
<div class="acc-content">
<table>
<tr><th>キー</th><th>意味</th></tr>
<tr><td><code>active</code></td><td>ループ生存フラグ(false で即終了)</td></tr>
<tr><td><code>current</code></td><td>実行中タスク(<code>repo</code> は per-task・真実のソース)</td></tr>
<tr><td><code>pending</code></td><td>待機中タスク群(多 repo 混在可)</td></tr>
<tr><td><code>completed</code> / <code>blocked</code></td><td>各タスクの <code>repo</code> を記録</td></tr>
</table>
<blockquote>Phase3.1 で top-level <code>repo_path</code> は廃止。per-task <code>repo</code> が真実。</blockquote>
</div>
</div>
<h2 class="part-h2">第3部 運用</h2>
<div class="acc-item" data-key="op8">
<button class="acc-header" onclick="toggle(this)">運用コマンド <span class="caret">▼</span></button>
<div class="acc-content">
<pre><code># 手動で Daily Triage 実行(通知なし)
bash ~/projects/claude-config/scripts/auto-dev/daily-triage.sh
# 承認(候補選択 → state.json 登録・最初のタスク起動)
python3 ~/projects/claude-config/scripts/auto-dev/approve.py
# state.json 確認
cat ~/.claude/state/state.json
# テスト(claude-config/scripts/auto-dev で)
python3 -m pytest -q</code></pre>
</div>
</div>
<div class="acc-item" data-key="op9">
<button class="acc-header" onclick="toggle(this)">トラブル対応 <span class="caret">▼</span></button>
<div class="acc-content">
<table>
<tr><th>症状</th><th>原因・対処</th></tr>
<tr><td>承認時に today-tasks.md が変わる</td><td>別セッションが daily-triage.sh を実行した可能性。承認直前に再読 or タイムスタンプ確認(Phase3.1 残課題・根本対応は別タスク)</td></tr>
<tr><td>検証 NG で停止</td><td><code>blocked</code> を確認・手動で修正後、<code>active: true</code> + pending 追加で再開</td></tr>
<tr><td>cron 期限切れ</td><td>durable ジョブは7日で自動削除。<code>renew-crons.sh</code> で再登録</td></tr>
</table>
</div>
</div>
<div class="acc-item" data-key="op10">
<button class="acc-header" onclick="toggle(this)">補足: 旧Issue自律ループ(短縮) <span class="caret">▼</span></button>
<div class="acc-content">
<p>Loop Engineering 導入前に使っていた方式。Phase4(OSS Issue自律ループ復活)で別途再設計済み。</p>
<ul>
<li><strong>方式</strong>: <code>CronCreate</code>(durable・毎時)で <code>gh issue list</code> のオープンIssueを自律実装(ブランチ作成→実装→テスト→PR)</li>
<li><strong>現状</strong>: <code>start.sh --auto</code> 廃止 → Phase4 で <code>fetch_issues.py</code>/<code>set-mode.sh</code>/<code>auto-loop-config.yaml</code> + ハイブリッド承認ゲート(manual/auto)・単一 auto-loop ラベル・max3 安全装置で復活(60テスト passed)</li>
</ul>
<a class="src" href="https://fukukei23.github.io/loop-engineering-guide/">📎 詳細は概念教科書の第3部(自分の環境マップ)を参照</a>
</div>
</div>
<h2 class="part-h2">第4部 外部LLMレビューを運用ループに組み込む</h2>
<div class="acc-item" data-key="op11">
<button class="acc-header" onclick="toggle(this)">なぜ「別のLLM」にレビューさせるのか(実測データ付き) <span class="caret">▼</span></button>
<div class="acc-content">
<p>設計・監査など<strong>作者バイアスが乗る成果物</strong>は、作った本人(LLM含む)がレビューしても死角が残る。設計者は自分の前提の内側では完結した論理を組めてしまうため、<strong>モデルの能力を上げても自己レビューではこの穴は消えにくい</strong>。2026-07-05〜06に最高級モデルの監査・設計成果物へ別LLMのレビューを3回(計15指摘)実施し、採否を全件記録した実測がこの根拠になっている。</p>
<table>
<tr><th>レビュー指摘の内訳</th><th>件数</th></tr>
<tr><td>致命的な穴の検出(設計テーマ自体の見落とし)</td><td>1</td></tr>
<tr><td>優先度の再重み付け・有用な盲点追加</td><td>5</td></tr>
<tr><td>ホスト側の改善を誘発(触媒)</td><td>2</td></tr>
<tr><td>妥当性の確認(安心材料)</td><td>4</td></tr>
<tr><td>誤指摘(却下)</td><td>3</td></tr>
</table>
<p>運用ルールは3つ。</p>
<ul>
<li><strong>革新は期待しない</strong> — レビューから新アーキテクチャは出ない。出るのは「死角の消し込み」。それで十分黒字(採用率6割・致命穴1件/回のオーダー)</li>
<li><strong>価値の源泉は賢さでなく視点の独立性</strong> — レビュアーが作成者より賢い必要はない</li>
<li><strong>採否判定は必ず文脈を持つ側がやる</strong> — 誤指摘(2割)は環境文脈の不足から生じる。鵜呑みにすると逆に品質が下がる。環境固有の制約(無人cron・2層構造等)はレビュー依頼文に明示する</li>
</ul>
<a class="src" href="https://fukukei23.github.io/claude-code-guide/chapters/18-loop-system.html">📎 全体像は Claude Code ガイド 第18章「ループシステム」参照</a>
</div>
</div>
<footer style="margin-top:3rem; padding-top:1.5rem; border-top:1px solid var(--border,rgba(0,0,0,.1)); font-size:.82rem; opacity:.7;">
<p>🔧 私のLoop Engineering運用ガイド — 2026-06-29 作成(自分用運用手順整理)/2026-07-06 第4部(外部LLMレビュー)追加</p>
<p>原本: SSOT <code>00_SYSTEM/共通ルール/自律開発ループ.md</code> / 姉妹ページ: <a href="index.html">ループエンジニアリング教科書</a> / <a href="https://github.com/fukukei23/loop-engineering-guide">GitHub</a></p>
</footer>
</main>
<script>
function toggle(h){const i=h.closest(".acc-item"),o=i.classList.contains("open");i.classList.toggle("open",!o);const k=i.dataset.key;if(k)localStorage.setItem("acc_"+k,(!o).toString());}
document.querySelectorAll(".acc-item[data-key]").forEach(i=>{const k=i.dataset.key;if(localStorage.getItem("acc_"+k)==="true")i.classList.add("open");});
// テーマ切替
const themeToggle=document.getElementById('themeToggle');
const savedTheme=localStorage.getItem('theme')||'light';
document.documentElement.setAttribute('data-theme',savedTheme);
themeToggle.addEventListener('click',()=>{const cur=document.documentElement.getAttribute('data-theme');const next=cur==='dark'?'light':'dark';document.documentElement.setAttribute('data-theme',next);localStorage.setItem('theme',next);});
// メニュー(モバイル)
const menuToggle=document.getElementById('menuToggle');
if(menuToggle){menuToggle.addEventListener('click',()=>{document.body.classList.toggle('nav-open');});}
// === 注釈強化: term tooltip(ホバー/フォーカス/タップ対応・a11y) ===
(function(){
var tip = document.createElement('div');
tip.id = 'term-tooltip';
document.body.appendChild(tip);
var currentTerm = null;
function showTip(term) {
var popup = term.querySelector('.term-popup');
if (!popup) return;
tip.textContent = popup.textContent;
tip.style.display = 'block';
tip.classList.remove('below');
var r = term.getBoundingClientRect();
var tw = tip.offsetWidth;
var th = tip.offsetHeight;
var left = r.left + r.width / 2 - tw / 2;
left = Math.max(8, Math.min(left, window.innerWidth - tw - 8));
var top = r.top - th - 10;
if (top < 8) { top = r.bottom + 10; tip.classList.add('below'); }
tip.style.left = left + 'px';
tip.style.top = top + 'px';
currentTerm = term;
}
function hideTip() { tip.style.display = 'none'; currentTerm = null; }
document.querySelectorAll('.term').forEach(function(t){
t.setAttribute('tabindex', '0');
t.addEventListener('mouseenter', function(){ showTip(t); });
t.addEventListener('mouseleave', function(){ hideTip(); });
t.addEventListener('focus', function(){ showTip(t); });
t.addEventListener('blur', function(){ hideTip(); });
t.addEventListener('click', function(e){
e.stopPropagation();
if (currentTerm === t) { hideTip(); } else { showTip(t); }
});
});
document.addEventListener('click', function(){ hideTip(); });
window.addEventListener('scroll', function(){ if (currentTerm) showTip(currentTerm); }, {passive:true});
})();
</script>
</body>
</html>