<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Yoshihiko</title>
        <link>https://www.yoshihiko.tech/</link>
        <description>Yoshihiko tech siteです。技術ブログ兼ポートフォリオにもなっています。</description>
        <lastBuildDate>Tue, 03 Nov 2026 17:12:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>ja</language>
        <image>
            <title>Yoshihiko</title>
            <url>https://www.yoshihiko.tech/apple-touch-icon.png</url>
            <link>https://www.yoshihiko.tech/</link>
        </image>
        <copyright>© 2026 Yoshihiko</copyright>
        <category>blog</category>
        <atom:link href="https://www.yoshihiko.tech/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[ 認知負債は「承認」で生まれる ── コーディングエージェント時代の意図負債と、個人・チームの向き合い方]]></title>
            <link>https://www.yoshihiko.tech/blog/ai-cognitive-debt-approval</link>
            <guid isPermaLink="false">ai-cognitive-debt-approval</guid>
            <pubDate>Tue, 03 Nov 2026 17:12:07 GMT</pubDate>
            <description><![CDATA[コーディングエージェントに実装を任せると、コードは速く書けるのに人の理解が追いつかない。認知負債・意図負債・理解負債の違いを「下り」と「上り」で整理し、負債が生まれる承認の瞬間と、個人とチームでできる対策、まだ答えのない問いを、自分の体験から考える。]]></description>
            <content:encoded><![CDATA[<p>AI が「この方針で実装します」と計画を出す。私はそれを読み、承認する。実装が進む。そして後になって、あれはどういう仕様だったっけ、と振り返る。コーディングエージェントを毎日使っていて、私にはこういうことがよくある。</p>
<p>先に結論を書く。<strong>認知負債は、AI がコードを書いた瞬間ではなく、人が考えずに承認した瞬間に生まれる</strong>。AI は書く負荷を引き受けた代わりに、読む負荷を人に残した。読むのは重いので、AI の計画や差分は流し見のまま承認される。そこで、コードの中身が人の頭に戻ってこなくなる。</p>
<p>ただ、承認には 2 つの段階がある。考えずに承認すれば、負債は確実に生まれる。そして、理解したうえで承認しても、後で残っていないことがある。冒頭の場面は後者だ。前者には打てる手がある。後者については、私はまだ答えを持っていない。</p>
<p>認知負債（cognitive debt。認知的負債とも書く）は、ソフトウェア工学の研究者 Margaret-Anne Storey が「チームの共有理解が時間とともに侵食されること」と定義した言葉だ。実務の記事では「理解負債」と呼ばれることも多い。</p>
<p>私は 6 月の記事「<a href="https://zenn.dev/yoshihiko555/articles/78191b5e470806" target="_blank" rel="noopener noreferrer">AIは人を減らさない、守備範囲を広げる</a>」で、技術的負債と意図負債は自動レビューや品質ゲート、ADR でかなり潰せる、厄介なのは AI 以前からある認知負債だ、と書いた。今はそこを一部改めたい。変わった点は 2 つある。意図負債は ADR を書くだけでは潰しきれない。認知負債は AI 以前からある問題だが、AI がその溜まる速さを上げている。一方で、技術的負債は AI のレビューと品質ゲートでかなり減らせるという見立ては変わらない。ただし AI のレビューで減るのはコードの問題で、人の理解は増えない。Storey 自身も論文で「生成 AI は技術的負債を減らしながら、認知負債と意図負債の蓄積を速めうる」と書いている。</p>
<h2 id="ai-コーディングで認知負荷と認知負債が同時に増えている" tabindex="-1">AI コーディングで認知負荷と認知負債が同時に増えている</h2>
<p>AI は書く負荷を下げたが、読む負荷と、理解が抜けていく速さを上げた。いま現場で起きていることの多くは、この 2 つの組み合わせで説明できる。</p>
<p>私自身、AI に実装を任せた部分は頭に入ってきにくい。それで困った、あるいは困りそうだと感じたのは、変更や障害対応のとき、人に説明しようとしたとき、レビューで承認するとき、設計の選択を迫られたときだ。</p>
<p>この記事で「承認」と呼ぶのは、AI が出してきた計画や差分を受け入れることだ。Plan Mode で実装方針に「はい」と答えるのも、差分を見てマージするのも、どちらも承認である。</p>
<h3 id="承認が形だけになる" tabindex="-1">承認が形だけになる</h3>
<p>コードの生成速度が人の読む速度を超えた。twada さん（和田卓人さん）の講演「負債のメタファと2026年」でも、レビューが形骸化し始めていると指摘されていた。Addy Osmani は「<a href="https://addyosmani.com/blog/comprehension-debt/" target="_blank" rel="noopener noreferrer">Comprehension Debt</a>」で、「レビューされたコードは理解されたコードだ」という前提はもう成り立たない、と書いている。承認のボタンは押されている。だが、中身を読んで判断した結果とは限らない。</p>
<h3 id="チームの誰もシステムを説明できなくなる" tabindex="-1">チームの誰もシステムを説明できなくなる</h3>
<p>認知負債は、人が辞めなくても溜まる。以前は、メンバーが抜けて理解が失われるのが典型だった。いまは全員がそろったまま、誰も説明できない部分が増えていく。そうなると、変更は当てずっぽうになり、障害対応は博打になる。twada さんの講演の言葉を借りれば、AI も直せない障害が起きたとき、直せる人がいない。責任の空白である。</p>
<h3 id="理解が欠けても速度の指標には表れない" tabindex="-1">理解が欠けても速度の指標には表れない</h3>
<p>技術的負債はコードを開けば見える。認知負債は見えない。理解の欠け具合を測る指標もまだない。一方で、デプロイ頻度やリードタイムのような速度の指標は、設計の理解を置き去りにしても上げられる。twada さんはこれを「『生産性』を上げたい組織と個人の共犯関係が成り立ってしまう」と表現していた。評価が速さを褒め続ける限り、理解に時間を使う理由は生まれない。</p>
<p>若手にとってはなお厳しい。6 月の記事「<a href="https://zenn.dev/yoshihiko555/articles/807763e547a27c" target="_blank" rel="noopener noreferrer">AIは若手を奪わない、判断の回数を増やす</a>」で書いたとおり、判断の基礎は自分で判断した回数で育つ。AI の案を承認するだけの毎日では、その回数が増えない。</p>
<h2 id="3-つの負債を「下り」と「上り」で整理する" tabindex="-1">3 つの負債を「下り」と「上り」で整理する</h2>
<p>意図負債は、人の意図がコードになるまでの「下り」でずれる。認知負債は、コードの中身が人の頭に戻る「上り」で抜ける。こう分けると、2 つの負債の重なりと違いが見えやすい。</p>
<p>Storey は負債を 3 層に分けた（<a href="https://arxiv.org/abs/2603.22106" target="_blank" rel="noopener noreferrer">arXiv:2603.22106</a>）。</p>
<table>
<thead>
<tr>
<th>負債</th>
<th>宿る場所</th>
<th>Storey の定義（筆者訳）</th>
</tr>
</thead>
<tbody>
<tr>
<td>技術的負債</td>
<td>コード</td>
<td>コードの層にある問題</td>
</tr>
<tr>
<td>認知負債</td>
<td>人</td>
<td>チームの共有理解が時間とともに侵食される</td>
</tr>
<tr>
<td>意図負債</td>
<td>要求・ADR・計画・テストなどの記録</td>
<td>目標・制約・根拠が外に書き出されていない</td>
</tr>
</tbody>
</table>
<p>なお、MIT の Kosmyna らも「認知的負債」という言葉を使っている。こちらは LLM に頼ったときの個人の思考力の話で、Storey とは系統が別だ（最後の章で少し触れる）。</p>
<p><img src="//images.ctfassets.net/d3b1looh8iwg/6COpAHyV4YsilT4CYmX9Nb/1ae685d442b1699429b29d6007e5c53a/01_down-up-debt.png" alt="01 down-up-debt" loading="lazy"></p>
<h3 id="意図負債は「下り」でずれる" tabindex="-1">意図負債は「下り」でずれる</h3>
<p>人の意図が AI を経てコードになるまでに、意図は 3 か所でずれる。</p>
<ol>
<li>指示の足りない部分を、AI がもっともらしく埋める</li>
<li>実装の途中で、小さな判断を AI が黙って下す</li>
<li>後で読むと、ADR や計画に書かれた意図が、自分のものか AI のものか分からない</li>
</ol>
<p>3 つ目が特に厄介だ。たとえば、AI に書かせた ADR の「この方式を選んだ理由」の欄を考えてみる。読めば筋は通っている。だが、それが自分で考えて決めた理由なのか、AI が後から補った理由なのかは、もう区別できない。twada さんも、AI が生成したドキュメントに残る「意図」の多くは AI が行間をもっともらしく埋めた結果で、人の判断と見分けがつきにくい、と指摘していた。</p>
<p>だから、ADR を書くだけでは意図負債は潰しきれない。記録は残る。だが、それが人の意図だという保証がない。ここが、6 月に私が楽観しすぎていた点だ。</p>
<h3 id="認知負債は「上り」で抜ける" tabindex="-1">認知負債は「上り」で抜ける</h3>
<p>上りはコードの中身が人の頭に戻ってくる流れだ。自分で書いていた頃は、書くこと自体がこの流れを作っていた。AI に任せると、その流れは承認の場面にしか残らない。承認は、理解が人に戻る最後の関所になる。そこを流し見で通れば、理解は戻らない。</p>
<h2 id="承認で負債が生まれる理由" tabindex="-1">承認で負債が生まれる理由</h2>
<p>AI は書く負荷を引き受けた。その分、人には読む負荷が残った。自分で書いていないコードを読んで正しさを確かめるのは、書くより重いことが多い。だから承認は流し見になる。</p>
<p>ここで言う認知負荷は作業中に頭にかかる負担のことだ。負荷はその場の負担で、負債は時間とともに溜まっていくもの。読む負荷が重すぎて流し見になり、流し見の承認が積み重なって負債になる。この記事では負荷と負債の関係をそう捉えている。</p>
<p><img src="//images.ctfassets.net/d3b1looh8iwg/7t4LpyCsLOjXbojHASNnN/2e9404ac11dce427b152f99f284d164a/03_write-load-to-read-load.png" alt="03_write-load-to-read-load" loading="lazy"></p>
<h3 id="書くことは理解する手段でもあった" tabindex="-1">書くことは理解する手段でもあった</h3>
<p>AI 以前は、自分でコードを書くことで、そのコードを理解できた。Peter Naur は 1985 年の論文「Programming as Theory Building」で、プログラミングの本質は、問題がどう解かれるかという「理論」をプログラマが頭の中に築くことだと論じた。その理論はコードや文書だけでは表しきれない。書く手間と摩擦を通して、この理論が頭の中にできていた。</p>
<p>twada さんは講演で、技術的負債という比喩が生まれた 1992 年と今とを対比していた。1992 年は「人間達の理解が先行し、コードがついてきていない状態」だった。2026 年は「AI によるコード生成が先行し、人間の理解がついてきていない状態」だ。34 年で構図が逆転した。</p>
<p><img src="//images.ctfassets.net/d3b1looh8iwg/6rs4qtxq1RARsuuSmI5Psc/e24e272b4fe83c4d03153a23d11a6aaf/02_inversion-1992-2026.png" alt="1992 年は人の理解が先行してコードが追いつかず、2026 年は AI の生成が先行して人の理解が追いつかない、という構図の逆転を示す図" loading="lazy"></p>
<h3 id="6-月の記事との線引き" tabindex="-1">6 月の記事との線引き</h3>
<p>6 月の若手の記事では「手を動かす基礎は AI に任せていい」と書いた。今もそう思っている。シンタックスや定型コードを書く力は任せてよい。</p>
<p>ただ、書くことにはもう一つの役割があった。そのシステムを理解する手段だ。任せてよいのはスキルとしての「書く」で、理解の手段としての「書く」は、任せた時点で失われる。その穴は別の方法で埋める必要がある。</p>
<p>6 月には、AI と壁打ちすれば判断の回数を増やせる、とも書いた。この回数は自分で判断した回数のことだ。AI の推奨を承認しただけの回は、数に入らない。</p>
<h3 id="認知的降伏" tabindex="-1">認知的降伏</h3>
<p>AI の出力は流暢で、自信ありげに見える。そのため人は自分で考えずに判断を委ねてしまう。Shaw と Nave は 2026 年のワーキングペーパー（未査読）で、この傾向を「認知的降伏（cognitive surrender）」と呼んだ。twada さんのスライドでは、判断・労力・責任を AI の出力に委ねてしまう傾向と説明されている。</p>
<p>二人は人の思考を速い直感（システム 1）と遅い熟慮（システム 2）に分ける考え方に、AI という外の認知を「システム 3」として加えた。twada さんのスライドの説明では、熟慮は自動では動かない。直感が「何かおかしい」と引っかかったときに、はじめて動き出す。ところが AI の流暢な出力は、この引っかかりを生まない。読み手はそれを詳しい人の意見のように受け取り、AI の答えがそのまま自分の答えになる。</p>
<p>Addy Osmani の紹介によれば、研究は 3 つの実験、参加者 1,372 人で行われた。厄介なのは、AI が間違っていたときでさえ人の自信が上がることだ。だから本人は降伏していることに気づけない。</p>
<p>Osmani は「<a href="https://addyosmani.com/blog/cognitive-surrender/" target="_blank" rel="noopener noreferrer">Cognitive Surrender</a>」で、任せることと明け渡すことを分けている。AI に作業を任せても、答えを自分のものとして持っているなら、それは任せただけ（offloading）だ。AI の出力が気づかないうちに自分の出力になり、自分で答えを組み立てるのをやめたとき、それが降伏（surrender）だという。この区別は次の章で書く線引きに近い。実装の細部は任せてよい。意図と設計の判断まで明け渡すと、降伏になる。</p>
<p>twada さんのスライドには「AI が選択肢を作って人が選ぶだけでは理解は育ちにくい」とある。私にも覚えがある。AI が推奨つきの選択肢を出してくると、私はその推奨を選ぶだけになる。計画も同じで、AI が「この方針でいきます」と出したものを、割とそのまま承認する。この流れで理解のステップを踏まないと、AI の出力を流し見しただけになり、自分のインプットにならない。</p>
<h3 id="承認には-2-つの段階がある" tabindex="-1">承認には 2 つの段階がある</h3>
<ul>
<li>考えずに承認すると、負債は確実に生まれる。この記事の対策は主にこの段階に効く。</li>
<li>理解して承認したのに、後で残っていない。この段階は最後の章で扱う。</li>
</ul>
<p><img src="//images.ctfassets.net/d3b1looh8iwg/3wpwBa7T8VCk5iTxpH7mIH/47d682b12b24b535f2c8e18bd68f7566/04_two-stages-of-approval.png" alt="04 two-stages-of-approval" loading="lazy"></p>
<h2 id="反論:-理解を手放して要るときに作り直せばいいのでは" tabindex="-1">反論: 理解を手放して要るときに作り直せばいいのでは</h2>
<p>実装の細部についてはこの反論に賛成する。意図と設計については賛成しない。</p>
<p>人の理解に頼るのをやめようという立場がある。型・テスト・監視で振る舞いを守り、必要になったら AI に説明させて理解し直せばいい、という考え方だ。James Phoenix の「<a href="https://understandingdata.com/posts/comprehension-debt-is-refinanceable/" target="_blank" rel="noopener noreferrer">Comprehension Debt Is Refinanceable</a>」は、理解を増やすより、理解を作り直すコストを下げるほうがよいと論じている。</p>
<p>実装の細部なら、私も手放してよいと思う。関数の中身を全部覚えておく必要はない。振る舞いはテストが守ってくれる。euglena1215 さんの「<a href="https://euglena1215.hatenablog.jp/entry/2026/09/27/113320" target="_blank" rel="noopener noreferrer">理解をどこまで手放すかは、リスク許容度と仕組みで決まる</a>」も、実装の理解は手放してよく、意図は人の記憶に頼らず残すべきだとしていて、私の立場に近い。</p>
<p>それでも、意図と設計の理解は手放せない。理由は 2 つある。</p>
<p>1 つ目は意図が判断した瞬間にしか残せないことだ。twada さんも、意図を捉える一番よいタイミングは重要な判断を下す瞬間で、後から取り戻すのは難しい、時には不可能だと述べていた。後から AI に説明させても、出てくるのはコードから逆算したもっともらしい理由で、当時の判断そのものではない。</p>
<p>2 つ目は作り直した説明が正しいかを判断する人が要ることだ。その人がいなければ、最初の章で書いた責任の空白に戻る。</p>
<p>ただし、最後の章で書く問題が本当なら、この反論の側に分がある。</p>
<h2 id="個人でできること" tabindex="-1">個人でできること</h2>
<p>下りでは意図を先に書く。上りでは承認する前に自分の言葉で出す。私が一番効いていると感じているのは後者だ。</p>
<h3 id="意図と完了条件を先に書く" tabindex="-1">意図と完了条件を先に書く</h3>
<p>AI に渡す前に、何をしたいのか、何ができたら終わりなのかを、自分の言葉で書く。Qiita の記事「<a href="https://qiita.com/Sicut_study/items/b9e38c0584d4aa720d31" target="_blank" rel="noopener noreferrer">【未来を解説】AIでエンジニア格差を生み出す質とスピードの考え方</a>」も、まずこの 1 つを勧めている。</p>
<p>書く形は何でもいい。例を置いておく。最後の欄は下りで起きる「途中の小さな判断を AI が黙って下す」を減らすためのものだ。</p>
<pre><code class="language-markdown">## やりたいこと

- 何を、なぜ変えるか（1〜2 行）

## 完了条件

- 何ができたら終わりか。どう確かめるか

## 決めていること / 決めていないこと

- 決めている: 使う方式、触らないファイルなど
- 決めていない: AI に提案してほしいこと。勝手に決めずに聞いてほしいこと
</code></pre>
<h3 id="大きい変更のときは計画を図解させる" tabindex="-1">大きい変更のときは計画を図解させる</h3>
<p>私は大きい変更だと感じたときや ADR を書くときに、計画が何をしようとしているのかを AI に図解させ、それを読んでから進めている。基準は決めておらず、自分の感覚で判断している。毎回やると重いからだ。twada さんも講演の口頭で、毎回の理解度チェックは運用コストが重く、図解に寄せていった、という趣旨の話をしていた（スライドには載っていない。私の聞き取りによる）。</p>
<p>頼み方の例を置いておく。</p>
<pre><code class="language-text">実装に入る前に、この計画を図で説明してください。
- 変更前と変更後で、モジュールどうしの関係がどう変わるか
- データがどこから来て、どこへ行くか
- この計画の中で、私に確認せずに決めたことがあれば列挙する
図は Mermaid で。説明は図 1 枚につき 3 行まで。
</code></pre>
<p>ただし、図は分かった気にさせやすい。twada さんのスライドにも、豊かな形式のほうが理解は進むが、同時に認知的降伏のリスクも高まる、と注意がある。図を読んで終わりにしない。</p>
<h3 id="承認する前に自分の言葉で説明する" tabindex="-1">承認する前に自分の言葉で説明する</h3>
<p>図を読んだら、承認する前に、計画が何をするのかを自分の言葉で書いてみる。3 行でいい。書けなければ、まだ理解していない。</p>
<p>twada さんのスライドはこれをプロテジェ効果として紹介している。他者に説明しようとすると、自分の理解の足りなさがはっきりし、知識が定着して本当の理解につながる。私はアウトプットが認知負債を埋めるのに一番有効な方法だと感じている。</p>
<h2 id="チームでできること" tabindex="-1">チームでできること</h2>
<p>個人の対策をチームの仕組みに移す。理解を関門にする、説明し合う場を作る、評価の物差しを変える、の 3 つだ。</p>
<h3 id="理解を関門にする" tabindex="-1">理解を関門にする</h3>
<p>大きい変更はマージする前に、変更した人が中身を説明できるかを確かめる。twada さんのスライドでも、Plan Mode を抜けるときに理解をチェックする試みが紹介されている。全部の PR でやると回らないので、対象は大きい変更に絞る。個人の図解と同じ考え方だ。</p>
<h3 id="説明し合う場を作る" tabindex="-1">説明し合う場を作る</h3>
<p>設計の説明会でも、ペア作業でもいい。誰かに説明する機会をチームで用意する。個人のアウトプットと同じ効果があるうえに、一人の頭の中にあった理解がチームのものになる。認知負債はチームの共有理解の問題なので、ここが一番効くはずだ。</p>
<h3 id="評価の物差しを変える" tabindex="-1">評価の物差しを変える</h3>
<p>速度を上げても、理解が欠けていれば意味がない。理解は測りにくいが、速さだけを褒めるのはやめられる。たとえば振り返りで「何件出したか」と一緒に「その変更を説明できるか」を聞く。それだけでも、理解に時間を使う理由ができる。</p>
<p>最初の章の課題とこの章の対策はこう対応する。関門と説明の場は理解を人に戻す上りに効く。評価の物差しは意図を書く時間と理解する時間を作るので、上りと下りの両方に効く。</p>
<p><img src="//images.ctfassets.net/d3b1looh8iwg/3EFHukqbPOlxk7c2XYLNw2/0202253d10e735094e352c568ed392c2/05_team-measures.png" alt="05 team-measures" loading="lazy"></p>
<h2 id="理解して承認しても残らない問題" tabindex="-1">理解して承認しても残らない問題</h2>
<p>最後に、答えの出ていない問題を書いておく。</p>
<p>図解させて AI の出力を読み、その場では理解したうえで承認する。それでも後になって、あれはどういう仕様だったっけ、と振り返ることがよくある。ここまでの対策は承認の時点の理解には効く。だが、時間がたっても残ることまでは保証しない。</p>
<p>これは私だけの話ではないかもしれない。MIT の Kosmyna らの研究（<a href="https://arxiv.org/abs/2506.08872" target="_blank" rel="noopener noreferrer">arXiv:2506.08872</a>）では、LLM を使ってエッセイを書いた群の 83.3%（18 人中 15 人）が、書いた後に自分の文を正しく引用できなかった。検索エンジンを使った群と、何も使わなかった群では、どちらも 11.1%（18 人中 2 人）だった。未査読のプレプリントで、被験者は 54 人、課題は短いエッセイなので、そのまま開発に当てはめることはできない。ただ、自分で関わったはずのものが手元に残らないという点は、私の感覚に近い。</p>
<p>これが本当なら、前の章の反論の側に分がある。理解は守っても残らないのだから、作り直せるようにしておくほうが現実的だ、と。それでも私は、意図と設計は手放せないと考えている。では、残らない理解をどう残すのか。あるいは残らない前提で意図をどう守るのか。そこはまだ分からない。</p>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>認知負債は、AI がコードを書いた瞬間ではなく、人が考えずに承認した瞬間に生まれる。意図は下りでずれ、理解は上りで抜ける。実装の細部の理解は手放してよいが、意図と設計の理解は手放せない。</p>
<p>次に大きい変更を AI に任せるとき、2 つだけ試してほしい。</p>
<ol>
<li>渡す前に、意図と完了条件を 3 行で書く</li>
<li>承認する前に、計画を自分の言葉で 3 行で説明する</li>
</ol>
<p>どちらも考えずに承認するのを防ぐためのものだ。理解して承認しても残らない方は、私もまだ悩んでいる。</p>
<h2 id="参考文献" tabindex="-1">参考文献</h2>
<ul>
<li>和田卓人「<a href="https://speakerdeck.com/twada/debt-metaphor-in-agentic-engineering-age-202609-edition" target="_blank" rel="noopener noreferrer">負債のメタファと2026年</a>」技術的負債に向き合うConference 2026、2026-09-17</li>
<li>Margaret-Anne Storey, &quot;<a href="https://arxiv.org/abs/2603.22106" target="_blank" rel="noopener noreferrer">From Technical Debt to Cognitive and Intent Debt: Rethinking Software Health in the Age of AI</a>&quot;, arXiv:2603.22106, 2026（<a href="https://queue.acm.org/detail.cfm?id=3807966" target="_blank" rel="noopener noreferrer">ACM Queue 版</a>）</li>
<li>Steven D. Shaw, Gideon Nave, &quot;<a href="https://osf.io/n84tx" target="_blank" rel="noopener noreferrer">Thinking—Fast, Slow, and Artificial: How AI is Reshaping Human Reasoning and the Rise of Cognitive Surrender</a>&quot;, 2026（ワーキングペーパー、未査読）</li>
<li>Nataliya Kosmyna ほか, &quot;<a href="https://arxiv.org/abs/2506.08872" target="_blank" rel="noopener noreferrer">Your Brain on ChatGPT: Accumulation of Cognitive Debt when Using an AI Assistant for Essay Writing Task</a>&quot;, arXiv:2506.08872, 2025（プレプリント、未査読）</li>
<li>Peter Naur, &quot;Programming as Theory Building&quot;, Microprocessing and Microprogramming 15(5), 1985</li>
<li>Addy Osmani, &quot;<a href="https://addyosmani.com/blog/comprehension-debt/" target="_blank" rel="noopener noreferrer">Comprehension Debt</a>&quot;, 2026</li>
<li>Addy Osmani, &quot;<a href="https://addyosmani.com/blog/cognitive-surrender/" target="_blank" rel="noopener noreferrer">Cognitive Surrender</a>&quot;, 2026-05-05</li>
<li>James Phoenix, &quot;<a href="https://understandingdata.com/posts/comprehension-debt-is-refinanceable/" target="_blank" rel="noopener noreferrer">Comprehension Debt Is Refinanceable</a>&quot;, 2026</li>
<li>euglena1215「<a href="https://euglena1215.hatenablog.jp/entry/2026/09/27/113320" target="_blank" rel="noopener noreferrer">理解をどこまで手放すかは、リスク許容度と仕組みで決まる</a>」2026-09-27</li>
<li>Watanabe Jin「<a href="https://qiita.com/Sicut_study/items/b9e38c0584d4aa720d31" target="_blank" rel="noopener noreferrer">【未来を解説】AIでエンジニア格差を生み出す質とスピードの考え方</a>」Qiita</li>
</ul>
]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[デザインハーネスを個人開発で組んだ — AI が作る画面のずれを止めるまで]]></title>
            <link>https://www.yoshihiko.tech/blog/design-harness-personal-product</link>
            <guid isPermaLink="false">design-harness-personal-product</guid>
            <pubDate>Tue, 03 Nov 2026 15:39:02 GMT</pubDate>
            <description><![CDATA[デザイナーのいない個人開発で、AI が作る画面のずれを止めるまでの記録。Codex の Product Design で画像の案を出して画面を作り直し、デザインファイルとコードの食い違いを正本の分担で片付け、lint・見た目の比較・AI の評価役からなるデザインハーネスで外れたら止める仕組みを組んだ。]]></description>
            <content:encoded><![CDATA[<p>AI に画面を作らせると、1 画面ずつは悪くないのに、並べると少しずつ違う。余白が数 px ずれる。同じ青に別々の名前が付く。似たボタンがまた 1 つ増える。</p>
<p>この記事は、デザイナーのいない個人開発で、この「ずれ」を止めるまでの記録だ。やったことは 3 つある。</p>
<ol>
<li><strong>作る</strong>: AI に画像で案をいくつも出させ、自分で選んで画面を作り直した</li>
<li><strong>見直す</strong>: 作り直したら、デザインファイルとコードで色や文字サイズの決め方が食い違っていたので、どちらを正とするか決め直した</li>
<li><strong>守る</strong>: AI が作った画面がデザインから外れたら、自動で止まる仕組みを入れた</li>
</ol>
<p>3 つ目の仕組みを、デザインハーネスと呼んでいる。AI に画面を作らせても、デザインから外れないようにする仕組み全体のことだ。作らせる前に守るべきデザインを渡し、作った後に外れていたら止め、同じ指摘が続いたら仕組みの方を直す。</p>
<p>作っているのは学習アプリで、画面の実装の大半を Claude Code と Codex に任せている。技術スタックは Next.js、Tailwind CSS v4、shadcn/ui だ。作り直しから検査の仕組みまでは、2026 年 9 月末から 10 月 8 日までの約 2 週間で組んだ。画面のスクリーンショットは載せない。ずれを止める方法だけ知りたい場合は、「見直す」の章から読んでも話は通じる。</p>
<p><img src="https://static.zenn.studio/user-upload/9c5a0fcd1f99-20261010.png" alt="全体の流れ。左から「作る」（画像で方向を出して選び、デザインファイルに写す）、「見直す」（見た目・値・振る舞いの正本を分ける）、「守る」（機械の検査と評価役 3 票の 2 つの門を通し、採否は人が決める）の 3 段が並ぶ。「守る」から「見直す」へ、繰り返す指摘を lint に戻す矢印がある" loading="lazy"></p>
<h2 id="作る:-画像で案を出し、選んで、作り直す" tabindex="-1">作る: 画像で案を出し、選んで、作り直す</h2>
<p>画面の方向は、AI に画像で何案も出させて自分で選ぶと早く決まった。ただ、画像は方向を決める材料で、寸法の仕様ではない。画像を仕様のように扱うと、できた画面が選んだ案から離れる。</p>
<h3 id="継ぎ足しの見た目から出るために" tabindex="-1">継ぎ足しの見た目から出るために</h3>
<p>作り直す前の画面は、機能を足すたびに継ぎ足してきた見た目だった。部品を少しずつ直しても、元の見た目の延長からは出られない。そこで今の画面をいったん忘れ、画面ごとに何を見せるかを書いた設計書から考え直した。</p>
<p>方向を探すなら、コードより画像の方が速い。1 案を数分で出せて、並べて比べられる。使ったのは、OpenAI が 2026 年 6 月に出した Codex の Product Design プラグインだ[^openai]。画面の方向を画像で出し、人が選ぶまで実装に進まない作りになっている。既定では 3 案だが、もう少し案が欲しかったので 9 案まで出させた。</p>
<p>使ってみて、1 画面だけで選ばない方がよいと分かった。ホームだけで選ぶと、ほかの画面に広げたときに崩れる案を拾ってしまう。候補ごとに主な画面を何枚か並べてから選んだ。別の AI に評価させるのも役に立った。Codex が出した案を Claude に見せて使い勝手の観点で比べさせると、「A を土台にして、B の現在地の見せ方を足す」のように、2 案を組み合わせる提案が返ってきた。1 案をそのまま選ぶより、こちらの方がしっくりきた。</p>
<p>全画面で案を出す必要はなかった。画像で方向を探したのは、トップページやダッシュボードのような、新しい見せ方が要る画面だけだ。先に作り直した部品で組める画面は、画像を作らずにそのまま作った。</p>
<h3 id="画像は測って、デザインファイルに写す" tabindex="-1">画像は測って、デザインファイルに写す</h3>
<p>選んだ案は、そのまま実装させずにデザインファイルに写した。デザインツールは、Figma の代わりに Pencil を使っている。AI が MCP（AI が外部のツールを操作するための接続の規格）経由でファイルを読み書きできるツールで、ファイルの拡張子は <code>.pen</code> だ。</p>
<p>写すときは、採用案の画像をデザインファイルの枠に重ね、左右の比率、部品の高さ、文字の大きさ、余白を測る。画像を見ながら「それっぽく」描かせると、少しずつ別物になる。測って写す方が、結局は早かった。写した後は、採用案とデザインファイル、デザインファイルと実際の画面の 2 段で比べ、ずれは 3px 以内に収める。</p>
<p>画像から動く試作を作ることもでき、最初は作って触ってみた。ただ、試作は本番とは別のコードなので、育て始めると画像・試作・本番の 3 つを見比べ続けることになる。試作は確認に使ったら捨てた。</p>
<p><img src="https://static.zenn.studio/user-upload/4610077de23e-20261010.png" alt="画像の案からデザインファイル、実装までの流れ。画像で案を出す、人が選ぶ、採用案を測ってデザインファイルに写す、実装する、の順に進み、比較は採用案とデザインファイル、デザインファイルと実画面（3px 以内）の 2 段で行う" loading="lazy"></p>
<h2 id="見直す:-作り直したら、デザインファイルとコードが食い違っていた" tabindex="-1">見直す: 作り直したら、デザインファイルとコードが食い違っていた</h2>
<p>画像から作り直すと、色や文字サイズは画像から推し量るしかない。作り直しが終わってから見直すと、デザインファイルとコードと文書で、同じものの名前や値が食い違っていた。最終的に、見た目・値・振る舞いの置き場所を分け、値はコードを正にした。</p>
<h3 id="見えた食い違い" tabindex="-1">見えた食い違い</h3>
<p>たとえば次のようなものだ。</p>
<ul>
<li>デザインファイルで <code>accent</code> と呼んでいた色は、コードでいう主なボタンの色だった。ところが shadcn/ui では、<code>accent</code> はホバーしたときの背景の色を指す。このままだと、AI に「accent の色で」と頼んだとき、どちらの色が出るか分からない</li>
<li>本文の文字サイズが、デザインファイルとコードで違っていた</li>
<li>色や文字サイズの一覧を写した文書があり、「変えたら一緒に直す」と決めていたのに、追いついていなかった</li>
</ul>
<p>Tailwind CSS にも落とし穴があった。存在しないクラス名を書いてもエラーにならず、そのクラスは無視される。AI はそれらしいクラス名を作るので、効いていない指定が画面に残っていても、書かせた本人には見えにくい。</p>
<h3 id="何を正にするかを分ける" tabindex="-1">何を正にするかを分ける</h3>
<p>そこで、正とするもの（正本）を 3 つに分けた。デザイナーがいないので、値の正はコードに置いている。</p>
<ul>
<li><strong>見た目</strong>（配置・余白・どの色を使うか）: デザインファイル（<code>.pen</code>）。見た目はここで先に決める</li>
<li><strong>値</strong>（色のコードや文字サイズの数値）: コード。Tailwind CSS v4 で値を定義する <code>@theme</code> という場所に置く。ここに書いた名前が、そのままクラス名として使える</li>
<li><strong>振る舞い</strong>（押したとき、無効のときなどの部品の動き）: Storybook。部品を 1 つずつ描いて確かめるカタログだ</li>
</ul>
<p>値をコードに置いたのは、デザイナーがいないことのほかに、影の濃さやコンテナの幅のように、デザインファイルに無い値があったからでもある。デザイナーがデザインファイルを持つチームなら、デザインファイルを正にしてコードを生成する方が合うかもしれない。</p>
<p>デザインファイルとコードで値が食い違ったら、コードに合わせてデザインファイルを直す。この 2 つは名前をそろえ、同期テストを置いた。同じ名前の値が両方で一致しているかを確かめるテストで、食い違うと CI（変更のたびに自動でテストを回す仕組み）が落ちる。値を写した文書はやめた。人もエージェントも追いつけなかったので、写しを持つこと自体をやめた方が早い。</p>
<p><img src="https://static.zenn.studio/user-upload/e8db674d2dca-20261010.png" alt="正本の分担。見た目はデザインファイル（.pen）、値はコードの @theme、振る舞いは Storybook が持つ。デザインファイルと @theme の間は同期テストでつながり、食い違うと CI が落ちる。入口の DESIGN.md は値を持たず、3 つの正本を指す" loading="lazy"></p>
<h3 id="ai-が勝手な色を使えないようにする" tabindex="-1">AI が勝手な色を使えないようにする</h3>
<p>Tailwind には、最初から青や赤などの色がたくさん入っている。AI はこれを気軽に使うので、決めていない青が増える。そこで標準の色を外し、「主なボタンの色」「本文の文字の色」のような、用途の名前が付いた色だけを使える状態にした。用途の名前の裏には具体的な色が付いているが、その具体的な色はクラス名としては書けない。</p>
<p>ただ、これだけでは <code>bg-[#2455cf]</code> のように色を直接書く書き方（任意値）は防げない。そこは次の章の lint で止めている。</p>
<h3 id="入口の文書には値を書かない" tabindex="-1">入口の文書には値を書かない</h3>
<p><code>DESIGN.md</code> は、人とエージェントの両方が最初に読む、デザインの説明書だ。書いているのは、どこが正本か、目指す印象（落ち着いていて、安っぽくない）、色の使い分け、やってよいこととよくないことで、値は書かない。書けばそれは写しになり、また追いつかなくなる。値や部品は、コードや Storybook を見に行かせる。</p>
<h2 id="守る:-デザインハーネスで、外れたら止める" tabindex="-1">守る: デザインハーネスで、外れたら止める</h2>
<p>正本を決めて渡しても、AI はときどき外れる。だから、作った後に外れていたら止まる仕組みを入れた。止めるのはまず機械で、AI の評価は人の判断材料にとどめる。</p>
<h3 id="言葉の出どころと、各社の事例" tabindex="-1">言葉の出どころと、各社の事例</h3>
<p>デザインハーネスという言葉は、ハーネスエンジニアリングから来ている。AI エージェントの周りを設定や検査で固めて、外れにくくする考え方だ（<a href="/blog/prompt-context-harness-engineering">以前まとめた記事</a>）。それをデザインに当てはめた。私が見つけた最も早い用例は、こぎそ氏の note（2026 年 5 月）だった[^kgsi]。</p>
<p>組む前に各社の公開事例を調べると、多くは「作らせる前に渡す」側の話だった。「作った後に止める」側を中身まで出している例は少ない。私の環境も同じで、渡す側はあったが、止める側が手薄だった。この章では、止める側を中心に書く。</p>
<h3 id="機械で止める" tabindex="-1">機械で止める</h3>
<p>機械の検査は、図 1 にも書いた lint・a11y・見た目の比較だ。</p>
<ul>
<li><strong>lint</strong>（コードを機械的に調べ、決まりに反した書き方を落とす仕組み）: 用途の名前に無い色やクラス名、決めていない文字サイズ、任意値の色を書くと落ちる。ボタンや入力欄を、共通の部品を使わずに直接書いたときも落ちる</li>
<li><strong>a11y</strong>（アクセシビリティ）: 文字と背景のコントラストなどを、部品ごとに自動で検査する</li>
<li><strong>見た目の比較</strong>: 主な画面のスクリーンショットを撮って前回と比べ、変わっていたら知らせる。撮る環境は Docker で固定している。環境が変わるとフォントの描かれ方が変わり、それだけで差分が出るからだ</li>
</ul>
<p>見た目の比較だけは性質が違う。差分を見つけるのは機械だが、それが意図した変更かどうかは、人が画像を見て決める。</p>
<h3 id="ai-の評価役は、合否に使わない" tabindex="-1">AI の評価役は、合否に使わない</h3>
<p>機械の検査では、「共通部品で済むのに新しく部品を作っていないか」「読み込み中の表示が抜けていないか」は分からない。そこで、AI に評価させるスキルを作った。変更前後の画面を撮り、3 体の評価役が互いの答えを見ずに、観点ごとに判定する。観点は、<code>DESIGN.md</code> の原則を 11 個の判定項目（DC-01〜11）に書き直したものだ。判定は 2 票以上そろったものを採り、割れたら「割れた（split）」と書く。</p>
<p>この結果は、合否（変更を通すか止めるか）には使っていない。指摘を採るか採らないかは、人が決める。AI の評価には、まだ任せきれないところがあるからだ。30 の UI を AI に評価させた研究では、人の採点との相関は 0.7 前後で、著者は補助として使うべきだとしている[^uijudge]。それに 3 体とも同じモデルなので、1 回ごとの揺れは多数決で少し抑えられても、モデルの癖は 3 体そろって同じ方向に出る。</p>
<p>観点は、本来ならデザイナーが決めて言葉にするものだと思う。デザイナーのいない私は自分で書いたが、変えるのは人に限っている。評価される変更と一緒に観点を書き換えると、スキルが止まるようにしてある。指摘を通すために観点を緩める、という抜け道を塞ぐためだ。</p>
<p>これまで回したのは 2 回で、無効状態のボタンの見た目を直すきっかけにはなった。役に立つと言い切るには、まだ回数が足りない。</p>
<p><img src="https://static.zenn.studio/user-upload/6ce7e498da18-20261010.png" alt="評価役のレビュー。変更前後の画面の画像を、互いの票を見ない 3 体の評価役が観点（DC-01〜11）ごとに判定する。2 票以上で多数決、割れたら split。結果のレポートを見て、指摘を採るかどうかは人が決める。合否には使わない" loading="lazy"></p>
<h3 id="繰り返す指摘は、仕組みに上げる" tabindex="-1">繰り返す指摘は、仕組みに上げる</h3>
<p>ハーネスは最初から完成させるものではないと考えている。同じ指摘が繰り返されたら、注意書きで終わらせずに lint やテストへ上げる。</p>
<p>たとえば、ボタンや入力欄を直接書くと、共通部品とよく似た別物ができる。何度も指摘していたので、lint で止めるようにした。入れた時点で、刷新を終えた画面にも 15 か所残っていた。いきなり全部は直さず、理由を書いた例外にして入れ、少しずつ部品に置き換えて、いまは 8 か所まで減っている。</p>
<h2 id="個人でやってみた限界" tabindex="-1">個人でやってみた限界</h2>
<ul>
<li>試したのは個人の 1 プロダクトで、画面の数もそう多くない。ダークモードや複数のブランドは、まだ扱っていない</li>
<li>「落ち着いているか」「安っぽくないか」のようなプロダクトらしさは、AI の評価役に見させているが、判定できているとは言えない</li>
<li>見た目の比較と評価役は Docker に、デザインファイルの編集は macOS で動く Pencil に頼っている</li>
<li>最後に決めるのは人なので、品質を自動で保証する仕組みではない。1 か月の計測を始めたところで、効果を示す数字もまだない</li>
</ul>
<p>チームに持ち込むなら、評価の観点を誰が持ち、誰が変えられるかを先に決めたい。デザイナーがいるなら、観点やプロダクトらしさの定義はデザイナーが持ち、AI の評価役はその補助にするのが自然だと思う。観点の持ち主を決めておかないと、指摘を通すために観点を緩める変更が出てくる。</p>
<h2 id="何から始めるか" tabindex="-1">何から始めるか</h2>
<p>これから始めるなら、次の順番を勧める。</p>
<ol>
<li><strong>値の正を 1 か所に決める</strong>: Tailwind CSS v4 なら、<code>@theme</code> に用途の名前で色や文字サイズを書く。同じ <code>@theme</code> の中に <code>--color-*: initial;</code> と書くと、標準の色が外れる。決めていないクラス名は、ESLint のプラグイン <code>eslint-plugin-tailwindcss</code> で止められる。デザインファイルが無くてもできる</li>
<li><strong>a11y の検査を CI で回す</strong></li>
<li><strong>主な画面を数枚、見た目の比較に入れる</strong>。撮る環境は CI と同じにする</li>
<li><strong>デザインの原則を観点にして、AI の評価役を足す</strong>。合否には使わない</li>
</ol>
<p>画像で方向を出すのは、新しい見せ方が要る画面だけで足りる。</p>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<ul>
<li>画像で何案も出して選ぶと、作り直しの方向は早く決まる。ただし画像は方向で、仕様ではない。採用案は測ってデザインファイルに写す</li>
<li>作り直すと、デザインファイルとコードが食い違う。見た目・値・振る舞いの正本を分け、写しの文書をやめ、食い違いは同期テストで見つける</li>
<li>外れたら、まず機械で止める。AI の評価役は人の判断材料にして、繰り返す指摘は lint に上げる</li>
</ul>
<p>1 か月の計測が終わったら、数字と、チームに持ち込むかどうかを、また書くつもりだ。</p>
]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[Claude Codeの「Claude Mods」とは？ 入れてみた3つのmodと、安全に入れる手順]]></title>
            <link>https://www.yoshihiko.tech/blog/claude-code-mods-intro</link>
            <guid isPermaLink="false">claude-code-mods-intro</guid>
            <pubDate>Tue, 03 Nov 2026 15:30:44 GMT</pubDate>
            <description><![CDATA[Claude Codeの新機能Claude Mods（v2.1.287〜）を紹介する。hooks・skills・MCPとの違い、入れてみたmod 3つ（prompt-rail・md-prompt・qa-guide）の使い方、claude plugin validateで中身を確かめて入れる手順をまとめた。]]></description>
            <content:encoded><![CDATA[<p>Claude Modsは、Claude Codeの公式の拡張の仕組みだ。Claude Codeの中で動くTypeScript（またはJavaScript）の関数で、画面にペインを足したり、プロンプトやツール呼び出しを書き換えたりできる。2026年10月1日（米国時間）のv2.1.287で正式にリリースされた（<a href="https://claude.com/blog/claude-code-mods" target="_blank" rel="noopener noreferrer">公式ブログ</a>、<a href="https://github.com/anthropics/claude-code/releases/tag/v2.1.287" target="_blank" rel="noopener noreferrer">リリースノート</a>）。</p>
<p>私は発表の数日後に、公開されているmodを3つ入れた。この記事では、modsでできること、入れた3つのmodの使い方、入れる前に中身を確かめる手順を順に書く。</p>
<p>先に結論を書く。</p>
<ul>
<li>modsはpluginに入れて配られる。入れて使うだけならTypeScriptは書かなくてよく、<code>/plugin</code>のコマンド2行で入る。v2.1.287以降は既定で有効</li>
<li>settings hook（settings.jsonに書く従来のhooks）・skill・MCPと違い、modsはClaude Codeの中で動く。そのため画面に描いたり、組み込みの機能を差し替えたりできる</li>
<li>modはサンドボックスの外で、自分のユーザー権限で動く。入れる前に<code>claude plugin validate</code>で、どのイベントを拾い、どのAPIを呼ぶかを見る</li>
<li>私が入れたのはprompt-rail（プロンプトの目次）、md-prompt（入力欄のMarkdownに色を付ける）、qa-guide（Claudeの質問に解説を添える）の3つ。最初の1つなら、呼ぶAPIが3つだけのmd-promptが試しやすい</li>
</ul>
<p>検証環境はmacOSとClaude Code 2.1.289（ターミナル、<code>&quot;tui&quot;: &quot;fullscreen&quot;</code>）で、2026年10月5日に確認した。</p>
<h2 id="claude-modsとは:-claude-codeの中で動き、画面と挙動を変えられる関数" tabindex="-1">Claude Modsとは: Claude Codeの中で動き、画面と挙動を変えられる関数</h2>
<p>Claude Codeは、ツールを呼ぶ、権限を確認する、画面の一部を描く、といった動作のたびにイベントを出す。modはこのイベントに差し込む関数で、イベントの前・後・代わりに処理を走らせられる。公式ブログは、1つの関数でできることとして次の例を挙げている。</p>
<ul>
<li>モデルに届く前にプロンプトを書き換える</li>
<li>ツール呼び出しを止める、書き換える、やり直す</li>
<li>権限リクエストを承認・拒否する</li>
<li>ツールの出力から秘密情報を消してからClaudeに読ませる</li>
</ul>
<p>画面にも手を入れられる。会話の横にペインを開く、入力欄の上に帯を出す、ツールの結果やClaudeの質問ダイアログの表示を差し替える、ボタンや入力欄を置く、といったことができる（<a href="https://code.claude.com/docs/en/plugins/mods/overview" target="_blank" rel="noopener noreferrer">公式ドキュメント</a>）。</p>
<h3 id="settings-hook・skill・mcpとの違い" tabindex="-1">settings hook・skill・MCPとの違い</h3>
<p>公式ドキュメントは、modsをsettings hook・skill・MCPサーバーと並べて比べている。mods以外の3つを1行ずつ説明しておく。</p>
<ul>
<li>settings hook: settings.jsonに書く従来のhooks。ツール呼び出しの前後などに、シェルコマンドやHTTPリクエスト、プロンプトを走らせる</li>
<li>skill: <code>SKILL.md</code>に書いた手順書。Claudeが読んで、その通りに作業する</li>
<li>MCPサーバー: 外部のプロセスやサービスで、Claudeに新しいツールを渡す</li>
</ul>
<p>公式ドキュメントの比較表を要約した。</p>
<table>
<thead>
<tr>
<th></th>
<th>mod</th>
<th>settings hook</th>
<th>skill</th>
<th>MCPサーバー</th>
</tr>
</thead>
<tbody>
<tr>
<td>何を書くか</td>
<td>JavaScript / TypeScript</td>
<td>スクリプトとsettings.jsonの設定</td>
<td>Markdown</td>
<td>任意の言語のサーバー</td>
</tr>
<tr>
<td>変えられるもの</td>
<td>ツール呼び出し、プロンプト、コマンド、ターン、画面の表示</td>
<td>ツール呼び出しやプロンプトを通すかどうか、ツールの引数と結果、Claudeに足す文脈</td>
<td>Claudeが知っていること・すること</td>
<td>Claudeが使えるツール</td>
</tr>
<tr>
<td>画面に描けるか</td>
<td>描ける</td>
<td>描けない</td>
<td>描けない</td>
<td>描けない</td>
</tr>
<tr>
<td>選ぶとき</td>
<td>ペインや帯、独自コマンドがほしい、イベントを書き換えたい</td>
<td>手元のスクリプトでイベントを止める・許可する・記録する</td>
<td>同じ指示を何度も貼っている</td>
<td>Claudeを外部のシステムにつなぐ</td>
</tr>
</tbody>
</table>
<p>settings hookもツールの引数を書き換えられるので、書き換えられるかどうかは分かれ目にならない。違いは、modがClaude Codeのプロセスの中で動くことにある。そのため、画面を描くことも、組み込みの機能を差し替えることもできる。実際に<code>/diff</code>はmodとして作り直されていて、<code>/plugin</code>から無効にしたり、自作のものに置き換えたりできる。</p>
<p><img src="https://static.zenn.studio/user-upload/bdfe873b0cc4-20261005.png" alt="modはClaude Codeの中でツール呼び出し・プロンプト送信・画面の描画のイベントに差し込まれ、ペインや帯を描く。settings hook・skill・MCPサーバーはClaude Codeの外から関わる" loading="lazy"></p>
<h2 id="私が入れたmod-3つ" tabindex="-1">私が入れたmod 3つ</h2>
<p>私が入れた3つのmodは、どれも作者のGitHubリポジトリがマーケットプレイスを兼ねていて、2行のコマンドで入る。入れ方は次の章に、入れる前の確認も含めて書いた。</p>
<h3 id="prompt-rail:-長いセッションで「さっきの指示」に戻れる" tabindex="-1">prompt-rail: 長いセッションで「さっきの指示」に戻れる</h3>
<p>1つのセッションで何十回もやり取りしていると、最初に出した条件をどう書いたかを確かめるために、スクロールで遡ることになる。<a href="https://github.com/oikon48/prompt-rail" target="_blank" rel="noopener noreferrer">prompt-rail</a>は、セッション内で自分が送ったプロンプトを、入力欄の上に並ぶ短い線（レール）として表示する。線にマウスを乗せるとそのプロンプトとターンの要約が出て、クリックすると会話のその位置まで飛ぶ。</p>
<p><img src="https://static.zenn.studio/user-upload/62d122b3633d-20261005.png" alt="prompt-railの横レール。入力欄の上に自分のプロンプトが線で並び、マウスを乗せた1本の中身が表示されている" loading="lazy"></p>
<p>主な使い方は次の通り。</p>
<ul>
<li><code>/prompt-rail horizontal</code>（入力欄の上、既定）と<code>/prompt-rail vertical</code>（会話の横のペイン）で表示を切り替える。<code>/prompt-rail off</code>で隠す</li>
<li><code>/prompt-rail next</code> / <code>prev</code> / <code>first</code> / <code>last</code>で前後のプロンプトへ飛ぶ。<code>/prompt-rail 12</code>は12番目へ、<code>/prompt-rail find &lt;語&gt;</code>はその語を含む最新のプロンプトへ飛ぶ</li>
<li><code>~/.claude/keybindings.json</code>にキーを割り当てると、Claudeの作業中でも飛べる</li>
</ul>
<pre><code class="language-json">{
  &quot;bindings&quot;: [
    {
      &quot;context&quot;: &quot;Chat&quot;,
      &quot;bindings&quot;: {
        &quot;ctrl+k&quot;: &quot;command:prompt-rail-prev&quot;,
        &quot;meta+j&quot;: &quot;command:prompt-rail-next&quot;
      }
    }
  ]
}
</code></pre>
<p>READMEは、マウスで操作するなら<code>&quot;tui&quot;: &quot;fullscreen&quot;</code>を推奨している。Claude DesktopのCodeタブでは、レールとプレビューは出るが、クリックしてもその位置へは飛べない。Desktopがmodに会話のスクロールを許していないためだ（READMEに記載）。</p>
<h3 id="md-prompt:-入力欄に書いたコードブロックや表に色が付く" tabindex="-1">md-prompt: 入力欄に書いたコードブロックや表に色が付く</h3>
<p>プロンプトにコードや表を書いて指示するとき、入力欄は素のテキストなので、どこからどこまでがコードかが見分けにくい。<a href="https://github.com/nogu66/md-prompt" target="_blank" rel="noopener noreferrer">md-prompt</a>は、入力欄のMarkdownに打った端から色を付ける。<code>```</code>で始めたコードブロックは、閉じる前から背景つきのカードになり、言語に応じてシンタックスハイライトされる。見出し、太字、リスト、チェックボックス、引用、表、リンクにも色が付く。</p>
<p><img src="https://static.zenn.studio/user-upload/56bb3e6a2a55-20261005.png" alt="md-promptで入力欄に書いたTypeScriptのコードブロックがハイライトされている様子" loading="lazy"></p>
<p>md-promptは色を付けるだけで、文字は変えない。送信されるのは打った文字そのままで、<code>```</code> や <code>**</code>などの記号も消えずに薄く表示される。</p>
<ul>
<li><code>/md-prompt on</code> / <code>off</code> / <code>toggle</code>で切り替える。<code>/config</code>で「Markdown」を検索しても切り替えられる</li>
<li>複数行の下書きは、<code>\</code>のあとにEnterで改行する（ターミナルが対応していればShift+Enterでもよい）</li>
</ul>
<p>4行以上を貼り付けた部分には色が付かない。Claude Codeが貼り付けを<code>[Pasted text #N +M lines]</code>に畳み、md-promptが受け取る編集イベントが発生しないためだ（READMEに記載）。1〜3行の貼り付けには色が付く。</p>
<h3 id="qa-guide:-claudeの質問に、文脈と選択肢ごとの影響を添えてくれる" tabindex="-1">qa-guide: Claudeの質問に、文脈と選択肢ごとの影響を添えてくれる</h3>
<p>ClaudeがAskUserQuestionで選択肢を出してきたとき、セッションが長いと、何の話だったかを思い出すために会話を遡ることがある。<a href="https://github.com/aieo-product/claude_qamods" target="_blank" rel="noopener noreferrer">qa-guide</a>は、質問ダイアログの横にペインを開き、次の内容を表示する。</p>
<ul>
<li>AIによる解説: いまClaudeが従っている指示の要約、なぜ今この質問をしているか、選択肢ごとに何が起きるか、おすすめ1行</li>
<li>自分が直近に入力した指示</li>
<li>質問の直前にClaudeが書いた説明の末尾</li>
</ul>
<p><img src="https://static.zenn.studio/user-upload/d8fe6816a1c8-20261005.png" alt="qa-guideのペイン。左にClaudeの質問ダイアログ、右にAI解説と直近の指示が出ている" loading="lazy"></p>
<p>ターミナルの幅が144列以上あると、ペインは質問のたびに自動で開く。狭いときは<code>/qa-guide</code>で開ける。ペインにフォーカスを移す（<code>Ctrl+X</code> → <code>Tab</code>、またはクリック）と、<code>p</code> / <code>n</code>で過去の質問、<code>l</code>で最新の質問、<code>h</code>で履歴一覧を表示できる。<code>a</code>を押すと、AI解説のON / OFFが切り替わる。表示言語は<code>/config</code>の<code>language</code>で<code>auto</code> / <code>en</code> / <code>ja</code>から選ぶ。</p>
<p>AI解説は、質問1件ごとにモデル呼び出しを1回増やす。セッションのプロンプトキャッシュを使えるときは安く済むが、無料ではない。気になるなら、ペインで<code>a</code>を押すと次の質問からAI解説が止まる（すでに始まった呼び出しは止まらない）。qa-guide自身は独自の通信をせず、質問や回答をディスクに書かない。ただしAI解説の追加リクエストでは、会話や質問文がClaude Code経由でモデルに渡る（READMEに記載）。</p>
<h2 id="安全に入れる手順:-入れる前にclaude-plugin-validateで中身を見る" tabindex="-1">安全に入れる手順: 入れる前に<code>claude plugin validate</code>で中身を見る</h2>
<p>modはClaude Codeの中で、自分のユーザー権限で動く。公式ドキュメントは、modが読み込まれたあとにできることとして、次を挙げている。</p>
<ul>
<li>自分のアカウントで読める範囲のファイルの読み書き、プログラムの起動、ネットワーク通信</li>
<li>環境変数や設定ファイルの読み取り（そこに置いたAPIキーも含む）</li>
<li>送ったプロンプトとClaudeのツール呼び出しをすべて見る、書き換える</li>
<li>ツール呼び出しを、確認ダイアログが出る前に承認する</li>
<li>自分のプランやAPIキーでモデルを呼ぶ</li>
</ul>
<p>Claude Codeのサンドボックスを有効にしていても、隔離されるのはClaudeが実行するBashコマンドで、modが起動したプロセスはサンドボックスの外で動く。そこで、入れる前に<code>claude plugin validate</code>で、そのmodがどのイベントを拾い、どのAPIを呼ぶかを確かめる。validateはコードを実行せずに読むだけなので、入れる前に使える。</p>
<p><img src="https://static.zenn.studio/user-upload/db177f676748-20261005.png" alt="サンドボックスが囲むのはClaudeのBashコマンドだけで、modはその外からファイルの読み書き、プログラムの起動、ネットワーク通信、環境変数・APIキー、モデル呼び出しに手が届く" loading="lazy"></p>
<h3 id="手順" tabindex="-1">手順</h3>
<p><img src="https://static.zenn.studio/user-upload/5deb11d2de0c-20261005.png" alt="marketplace addでリポジトリを取得し、claude plugin validateでhooks:とcalls:を読み、納得できたら/plugin installで入れ、/pluginで1 mod activeを確認する。更新したらmarketplace updateのあとにもう一度validateする" loading="lazy"></p>
<p>prompt-railを例に書く。</p>
<ol>
<li>
<p>マーケットプレイスを追加する。この時点ではまだmodは入っておらず、リポジトリが<code>~/.claude/plugins/marketplaces/oikon48/</code>に取得される</p>
<pre><code class="language-text">/plugin marketplace add oikon48/prompt-rail
</code></pre>
</li>
<li>
<p>シェルでmodのディレクトリをvalidateする。mod本体の場所は、リポジトリの<code>.claude-plugin/marketplace.json</code>の<code>source</code>に書かれている（今回の3つはどれも<code>plugins/&lt;mod 名&gt;</code>）</p>
<pre><code class="language-bash">claude plugin validate ~/.claude/plugins/marketplaces/oikon48/plugins/prompt-rail
</code></pre>
</li>
<li>
<p>出力の<code>hooks:</code>と<code>calls:</code>の行を読み、納得できたら入れる</p>
<pre><code class="language-text">/plugin install prompt-rail@oikon48
</code></pre>
</li>
<li>
<p>インストール後の案内に従って有効にする。<code>Run /reload-plugins to activate.</code>と出たら<code>/reload-plugins</code>を実行するか、新しいセッションを始める（prompt-railのREADMEは新しいセッションを案内している）。シェルから入れた場合は、開いているセッションで<code>/reload-plugins</code>を実行する。<code>/plugin</code>を開くと、タブの下に<code>1 mod active · prompt-rail</code>のような行が出る</p>
</li>
</ol>
<p>ほかの2つは次のコマンドで入る。</p>
<pre><code class="language-text">/plugin marketplace add nogu66/md-prompt
/plugin install md-prompt@nogu66

/plugin marketplace add aieo-product/claude_qamods
/plugin install qa-guide@claude-qamods
</code></pre>
<h3 id="出力の読み方" tabindex="-1">出力の読み方</h3>
<p><code>hooks:</code>の行はそのmodが受け取るイベント、<code>calls:</code>の行はコードが呼ぶmods APIの一覧だ。modがファイルやプロセス、ネットワークに触るには必ずmods APIを通るので、<code>calls:</code>を見ればできることの範囲がわかる。公式ドキュメント（<a href="https://code.claude.com/docs/en/plugins/mods/admin" target="_blank" rel="noopener noreferrer">組織向けの管理ページ</a>）が見ておくべきものとして挙げている名前のうち、主なものを表にした。</p>
<table>
<thead>
<tr>
<th>出力に出る名前</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$.fs.read</code> / <code>$.fs.write</code></td>
<td>自分が読み書きできるファイルなら、どこでも読み書きできる</td>
</tr>
<tr>
<td><code>$.process.run</code> / <code>$.process.spawn</code></td>
<td>自分の権限でプログラムを起動する</td>
</tr>
<tr>
<td><code>$.http.fetch</code></td>
<td>ネットワークに通信する</td>
</tr>
<tr>
<td><code>$.env.get</code> / <code>$.settings.read</code></td>
<td>環境変数や設定を読む（APIキーが入っていることがある）</td>
</tr>
<tr>
<td><code>$.model.complete</code></td>
<td>自分のプランやAPIキーでモデルを呼ぶ</td>
</tr>
<tr>
<td><code>$.prompt.submit</code></td>
<td>自分が打ったものとしてプロンプトを送る</td>
</tr>
<tr>
<td>hooksの<code>tool.call</code> / <code>prompt.submit</code></td>
<td>すべてのツール呼び出し・プロンプトを見て、書き換えられる</td>
</tr>
<tr>
<td>hooksの<code>tool.check</code></td>
<td>確認ダイアログが出る前に、ツール呼び出しを承認・拒否できる</td>
</tr>
</tbody>
</table>
<h3 id="3つのmodの実際の出力" tabindex="-1">3つのmodの実際の出力</h3>
<p>私の環境で3つをvalidateした結果と、それを見てどう判断したかを書く。</p>
<p>md-promptは、<code>calls:</code>が3つしかない。コマンドの登録、自分の設定の書き込み、ログの出力だけで、ファイル・プロセス・ネットワークには触らない。</p>
<pre><code class="language-text">❯ ./register.tsx hooks: session.start, command.run{command=md-prompt}, prompt.edit, prompt.fill
❯ ./register.tsx calls: $.command.register, $.config.set, $.ui.log
</code></pre>
<p>prompt-railには<code>$.process.spawn</code>と<code>$.fs.read</code>が出る（以下は<code>calls:</code>行の抜粋）。</p>
<pre><code class="language-text">❯ ./index.tsx calls: $.clock.after, $.command.register, $.config.set (via writeMode),
  $.fs.read (via readTranscript), $.fs.stat (via readTranscript),
  $.process.spawn (via streamRows), ..., $.ui.scroll (via jumpTo), $.ui.toast
</code></pre>
<p>プロセスを起動するので、ソースの<code>streamRows</code>を読んだ。起動しているのは<code>tail -c +&lt;位置&gt; &lt;会話ログのパス&gt;</code>で、セッションの会話ログ（transcript）の続きを読んでプロンプトの一覧を作っているだけだった。</p>
<p>qa-guideには<code>$.model.fork</code>と<code>$.model.complete</code>が出る。前の章で書いた、質問ごとのモデル呼び出しがこれにあたる。<code>$.env.get</code>で読むのは<code>LANG</code>と<code>LC_ALL</code>だけで、表示言語の自動判定に使っている（以下も抜粋）。</p>
<pre><code class="language-text">❯ ./register.tsx calls: $.clock.now, $.command.register, $.config.list (via resolveLang),
  $.env.get (via resolveLang), $.model.complete, $.model.fork, $.session.messages, ...
❯ ./register.tsx env reads: LANG, LC_ALL
</code></pre>
<h3 id="validateでわからないこと" tabindex="-1">validateでわからないこと</h3>
<p>validateの出力は、コードが呼ぶAPIの一覧で、安全の保証ではない。prompt-railの<code>$.process.spawn</code>のように、何を起動するかはソースを読まないとわからない。更新すると中身が変わるので、更新のたびに見直す。<code>/plugin marketplace update &lt;マーケットプレイス名&gt;</code>で取得元を新しくしたら、もう一度validateして前の出力と比べる。問題がなければ、<code>/plugin update &lt;mod 名&gt;@&lt;マーケットプレイス名&gt;</code>で入れている版を更新する。公式以外のマーケットプレイスは自動更新が既定でオフなので、この順番で更新できる。自動更新をオンにすると、validateを挟まずに新しい版が入る。</p>
<p>自分の権限設定（settings.jsonの<code>permissions</code>）で守れる範囲にも限りがある。denyルールがかかるのはClaudeのツール呼び出しで、mod自身の<code>$.fs.read</code>や<code>$.process.spawn</code>には効かない。<code>Read(.env)</code>をdenyしていても、modは<code>$.fs.read</code>でそのファイルを読める。また、ツール呼び出しを承認するmodは、askルールで確認が出るはずの呼び出しや、自分のPreToolUse hookが止めた呼び出しも承認できる。auto modeでは、modが承認した呼び出しは分類器のチェックを通らずに実行される。managed settingsのない端末で、Team / Enterpriseプランでサインインしていない場合（個人のプランなど）は、denyルールで拒否される呼び出しもmodが承認できる（<a href="https://code.claude.com/docs/en/permissions#extend-permissions-with-hooks" target="_blank" rel="noopener noreferrer">権限のドキュメント</a>に記載）。</p>
<h3 id="チームで使う場合" tabindex="-1">チームで使う場合</h3>
<p>組織でClaude Codeを配っている場合は、管理者がmanaged settingsでmodを制限できる（<a href="https://code.claude.com/docs/en/plugins/mods/admin" target="_blank" rel="noopener noreferrer">公式ドキュメント</a>）。</p>
<ul>
<li>Team / Enterpriseプランでサインインしている端末や、managed settingsがある端末では、既定で組み込みのガード<code>sec-default</code>が最初に読み込まれる。このガードがあると、ユーザーが入れたmodはdenyルールで拒否された呼び出しを承認できない。ただし管理者が<code>allowModsToOverrideDenyRules</code>を有効にすると、この制限は外れる</li>
<li>managed settingsでこのガードのオプション<code>allowManagedModsOnly</code>を有効にすると、組織のものとして配られたmodと組み込みのmod以外は読み込まれなくなる（どのmodが組織のものと数えられるかは公式ドキュメントに条件がある）</li>
<li><code>strictKnownMarketplaces</code>で、入れられるマーケットプレイスを絞れる</li>
<li><code>disableSideloadFlags</code>で、<code>--plugin-dir</code>や<code>--plugin-url</code>での読み込みを禁止できる</li>
</ul>
<p>会社のマシンに入れるなら、先に社内のルールと、これらの設定が入っているかを確認したほうがいい。</p>
<h2 id="外し方と、表示されないとき" tabindex="-1">外し方と、表示されないとき</h2>
<p>modを止める方法は、範囲によって3つある。</p>
<ul>
<li>1つだけ止める: <code>/plugin</code>のInstalledタブで、そのpluginを無効化またはアンインストールする</li>
<li>1セッションだけ、入れたmodを全部止める: <code>claude --safe-mode</code>で起動する（mod以外のカスタマイズも止まる）</li>
<li>常に、入れたmodを全部止める: <code>~/.claude/settings.json</code>に<code>&quot;disableAllHooks&quot;: true</code>を書く。settings hookと独自のステータスラインも止まる。組織が管理しているものは動き続ける</li>
</ul>
<p>どちらの方法でも、<code>/diff</code>などの組み込みのmodは止まらない。組み込みのmodは、無効にできるものだけ<code>/plugin</code>のInstalledタブで個別に止められる。</p>
<p>入れたのに何も表示されないときは、次を確認する。</p>
<ul>
<li>バージョン: modsはv2.1.287以降で動く。<code>claude --version</code>で確かめる</li>
<li>ワークスペースの信頼: Claude Codeは、信頼していないディレクトリではmodのコードを読み込まない。作ったばかりのディレクトリで起動したときに起きやすい（md-promptのREADMEに記載）</li>
<li>ターミナルの幅: ユーザーが開いていないペインは、ターミナルが144列以上のときだけ自動で開く。一度開いたあとは110列から開く</li>
<li>使っている場所: 描画されるのはターミナルとClaude DesktopのCodeタブ（WSLのセッションを除く）だけ。VS Code拡張のチャット欄と<code>claude -p</code>では、modのフックは動くが画面には何も出ない。クラウドセッションでは、pluginがセッションに引き継がれた場合だけフックが動き、描画はされない</li>
</ul>
<p>なお、早期アクセス期に書かれたREADMEには、<code>~/.claude/settings.json</code>に<code>CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1</code>を書く手順が残っている。v2.1.287以降はこの環境変数を無視するので、書かなくてよい。書いてあれば消してよい。</p>
<h2 id="自分で作るなら" tabindex="-1">自分で作るなら</h2>
<p>Claude Codeのセッションで、ほしいmodの内容を説明すると、Claudeが組み込みのplugin-authoringスキルを使ってコードを書く。最初のファイルを保存したときにホットリロードを有効にするか聞かれ、有効にするとそのセッションで動く。自分でコードを書く場合は、公式の<a href="https://claude.dev/blog/getting-started-with-claude-code-mods/" target="_blank" rel="noopener noreferrer">入門ガイド</a>と、Anthropicの<a href="https://github.com/anthropics/claude-code-playground/tree/main/claude-code/mods" target="_blank" rel="noopener noreferrer">サンプル集</a>がわかりやすい。</p>
<p>公開されているmodを自分用に直すのも手だ。私は<a href="https://github.com/yash-gadodia/claude-mods" target="_blank" rel="noopener noreferrer">yash-gadodia/claude-mods</a>の<code>receipt</code>（MITライセンス）をもとに、turn-receiptというmodを作って使っている。ターンごとに編集とコマンド実行の件数をフッターに出し、編集したのに何も実行していないターンには<code>⚠ no run</code>を付ける。何も実行せずに「修正しました」と完了を宣言したときは、次のスピナーに<code>unverified claim pending</code>を出す。元のmodから、何を「実行」と数えるかの判定を変え、日本語の完了宣言も拾えるようにした。</p>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>modsは、Claude Codeの画面と挙動を自分の使い方に合わせるための仕組みだ。入れて使うだけなら、<code>/plugin</code>のコマンド2行で済む。ただ、中身は自分の権限で動くコードなので、入れる前にvalidateの出力を見る。プロセスの起動やネットワーク、モデル呼び出しが出ていたら、ソースの該当箇所を読む。</p>
<p>まだ入れていないなら、呼ぶAPIが少ないmd-promptから試すのがいい。</p>
<ol>
<li><code>/plugin marketplace add nogu66/md-prompt</code>を実行する</li>
<li>シェルで<code>claude plugin validate ~/.claude/plugins/marketplaces/nogu66/plugins/md-prompt</code>を実行し、出力を見る</li>
<li><code>/plugin install md-prompt@nogu66</code>で入れ、新しいセッションの入力欄にコードブロックを書いてみる</li>
<li>合わなければ<code>/plugin</code>のInstalledタブで無効にする</li>
</ol>
<p>settings hookやCLAUDE.mdの設定は<a href="/blog/claude-code-harness-engineering">ハーネスエンジニアリングの記事</a>に、普段使っている道具は<a href="/blog/claude-code-dev-toolchain">ツールチェインの記事</a>にまとめている。</p>
]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[AI 時代の並列開発の環境構築 — git worktree ごとにアプリを同時に動かす]]></title>
            <link>https://www.yoshihiko.tech/blog/worktree-shared-reverse-proxy</link>
            <guid isPermaLink="false">worktree-shared-reverse-proxy</guid>
            <pubDate>Tue, 03 Nov 2026 15:24:32 GMT</pubDate>
            <description><![CDATA[git worktree の並列開発で、各 worktree のフロントとバックを同時に動かす Docker Compose 構成を紹介する。ポートはずらさず共有のリバースプロキシでホスト名に振り分け、DB は 1 つを共有ネットワークに置いて全 worktree から参照する。]]></description>
            <content:encoded><![CDATA[<p>AI エージェントに worktree を 1 つずつ渡して並列で開発していると、「この worktree のブランチで、画面と API がちゃんと動くか見たい」という場面が何度も来る。ところが同じアプリを 2 つ目、3 つ目と起動すると、ポートもコンテナ名も DB もぶつかる。</p>
<p>私が開発している Web アプリは、Next.js（frontend）、Go（backend）、Python（AI 用の API）、PostgreSQL、ベクトル DB を Docker Compose で動かしている。これまでは worktree を作るときのスクリプトでポートをずらしてしのいでいたが、2026 年 9 月にこれをやめ、共有のリバースプロキシでホスト名に振り分ける構成に変えた。この記事では、その構成を「何を共有し、何を worktree ごとに分けるか」でまとめる。</p>
<p>先に結論を書く。</p>
<ul>
<li>全 worktree で 1 つを共有するもの（入口のリバースプロキシ、PostgreSQL）と、worktree ごとに分けるもの（アプリのコンテナ、依存、ベクトル DB）を分ける</li>
<li>HTTP のポートは worktree ごとにずらさない。ホストに開くのはプロキシの 3000 番だけにして、<code>http://&lt;プロジェクト名&gt;.localhost:3000</code> のホスト名で各 worktree に振り分ける</li>
<li>DB は共有ネットワークに 1 つ置き、どの worktree の backend も同じ接続先 <code>db:5432</code> で参照する。ただし、互換性のない migration をブランチごとに並行して試す使い方には向かない</li>
<li>ポートずらしの頃と比べて、どの方法で作った worktree も同じ手順で起動でき、URL を見ればどの worktree か分かるようになった</li>
</ul>
<p>検証環境は macOS、OrbStack（Docker 29.4、Compose 5.1.2）、Traefik v3.6 で、2026 年 9 月 29 日に確認した。Windows（WSL2）では試していない。</p>
<h2 id="やりたいこと:-各-worktree-のアプリを同時に起動して確かめたい" tabindex="-1">やりたいこと: 各 worktree のアプリを同時に起動して確かめたい</h2>
<p>git worktree は、1 つのリポジトリから作業ディレクトリを複数作り、それぞれで別のブランチをチェックアウトしておける git の機能だ。ブランチを切り替えずに並行して作業できるので、AI エージェントを並べて動かすときによく使われる。私は作成と削除に git-worktree-runner（gtr）を使っている（<a href="/blog/git-worktree-runner-guide">git-worktree-runner の紹介記事</a>）。</p>
<p>コードを書くのはエージェントに任せても、動くかどうかは自分の目で見たい。そこで worktree A と worktree B のアプリを同時に <code>docker compose up</code> すると、次のものがぶつかる。</p>
<table>
<thead>
<tr>
<th>ぶつかるもの</th>
<th>何が起きるか</th>
<th>解き方（章）</th>
</tr>
</thead>
<tbody>
<tr>
<td>コンテナ名・ネットワーク・volume</td>
<td>後から起動した側が、同じ名前のコンテナを置き換える</td>
<td>worktree ごとに compose のプロジェクト名を分ける（名前）</td>
</tr>
<tr>
<td>ホストのポート</td>
<td>2 つ目の frontend が 3000 番を取れずに起動しない</td>
<td>入口を 1 つにしてホスト名で振り分ける（ポート）</td>
</tr>
<tr>
<td>cookie</td>
<td>ポートをずらしても、同じ <code>localhost</code> なので cookie は共有される</td>
<td>worktree ごとにホスト名を変える（ポート）</td>
</tr>
<tr>
<td>DB</td>
<td>worktree ごとに立てるとポートとコンテナ名で衝突し、データも別々になる</td>
<td>1 つを共有ネットワークに置く（DB）</td>
</tr>
<tr>
<td>依存（node_modules）</td>
<td>別の worktree が入れた依存で動いてしまう</td>
<td>worktree ごとの volume に置く（依存と起動）</td>
</tr>
</tbody>
</table>
<p>cookie はポートを区別しない（<a href="https://www.rfc-editor.org/rfc/rfc6265#section-8.5" target="_blank" rel="noopener noreferrer">RFC 6265 §8.5</a>）。ポートをずらすだけでは、ある worktree でログインした cookie が別の worktree にも送られる。</p>
<h2 id="結論:-入口と-db-は共有し、アプリは-worktree-ごとに分ける" tabindex="-1">結論: 入口と DB は共有し、アプリは worktree ごとに分ける</h2>
<p>全体はこうなる。</p>
<p><img src="https://static.zenn.studio/user-upload/52d3e43126a8-20261001.png" alt="全体構成図。ブラウザは myapp-feat-a.localhost:3000 と myapp-feat-b.localhost:3000 でリバースプロキシにアクセスする。プロキシは全 worktree で共有し、ホストには 127.0.0.1:3000 だけを開く。プロキシは / を各 worktree の frontend に、/api/v1 を backend に転送する。worktree A と B はそれぞれ frontend・backend・ベクトル DB を持ち、両方の backend が全 worktree で共有する PostgreSQL（別名 db）に db:5432 で接続する" loading="lazy"></p>
<p>さっきの表と対応させると、共有するものと分けるものは次のとおり。</p>
<table>
<thead>
<tr>
<th>対象</th>
<th>全 worktree で 1 つを共有</th>
<th>worktree ごとに分ける</th>
</tr>
</thead>
<tbody>
<tr>
<td>名前</td>
<td>—</td>
<td>compose のプロジェクト名（ホスト名にも使う）</td>
</tr>
<tr>
<td>入口（HTTP のポート）</td>
<td>リバースプロキシ（ホストの 3000 番）</td>
<td>—</td>
</tr>
<tr>
<td>DB</td>
<td>PostgreSQL と DB の管理画面</td>
<td>ベクトル DB</td>
</tr>
<tr>
<td>アプリ</td>
<td>—</td>
<td>frontend / backend / AI 用 API のコンテナ</td>
</tr>
<tr>
<td>依存</td>
<td>—</td>
<td>node_modules の volume</td>
</tr>
<tr>
<td>設定</td>
<td><code>.env</code>（同じ中身を使い回す）</td>
<td>—</td>
</tr>
</tbody>
</table>
<p>ここで compose の用語を補っておく。compose のプロジェクトは、<code>docker compose</code> が一緒に管理するコンテナ・ネットワーク・volume のまとまりで、プロジェクト名がそれぞれの名前の頭に付く。既定ではディレクトリ名がプロジェクト名になる。ネットワークのほうは、compose で <code>external: true</code> と宣言すると、別のプロジェクトが作ったものに参加できる。</p>
<p>共有するプロキシと DB は、それぞれ別のプロジェクトとして 1 つだけ起動する。各 worktree のプロジェクトは、そのネットワークに参加するだけだ。この記事の構成は、ほぼこれで全部である。</p>
<p>ただ、ホスト名で振り分けられるのは HTTP だけだ。PostgreSQL の 5432 番とデバッガのポートは固定のまま残る。</p>
<h2 id="まず-worktree-ごとに名前を分ける（compose-のプロジェクト名）" tabindex="-1">まず worktree ごとに名前を分ける（compose のプロジェクト名）</h2>
<p>ポートより先に名前を分ける。compose が自動で付ける名前なら、コンテナ名・ネットワーク・volume の頭にプロジェクト名が付くので、プロジェクト名が worktree ごとに違えばぶつからない。例外は自分で固定した名前で、<code>container_name:</code> や、ネットワーク・volume の <code>name:</code> はプロジェクト名を変えても同じになる。私の環境ではベクトル DB と DB の管理画面に固定のコンテナ名が残っていたので、移行のときに外した（共有する DB とプロキシは、逆に固定の名前をわざと付けている）。</p>
<p>プロジェクト名は後でホスト名にも使うので、DNS のラベルとして使える形にする。gtr で作る worktree は、作成時のフックが <code>&lt;リポジトリ名&gt;-&lt;worktree のディレクトリ名&gt;</code> を小文字・ハイフン区切りにして <code>.env</code> に書く。</p>
<pre><code class="language-bash"># gtr の postCreate フック（抜粋）
project_name=&quot;${repo_name}-${worktree_name}&quot;
project_name=&quot;$(printf '%s' &quot;$project_name&quot; \
  | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | tr -s '-' | sed 's/^-//; s/-$//')&quot;
printf 'COMPOSE_PROJECT_NAME=%s\n' &quot;$project_name&quot; &gt;&gt; &quot;$WORKTREE_PATH/.env&quot;
</code></pre>
<p>以前はアンダースコア区切りだったが、ホスト名にアンダースコアは使えないので変えた。gtr を通さずに作った worktree は、プロジェクト名を書かなければ compose の既定どおりディレクトリ名になる。</p>
<p><code>.env</code> は全 worktree で同じ中身にした。worktree に <code>.env</code> が無ければ、起動タスクがメインのチェックアウトの <code>.env</code> へシンボリックリンクを張る。このとき、共有元の <code>.env</code> に <code>COMPOSE_PROJECT_NAME</code> が書かれていると全 worktree が同じプロジェクトになってしまうので、その場合はリンクせずに止める。起動の直前には、同じプロジェクト名が別のディレクトリのコンテナに使われていないかも確かめている。</p>
<h2 id="ポート:-ずらすのをやめ、入口を-1-つにしてホスト名で振り分ける" tabindex="-1">ポート: ずらすのをやめ、入口を 1 つにしてホスト名で振り分ける</h2>
<p>この章でやることを先に 1 枚で示す。左が以前の形、右が今の形だ。</p>
<p><img src="https://static.zenn.studio/user-upload/1d40fabd03e5-20261001.png" alt="ポートずらしと入口 1 つの比較。Before では main・worktree A・worktree B がそれぞれ frontend を 3000・3001・3002、backend を 8080・8081・8082 で公開し、作成フックが 8 種類のポートを採番するため、フックを通らない worktree は衝突する。After ではリバースプロキシの 3000 番だけを開き、myapp.localhost・myapp-feat-a.localhost・myapp-feat-b.localhost のホスト名で各 worktree に振り分ける。各 worktree は ports を持たず、作り方を問わない" loading="lazy"></p>
<h3 id="以前はポートをずらしていた" tabindex="-1">以前はポートをずらしていた</h3>
<p>worktree を作るときのフックで、ほかの worktree の <code>.env</code> を読んで空いているオフセットを探し、8 種類のポートに同じ値を足して書き込んでいた。</p>
<pre><code class="language-bash"># 旧フック（抜粋）。8 種類のポートに同じオフセットを足して .env に書く
PORT_KEYS=(FRONTEND_PORT STORYBOOK_PORT BACKEND_PORT AI_GATEWAY_PORT
           DB_GUI_PORT FRONTEND_DEBUG_PORT BACKEND_DEBUG_PORT AI_GATEWAY_DEBUG_PORT)
PORT_BASES=(3000 6006 8080 8000 5050 9229 40000 5678)
</code></pre>
<p>2 つ目の worktree なら frontend が 3001、backend が 8081、という具合だ。<a href="/blog/ghostty-herdr-dev-env">Ghostty + herdr の開発環境の記事</a>を書いた時点では、まだこの方式だった。動きはした。ただ、使い続けるうちに困ることが増えてきた。</p>
<ol>
<li>フックを通らない worktree は採番されない。<code>git worktree add</code> で作った worktree や、AI エージェント（私は TAKT を使っている）が作る独立したクローンは既定のポートのまま起動して衝突した。エージェントが作業している途中の画面を見られなかったのが一番困った</li>
<li>ポートや URL を使う設定を足すたびにフックの修正が要り、追従漏れが出た。ベクトル DB のポート、メール内リンクの基点 URL、エディタのデバッグ設定が、それぞれ漏れていた</li>
<li>URL がポート番号でしか区別できない。<code>localhost:3002</code> がどのブランチなのか覚えていられない</li>
</ol>
<h3 id="仕組み:-ホスト名で転送先を選ぶ" tabindex="-1">仕組み: ホスト名で転送先を選ぶ</h3>
<p>ホストに開くポートをリバースプロキシの 1 つだけにする。ブラウザは <code>http://myapp-feat-a.localhost:3000</code> のようにホスト名を変えてアクセスし、プロキシはリクエストの Host ヘッダを見て、その worktree のコンテナへ転送する。</p>
<p><img src="https://static.zenn.studio/user-upload/529f65f2dc5b-20261001.png" alt="ホスト名とパスで転送先を選ぶ図。myapp-feat-a.localhost:3000/ は worktree A の frontend へ、myapp-feat-a.localhost:3000/api/v1/ 以下は /api/v1 を外して worktree A の backend へ、myapp-feat-b.localhost:3000/ は worktree B の frontend へ転送される。リバースプロキシは Host ヘッダとパスを見て、ルールはコンテナのラベルから自動で作る。画面と API が同じホスト名なので同じオリジンになり、cookie と CSRF の設定が 1 つで済む" loading="lazy"></p>
<p><code>*.localhost</code> は Chrome と Firefox が 127.0.0.1 に解決するので、<code>/etc/hosts</code> も DNS の設定も要らない。<a href="https://www.rfc-editor.org/rfc/rfc6761#section-6.3" target="_blank" rel="noopener noreferrer">RFC 6761 §6.3</a> は localhost とそのサブドメインをループバックに解決するよう求めている。</p>
<p>プロキシには Traefik を使った。Traefik は Docker のコンテナに付いたラベルを読んで、転送のルールを自動で作る。worktree のアプリが起動すればルールが増え、止めれば消える。コンテナのラベルから設定を拾えるプロキシなら、Traefik でなくても同じ形は作れる。</p>
<h3 id="設定" tabindex="-1">設定</h3>
<p>プロキシは、どの worktree にも属さない別のプロジェクトとして 1 つだけ起動する。</p>
<pre><code class="language-yaml"># compose/proxy.yaml（共有リバースプロキシ）
services:
  proxy:
    image: traefik:v3.6 # 実際はダイジェストで固定している
    container_name: myapp_proxy
    restart: unless-stopped
    command:
      - --providers.docker=true
      # traefik.enable=true のラベルを付けたコンテナだけを公開する
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=myapp_shared_proxy
      - --entrypoints.web.address=:80
    ports:
      - &quot;127.0.0.1:3000:80&quot; # LAN には公開しない
    volumes:
      # コンテナのラベルを読むため
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - shared_proxy

networks:
  shared_proxy:
    name: myapp_shared_proxy
    external: true
</code></pre>
<p>起動するときは、ネットワークが無ければ作ってから、固定のプロジェクト名で <code>up</code> する。</p>
<pre><code class="language-bash">docker network inspect myapp_shared_proxy &gt;/dev/null 2&gt;&amp;1 \
  || docker network create myapp_shared_proxy
docker compose --project-name myapp_shared_proxy -f compose/proxy.yaml up -d proxy
</code></pre>
<p>各 worktree の側は、アプリのコンテナにラベルを付けて共有ネットワークに参加させる。ホスト名にプロジェクト名を埋め込むので、同じ定義のまま worktree ごとに別のホスト名になる。<code>COMPOSE_PROJECT_NAME</code> を <code>.env</code> に書いていない worktree でも、compose がプロジェクト名を入れて展開した（Compose 5.1.2 で確認）。</p>
<pre><code class="language-yaml"># compose/routing.yaml（各 worktree の frontend をプロキシに登録する）
services:
  frontend:
    environment:
      - NEXT_PUBLIC_API_BASE_URL=/api/v1
    labels:
      - traefik.enable=true
      - traefik.docker.network=myapp_shared_proxy
      - &quot;traefik.http.routers.${COMPOSE_PROJECT_NAME}-frontend.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`)&quot;
      - traefik.http.routers.${COMPOSE_PROJECT_NAME}-frontend.entrypoints=web
      - traefik.http.services.${COMPOSE_PROJECT_NAME}-frontend.loadbalancer.server.port=3000
    networks:
      - default
      - shared_proxy

networks:
  shared_proxy:
    name: myapp_shared_proxy
    external: true
</code></pre>
<p>そして、アプリ側からは <code>ports:</code> を外す。</p>
<pre><code class="language-diff"> services:
   frontend:
-    ports:
-      - &quot;${FRONTEND_PORT:-3000}:3000&quot;
   backend:
-    ports:
-      - &quot;${BACKEND_PORT:-8080}:8080&quot;
</code></pre>
<p>これでホストに開く HTTP のポートは、プロキシの 3000 番だけになった。URL の <code>:3000</code> は全 worktree で同じだが、ホスト名が違うので行き先も違う。</p>
<h3 id="フロントと-api-を同じオリジンにする" tabindex="-1">フロントと API を同じオリジンにする</h3>
<p>ホスト名で振り分けるついでに、1 つのホスト名をパスで分けて、<code>/api/v1</code> から始まるリクエストを backend に送るようにした。backend のルートは <code>/</code> 直下にあるので、プロキシで <code>/api/v1</code> を外してから転送する。</p>
<pre><code class="language-yaml"># compose/routing.yaml（続き）
backend:
  environment:
    # 許可元はこの worktree の URL 1 つだけ
    - AUTH_CSRF_TRUSTED_ORIGINS=http://${COMPOSE_PROJECT_NAME}.localhost:3000
    # メール内リンクの基点もこの worktree の URL にそろえる
    - APP_BASE_URL=http://${COMPOSE_PROJECT_NAME}.localhost:3000
  labels:
    - traefik.enable=true
    - traefik.docker.network=myapp_shared_proxy
    # /api/v10 などを拾わないよう、Path と PathPrefix を分けて書く
    - &quot;traefik.http.routers.${COMPOSE_PROJECT_NAME}-api.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`) &amp;&amp; (Path(`/api/v1`) || PathPrefix(`/api/v1/`))&quot;
    - traefik.http.routers.${COMPOSE_PROJECT_NAME}-api.entrypoints=web
    - traefik.http.routers.${COMPOSE_PROJECT_NAME}-api.middlewares=${COMPOSE_PROJECT_NAME}-api-strip
    - traefik.http.middlewares.${COMPOSE_PROJECT_NAME}-api-strip.stripprefix.prefixes=/api/v1
    - traefik.http.services.${COMPOSE_PROJECT_NAME}-api.loadbalancer.server.port=8080
  networks:
    - default
    - shared_db # 共有 PostgreSQL 用（DB の章）
    - shared_proxy
</code></pre>
<p>frontend は API の基点を相対パスの <code>/api/v1</code> にしてあるので、どの worktree でも自分の backend を呼ぶ。変更前後の URL を並べるとこうなる。</p>
<table>
<thead>
<tr>
<th>状態</th>
<th>画面</th>
<th>API</th>
<th>オリジン</th>
</tr>
</thead>
<tbody>
<tr>
<td>変更前（2 つ目の worktree）</td>
<td><code>http://localhost:3001</code></td>
<td><code>http://localhost:8081</code></td>
<td>別（ポートが違う）</td>
</tr>
<tr>
<td>変更後</td>
<td><code>http://myapp-feat-a.localhost:3000/</code></td>
<td><code>http://myapp-feat-a.localhost:3000/api/v1/</code></td>
<td>同じ</td>
</tr>
</tbody>
</table>
<p>同じオリジンになって、cookie と CSRF の扱いが単純になった。backend は refresh cookie を Domain 属性なし（ホスト限定）・<code>Path=/</code> で発行している。画面へのリクエストにもこの cookie が付くので、frontend の middleware がそのまま読める。ホスト名が worktree ごとに違うので、cookie も worktree ごとに分かれる。CSRF の許可元も、以前はフックが worktree ごとのポートから組み立てて <code>.env</code> に書いていたが、今は compose がプロジェクト名から組み立てる。</p>
<p>Next.js の dev サーバーは <code>localhost</code> とそのサブドメインを既定で許可するので、<a href="https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins" target="_blank" rel="noopener noreferrer"><code>allowedDevOrigins</code></a> は設定していない。ホットリロード（HMR）の WebSocket もプロキシ越しにつながった。</p>
<h3 id="動作確認" tabindex="-1">動作確認</h3>
<pre><code class="language-bash"># この worktree のプロジェクト名（= ホスト名）
docker compose config | sed -n '1s/^name: //p'   # → myapp-feat-a

curl -s -o /dev/null -w '%{http_code}\n' http://myapp-feat-a.localhost:3000/          # frontend
curl -s -o /dev/null -w '%{http_code}\n' http://myapp-feat-a.localhost:3000/api/v1/   # backend
</code></pre>
<p>どちらも 200 が返れば振り分けはできている。curl は <a href="https://curl.se/ch/7.85.0.html" target="_blank" rel="noopener noreferrer">7.85.0</a> から <code>*.localhost</code> を自分でループバックアドレス（127.0.0.1 と ::1）に解決する。</p>
<h3 id="ほかの解き方と比べて" tabindex="-1">ほかの解き方と比べて</h3>
<p>この形に落ち着くまでには回り道があった。きっかけは Vercel Labs の <a href="https://github.com/vercel-labs/portless" target="_blank" rel="noopener noreferrer">portless</a> だ。ホストで起動した dev サーバーに <code>myapp.localhost</code> のような名前付きの URL を付けるツールで、git worktree の中ではブランチ名をホスト名に入れてくれる。紹介記事を読んで、これを入れればポートずらしのフックを消せるのではないかと思い、調べ始めた。</p>
<p>調べてみると、portless の自動の機能（<code>PORT</code> の割り当てや、ブランチ名からのホスト名）は、portless 経由で起動したプロセスにしか効かない。私の環境はアプリを全部コンテナで動かしているので、そのままでは効かない。<code>portless alias &lt;名前&gt; &lt;ポート&gt;</code> で、コンテナがホストに公開したポートに名前を付けることはできる。ただ、公開するポートを worktree ごとにずらす作業は残る。フックを消せないなら、入れる意味は薄い。</p>
<p>次に見たのが、Docker の実行環境に使っている OrbStack の<a href="https://docs.orbstack.dev/docker/domains" target="_blank" rel="noopener noreferrer">ドメイン機能</a>だ。Compose のサービスに <code>service.project.orb.local</code> の名前が自動で付き、設定はほとんど要らない。ただ OrbStack は macOS 向けのアプリで、商用利用には有料の<a href="https://docs.orbstack.dev/licensing" target="_blank" rel="noopener noreferrer">ライセンス</a>が要る。Windows の人もいるチームで標準にするのは難しかった。</p>
<p>そこで、全部 Compose のまま、プロキシも Compose で 1 つ立てる形にした。判断の基準はこう置いた。</p>
<ul>
<li>Mac と Windows で同じ手順で動く</li>
<li>Docker で環境を再現できる状態を保つ</li>
<li>worktree の作り方（gtr、<code>git worktree add</code>、エージェントのクローン）に依存しない</li>
<li>CI の起動経路を壊さない</li>
</ul>
<table>
<thead>
<tr>
<th>方式</th>
<th>前提</th>
<th>今回合わなかった点</th>
<th>入れるときのコスト</th>
</tr>
</thead>
<tbody>
<tr>
<td>ポートをずらす（従来）</td>
<td>worktree 作成時にフックを通す</td>
<td>フックを通らない worktree が漏れる。設定の追従漏れ</td>
<td>フックの保守</td>
</tr>
<tr>
<td>portless（0.9.0 以降の既定）</td>
<td>アプリを portless 経由でホストで起動</td>
<td>自動の機能がコンテナに効かず、ポートの採番が残る</td>
<td>443 番に sudo で常駐する。ローカル CA をトラストストアに入れる</td>
</tr>
<tr>
<td>OrbStack のドメイン</td>
<td>OrbStack を Docker の実行環境に使う</td>
<td>macOS 向けで、商用利用は有料</td>
<td>ほぼ設定不要</td>
</tr>
<tr>
<td>devcontainer</td>
<td>エディタからコンテナに入って開発する</td>
<td>ツールの再現性はそろうが、ポートの問題は解かない</td>
<td>設定ファイル一式</td>
</tr>
<tr>
<td>共有リバースプロキシ（採用）</td>
<td>Docker（実行環境は問わない）</td>
<td>—</td>
<td>3000 番を 1 つ占有する。HTTP 以外は対象外</td>
</tr>
</tbody>
</table>
<p>アプリをホストで直接起動している構成なら、portless はかなり手軽な選択肢だと思う。私の場合に合わなかったのは、全部を Compose で動かしていたからだ。</p>
<h3 id="ポートずらしの頃と比べて変わったこと" tabindex="-1">ポートずらしの頃と比べて変わったこと</h3>
<table>
<thead>
<tr>
<th>項目</th>
<th>ポートずらし</th>
<th>共有リバースプロキシ</th>
</tr>
</thead>
<tbody>
<tr>
<td>URL</td>
<td><code>http://localhost:3001</code>（番号で区別）</td>
<td><code>http://myapp-feat-a.localhost:3000</code>（名前で区別）</td>
</tr>
<tr>
<td>ホストに開く HTTP のポート</td>
<td>worktree ごとに最大 6 つ（ツール用を含む）</td>
<td>全体でプロキシの 1 つ</td>
</tr>
<tr>
<td>衝突しない worktree</td>
<td>gtr で作ったものだけ</td>
<td>作り方を問わない</td>
</tr>
<tr>
<td>worktree 作成フック</td>
<td>169 行（大半がポートの採番）</td>
<td>69 行（名前の設定と依存の install）</td>
</tr>
<tr>
<td>CSRF の許可元</td>
<td>フックがポートから生成して <code>.env</code> に書く</td>
<td>compose がホスト名から組み立てる</td>
</tr>
<tr>
<td>LAN への公開</td>
<td>frontend などを全インターフェースに公開</td>
<td><code>127.0.0.1</code> だけ</td>
</tr>
</tbody>
</table>
<h2 id="db:-1-つだけ立てて、全-worktree-から同じ名前で参照する" tabindex="-1">DB: 1 つだけ立てて、全 worktree から同じ名前で参照する</h2>
<p>PostgreSQL は全 worktree で 1 つを共有している。これはプロキシより前、2026 年 7 月から続けている構成だ。</p>
<h3 id="仕組み" tabindex="-1">仕組み</h3>
<p>共有の DB も、プロキシと同じく別のプロジェクトとして 1 つだけ起動し、専用のネットワーク <code>myapp_shared_db</code> に <code>db</code> という別名で参加させる。</p>
<pre><code class="language-yaml"># compose/db.yaml（共有 PostgreSQL）
services:
  db:
    image: postgres:16-alpine
    container_name: myapp_postgres
    restart: always
    ports:
      - &quot;127.0.0.1:5432:5432&quot; # ホストの psql などから使う。LAN には公開しない
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      shared_db:
        aliases:
          - db

volumes:
  postgres_data:
    name: myapp_postgres_data # プロジェクト名に左右されない固定の名前

networks:
  shared_db:
    name: myapp_shared_db
    external: true
</code></pre>
<p>各 worktree の backend は、自分のプロジェクトの <code>default</code> ネットワークと、この共有ネットワークの両方に入る。</p>
<pre><code class="language-yaml"># compose/base.yaml（各 worktree）
services:
  backend:
    networks:
      - default # 自分の worktree の frontend やベクトル DB と話す
      - shared_db # 共有 PostgreSQL と話す

networks:
  shared_db:
    name: myapp_shared_db
    external: true
</code></pre>
<p><img src="../assets/2026-10-01_worktree-shared-reverse-proxy/fig4-shared-db-network.png" alt="共有 DB のネットワーク図。worktree A と worktree B のネットワークにはそれぞれ frontend・ベクトル DB・backend がある。backend だけが自分の worktree のネットワークと共有ネットワーク（myapp_shared_db）の両方に入り、共有ネットワーク上の PostgreSQL（別名 db）に db:5432 で接続する。接続先はどの worktree でも db:5432 で、frontend とベクトル DB は DB に触れない" loading="lazy"></p>
<p>Docker のネットワークの中では、コンテナをサービス名や別名で引ける。backend から見ると、共有ネットワークの上に <code>db</code> という名前のホストが 1 つあるだけだ。接続文字列は <code>postgres://...@db:5432/...</code> のまま、どの worktree でも変えずに済む。frontend とベクトル DB は共有ネットワークに入らないので、DB には直接触れない。</p>
<p>以前は DB を各 worktree の compose に入れていた。volume はプロジェクトごとに分かれるので、同じ DB を使うつもりでもデータは別々になり、2 つ目の worktree を起動すると固定のコンテナ名と 5432 番で衝突した。DB を別のプロジェクトに切り出し、volume に固定の名前を付けたのはそのためだ。</p>
<h3 id="なぜネットワークを共有するのか" tabindex="-1">なぜネットワークを共有するのか</h3>
<p>DB の持ち方は、ほかにも考えられる。並べるとこうなる。</p>
<table>
<thead>
<tr>
<th>方式</th>
<th>良いところ</th>
<th>困るところ</th>
</tr>
</thead>
<tbody>
<tr>
<td>worktree ごとに DB を立てる</td>
<td>スキーマとデータがブランチ間で干渉しない</td>
<td>DB の数だけメモリとディスクを使う。データを使い回せない</td>
</tr>
<tr>
<td>ホスト経由でつなぐ（<code>host.docker.internal</code>）</td>
<td>ネットワークを共有しなくてよい</td>
<td>接続先の書き方が OS や Docker の実行環境に依存する</td>
</tr>
<tr>
<td>共有ネットワークに置く（採用）</td>
<td>接続先 <code>db:5432</code> を変えずに 1 つを使い回せる</td>
<td>migration やデータの変更が全 worktree に効く</td>
</tr>
</tbody>
</table>
<p>共有にしておくと、worktree を作るたびにテスト用のユーザーやデータを作り直さずに済む。ホストから見たいときは <code>127.0.0.1:5432</code> で psql などをつなぐ。</p>
<h3 id="共有して困るのは、スキーマが分かれるとき" tabindex="-1">共有して困るのは、スキーマが分かれるとき</h3>
<p>共有 DB には向かない使い方がある。互換性のない migration を、ブランチごとに並行して試す使い方だ。</p>
<ul>
<li>migration ツールが記録する版が、先に適用したブランチの版まで進む。その migration ファイルを持たない別のブランチでは、ツールが版を解決できずに止まることがある</li>
<li>あるブランチで migration を戻す（down）と、そのテーブルを使っている別のブランチが壊れる</li>
<li>DB をリセットすると、全 worktree のデータが消える</li>
</ul>
<p>今のところ、これは README で「migration、DB のリセット、手動のデータ変更は、起動中の全 worktree に影響する」と注意しているだけで、仕組みでは防いでいない。</p>
<p>中間の案もある。PostgreSQL のコンテナは 1 つのまま、worktree ごとに database（または schema）を分ける方法だ。<code>CREATE DATABASE myapp_feat_a</code> のように worktree ごとの DB を作り、接続文字列の DB 名だけを変える。データは使い回せなくなるが、スキーマは干渉しない。この案は当時の比較に入れていなかった。スキーマを変える作業を並行することが増えたら、次の候補になる。</p>
<h3 id="ベクトル-db-は-worktree-ごとに分けた" tabindex="-1">ベクトル DB は worktree ごとに分けた</h3>
<p>一方、ベクトル DB（Qdrant）は worktree ごとに持たせた。共有すると困ることがあるからだ。</p>
<ul>
<li>E2E テストでは LLM を偽物に差し替えていて、意味のないベクトルが作られる。共有すると、それが実データに混ざる</li>
<li>ブランチで埋め込みの方式やコレクションの形式を変えると、その変更が全 worktree に効いてしまう</li>
</ul>
<p>この組み合わせには副作用がある。worktree A で登録したデータは PostgreSQL に入るので、worktree B の画面にも出る。ところがベクトルは A のベクトル DB にしか無いので、B の検索では使えない。B で使いたければ、B で索引を作り直す。</p>
<h3 id="共有するか分けるかの目安" tabindex="-1">共有するか分けるかの目安</h3>
<p>DB に限らず、何を共有するかは次の観点で決めた。</p>
<ul>
<li>共有する: ブランチをまたいでデータを使い回したい。スキーマの変更がまれ。起動の手間とリソースを減らしたい</li>
<li>分ける: ブランチで形式やスキーマが変わる。テスト用のデータが混ざると困る。作り直しが安い</li>
</ul>
<p>PostgreSQL は前者、ベクトル DB と node_modules は後者だった。</p>
<h2 id="依存と起動:-1-コマンドで、worktree-の作り方に依存しない" tabindex="-1">依存と起動: 1 コマンドで、worktree の作り方に依存しない</h2>
<h3 id="node_modules-は-worktree-ごとの-volume-に置く" tabindex="-1">node_modules は worktree ごとの volume に置く</h3>
<p>イメージ名は全 worktree で共有している。worktree ごとに名前を分けると、消した worktree の分だけイメージが残るからだ。その代わり、起動のたびにその worktree のソースからビルドし直す（差分が無ければキャッシュですぐ終わる）。node_modules はイメージの中のものを使わず、worktree ごとの名前付き volume に置いて、起動時にその worktree の lockfile で入れ直す。</p>
<pre><code class="language-yaml"># compose/override.yaml（ローカル開発用）
services:
  frontend:
    # lockfile が変わっていなければ数秒で終わる
    command: sh -c &quot;pnpm install --frozen-lockfile &amp;&amp; exec pnpm run dev&quot;
    volumes:
      - ./frontend:/app
      # volume 名にプロジェクト名が付くので、worktree ごとに分かれる
      - frontend_node_modules:/app/node_modules

volumes:
  frontend_node_modules:
</code></pre>
<p>pnpm の store もこの volume の中に置いた（Dockerfile で <code>npm_config_store_dir=/app/node_modules/.pnpm-store</code>）。ハードリンクが効き、worktree を消せば volume と一緒に消える。2 回目以降の install は 1〜4 秒ほどで、volume が空の初回は十数秒かかる。</p>
<h3 id="起動の流れ" tabindex="-1">起動の流れ</h3>
<p>起動はタスクランナー（Task）の <code>task compose:up</code> 1 つにまとめた。</p>
<pre><code class="language-yaml"># taskfiles/Taskfile.compose.yml（抜粋）
up:
  cmds:
    - task: env:link # .env が無ければメインの .env にリンク
    - task: project:check # プロジェクト名が別のディレクトリで使われていないか
    - task: db:up # 共有 PostgreSQL（無ければ作り、あれば使う）
    - task: proxy:up # 共有プロキシ（同上）
    - docker compose up -d --build # この worktree のアプリ
</code></pre>
<p>Task を使わない場合は、次の 3 つを順に実行すれば同じことになる。</p>
<pre><code class="language-bash">./scripts/shared-db.sh up      # 共有ネットワーク + PostgreSQL（別名 db）
./scripts/shared-proxy.sh up   # 共有ネットワーク + Traefik
docker compose up -d --build   # この worktree のアプリ
</code></pre>
<p>共有の DB とプロキシは「無ければ作り、あれば使う」ので、最初に起動した worktree が作り、2 つ目以降はそれを使う。gtr で作った worktree でも、<code>git worktree add</code> で作ったものでも、AI エージェントのクローンでも、同じ <code>task compose:up</code> で起動できる。前提は、プロジェクト名が worktree ごとに違うこと。ディレクトリ名が同じ別のクローンなどで名前が重なったら、起動前のチェックが止める。名前を自動で付け直してくれるわけではない。エージェントが作業している途中の画面も、プロジェクト名の URL で開ける。</p>
<h3 id="片付け" tabindex="-1">片付け</h3>
<p>gtr で worktree を消すときは、削除前のフックで <code>docker compose down --volumes</code> を流す。gtr を通さずに消した worktree の残骸（コンテナ・ネットワーク・volume）は、<code>task compose:prune</code> で一覧にしてから消す。作業ディレクトリが存在しないプロジェクトを探し、共有の DB とプロキシは名前で除外する。不要になったイメージも、自分のプロジェクトのラベルが付いたものだけを消す（<code>docker image prune</code> はホスト全体が対象になるので使わない）。</p>
<h3 id="ci-の-e2e-も同じ経路にした" tabindex="-1">CI の E2E も同じ経路にした</h3>
<p>最初は CI だけ、frontend と backend をポートで公開した別オリジンのまま E2E を動かしていた。これだとローカルと CI で cookie と CSRF の条件がそろわない。そこで CI でもプロキシを起動し、<code>http://myapp-ci.localhost:3000</code> で E2E を流すようにした。GitHub Actions の runner では、Playwright の API リクエストなど OS の名前解決を使うクライアントのために、<code>/etc/hosts</code> に <code>myapp-ci.localhost</code> を登録している。</p>
<h2 id="まとめ:-自分の環境に持ち込むチェックリスト" tabindex="-1">まとめ: 自分の環境に持ち込むチェックリスト</h2>
<ol>
<li><code>docker compose config</code> の出力から <code>ports:</code> と <code>container_name:</code> を洗い出す。固定の <code>container_name:</code> は外す</li>
<li>プロジェクト名の付け方を決める（小文字・ハイフン区切りで、ホスト名に使える形）。<code>.env</code> を共有するなら、そこに <code>COMPOSE_PROJECT_NAME</code> を書かない</li>
<li>共有するもの（DB など）を別のプロジェクトに切り出し、<code>docker network create</code> で作ったネットワークに別名付きで参加させる。volume には <code>name:</code> で固定の名前を付ける</li>
<li>リバースプロキシを別のプロジェクトで 1 つ立て、アプリにラベルを付けて <code>ports:</code> を外す。ホストへの公開は <code>127.0.0.1</code> だけにする</li>
<li>frontend の API の基点を相対パス（<code>/api/v1</code>）にし、CSRF などの許可元を <code>&lt;プロジェクト名&gt;.localhost</code> から組み立てる</li>
<li>「共有 DB → プロキシ → <code>docker compose up -d --build</code>」を 1 つのコマンドにまとめる</li>
</ol>
<p>アプリをホストで直接起動しているなら portless、Mac で個人開発しているなら OrbStack のドメイン機能のほうが手軽だと思う。全部を Compose で動かしていて、作り方の違う worktree が混ざるなら、入口を 1 つにする形が合う。</p>
<p>フックが 100 行減ったのはおまけだった。変わったのは、エージェントが作業中の worktree を、ポート番号を調べずにプロジェクト名の URL で開けるようになったことだ。</p>
]]></content:encoded>
            <category>Dev Env</category>
        </item>
        <item>
            <title><![CDATA[Ghostty + herdr で組む開発環境（2026年9月版） — AI エージェントを並べてターミナルで開発する]]></title>
            <link>https://www.yoshihiko.tech/blog/ghostty-herdr-dev-env</link>
            <guid isPermaLink="false">ghostty-herdr-dev-env</guid>
            <pubDate>Tue, 03 Nov 2026 15:14:47 GMT</pubDate>
            <description><![CDATA[WezTerm + tmux + 自作ツールから Ghostty + herdr に移した私の開発環境を紹介する。Claude Code・Codex・takt を並べて動かす 1 タスクの流れ、ターミナル内ブラウザで画面の要素を AI に渡す方法、Neovim・lazygit・hunk・Nix まで層ごとにまとめた。]]></description>
            <content:encoded><![CDATA[<h1 id="ghostty-+-herdr-で組む開発環境（2026年9月版）" tabindex="-1">Ghostty + herdr で組む開発環境（2026年9月版）</h1>
<p>半年前、<a href="/blog/claude-code-dev-toolchain">Claude Code 開発を支える私のツールチェイン</a> という記事で WezTerm + tmux + 自作ツール Baton の構成を紹介した。あれから中身がだいぶ入れ替わったので、現時点の開発環境をまとめ直す。</p>
<p>入れ替えたのはターミナルエミュレーター・マルチプレクサ・AI セッション管理の 3 つで、エディタやバージョン管理まわりはほぼそのままだ。この記事ではまず「今どういう構成で、どう開発しているか」を書き、そのあとに「なぜこの形になったか」を短く添える。想定している読者は、ターミナル中心で開発したい人と、Claude Code を複数並べて動かしている人だ。</p>
<h2 id="結論から" tabindex="-1">結論から</h2>
<ul>
<li>常用の構成は <strong>Ghostty（ターミナルエミュレーター）+ herdr（ターミナルマルチプレクサ）</strong> にした。WezTerm + tmux + Baton は捨てずに、戻るための退路として残している</li>
<li>herdr を選んだ決め手は、tmux 時代にできなかった「Neovim での画像表示」と「ターミナル内ブラウザで画面の要素を選んで AI に渡すこと」ができるようになったことだ。承認待ちの検知や使い心地は、Baton とほとんど変わらない</li>
<li>Ghostty に移ったのは、そのターミナル内ブラウザが WezTerm では重くて使い物にならず、Ghostty では普通に動いたから</li>
</ul>
<p>全体の構成を 1 枚にするとこうなる。</p>
<p><img src="https://static.zenn.studio/user-upload/c1f421414ce9-20260924.png" alt="開発環境のレイヤー構成図。下から基盤（Nix、dotfiles）、OS 操作（AeroSpace、Alfred、Karabiner、Loupedeck）、ターミナルエミュレーター（Ghostty）、マルチプレクサ（herdr とプラグイン）が積み重なり、最上段に AI エージェント（Claude Code、Codex、takt、OpenCode、Antigravity）と開発ツール（Neovim、lazygit、hunk、gtr、terminal-browser）が並ぶ" loading="lazy"></p>
<h2 id="半年前から変わったところ" tabindex="-1">半年前から変わったところ</h2>
<p>変わったのは下の 3 層だけで、そのほかは据え置きだ。</p>
<p><img src="https://static.zenn.studio/user-upload/abfa8a9d41bf-20260924.png" alt="2026年3月と9月の構成比較図。ターミナルエミュレーターが WezTerm から Ghostty に、マルチプレクサが tmux から herdr に、AI セッション管理が自作の Baton から herdr のサイドバーに変わった。Neovim、lazygit、gtr、Nix、Alfred は変わっていない" loading="lazy"></p>
<table>
<thead>
<tr>
<th>層</th>
<th>2026年3月</th>
<th>2026年9月</th>
</tr>
</thead>
<tbody>
<tr>
<td>ターミナルエミュレーター</td>
<td>WezTerm</td>
<td>Ghostty（WezTerm は退路として残す）</td>
</tr>
<tr>
<td>マルチプレクサ</td>
<td>tmux</td>
<td>herdr</td>
</tr>
<tr>
<td>AI セッション管理</td>
<td>Baton（自作の Go 製 TUI）</td>
<td>herdr のサイドバー + 自作スクリプト（Baton は休止）</td>
</tr>
<tr>
<td>ブラウザ連携</td>
<td>なし</td>
<td>terminal-browser（ターミナル内の Chromium）</td>
</tr>
<tr>
<td>Neovim の画像表示</td>
<td>tmux の中では実用にならない</td>
<td>使える</td>
</tr>
<tr>
<td>エディタ / Git / worktree</td>
<td>Neovim / lazygit / gtr</td>
<td>変わらず（hunk を試用中）</td>
</tr>
<tr>
<td>環境管理</td>
<td>dotfiles</td>
<td>dotfiles + Nix（nix-darwin + home-manager）</td>
</tr>
</tbody>
</table>
<p>Nix 化は 8 月の話で、経緯は <a href="/blog/dotfiles-operation-and-nix">dotfiles 運用のすすめと、その先の Nix</a> に書いた。この記事の範囲は 9 月の入れ替えである。</p>
<h2 id="ghostty-+-herdr-でどう開発しているか-—-1-タスクの流れ" tabindex="-1">Ghostty + herdr でどう開発しているか — 1 タスクの流れ</h2>
<p>道具の一覧より先に、1 つのタスクをどう進めているかを書く。私の場合、常時 10 本前後のエージェントを並べて動かしている。</p>
<p><img src="https://static.zenn.studio/user-upload/4efad4a78f60-20260924.png" alt="1 タスクの開発フロー図。1 プロジェクトを開く、2 worktree を切る、3 分割してエージェントを起動、4 状態を見て承認、5 ブラウザで確認して要素を渡す（Web 開発のときのみ）、6 差分を読む、7 コミット。4 から 6 は繰り返す" loading="lazy"></p>
<ol>
<li><strong>プロジェクトを開く。</strong> Ghostty を起動すると自動で herdr にアタッチし、前回の作業がそのまま残っている。新しく始めるときは <code>prefix+f</code> で ghq 管理下のリポジトリを fzf で選び、herdr のワークスペースとして開く。Alfred から開くこともある</li>
<li><strong>worktree を切る。</strong> 並列で進めるタスクごとに <code>wt</code>（gtr のラッパー）で worktree を作り、同じピッカーから開く</li>
<li><strong>分割してエージェントを起動する。</strong> <code>prefix+2</code> 〜 <code>prefix+8</code> で N 分割し、<code>prefix+0</code> でペイン数に応じて Neovim・Claude Code・Codex を起動する。全ペインで Claude Code を一斉に立ち上げたいときは、Loupedeck（物理ボタンの左手デバイス）の 1 ボタンで済ませる</li>
<li><strong>状態を見て承認する。</strong> 各ペインが working / blocked / idle のどれかを、herdr がサイドバーに出す。承認待ちが出たら <code>prefix+b</code> の承認コンソールで <code>a</code>（承認）か <code>d</code>（拒否）を押すか、通知をクリックしてそのペインへ飛ぶ</li>
<li><strong>（Web アプリのとき）ブラウザで確認して、要素ごと直させる。</strong> <code>tb</code> で localhost を隣のペインに開き、気になる要素を <code>Ctrl+G</code> で選ぶ。選んだ要素の情報がエージェントの入力欄に入るので、そのあとに「ここの余白を詰めて」と書いて送る</li>
<li><strong>差分を読む。</strong> エージェントが書いた変更は hunk でまとめて読む。気になるところは Neovim で開いて直接確認する</li>
<li><strong>コミットする。</strong> 基本はエージェントに commit スキルを実行させ、変更内容からコミットを作らせる。手で直したファイルは <code>prefix+g</code> で開く lazygit でステージし、<code>Ctrl+G</code> で Antigravity にコミットメッセージを作らせてそのままコミットする</li>
</ol>
<p><img src="https://static.zenn.studio/user-upload/122881ea4d52-20260924.png" alt="herdr の画面。左のサイドバーにワークスペースと、blocked・done・working・idle の状態付きのエージェント一覧が並ぶ。右側は 4 分割で、Claude Code、Codex、Antigravity、OpenCode が動いており、Antigravity のペインはコマンド実行の承認待ちになっている" loading="lazy"></p>
<p>4〜6 を何周も回すのが日常だ。5 は Web アプリを作っているときだけの手順で、それ以外の開発ではターミナル内ブラウザは使わない。</p>
<p>Web アプリの開発でいちばん変わったのは、その 5 である。以前は、ブラウザ（Dia）用に自作した拡張で要素の情報を拾い、ターミナルに戻って AI に渡していた。今はブラウザを切り替えず、<code>Ctrl+G</code> で要素を指すだけで済む。</p>
<p><img src="https://static.zenn.studio/user-upload/93a89ae187f0-20260924.png" alt="terminal-browser で Ghostty の公式ドキュメントを開き、Ctrl+G の要素ピッカーで見出しの div を選んでいるところ。選択中の要素が枠で囲まれ、要素名のラベルが出ている" loading="lazy"></p>
<p><img src="https://static.zenn.studio/user-upload/7915fe1021f8-20260924.png" alt="要素を選んだ直後の画面。左のペインの Claude Code の入力欄に、選んだ div の HTML の抜粋と CSS セレクタが 1 行で入っている" loading="lazy"></p>
<p>※ 開発中のプロダクトは見せられないので、画面はサンプルとして Ghostty の公式ドキュメントを開いている。普段は localhost で動かしている手元のアプリに対して同じ操作をする。</p>
<h2 id="レイヤー別のツール紹介" tabindex="-1">レイヤー別のツール紹介</h2>
<p>ここからは図 1 の層ごとに、使っているものを紹介する。</p>
<h3 id="ターミナルエミュレーター:-ghostty" tabindex="-1">ターミナルエミュレーター: Ghostty</h3>
<p><a href="https://ghostty.org" target="_blank" rel="noopener noreferrer">Ghostty</a> は Zig 製の、GPU で描画するターミナルエミュレーターだ。公式ドキュメントに「Zero Configuration Philosophy」という節があり、設定なしでもほとんどの人がそのまま使えることを目標にしている（<a href="https://ghostty.org/docs/config" target="_blank" rel="noopener noreferrer">Ghostty Configuration</a>）。</p>
<p>私の設定は本体 55 行と herdr 用のキー設定 51 行で、合わせて 106 行だ。WezTerm のときは Lua で 7 ファイル 388 行あった。ただ、減った分の大半は Ghostty の手柄ではない。タブや分割の管理を herdr に移したので、ターミナルエミュレーター側の仕事がそもそも減った。</p>
<p>Ghostty 側でやっていることは、ほぼ 1 つしかない。Cmd 系のキーを「herdr の prefix + キー」のバイト列に変換して送ることだ。Ghostty には Lua のようなスクリプトがないので、<code>text:</code> アクションでバイト列を直接書く。</p>
<pre><code class="language-ini"># config/ghostty/herdr.conf（抜粋）
# Cmd 系のキーを「herdr の prefix（Ctrl+Q = \x11）+ キー」のバイト列で送る

# Cmd+D でペインを縦に分割、Cmd+T で新規タブ、Cmd+W でペインを閉じる
keybind = cmd+d=text:\x11v
keybind = cmd+t=text:\x11c
keybind = cmd+w=text:\x11x

# Cmd+1..9 を herdr のタブ切替（Alt+1..9）に回す。
# Ghostty 既定の super+digit_N=goto_tab が先にマッチするので、先に外しておく
keybind = cmd+digit_1=unbind
# Alt+1 は ESC 前置ではなく CSI u 形式で送る（herdr が kitty keyboard protocol を使うため）
keybind = cmd+1=text:\x1b[49;3u
</code></pre>
<p>ハマったのは Cmd+数字だ。<code>cmd+1</code> を上書きしても、Ghostty 既定の物理キー指定（<code>super+digit_1</code>）が先にマッチしてしまう。<code>cmd+digit_N=unbind</code> で既定を外してから割り当てる必要があった。Alt の送り方も、よく見る ESC 前置（<code>\x1b1</code>）ではなく CSI u 形式（<code>\x1b[49;3u</code>）にしている。herdr が外側のターミナルエミュレーターに kitty keyboard protocol を要求するためだ。</p>
<p>見た目では、WezTerm で使っていた背景画像を引き継いだ。Ghostty には背景画像の明るさを下げる設定がないので、画像そのものを事前に暗く加工して敷いている。</p>
<h3 id="マルチプレクサ:-herdr" tabindex="-1">マルチプレクサ: herdr</h3>
<p><a href="https://github.com/herdrdev/herdr" target="_blank" rel="noopener noreferrer">herdr</a> は AI エージェントを動かす前提で作られたターミナルマルチプレクサで、Rust 製の単一バイナリだ。tmux と同じ層にあり、tmux の代わりに使う。</p>
<p>tmux から移って違いを感じたのは次の点である。</p>
<ul>
<li><strong>各ペインのエージェントの状態が見える。</strong> Claude Code や Codex を自動で検出し、working / blocked / idle をサイドバーに出す</li>
<li><strong>マウスも普通に使える。</strong> README に「keyboard and mouse, both first-class」とあり、prefix キーとクリック・ドラッグのどちらでも操作できる</li>
<li><strong>エージェントから操作できる。</strong> CLI とソケット API があり、ペインの分割やキー送信をスクリプトから叩ける</li>
</ul>
<p>キーバインドは tmux 時代のものをそのまま移植した。prefix も tmux と同じ <code>Ctrl+Q</code> にしている（herdr 既定の <code>Ctrl+B</code> は、Karabiner で左矢印に変換しているので届かない）。</p>
<pre><code class="language-toml"># config/herdr/config.toml（抜粋）
[keys]
# prefix は tmux 時代と同じ Ctrl+Q
prefix = &quot;ctrl+q&quot;

# lazygit をポップアップで開く
[[keys.command]]
key = &quot;prefix+g&quot;
type = &quot;popup&quot;
command = &quot;zsh -ic 'lazygit'&quot;
width = &quot;80%&quot;
height = &quot;80%&quot;
</code></pre>
<p>herdr 単体で足りない部分は、自作スクリプト 10 本で埋めた。主なものは、承認待ちのエージェントを一覧して <code>a</code> / <code>d</code> で承認・拒否できる承認コンソール（Baton の TUI の代わり）、ghq のリポジトリピッカー、N 分割、ペイン数に応じたプロセスの一括起動である。スクリプトはすべて dotfiles の <code>config/herdr/bin/</code> に置いている。</p>
<p>プラグインはタブ名を自動で付ける <a href="https://github.com/qu8n/herdr-automatic-rename" target="_blank" rel="noopener noreferrer">herdr-automatic-rename</a> と、OS の通知バナーをクリックすると該当ペインへ飛べる <a href="https://github.com/dot/herdr-terminal-notifier" target="_blank" rel="noopener noreferrer">herdr-terminal-notifier</a> の 2 つを入れている。プラグインは <code>herdr plugin install</code> で入れる仕組みなのでリポジトリで管理しにくい。そこで、使うプラグインと commit SHA を書いたリストを dotfiles に置き、Nix の switch のたびにそのリストへ揃えるようにした。導入前にはスクリプトと同梱アプリの中身を確認し、commit を固定している。</p>
<p>細かいところでは、サイドバーのエージェント名を Claude はオレンジ、Codex は青緑というように色分けした。10 本並ぶと、色がないとどれが何か分からない。</p>
<h3 id="ai-エージェント:-claude-code-/-codex-/-takt-/-opencode-/-antigravity" tabindex="-1">AI エージェント: Claude Code / Codex / takt / OpenCode / Antigravity</h3>
<p>エージェントは Claude Code と Codex を主に使い、手順が決まった作業は nrslib さんの <a href="https://github.com/nrslib/takt" target="_blank" rel="noopener noreferrer">takt</a> に流している。takt はステップごとにプロバイダを割り当てられるので、実装とテストは Codex、計画とレビューは Claude Code、AI 特有のアンチパターン検出だけは OpenCode、という分担にしている。</p>
<p>承認まわりは半年で様子が変わった。Baton を作った当初は、Claude Code にも Codex にも、承認を自動で判定する仕組みがなかった。すべてを許可するモード（<code>--dangerously-skip-permissions</code>）で起動すれば承認は出なくなるが、それは怖い。そこで Baton に「safe auto mode」を実装した。決まったルールで <code>rm</code> や本番操作のような危険な操作を弾き、判断に迷うものだけ別の LLM に審査させる仕組みだ。その後 Claude Code に auto mode が入り、Claude Code にはあるのに Codex にはない時期も Baton で補っていた。今は Codex にも承認リクエストを自動でレビューする設定があり、どちらも組み込みの機能で足りるようになった。</p>
<p>このほかに OpenCode と Antigravity（<code>agy</code>）も入れている。OpenCode は takt のレビュー工程の一部を担当し、Antigravity は調べものの委譲と、後述する lazygit のコミットメッセージ生成に使っている。herdr はどちらも検出してサイドバーに出すので、Claude Code や Codex と同じ一覧で状態を追える。</p>
<h3 id="開発ツール" tabindex="-1">開発ツール</h3>
<p><strong>Neovim。</strong> 全部入りのディストリビューションは使わず、プラグインマネージャーの lazy.nvim で 1 つずつ足してきた。今は 29 ファイルで、選んだ理由は ADR としてリポジトリに残している。herdr に移って、<a href="https://github.com/folke/snacks.nvim" target="_blank" rel="noopener noreferrer">snacks.nvim</a> の画像表示が使えるようになった。Ghostty では Markdown の画像リンクのすぐ下に画像が埋め込まれるので、記事を書きながら図の見た目を確かめられる。WezTerm のときは埋め込みに対応しておらず、カーソルを乗せたときのフロート表示までだった。</p>
<p><img src="https://static.zenn.studio/user-upload/a41711668afb-20260924.png" alt="Neovim でこの記事の Markdown を開いたところ。画像リンクの行のすぐ下に、1 タスクの流れの図がバッファ内に埋め込まれて表示されている" loading="lazy"></p>
<p>実は 9/15 に一度、この画像表示を諦めている。tmux の中では画像の残像や位置ずれが出たので、画像表示を切り、パスをコピーして Alfred で開く運用にしていた。herdr に移ってから有効に戻したところ、残像も位置ずれも出なかった。一度諦めた機能だったので、これはかなり嬉しかった。</p>
<p><strong>lazygit。</strong> 手で直したファイルをコミットするときに使う。herdr の <code>prefix+g</code> でポップアップとして開くので、作業中のペインを崩さない。ステージしてから <code>Ctrl+G</code> を押すと、Antigravity が差分から Conventional Commits 形式のメッセージを作り、そのままコミットする（lazygit の customCommands に登録している）。エージェントが書いた変更は、エージェント自身に commit スキルを実行させてコミットすることが多い。</p>
<p><strong>hunk。</strong> <a href="https://hunk.dev/" target="_blank" rel="noopener noreferrer">hunk</a> は、エージェントが書いた変更をまとめて読むための diff ビューアで、「review-first」をうたっている。今回の移行と同じタイミングで試し始めた。1 行ずつステージするなら lazygit、エージェントが複数ファイルにまたがって書いた変更の全体を読むなら hunk、という分担を考えている。</p>
<p><strong>gtr。</strong> Git worktree の作成と削除をまとめるツールで、<code>wt</code> というラッパーでブランチ名も自動で付けている。worktree を作ったあとにポートの割り当てや依存のインストールを流す処理も、gtr の hook に書いてある。</p>
<p><strong>terminal-browser。</strong> <a href="https://github.com/zenbu-labs/terminal-browser" target="_blank" rel="noopener noreferrer">terminal-browser</a> は、Electron で本物の Chromium を動かし、その画面を Kitty graphics protocol でペインに描くブラウザである。localhost で動かしている手元のプロダクトも、普通の Web サイトも、実ブラウザと同じ見た目で表示される。起動は短いシェル関数にしている。</p>
<pre><code class="language-zsh"># tb          → localhost:3000 を右に分割して開く
# tb 5173     → localhost:5173
# tb ... down → 2 つ目の引数で分割方向
tb() {
  local target=&quot;${1:-localhost:3000}&quot;
  local dir=&quot;${2:-right}&quot;
  [[ &quot;$target&quot; == &lt;-&gt; ]] &amp;&amp; target=&quot;localhost:$target&quot;
  terminal-browser open &quot;$target&quot; --split &quot;$dir&quot;
}
</code></pre>
<p>要素を選んで AI に渡す <code>Ctrl+G</code> は terminal-browser の組み込み機能だ。要素ピッカー（react-grab）が起動し、選んだ要素の HTML の抜粋が、同じタブにあるエージェントの入力欄に <code>&gt; ...</code> の 1 行で入る。React の開発ビルドなら、コンポーネント名とソースの位置も付く。Enter は押されないので、続けて指示を書いてから送る。逆に AI 側からは <code>terminal-browser action</code> で、開いているタブのスクリーンショット・DOM の取得・クリックなどができる。</p>
<h3 id="os-操作:-aerospace-/-alfred-/-karabiner-/-loupedeck" tabindex="-1">OS 操作: AeroSpace / Alfred / Karabiner / Loupedeck</h3>
<p>ターミナルの外側は、キーボードから手を離さずに済むことを基準に選んでいる。</p>
<ul>
<li><strong>AeroSpace</strong>: タイル型のウィンドウマネージャー。Ghostty は決まったワークスペースに固定で置いている</li>
<li><strong>Alfred</strong>: ランチャー。リポジトリを herdr のワークスペースとして Ghostty で開くワークフローや、Taskfile のタスクを実行するワークフローを自作している</li>
<li><strong>Karabiner-Elements</strong>: キーの変換</li>
<li><strong>Loupedeck</strong>: 物理ボタンの左手デバイス。全ペインで Claude Code を起動する、全ペインで <code>/clear</code> を実行する、といった一括操作を割り当てている</li>
</ul>
<h3 id="基盤:-nix-+-dotfiles" tabindex="-1">基盤: Nix + dotfiles</h3>
<p>ここまでのツールは、すべて dotfiles リポジトリで管理し、nix-darwin + home-manager で配っている。herdr・Ghostty・terminal-browser も Homebrew の宣言に入れてある。宣言にないパッケージは switch のたびに消える設定（<code>cleanup = &quot;zap&quot;</code>）にしているので、試しに入れたツールを片付け忘れることがない。</p>
<p>設定を変えるときは、判断の理由を ADR としてリポジトリに残すようにしている。今回の移行でも、試用の開始から採用の確定、ターミナル内ブラウザの経路の選択まで、2 本の ADR を書いた。この記事の数字や経緯も、ほぼその ADR から拾っている。</p>
<h2 id="なぜこの構成になったか" tabindex="-1">なぜこの構成になったか</h2>
<p>ここからは移行の経緯を短く書く。流れを図にするとこうなる。</p>
<p><img src="https://static.zenn.studio/user-upload/e1d36804def6-20260924.png" alt="構成が決まるまでの判断フロー。Orca の「要素を選んで AI に渡す」機能が欲しい、しかし Orca は GUI 前提なので見送り、ターミナルの中で実現したい、herdr を試す、Neovim の画像表示とターミナル内ブラウザが動く、Chawan は SVG アイコンが描けず見送り、terminal-browser は WezTerm では重い、Ghostty なら軽く動く、Ghostty + herdr に決定" loading="lazy"></p>
<h3 id="きっかけは-orca-の「要素を選んで-ai-に渡す」機能" tabindex="-1">きっかけは Orca の「要素を選んで AI に渡す」機能</h3>
<p>9 月の頭、AI エージェント向けの統合開発環境として話題になっていた <a href="https://github.com/stablyai/orca" target="_blank" rel="noopener noreferrer">Orca</a> を検討した。いちばん惹かれたのは、内蔵ブラウザで画面の要素を選び、コメントを付けてそのまま AI に渡せる機能だった。</p>
<p>ただ、Orca 自体は私の使い方に合わなかった。GUI 前提のツールで、私の環境は逆に GUI を減らしてターミナルに寄せる方向に進んでいる。Orca の売りである worktree 管理は gtr で、GitHub 連携は <code>gh</code> で足りていたし、複数エージェントの管理は当時 Baton で賄えていた。</p>
<p>それでも「要素を選んで AI に渡す」だけは欲しかった。同じことをターミナルの中でできないか。そう考えたのが、今回の入れ替えの出発点である。</p>
<h3 id="herdr:-画像とブラウザが動いたのが決め手" tabindex="-1">herdr: 画像とブラウザが動いたのが決め手</h3>
<p>herdr は、実は 9 月の頭に一度見送っている。当時は承認待ちの検知精度をいちばん重く見ていて、その観点では Baton と herdr に差がなかった。どちらも画面の文字を読んで判定する方式である。一方で移行の手間は重かった。あとで数えたところ、tmux のキーバインドが 89 個、自作スクリプトが 26 本、Baton の Go コードが 7,639 行あった。差がないなら移る理由はない、と判断した。</p>
<p>考えが変わったのは、開発環境全体を見直したときだ。Claude Code や Codex に自動承認の仕組みが組み込まれて、Baton の safe auto mode の出番が減っていた。自作ツールを直し続ける手間と、コミュニティが活発な herdr に乗る場合を比べると、後者のほうが先がある。そこで、机上で比べるのをやめて実際に使ってみることにした。</p>
<p>使ってみた結果、エージェント管理の使い心地は正直ほとんど変わらなかった。変わったのは別のところだ。tmux は Kitty graphics protocol に対応していない。そのため、ターミナルに画像を描く系統の機能が tmux の中では思うように動かなかった。herdr ではこれが動き、Neovim の画像表示とターミナル内ブラウザの両方が使えるようになった。takt を動かすと画面がちらつく問題も、tmux では設定で抑え込んでいたが、herdr では最初から出なかった。</p>
<p>決め手はここだった。やりたいことがあって、それを叶えてくれたのが herdr だった。tmux のままでも解決する道はあったのかもしれない。ただ、流行っているエージェント管理ツールを試したら、副産物として長く諦めていたことが解決した。それなら herdr でいい、となった。</p>
<p>移るときは、いきなり切り替えずに試用から始めた。最初は <code>~/.config/use-herdr</code> というファイルがあるかないかで、WezTerm が tmux と herdr のどちらを起動するかを切り替える形にした。戻すときは <code>rm</code> 1 回で済む。この状態で tmux + Baton と使い比べてから、採用を決めた。今も WezTerm を起動すれば tmux + Baton の環境に戻れるようにしてある。</p>
<p>Baton でやっていたことは、次のように置き換えた。</p>
<table>
<thead>
<tr>
<th>Baton / tmux でやっていたこと</th>
<th>herdr での置き換え</th>
</tr>
</thead>
<tbody>
<tr>
<td>セッション一覧と状態表示</td>
<td>サイドバーの working / blocked / idle 表示</td>
</tr>
<tr>
<td>TUI から承認・拒否</td>
<td>自作の承認コンソール（<code>prefix+b</code> → <code>a</code> / <code>d</code>）</td>
</tr>
<tr>
<td>承認待ちのペインへジャンプ</td>
<td>通知のクリック、または承認待ち優先の並び + <code>prefix+alt+1</code></td>
</tr>
<tr>
<td>ghq のプロジェクトピッカー</td>
<td>自作の <code>herdr-sessionizer</code>（<code>prefix+f</code>）</td>
</tr>
<tr>
<td>N 分割・ペインの初期化</td>
<td>自作の <code>herdr-split-layout</code> / <code>herdr-init-panes</code></td>
</tr>
<tr>
<td>tmux のキーバインド</td>
<td>同じ prefix（<code>Ctrl+Q</code>）で移植</td>
</tr>
<tr>
<td>safe auto mode</td>
<td><strong>置き換えなし</strong>（各エージェントの自動承認に任せる）</td>
</tr>
<tr>
<td>takt のセッション検知</td>
<td><strong>置き換えなし</strong>（Claude Code として一覧に出るので実害は小さい）</td>
</tr>
</tbody>
</table>
<h3 id="ghostty:-ターミナル内ブラウザが-wezterm-では重すぎた" tabindex="-1">Ghostty: ターミナル内ブラウザが WezTerm では重すぎた</h3>
<p>herdr に移って最初に試したターミナル内ブラウザは Chawan だった。独自エンジンで軽く、Wikipedia のような文書中心のページは問題なく読める。ところが私のプロダクト（Next.js）を開くと、アイコンがすべて <code>[img]</code> という文字に置き換わった。アイコンが HTML に直接埋め込まれた <code>&lt;svg&gt;</code> で、Chawan はこれを描けない。モダンな Web アプリの確認には使えないので、入れて 10 分足らずで外した。</p>
<p>次に試したのが terminal-browser だ。こちらは中身が本物の Chromium なので、表示は実ブラウザと変わらない。ただ、WezTerm + herdr の組み合わせでは、スクロールするだけで WezTerm が CPU を 1 コア以上使い続け、操作がまともに追いつかなかった。同じことを Ghostty + herdr で試すと、CPU 使用率は十数%に収まった。普通のブラウザと同じ感覚でスクロールでき、一気に快適になった。</p>
<p>原因は、terminal-browser が画面を転送する経路にあった。terminal-browser は herdr を通してターミナルエミュレーターへ画像を直接流す専用の経路（ストリーム）を開くが、WezTerm ではこれが約 3 秒で閉じてしまい、流量制限のない予備の経路に落ちていた。なぜ閉じるのかまでは突き止めていない。</p>
<p>もう 1 つ、Ghostty にしてよかったことがある。iPhone（Moshi）から herdr のセッションにアタッチしたあと PC に戻ると、WezTerm ではペインが実色で塗りつぶされ、背景画像が隠れてしまう現象があった。Ghostty にしてからは、今のところこれが起きていない。こちらも原因は切り分けていない。</p>
<p>ついでに書くと、起動直後のメモリは WezTerm の約 316MB に対して Ghostty は約 213MB だった。ただ、普段のテキスト表示の速さはほぼ変わらない。Ghostty に移って得たのは、ターミナル内ブラウザが使えるようになったことだ。</p>
<h2 id="失ったもの・割り切ったこと" tabindex="-1">失ったもの・割り切ったこと</h2>
<p>使い心地はほぼ変わらないと書いたが、herdr に移って失ったものもある。いちばん大きいのは見た目の自由度だ。tmux では好きに組めたステータス表示が、herdr では決まった枠の中でしかいじれない。サイドバーの列を揃える設定も、モード別に色を変える設定もない。</p>
<p>ほかにも、次のような制約を抱えたまま使っている。</p>
<ul>
<li><strong>ペインを ID で直接フォーカスできない。</strong> tmux 時代のペイン番号ジャンプは再現できなかった</li>
<li><strong>ターミナル内のリンクをクリックで開けない。</strong> herdr がマウスを捕まえているためで、マウス捕捉を切るとサイドバーのクリックやドラッグでのコピーも失う</li>
<li><strong>safe auto mode と takt の検知がない。</strong> どちらも Baton にしかない機能だった</li>
<li><strong>terminal-browser は herdr のクライアントが 1 つのときしか軽く動かない。</strong> iPhone から同じセッションに繋ぐと、Ghostty でも重い経路に落ちる。開き直すまで戻らない</li>
<li><strong>通知が二重に出る。</strong> Claude Code / Codex の hook の通知と herdr の通知が重なっている。まだ直していない</li>
<li><strong>Ghostty のメモリは観測中。</strong> Claude Code 由来のメモリリークの報告があったため、移行後 1 週間は 10 分おきに記録している。今のところ 200MB 前後で推移していて、増え続ける様子はない</li>
</ul>
<p>Baton は休止にした。WezTerm を起動すると従来どおり tmux + Baton で立ち上がるようにしてあり、いつでも戻れる。takt のセッション検知のように Baton にしかない機能もあるので、戻る可能性はゼロではない。ただ、takt のセッションも herdr のエージェント一覧には Claude Code として出てくるので、今のところ困っていない。たぶん戻らないと思う。</p>
<h2 id="ターミナル中心の環境を作る人へ" tabindex="-1">ターミナル中心の環境を作る人へ</h2>
<p>前提として、開発の軸をターミナルに置くところから始めてほしい。そのうえで大事だと思うのは、一度に全部を入れないことだ。</p>
<p>私は Neovim を組み始めたとき、プラグインを次々に入れていた。Neovim には LazyVim のような全部入りのディストリビューションと、lazy.nvim のようなプラグインマネージャーで 1 つずつ足していくやり方がある（名前が似ていてややこしい）。全部入りで始めると、自分の環境に何の機能が入っているのかを把握しきれなくなる。私は 1 つずつ入れて使い心地を確かめ、目的を 1 つずつ潰していくやり方のほうが合っていた。</p>
<p>設定が面倒な人には、Ghostty と herdr から始めるのがいいと思う。Ghostty は設定なしでまともに動くことを設計思想にしている。herdr も、tmux の取っつきにくさを減らす方向で作られている。キーボードとマウスの両方を正式な操作方法として扱っているので、prefix キーを覚える前からクリックとドラッグで分割や移動ができる。</p>
<p>そこから先は、使いながら育てればいい。やりたいことが出てきたとき、他の人の環境紹介を読んで「この機能いいな」と思ったときに、その考え方を 1 つずつ取り込む。私の今の構成も、Orca の機能に惹かれたところから始まっている。</p>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>半年前の記事と比べると、入れ替えたのはターミナルエミュレーター・マルチプレクサ・セッション管理の 3 つだけだ。エージェントを並べて承認を回す、という開発のやり方もほとんど変わっていない。</p>
<p>それでも、tmux 時代に諦めていたことが 2 つ戻ってきた。Neovim で画像を見ることと、ブラウザで要素を指して AI に直させることだ。環境を入れ替える理由として、私にはこれで十分だった。</p>
<p>使い込むうちに、また不満や入れ替えたいものが出てくると思う。そのときはまた ADR に書き足していく。</p>
]]></content:encoded>
            <category>Dev Env</category>
        </item>
        <item>
            <title><![CDATA[設定ファイルを端末に置いたままにしない — dotfiles 運用のすすめと、その先の Nix]]></title>
            <link>https://www.yoshihiko.tech/blog/dotfiles-operation-and-nix</link>
            <guid isPermaLink="false">dotfiles-operation-and-nix</guid>
            <pubDate>Tue, 03 Nov 2026 15:04:48 GMT</pubDate>
            <description><![CDATA[設定ファイルを Git リポジトリで管理する dotfiles 運用の始め方と、その先で効いてくる Nix の話。何を入れて何を入れないか、配線の 3 手法の比較、運用して見えてくる穴、3 端末を 1 つの設定で回すところまでを実例で書く。]]></description>
            <content:encoded><![CDATA[<h1 id="設定ファイルを端末に置いたままにしない" tabindex="-1">設定ファイルを端末に置いたままにしない</h1>
<h2 id="結論から" tabindex="-1">結論から</h2>
<p><strong>開発環境の設定ファイルを、端末に置いたままにしない。</strong> Git リポジトリに移して、端末側にはリンクを張るだけにする。dotfiles 運用と呼ばれているもので、やることは実質これだけだ。</p>
<p>見返りは分かりやすい。新しい端末で環境を作り直さなくて済む。clone してリンクを張れば元に戻る。設定をいじって壊しても、どこをどう変えたかが <code>git diff</code> で追える。2 台目以降で「あっちにはあるのにこっちには無い」が消える。</p>
<p>ただ、運用が回り始めると穴が見えてくる。設定ファイルは揃うのに、そのファイルを読むツール本体は揃わない。ここを埋めるのが Nix だ。</p>
<p>この記事は前半で dotfiles 運用の始め方を、後半で「その先に Nix を足すとどうなるか」を書く。私は現在 3 台（MacBook Pro / Mac mini / 会社支給 Windows の WSL2）をこの方式で回していて、<a href="https://github.com/yoshihiko555/dotfiles" target="_blank" rel="noopener noreferrer">リポジトリは公開している</a>。</p>
<hr>
<h2 id="dotfiles-とは何か" tabindex="-1">dotfiles とは何か</h2>
<p>Unix 系 OS では、設定ファイルの名前が <code>.</code> で始まる慣習がある。<code>.zshrc</code>、<code>.gitconfig</code>、<code>.vimrc</code> のたぐいだ。これらをまとめて <strong>dotfiles</strong> と呼ぶ。</p>
<p>最近は <code>~/.config/</code> 以下に置く形式（XDG Base Directory）が主流になっていて、<code>.</code> で始まらないものも増えた。私のリポジトリで管理している中身はこのあたりになる。</p>
<ul>
<li><strong>シェル</strong>: <code>.zshenv</code> / <code>.zprofile</code> / <code>.zshrc</code> と、分割した <code>zsh/</code> 配下のスクリプト群</li>
<li><strong>エディタ</strong>: <code>nvim/</code>（Lua の設定一式）</li>
<li><strong>ターミナル</strong>: <code>wezterm/</code> <code>ghostty/</code> <code>tmux/</code></li>
<li><strong>Git</strong>: <code>git/</code>（<code>.gitconfig</code> と <code>.gitignore_global</code>）</li>
<li><strong>プロンプト</strong>: <code>starship/</code></li>
<li><strong>その他</strong>: <code>mise/</code>（言語ランタイム管理）、<code>lazygit/</code>、<code>karabiner/</code>（キー配列）、<code>aerospace/</code>（ウィンドウ管理）</li>
</ul>
<p>新しい端末に移ったときに無いと困るもの。それが全部対象になる。</p>
<h3 id="端末に置いたままだと何が起きるか" tabindex="-1">端末に置いたままだと何が起きるか</h3>
<p><strong>セットアップが記憶頼りになる。</strong> 前の端末で何を入れて、どう設定したかを思い出しながらやる。移行前の私は「何をどの順で入れるか」が完全に頭の中にしか無かった。しかも順序には依存関係がある。Homebrew の前に Xcode Command Line Tools が要る、みたいなやつだ。それがどこにも書かれていない。</p>
<p><strong>端末間でドリフトする。</strong> 2 台目で <code>.zshrc</code> にエイリアスを 1 つ足す。1 台目には無い。3 か月後、1 台目で同じエイリアスを打って「あれ、効かない」となる。台数が増えるほど確実に起きる。</p>
<p><strong>壊したときに戻せない。</strong> 設定をいじって動かなくなったとき、どこを触ったか分からない。<code>.zshrc.bak</code> <code>.zshrc.bak2</code> <code>.zshrc.old</code> が生えていく。私はこれを何度もやった。</p>
<p>全部「バージョン管理されていないから」起きている。だから Git に載せる。</p>
<hr>
<h2 id="何を入れて、何を入れないか" tabindex="-1">何を入れて、何を入れないか</h2>
<h3 id="入れるもの" tabindex="-1">入れるもの</h3>
<p>判断基準はシンプルで、**「新しい端末で手で書き直すのが面倒なもの」**は全部入れる。</p>
<p>私のリポジトリはこういう構成になっている。</p>
<pre><code class="language-text">dotfiles/
├── shell/        # zsh 設定（zshenv / zprofile / zshrc + zsh/ 分割）
├── config/       # ~/.config へ配線するもの
│   ├── nvim/     # エディタ
│   ├── git/      # Git
│   ├── tmux/     # ターミナルマルチプレクサ
│   ├── starship/ # プロンプト
│   └── ...       # wezterm, ghostty, mise, lazygit, karabiner, aerospace, gh
├── home/         # $HOME 直下に置く単体ファイル（.editorconfig 等）
├── ssh/          # ~/.ssh/config（後述。鍵は入れない）
├── scripts/      # セットアップ用のシェルスクリプト
└── README.md     # 復元手順のメモ
</code></pre>
<p><code>README.md</code> を軽視しないでほしい。数か月後に読み返すための手順書になる。私はここに「まっさらな Mac から復元する手順」を書いていて、リポジトリの中で一番効いている資産はこれだと思っている。設定ファイルそのものより効いている。</p>
<h3 id="絶対に入れてはいけないもの" tabindex="-1">絶対に入れてはいけないもの</h3>
<p><strong>秘密情報はコミットしない。</strong> ここが dotfiles 運用で唯一、事故ると本当に痛いポイントになる。</p>
<ul>
<li>SSH の秘密鍵（<code>~/.ssh/id_ed25519</code> など）</li>
<li>API トークン、アクセスキー</li>
<li>パスワードを含む設定ファイル</li>
<li><code>.env</code> ファイル</li>
</ul>
<p><strong>公開リポジトリでなくても入れない。</strong> private でも、後で public に切り替えたときや、コラボレータを追加したときに漏れる。一度コミットしたものは履歴に残るので、消すのも面倒だ。</p>
<p>私は <code>~/.ssh/config</code> を入れているが、これは中身が接続先のホスト名とポート番号だけだからだ。鍵そのものは入れていない。</p>
<p>トークン類をどうしても管理下に置きたい場合は、暗号化したままコミットする仕組み（<code>sops</code> / <code>git-crypt</code> / <code>age</code> など）を使う。私は将来のために <code>sops-nix</code> の仕組みだけ用意してあるが、<strong>現時点で管理対象はゼロ</strong>にしている。必要になるまで入れない、というのも判断のうちだ。</p>
<h3 id="迷うもの" tabindex="-1">迷うもの</h3>
<ul>
<li><strong>端末固有の設定</strong>（ディスプレイ解像度依存のもの、会社 PC 専用の proxy 設定など）→ 入れてよい。ただし後述の「端末差分」の扱いを決めてから</li>
<li><strong>アプリが自動生成する設定</strong>（起動のたびに書き換わるもの）→ 入れると差分ノイズになる。<code>.gitignore</code> するか、必要な部分だけ切り出す</li>
<li><strong>巨大なバイナリ</strong>（フォント、プラグインのキャッシュ）→ 入れない。リポジトリが重くなるだけで、再取得できる</li>
</ul>
<hr>
<h2 id="端末へどう配線するか" tabindex="-1">端末へどう配線するか</h2>
<p>リポジトリを作ったら、次は <code>~/.zshrc</code> からリポジトリの中身を参照させる作業に入る。やり方は主に 3 つで、後になるほど強力で、後になるほど重い。</p>
<h3 id="方法-1:-手書きのシェルスクリプト" tabindex="-1">方法 1: 手書きのシェルスクリプト</h3>
<p><code>ln -s</code> を並べたスクリプトを書く。</p>
<pre><code class="language-sh">#!/usr/bin/env bash
DOTFILES=&quot;$HOME/ghq/github.com/yoshihiko555/dotfiles&quot;

ln -sfn &quot;$DOTFILES/shell/zshrc&quot;     &quot;$HOME/.zshrc&quot;
ln -sfn &quot;$DOTFILES/config/nvim&quot;     &quot;$HOME/.config/nvim&quot;
ln -sfn &quot;$DOTFILES/config/starship&quot; &quot;$HOME/.config/starship&quot;
# ... 以下続く
</code></pre>
<p><strong>最初はこれで十分だと思う。</strong> 依存が何もないし、何が起きているか全部読める。10 個や 20 個ならこれで回る。</p>
<p>弱点は、消すときの処理を手で書く必要があること（配線をやめたファイルのリンクが残り続ける）と、行数が増えると管理しづらくなることだ。</p>
<h3 id="方法-2:-gnu-stow" tabindex="-1">方法 2: GNU stow</h3>
<p><code>stow</code> はディレクトリ構造をそのまま <code>~/</code> に symlink として展開するツールで、dotfiles 界隈では長く定番だった。</p>
<pre><code class="language-sh"># dotfiles/nvim/.config/nvim/init.lua という構造にしておくと
stow nvim   # → ~/.config/nvim/init.lua へリンクが張られる
</code></pre>
<p>リポジトリ側を「<code>$HOME</code> からの相対パス」の形に整えておく必要があり、そのぶんディレクトリが 1 階層深くなる。私も以前は stow を使っていた。</p>
<p><strong>stow の限界は「張らない」を表現できないことにある。</strong> ヘッドレスのサーバーに GUI アプリの設定を配りたくない、という状況で、stow にできるのは「そのコマンドを打たない」だけだ。配らないという意思がリポジトリのどこにも書かれない。</p>
<h3 id="方法-3:-nix（home-manager）" tabindex="-1">方法 3: Nix（home-manager）</h3>
<p>後半で詳しく書くが、配線を宣言として書く方式になる。</p>
<pre><code class="language-nix">home.file.&quot;.zshrc&quot;.source = mkLink &quot;shell/zshrc&quot;;
xdg.configFile.&quot;nvim&quot;.source = mkLink &quot;config/nvim&quot;;
</code></pre>
<p>見た目は方法 1 とあまり変わらない。違うのは、<strong>宣言から外したものが自動で消える</strong>ことと、<strong>同じ仕組みでパッケージも管理できる</strong>ことだ。</p>
<h3 id="どれを選ぶか" tabindex="-1">どれを選ぶか</h3>
<table>
<thead>
<tr>
<th></th>
<th>手書きスクリプト</th>
<th>GNU stow</th>
<th>Nix (home-manager)</th>
</tr>
</thead>
<tbody>
<tr>
<td>導入コスト</td>
<td>ほぼゼロ</td>
<td>低（<code>brew install stow</code>）</td>
<td>高（Nix の学習が要る）</td>
</tr>
<tr>
<td>リポジトリ構造の制約</td>
<td>なし</td>
<td>あり（<code>$HOME</code> 相対の形）</td>
<td>なし</td>
</tr>
<tr>
<td>配線をやめたときの後始末</td>
<td>手で書く</td>
<td><code>stow -D</code></td>
<td>自動</td>
</tr>
<tr>
<td>「配らない」の表現</td>
<td>不可</td>
<td>不可</td>
<td>可能</td>
</tr>
<tr>
<td>パッケージ管理</td>
<td>別途</td>
<td>別途</td>
<td>同じ仕組みで可能</td>
</tr>
</tbody>
</table>
<p><strong>まだ dotfiles を運用していないなら、方法 1 から始めてほしい。</strong> 最初から Nix に行くと、dotfiles 運用そのものの学習と Nix の学習が同時に来て、たいてい途中で止まる。私自身、stow で 1 年以上運用してから Nix に移った。</p>
<hr>
<h2 id="運用してみると見えてくる穴" tabindex="-1">運用してみると見えてくる穴</h2>
<p>dotfiles 運用が回り始めると、これでは解決しない問題が残っているのに気づく。私が実際にぶつかった順に書く。</p>
<h3 id="設定ファイルは揃うが、ツール本体は揃わない" tabindex="-1">設定ファイルは揃うが、ツール本体は揃わない</h3>
<p><code>~/.config/nvim/init.lua</code> をリンクしても、<strong>Neovim 本体が入っていなければ意味がない。</strong> <code>.tmux.conf</code> があっても tmux が無ければ動かない。</p>
<p>つまり dotfiles リポジトリだけでは、新端末で</p>
<pre><code class="language-sh">brew install neovim tmux ripgrep fd fzf ghq lazygit starship zoxide ...
</code></pre>
<p>を手で打つ工程が残る。しかも「どれを入れたか」はリポジトリのどこにも書かれていない。<code>Brewfile</code> を置く手はあるが、今度は Brewfile と実態がずれていく（消したはずのツールが Brewfile に残る、逆もある）。</p>
<h3 id="端末差分がシェルスクリプトの-if-に溜まる" tabindex="-1">端末差分がシェルスクリプトの <code>if</code> に溜まる</h3>
<p>2 台目、3 台目と増えると、こういう記述が <code>.zshrc</code> に生えてくる。</p>
<pre><code class="language-sh">if [[ &quot;$OSTYPE&quot; == darwin* ]]; then
  eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;
fi

if [[ &quot;$(hostname)&quot; == &quot;work-mac&quot; ]]; then
  export HTTP_PROXY=&quot;http://proxy.example.com:8080&quot;
fi
</code></pre>
<p>1 つ 2 つなら問題ない。ただし減ることはまずない。増える一方で、しかも「この端末では何が有効なのか」を知るには全部読むしかない。</p>
<h3 id="手順の順序が、結局まだ暗黙のまま" tabindex="-1">手順の順序が、結局まだ暗黙のまま</h3>
<p>README に手順を書いても、それは実行されない文章でしかない。書き忘れれば抜ける。更新を忘れれば古くなる。手順が正しいことを保証する仕組みがどこにもない。</p>
<hr>
<h2 id="プラスアルファ:-ここで-nix-を足す" tabindex="-1">プラスアルファ: ここで Nix を足す</h2>
<p>この 3 つは、どれも「設定ファイル以外のものも宣言したい」という同じ要求に行き着く。Nix はそこを埋める道具だ。</p>
<h3 id="nix-が-dotfiles-に足すもの" tabindex="-1">Nix が dotfiles に足すもの</h3>
<p><strong>パッケージがリポジトリに書かれる。</strong> 私は CLI を 1 ファイルに宣言している。</p>
<pre><code class="language-nix"># home/packages.nix — 3 台共通の CLI
{ pkgs, ... }:
{
  home.packages = with pkgs; [
    fd fzf gh ghq git glow lazygit mise
    neovim ripgrep starship tmux tree yazi zoxide d2
  ];
}
</code></pre>
<p>このファイルがそのまま「この環境に入っているものの一覧」になる。バージョンは <code>flake.lock</code> で固定されるので、3 台が同じところに揃う。</p>
<p><strong>「入れない」が書ける。</strong> ヘッドレスの Mac mini には GUI アプリを配らない、という意思を宣言として残せる。stow との決定的な差はここだ。私の場合、GUI アプリ（cask）は MacBook Pro に 13 個、Mac mini に 7 個で宣言が分かれている。</p>
<p><strong>端末差分が宣言に移る。</strong> シェルスクリプトの <code>if</code> から、ホストごとのファイルへ移る。私は 3 層に分けた。</p>
<pre><code class="language-text">config/nix/
├── darwin/   # Mac 2 台共通のシステム設定（WSL2 は通らない）
├── home/     # 3 台すべてで共有するユーザー設定
└── hosts/    # ホスト固有（macbook / hermes / wsl）
</code></pre>
<p>ルールは 2 つだけにしている。<strong>新規はまず、使うホストの <code>hosts/</code> に書く。2 台以上で使い始めたら共通層へ昇格させる。</strong> 「将来使うかもしれないから共通層へ」をやると、共通層が条件分岐の塊に戻ってしまう。</p>
<p><strong>設定ファイルは書き直さなくていい。</strong> ここが一番言いたいところで、Nix を入れても既存の dotfiles を捨てる必要はない。</p>
<p>home-manager には設定内容を Nix の属性として書く流儀（<code>programs.git.userName = &quot;...&quot;</code> のような書き方）もあるが、私はそれを採らなかった。代わりに <code>mkOutOfStoreSymlink</code> を使って、リポジトリの実体を指すリンクを張っている。</p>
<pre><code class="language-nix"># home/dotfiles.nix
{ config, hostSpec, ... }:
let
  mkLink = path: config.lib.file.mkOutOfStoreSymlink &quot;${hostSpec.dotfilesDir}/${path}&quot;;
in
{
  home.file = {
    &quot;.zshenv&quot;.source = mkLink &quot;shell/zshenv&quot;;
    &quot;.zshrc&quot;.source  = mkLink &quot;shell/zshrc&quot;;
  };

  xdg.configFile = {
    &quot;nvim&quot;.source     = mkLink &quot;config/nvim&quot;;
    &quot;git&quot;.source      = mkLink &quot;config/git&quot;;
    &quot;starship&quot;.source = mkLink &quot;config/starship&quot;;
  };
}
</code></pre>
<p><code>.zshrc</code> は <code>.zshrc</code> のまま、Git 管理下の素のシェルスクリプトとして残る。リポジトリを編集した瞬間に反映されるので、1 文字直すたびに再ビルド、ということもない。</p>
<p>Nix の導入は、これまでの dotfiles 運用を捨てて乗り換えるのではなく、その上に載せる形にできる。手書きスクリプトや stow で積んできた資産はそのまま生きる。</p>
<h3 id="3-台を-1-つの設定で回す" tabindex="-1">3 台を 1 つの設定で回す</h3>
<p>私の場合、最終的にこうなっている。</p>
<table>
<thead>
<tr>
<th>ホスト</th>
<th>用途</th>
<th>Nix の適用範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>MacBook Pro</td>
<td>メイン機</td>
<td>CLI + dotfiles + GUI アプリ</td>
</tr>
<tr>
<td>Mac mini</td>
<td>ヘッドレスの常時稼働機</td>
<td>ほぼ全体（常駐デーモンの定義まで）</td>
</tr>
<tr>
<td>会社 Windows の WSL2</td>
<td>予備</td>
<td>WSL2 内部のみ（Windows 本体は対象外）</td>
</tr>
</tbody>
</table>
<p>Windows が混ざっているが、WSL2 の中身は Linux なので同じ仕組みが使える。構成の作り方は macOS 側と変わる（macOS は nix-darwin、WSL2 は home-manager 単体）。それでも共通のユーザー設定層は 3 台でそのまま共有できている。ここは一番不安だった部分だが、やってみたら一番あっさり通った。</p>
<h3 id="正直に言うと、nix-で楽にならないこと" tabindex="-1">正直に言うと、Nix で楽にならないこと</h3>
<p><strong>新端末構築は「1 コマンド」にはならない。</strong> 私の設計目標は「現状 8 手順・順序が暗黙 → 1 コマンド」だったが、実際にまっさらな Mac から復元する手順は今もこうなっている。</p>
<ol>
<li>Xcode Command Line Tools を入れる</li>
<li>Homebrew 本体を入れる（Nix はこれを入れてくれない）</li>
<li>GitHub 認証を設定する</li>
<li>Nix 本体を入れる</li>
<li>dotfiles を clone する</li>
<li>反映コマンドを 1 回打つ</li>
</ol>
<p>前提手順が 5 つ残っている。手順数でいえば 8 が 6 になった程度だ。</p>
<p>短縮の実体は手順数ではない。変わったのは、この手順と順序がリポジトリに書いてあることだ。移行前は私の記憶の中にしか無かった。思い出しながらやる作業が、読みながらやる作業になった。実感としてはそれが一番大きい。</p>
<p>他にも正直なところを書いておく。</p>
<ul>
<li><strong>Nix 言語の学習コストはある。</strong> 設定内容を書き直さない方針にしても、flake の外枠は書けるようにならないと始まらない</li>
<li><strong>ロールバックは設定の中身を戻さない。</strong> 世代を戻してもリンク先はリポジトリの現在の内容なので、設定を戻すのは <code>git</code> の仕事になる</li>
<li><strong>ディスクを食う。</strong> 世代を溜めると <code>/nix/store</code> が膨らむので、定期的な GC の設定が要る</li>
</ul>
<hr>
<h2 id="始め方" tabindex="-1">始め方</h2>
<h3 id="今日（30-分）" tabindex="-1">今日（30 分）</h3>
<p><strong>1. リポジトリを作る。</strong> <code>dotfiles</code> という名前で GitHub に作る。public でいい（秘密情報を入れなければ、の話だが）。</p>
<p><strong>2. 一番よく触るファイルを 1 つだけ入れる。</strong> <code>.zshrc</code> でも <code>.gitconfig</code> でもいい。</p>
<pre><code class="language-sh">mkdir -p ~/ghq/github.com/&lt;user&gt;/dotfiles/shell
mv ~/.zshrc ~/ghq/github.com/&lt;user&gt;/dotfiles/shell/zshrc
ln -sfn ~/ghq/github.com/&lt;user&gt;/dotfiles/shell/zshrc ~/.zshrc
</code></pre>
<p>新しいシェルを開いて、今までどおり動くことを確認する。動いたらコミットする。</p>
<p><strong>3. <code>.gitignore</code> を書く。</strong> 秘密情報を入れないための最初の防御になる。</p>
<pre><code class="language-gitignore">*.key
*.pem
.env
id_*
!id_*.pub
</code></pre>
<h3 id="今週" tabindex="-1">今週</h3>
<ul>
<li>残りの設定ファイルを順に移す。<strong>一度に全部やらない。</strong> 1 つ移してはシェルを開き直す、を繰り返す</li>
<li>リンクを張るスクリプト（前述の方法 1）を書いて、<code>scripts/link.sh</code> に置く</li>
<li>README に「新端末での復元手順」を書き始める。この時点では不完全でいい</li>
</ul>
<h3 id="今月" tabindex="-1">今月</h3>
<ul>
<li>2 台目の端末に clone して、実際に復元してみる。<strong>ここで README の穴が全部見つかる</strong></li>
<li>端末固有の設定が出てきたら、その分離方法を決める（<code>zshrc.local</code> を <code>.gitignore</code> する、など）</li>
</ul>
<h3 id="その先" tabindex="-1">その先</h3>
<ul>
<li>ツール本体の管理が面倒になってきたら、<code>Brewfile</code> か Nix を検討する</li>
<li>端末が 3 台以上になった、あるいはシェルスクリプトの <code>if</code> が増えてきたら、Nix を検討するタイミングだと思う</li>
</ul>
<p><strong>端末が 1 台で <code>if</code> も生えていないなら、Nix はまだ要らない。</strong> dotfiles 運用だけで得られるものが十分に大きいので、そこで止めていい。</p>
<hr>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>設定ファイルを端末に置かず、Git リポジトリに移してリンクを張る。これだけで新端末での復元・変更履歴・端末間の統一が同時に手に入る。秘密情報を入れないことと、配線は手書きスクリプトから始めていいこと。最初に押さえるのはこのくらいでいい。</p>
<p>それでも穴は残る。設定ファイルは揃ってもツール本体は揃わず、端末差分はシェルの <code>if</code> に溜まり、手順は暗黙のままになる。Nix はそこを埋める。既存の dotfiles を書き直さずに載せられるのも大きい。ただし「1 コマンドで新端末構築」には届かない。変わるのは手順の数ではなく、手順が記録されているかどうかだ。</p>
<p>正直に言うと、Nix を入れて一番効いたのは技術的な部分ではなかった気がしている。「なぜこうしたか」を書き残す習慣がついたことの方が大きい。設定そのものより、判断の履歴の方が後で効いてくる。もっともこれが Nix のおかげなのか、単に 3 台目で必要に迫られただけなのかは、まだ私自身、切り分けられていない。</p>
<p>私の構成は <a href="https://github.com/yoshihiko555/dotfiles" target="_blank" rel="noopener noreferrer">yoshihiko555/dotfiles</a> で公開している。Nix の部分は <code>config/nix/</code> 以下にあり、判断の経緯は同ディレクトリの <code>docs/</code> に ADR として残してある。</p>
]]></content:encoded>
            <category>Dev Env</category>
        </item>
        <item>
            <title><![CDATA[OpenCode Go プランを評価する — 月$10 で LLM オーケストレーションの中間層は作れるか]]></title>
            <link>https://www.yoshihiko.tech/blog/opencode-go-plan-evaluation</link>
            <guid isPermaLink="false">opencode-go-plan-evaluation</guid>
            <pubDate>Thu, 07 May 2026 12:12:31 GMT</pubDate>
            <description><![CDATA[OpenCode が提供する月額固定の Go プラン（初月$5、翌月以降$10/月）を、料金体系・想定ユースケース・3層 LLM 構成における中間モデル層としての適合性まで含めて評価する。]]></description>
            <content:encoded><![CDATA[<p>ChatGPT Plus が月$20、Claude Pro も月$20、Codex 利用枠が欲しければさらに上位プラン。AI コーディング系のサブスクを並行して回していると、月の固定費が知らず知らずのうちに $40〜$60 に積み上がっていく。「常用するエージェントの月額をもう一段下げたい」「ローカル LLM だけでは精度が足りない作業に、コストを抑えた中間層を当てたい」という用途で気になっていたのが、<code>opencode.ai</code> が出している <strong>Go プラン</strong>（初月$5、翌月以降$10/月）だった。</p>
<p>私自身は今、ローカル LLM ＋ Codex CLI の2層構成で個人エージェント（Hermes Agent ベース）を回しており、ここに「中間モデル層」を追加したいという背景があった。本記事では、契約前段階で行った評価をベースに、<strong>OpenCode Go プランの料金体系・実際の使い方・中間モデル層として活かせる余地</strong>を整理する。</p>
<p>なお Go プランの提供モデル内訳・レート制限など、公式仕様の最新値を直接確認すべき箇所は本文中で明示している。</p>
<h2 id="この記事でわかること" tabindex="-1">この記事でわかること</h2>
<ul>
<li>OpenCode Go プランが何を提供するサブスクか（位置づけと月額条件）</li>
<li>Go / Zen / Black / BYOK の料金プラン体系と、自分のユースケースでどれを選ぶべきか</li>
<li><code>opencode run</code> を中心とした、Go プランの実際の使い方</li>
<li>自前オーケストレーターから中間モデル層として呼ぶ発展用途の見立て</li>
<li>契約前に押さえるべき未確認事項</li>
</ul>
<h2 id="opencode-go-プランとは" tabindex="-1">OpenCode Go プランとは</h2>
<p><code>opencode.ai</code>（GitHub: <code>sst/opencode</code>）は OSS の TUI 型 AI コーディングエージェント。Claude Code や Aider と同種のカテゴリだが、複数の料金プランを束ねた「ハーネス」として作られているのが大きな違いになる。OpenCode 自体の利用は無料で、その上にいくつかの<strong>モデル利用プラン</strong>が乗っている。</p>
<p>Go プランは、その中で「常用ワーカーを月額固定費で確保する」用途に振った最安プラン。</p>
<table>
<thead>
<tr>
<th>項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td>価格</td>
<td>初月 $5、翌月以降 $10/月</td>
</tr>
<tr>
<td>対象モデル</td>
<td>低価格・低レイテンシな OSS / 軽量モデル中心（提供内訳は公式要確認）</td>
</tr>
<tr>
<td>課金モデル</td>
<td>月額固定</td>
</tr>
<tr>
<td>想定ユースケース</td>
<td>コーディング作業の大半を安いモデルで賄いたい用途</td>
</tr>
</tbody>
</table>
<p>設計思想としては「<strong>コーディング作業の8割は、フロンティアモデル（Opus / GPT-5 等）でなくてもこなせる</strong>」という前提に立っている。リファクタ、テスト追加、定型的な実装、軽い設計判断あたりを安いモデルで回し、本当に重い推論が必要なときだけ別の手段（後述の Zen 従量や BYOK）にスライドさせる、というのが Go プランの基本スタンスになる。</p>
<h2 id="料金プラン体系の全体像" tabindex="-1">料金プラン体系の全体像</h2>
<p>Go プランの位置を理解するには、隣接する3つの選択肢と並べた方が早い。</p>
<table>
<thead>
<tr>
<th>プラン</th>
<th>課金モデル</th>
<th>想定モデル層</th>
<th>月額の予測可能性</th>
<th>主用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Go</strong></td>
<td>月額固定（$5 → $10）</td>
<td>低価格・低レイテンシモデル中心</td>
<td>高い（上限が読める）</td>
<td>常用ワーカー、コスト計画を立てたい個人開発者</td>
</tr>
<tr>
<td><strong>Zen</strong></td>
<td>従量課金（per 1M token）</td>
<td>フロンティア含む 40+ モデル横断</td>
<td>利用量に依存</td>
<td>フロンティアまで使い分けたい、可変ワークロード</td>
</tr>
<tr>
<td><strong>Black</strong></td>
<td>フルアクセスサブスク</td>
<td>Claude / GPT / Gemini フロンティア</td>
<td>高い（が現在新規登録停止中）</td>
<td>フロンティア常用、定額で読みたい</td>
</tr>
<tr>
<td><strong>BYOK</strong></td>
<td>プロバイダー直接課金</td>
<td>自分の API キーで任意モデル</td>
<td>自前 API 利用量に依存</td>
<td>サブスクなしで OpenCode のハーネスだけ使う</td>
</tr>
</tbody>
</table>
<h3 id="zen-プランの料金感" tabindex="-1">Zen プランの料金感</h3>
<p>Zen は per 1M token の従量課金で、代表的な単価は以下のレンジ。</p>
<ul>
<li>Claude Opus 4.7: $5入力 / $25出力（per 1M tokens）</li>
<li>GPT-5、Gemini、Qwen、GLM、Kimi など 40+ モデルを横断選択可能</li>
<li>ベータ期間中の無料モデル枠（MiniMax M2.5、Big Pickle、Nemotron 3 Super 等）あり</li>
<li>残高 $5 を切ると自動で $20 をチャージするオートリロード</li>
<li>ワークスペース／メンバー単位の月次上限設定あり</li>
</ul>
<p>「特定モデル（Sonnet、GPT-5 mini など）を名指しで使いたい」用途は Zen 側になる。</p>
<h3 id="black-プラン" tabindex="-1">Black プラン</h3>
<p>Claude / GPT / Gemini のフロンティアモデルへのフルアクセスをサブスクで提供する位置づけだが、<strong>2026-05 時点で新規登録は一時停止</strong>されている。</p>
<h3 id="byok（bring-your-own-key）" tabindex="-1">BYOK（Bring Your Own Key）</h3>
<p>任意の API キー（Anthropic / OpenAI ほか）を <code>/connect</code> コマンドで OpenCode に持ち込める。Zen の中で他モデルと併用したり、Go / Zen を契約せずハーネスだけ使う運用も可能。</p>
<h3 id="go-プランを選ぶ判断基準" tabindex="-1">Go プランを選ぶ判断基準</h3>
<p>ここまでを踏まえて、Go プランを第一候補にすべきケースは以下のいずれかに当てはまる場合になる。</p>
<ul>
<li><strong>月額の予測可能性を最優先したい</strong>（Zen の従量課金で月末に請求額が振れるのが嫌）</li>
<li><strong>常用モデルを Go の提供モデル群で賄えると見込める</strong>（フロンティア常用が必要ない）</li>
<li><strong>既存の ChatGPT Plus / Claude Pro を一旦解約して、$10 のラインで体験を組み直したい</strong></li>
</ul>
<p>逆に、Sonnet / GPT-5 を常用モデルに据えたいなら最初から Zen 従量、フロンティア常用ならば Black（再開待ち）か BYOK での Anthropic / OpenAI 直接契約の方が筋が良い。</p>
<h2 id="実際にどう使うか" tabindex="-1">実際にどう使うか</h2>
<p>OpenCode の操作系は他の TUI 型エージェントと大きくは変わらない。Go プランでの典型的な実行パターンを確認しておく。</p>
<h3 id="基本:-tui-で起動" tabindex="-1">基本: TUI で起動</h3>
<pre><code class="language-bash">opencode
</code></pre>
<p>これでインタラクティブな TUI が立ち上がる。Claude Code や Aider を触ったことがあれば操作感はほぼ移植可能。</p>
<h3 id="headless-実行:-opencode-run" tabindex="-1">Headless 実行: <code>opencode run</code></h3>
<p>スクリプトや CI、外部エージェントから呼ぶ場合は <code>opencode run</code> を使う。</p>
<pre><code class="language-bash"># 一発実行
opencode run &quot;Refactor the login logic in src/auth.ts&quot;

# nd-JSON ストリーム + 静音モード（外部から呼ぶ用途）
opencode run --format json --quiet &quot;...&quot; | jq .

# モデルを明示指定
opencode run --model anthropic/claude-3-7-sonnet &quot;Write tests for math.go&quot;
</code></pre>
<p><code>--quiet</code> で UI 出力を抑制、<code>--format json</code> で newline-delimited JSON が stdout に流れる。完了時 exit 0、エラー時は stderr ＋ 非ゼロ exit。これは Codex CLI の <code>codex exec</code> と同型のインターフェースで、外部エージェントから「ワーカーとして」叩く用途を意識した設計になっている。</p>
<h3 id="サーバーモード:-opencode-serve" tabindex="-1">サーバーモード: <code>opencode serve</code></h3>
<p>長時間セッションや複数同時タスクが必要なら HTTP デーモンとして起動できる。</p>
<pre><code class="language-bash">opencode serve --port 8080
# OpenAPI 準拠の REST API がポートで待機
</code></pre>
<p><code>OPENCODE_SERVER_PASSWORD</code> でベーシック認証、複数セッション並列駆動も可能。Python / TypeScript どちらからでも HTTP で叩ける。</p>
<h3 id="設定ファイルの階層" tabindex="-1">設定ファイルの階層</h3>
<p>OpenCode は Claude Code とほぼ同じ階層の設定ファイル系を持つ。</p>
<table>
<thead>
<tr>
<th>種別</th>
<th>パス</th>
</tr>
</thead>
<tbody>
<tr>
<td>グローバル設定</td>
<td><code>~/.config/opencode/opencode.json</code></td>
</tr>
<tr>
<td>プロジェクト設定</td>
<td><code>&lt;repo&gt;/opencode.json</code> または <code>opencode.jsonc</code></td>
</tr>
<tr>
<td>プロジェクト指示</td>
<td><code>&lt;repo&gt;/AGENTS.md</code>（<code>/init</code> で生成）</td>
</tr>
<tr>
<td>グローバル指示</td>
<td><code>~/.config/opencode/AGENTS.md</code></td>
</tr>
<tr>
<td>カスタム資産</td>
<td><code>&lt;repo&gt;/.opencode/{agents,skills,commands}/</code></td>
</tr>
</tbody>
</table>
<p>特筆すべきは <strong>Skills フォーマットが Claude Code と互換</strong> な点で、<code>~/.claude/skills/</code> 配下の既存スキル（このリポジトリで使っている <code>/research</code> <code>/publish</code> 等）をそのまま OpenCode 側で使い回すことができる。Claude Code から OpenCode にメインハーネスを移したい場合の移行コストが小さい。</p>
<h3 id="permission-/-sandbox" tabindex="-1">Permission / Sandbox</h3>
<p>ツール別の <code>allow / ask / deny</code> をパターンマッチで設定でき、<code>sandbox: worktree</code> でエージェントを一時 git worktree 内に閉じ込めることもできる。</p>
<pre><code class="language-json">{
  &quot;permissions&quot;: {
    &quot;bash&quot;: {
      &quot;git *&quot;: &quot;allow&quot;,
      &quot;rm *&quot;: &quot;deny&quot;
    }
  },
  &quot;sandbox&quot;: &quot;worktree&quot;
}
</code></pre>
<h3 id="hooks" tabindex="-1">Hooks</h3>
<p>Claude Code と同等のフックポイント（<code>PreToolUse</code> / <code>PostToolUse</code> / <code>PostCompact</code> / <code>SubagentStart</code> / <code>TaskCreated</code>）を持つ。編集後 prettier / pytest 自動実行、危険コマンドのブロックなど、Claude Code 側で組んでいるフックは概念的にそのまま移植可能。</p>
<h2 id="発展:-自前オーケストレーターから中間モデル層として呼ぶ" tabindex="-1">発展: 自前オーケストレーターから中間モデル層として呼ぶ</h2>
<p>OpenCode Go プランを単独のコーディングエージェントとして使うだけなら、ここまでの話で十分。ただ私の関心はもう一歩先で、「ローカル LLM ＋ フロンティアモデル」の2層構成にもう一段、<strong>中間モデル層</strong>を挟む構成での適合性にあった。</p>
<p>具体的には、Hermes Agent のような自前オーケストレーターを上に置き、軽量タスクはローカル LLM、中難度の標準実装は OpenCode Go の提供モデル群、複雑な設計判断はフロンティアモデル（Codex / Claude 等）にルーティングする運用イメージ。Go プランの $10/月という上限が読める価格帯は、この「中間層を月額固定費で抱える」用途と相性が良さそうに見える。</p>
<p>ただし、自前オーケストレーター側から OpenCode Go のモデル群へどう接続するか — API キーが提供されてプロバイダー直叩きできるのか、それとも <code>opencode run</code> などハーネス経由になるのか — は、エンドポイント仕様や提供形態の確認が必要。本記事執筆時点ではこの統合は未着手のため、ここでは「そういう発展構成も考えられる」という言及に留めておく。具体的な接続方式と検証結果は、実装後に別記事でまとめる予定。</p>
<h2 id="導入判断の指針" tabindex="-1">導入判断の指針</h2>
<p>ここまでをまとめると、OpenCode Go プランが向く人と向かない人は次のように切り分けられる。</p>
<h3 id="向く人" tabindex="-1">向く人</h3>
<ul>
<li>ChatGPT Plus / Claude Pro の月$20 を払い続けるかどうか迷っている</li>
<li>ローカル LLM だけでは精度が足りないが、フロンティアモデル常用は予算的に避けたい</li>
<li>月額の予測可能性を重視する（Zen の従量課金で月末に振れるのが嫌）</li>
<li>Claude Code から OpenCode へ移行を検討中で、Skills 資産を流用したい</li>
<li>個人エージェントの中間モデル層として安価な選択肢を探している</li>
</ul>
<h3 id="向かない人" tabindex="-1">向かない人</h3>
<ul>
<li>フロンティアモデル（Opus / GPT-5）を常用モデルに据えたい → Zen 従量 or BYOK</li>
<li>エンタープライズ要件（SLA、監査ログ、SSO 等）が必須</li>
<li>既に Claude Pro / ChatGPT Plus で完結しており不満がない</li>
</ul>
<h3 id="月額試算" tabindex="-1">月額試算</h3>
<p>ざっくりした試算として：</p>
<ul>
<li><strong>Go プラン単独</strong>: $10/月（年 $120）— 常用ワーカーがすべて Go 提供モデルで足りる場合</li>
<li><strong>Go ＋ Zen 従量混在</strong>: $10 ＋ 利用量に応じた変動（例: Sonnet を月1〜2M tokens 程度使うなら $5〜30 上乗せ）</li>
<li><strong>BYOK（OpenCode ハーネスのみ利用）</strong>: $0 ＋ プロバイダー側の API 課金</li>
</ul>
<p>ChatGPT Plus（$20/月）を解約して Go プラン（$10/月）に振り替えれば、月$10 が浮く計算になる。Codex を別途使いたい場合は ChatGPT Plus の維持／別の経路を確保する必要があるため、純粋な置き換えにはならない点は注意。</p>
<h2 id="未確認事項と次のアクション" tabindex="-1">未確認事項と次のアクション</h2>
<p>公開情報の調査範囲では詰め切れなかった、契約前に1次情報で押さえるべき項目。</p>
<ol>
<li><strong>Go プランの具体的な提供モデルとレート制限</strong>: 公式 <a href="https://opencode.ai/go" target="_blank" rel="noopener noreferrer">opencode.ai/go</a> ページの最新仕様を直接確認する。本記事の「低価格 OSS モデル中心」は公開情報から推定したラベル付けで、提供モデルの内訳まで確定していない</li>
<li><strong>Compaction Agent の挙動</strong>: 内部要約ヒューリスティクスは変更不可（<code>maxSteps</code> のみ調整可能）。長セッションで Hermes 等の外部オーケストレーターと干渉する可能性は実機でのみ判定できる</li>
<li><strong><code>plan</code> エージェントの read-only 制約</strong>: ビルトインの <code>plan</code> エージェントは強制 read-only。プラン → 実装の自動遷移を作る場合はカスタムエージェント定義が必要</li>
<li><strong>ローカル LLM との物理リソース競合</strong>: ローカル LLM を回しつつ OpenCode サブプロセス／デーモンを並走させた場合のメモリ・GPU 競合は実機で要確認</li>
</ol>
<p>私自身の次のアクションは、Go プラン契約 → 典型タスク10〜20件で精度・レイテンシを実測 → エスカレーション判定ロジックを決めてから Hermes Agent 側に組み込む、の順で進める予定。</p>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<ul>
<li>OpenCode Go プランは月額固定（初月$5、翌月以降$10）で、低価格・低レイテンシモデル中心の常用ワーカー枠</li>
<li>Zen / Black / BYOK と組み合わせ可能で、必要に応じて従量・フロンティア・自前 API キーへスライドできる柔軟性がある</li>
<li>TUI / <code>opencode run</code> / <code>opencode serve</code> / Node.js SDK の4経路で扱え、外部エージェントから「ワーカーとして」呼びやすい設計</li>
<li>Skills が Claude Code 互換で、既存資産の流用コストが小さい</li>
<li>自前オーケストレーターから中間モデル層として呼ぶ発展用途も視野に入るが、接続方式（API 直叩き／ハーネス経由）の確認は別途必要</li>
</ul>
<p>ChatGPT Plus / Claude Pro の $20/月をそのまま払い続ける手前で、もう一段下の価格帯を試したい人にとって、Go プランは現実的な選択肢になる。私自身はまず PoC を回してから本契約に進む予定で、その結果はあらためて別記事で書きたい。</p>
<h2 id="参考" tabindex="-1">参考</h2>
<ul>
<li><a href="https://opencode.ai/docs/go/" target="_blank" rel="noopener noreferrer">Go | OpenCode docs</a></li>
<li><a href="https://opencode.ai/go" target="_blank" rel="noopener noreferrer">OpenCode Go 製品ページ</a></li>
<li><a href="https://opencode.ai/docs/zen/" target="_blank" rel="noopener noreferrer">Zen | OpenCode docs</a></li>
<li><a href="https://opencode.ai/docs" target="_blank" rel="noopener noreferrer">opencode.ai/docs</a></li>
<li><a href="https://github.com/sst/opencode" target="_blank" rel="noopener noreferrer">github.com/sst/opencode</a></li>
</ul>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/6Nh8tVuSCuHunaVeqyegdU/0fee3536b9ee406b986430ebc1b8ebfd/OpenCode_Go_%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A8__%C3%A4__%C3%A3__%C3%A3__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ハーネスエンジニアリングとは何か：プロンプト→コンテキスト→ハーネスへ至るAIエージェント設計の変遷]]></title>
            <link>https://www.yoshihiko.tech/blog/prompt-context-harness-engineering</link>
            <guid isPermaLink="false">prompt-context-harness-engineering</guid>
            <pubDate>Tue, 07 Apr 2026 13:04:49 GMT</pubDate>
            <description><![CDATA[「ハーネスエンジニアリング」とは何か。プロンプトエンジニアリング、コンテキストエンジニアリングから続く3段階の変遷を、論文引用（Lost in the Middle / SWE-bench）とClaude Codeの実装例（フック・ルール・Skill）で階層構造として整理する。]]></description>
            <content:encoded><![CDATA[<p>最近 X で「harness engineering」という言葉を見かける頻度が増えた。プロンプトエンジニアリングとどう違うのか、コンテキストエンジニアリングとは別物なのか、この辺りが自分の中でも整理できていなかった。本記事ではこの3つを <strong>階層的な包含関係</strong> として整理し、それぞれが何を解決し、どこで限界を迎え、次の概念に引き継がれたのかを論文と実装例で追う。結論を先に言う。</p>
<blockquote>
<p><strong>Harness ⊃ Context ⊃ Prompt</strong></p>
</blockquote>
<p>ハーネスエンジニアリングはプロンプトエンジニアリングを置き換えるものではない。プロンプトを含むコンテキストを含む、より外側の実行環境を設計する技術である。そしてその先には、設計書と実装の乖離を埋める <strong>AI駆動開発（SDD / TDD）</strong> という次の課題が待っている。</p>
<h2 id="3つの用語の早見表" tabindex="-1">3つの用語の早見表</h2>
<p>全体像を先に掴んでおこう。</p>
<table>
<thead>
<tr>
<th>観点</th>
<th>Prompt Engineering</th>
<th>Context Engineering</th>
<th>Harness Engineering</th>
</tr>
</thead>
<tbody>
<tr>
<td>対象範囲</td>
<td>1回の入出力の命令文</td>
<td>コンテキストウィンドウ全体の情報</td>
<td>モデルを取り囲む実行環境</td>
</tr>
<tr>
<td>主要技法</td>
<td>Few-shot / CoT / ReAct</td>
<td>RAG / XML構造化 / MCP / Memory管理</td>
<td>Hooks / Rules / Subagent / Sandbox</td>
</tr>
<tr>
<td>失敗モード</td>
<td>命令の誤解・脆弱性</td>
<td>Lost in the Middle / Context Rot</td>
<td>ループの不安定・仕様ドリフト</td>
</tr>
<tr>
<td>代表ツール</td>
<td>OpenAI Playground / LangChain</td>
<td>LlamaIndex / MCP サーバー</td>
<td>Claude Code / Cursor / Cline</td>
</tr>
<tr>
<td>比喩</td>
<td>CPU への命令コード</td>
<td>CPU の RAM 管理</td>
<td>モデルを動かす OS / 骨格</td>
</tr>
<tr>
<td>時間軸</td>
<td>静的（事前設計）</td>
<td>動的（実行中管理）</td>
<td>システム全体（長期運用）</td>
</tr>
</tbody>
</table>
<p>この表を頭の片隅に置いて、各層を順番に見ていく。</p>
<h2 id="プロンプトエンジニアリング-—-命令の文言を最適化する" tabindex="-1">プロンプトエンジニアリング — 命令の文言を最適化する</h2>
<p>プロンプトエンジニアリングの目的は単純で、<strong>LLM に渡す命令文を設計して、1回の出力品質を上げること</strong> だ。対象はあくまで「単一のプロンプト文字列」であり、それ以外は触らない。</p>
<h3 id="何を解決してきたか" tabindex="-1">何を解決してきたか</h3>
<p>2020年の GPT-3 論文（Brown et al., <a href="https://arxiv.org/abs/2005.14165" target="_blank" rel="noopener noreferrer">arXiv:2005.14165</a>）が示した Few-shot Learning が出発点だ。タスクを説明する代わりに、入出力例を2〜3個見せるだけでモデルがパターンを学習して続きを生成する。この発見によって、プロンプトは単なる命令文から「プログラミング可能な入力インターフェース」に変わった。</p>
<p>2022年末に ChatGPT が公開されると、この技術は一気に広まり、「プロンプトエンジニア」という職種まで生まれる。同時期に体系的な手法もまとまって出揃った。</p>
<h3 id="主要技法（論文引用付き）" tabindex="-1">主要技法（論文引用付き）</h3>
<ul>
<li><strong>Chain-of-Thought (CoT)</strong> — Wei et al. (2022) <a href="https://arxiv.org/abs/2201.11903" target="_blank" rel="noopener noreferrer">arXiv:2201.11903</a><br>
中間推論ステップを明示するだけで複雑な算数・論理問題の精度が大幅に向上する。ただし効果が顕著なのは100B以上の大規模モデルに限られる。</li>
<li><strong>Zero-Shot-CoT</strong> — Kojima et al. (2022) <a href="https://arxiv.org/abs/2205.11916" target="_blank" rel="noopener noreferrer">arXiv:2205.11916</a><br>
&quot;Let's think step by step&quot; の一文を足すだけで MultiArith の精度が 17.7% → 78.7% に跳ねた。プロンプト1行の威力を象徴する論文。</li>
<li><strong>Self-Consistency</strong> — Wang et al. (2022) <a href="https://arxiv.org/abs/2203.11171" target="_blank" rel="noopener noreferrer">arXiv:2203.11171</a><br>
複数の推論パスを生成して多数決を取る。Greedy decoding より安定する。</li>
<li><strong>ReAct</strong> — Yao et al. (2022) <a href="https://arxiv.org/abs/2210.03629" target="_blank" rel="noopener noreferrer">arXiv:2210.03629</a><br>
推論（Reason）と行動（Act）を交互に実行するフレームワーク。後のエージェント設計の原型になった。</li>
<li><strong>Tree of Thoughts</strong> — Yao et al. (2023) <a href="https://arxiv.org/abs/2305.10601" target="_blank" rel="noopener noreferrer">arXiv:2305.10601</a><br>
推論を木構造で展開し、BFS/DFS で探索・枝刈りする。</li>
<li><strong>Reflexion</strong> — Shinn et al. (2023) <a href="https://arxiv.org/abs/2303.11366" target="_blank" rel="noopener noreferrer">arXiv:2303.11366</a><br>
失敗を「言語的な反省」としてコンテキストに追記し、次の試行を改善する。</li>
</ul>
<h3 id="失敗モードと限界" tabindex="-1">失敗モードと限界</h3>
<p>プロンプトエンジニアリングは強力だが、以下の壁にぶつかる。</p>
<ul>
<li><strong>プロンプトの脆弱性</strong>: モデルバージョンが変わると、同じプロンプトでも結果が崩れる。特に OpenAI の GPT-4 系のマイナーアップデートで既存プロンプトが動かなくなる事例は枚挙にいとまがない。</li>
<li><strong>長期タスクで文脈が失われる</strong>: 単一プロンプトでは複数ファイルにまたがる実装や、数十ステップの推論を扱えない。</li>
<li><strong>近代モデルによる内部化</strong>: GPT-4o や Claude 3.5 以降、CoT は明示しなくてもモデル内部でほぼ自動的に実行される。「Let's think step by step」の効果は相対的に小さくなった。</li>
</ul>
<h3 id="今の位置付け" tabindex="-1">今の位置付け</h3>
<p>プロンプトエンジニアリングが消滅したわけではない。Fine-tuning のデータ設計、単一ターンのクリエイティブタスク、Few-shot でのタスク特化など、<strong>今でも必須の基礎技術</strong> だ。ただし複雑なエージェント系タスクでは、プロンプトは「より大きな仕組みの一部品」として扱われるようになった。この「より大きな仕組み」が次の2つの概念である。</p>
<h2 id="コンテキストエンジニアリング-—-情報アーキテクチャを設計する" tabindex="-1">コンテキストエンジニアリング — 情報アーキテクチャを設計する</h2>
<p>コンテキストエンジニアリングは、<strong>「何を言うか」ではなく「何を見せるか」</strong> にフォーカスする技術だ。プロンプトの文言を磨く代わりに、コンテキストウィンドウという限られた領域に <strong>どの情報を、どの順序で、どのタイミングで投入するか</strong> を設計する。</p>
<p>用語自体は比較的新しい。2025年6月18日に Shopify CEO の Tobi Lütke が X で次のように投稿した。</p>
<blockquote>
<p>&quot;I really like the term 'context engineering' over prompt engineering. It describes the core skill better: the art of providing all the context for the task to be plausibly solvable by the LLM.&quot; — <a href="https://x.com/tobi/status/1935533422589399127" target="_blank" rel="noopener noreferrer">Tobi Lütke (2025-06-18)</a></p>
</blockquote>
<p>これに <strong>Andrej Karpathy が返信する形</strong> で2025年6月25日に投稿したのが、後に広く引用されることになる次のツイートだ。</p>
<blockquote>
<p>&quot;+1 for 'context engineering' over 'prompt engineering'. People associate prompts with short task descriptions you'd give an LLM in your day-to-day use. When in every industrial-strength LLM app, context engineering is the delicate art and science of filling the context window with just the right information for the next step.&quot; — <a href="https://x.com/karpathy/status/1937902205765607626" target="_blank" rel="noopener noreferrer">Andrej Karpathy (2025-06-25)</a></p>
</blockquote>
<p>Karpathy は別の場面で LLM を OS に例え、<strong>「LLM は CPU、コンテキストウィンドウは RAM」</strong> という比喩も提唱している。モデルの能力を引き出すには、この RAM をどう管理するかが次の核心スキルだというわけだ。</p>
<h3 id="lost-in-the-middle-—-理論的根拠" tabindex="-1">Lost in the Middle — 理論的根拠</h3>
<p>コンテキストエンジニアリングが単なる流行語ではなく技術として成立する根拠を与えたのが、Liu et al. (2023) &quot;Lost in the Middle: How Language Models Use Long Contexts&quot; (<a href="https://arxiv.org/abs/2307.03172" target="_blank" rel="noopener noreferrer">arXiv:2307.03172</a>) である。</p>
<p>この論文が示したのは次の事実だ。</p>
<ul>
<li>LLM の情報取得精度は <strong>U字型カーブ</strong> になる。コンテキストの先頭と末尾の情報は正確に処理されるが、<strong>中間部分の情報は著しく無視される</strong>。</li>
<li>関連文書を先頭または末尾に配置するだけで精度が大きく改善する。</li>
<li>コンテキストが長くなるほど、中間部の「Needle」（重要情報）を見落とす確率が上がる。</li>
</ul>
<p>「全部放り込めば勝手に読んでくれる」という期待は、この論文でほぼ崩された。長いコンテキストは高価なだけでなく、性能劣化の原因にもなる。後の &quot;Context Rot&quot;（文脈の腐敗）や &quot;Context Pollution&quot;（文脈の汚染）という概念は、この論文の実証を出発点にしている。</p>
<h3 id="主要テクニック" tabindex="-1">主要テクニック</h3>
<ul>
<li><strong>RAG (Retrieval-Augmented Generation)</strong> — Lewis et al. (2020) <a href="https://arxiv.org/abs/2005.11401" target="_blank" rel="noopener noreferrer">arXiv:2005.11401</a><br>
実は RAG の原論文は GPT-3 と同じ2020年に出ている。当時は「外部知識を注入する1手法」として見られていたが、コンテキストエンジニアリングの文脈では <strong>中心的な技法</strong> として再評価されている。</li>
<li><strong>Structured Context（XML タグ）</strong><br>
Anthropic が推奨する手法で、システム指示・ユーザー入力・ツール出力を XML タグで明確に区切る。Claude ファミリーはこの構造を学習時から意識しているため、特に効果が高い。</li>
<li><strong>Context Compression / Summarization</strong><br>
ツール実行の生ログや過去の会話履歴を要約に置き換える。Attention Budget を節約するための基本技法。</li>
<li><strong>Memory Management（短期 / 長期）</strong><br>
セッション内の情報は短期メモリ、外部ストレージ（ベクター DB、ファイルシステム）に永続化するのは長期メモリ。<code>claude-mem</code> のようなツールはこの長期メモリを担う。</li>
<li><strong>MCP (Model Context Protocol)</strong><br>
Anthropic が策定したオープン標準で、ツールやデータソースを「Just-in-Time」でコンテキストに注入する仕組み。ただし後述のように <strong>これ自体が新たな失敗モード</strong> を生んでいる。</li>
</ul>
<h3 id="失敗モード" tabindex="-1">失敗モード</h3>
<p>コンテキストエンジニアリングには独自の失敗パターンがある。</p>
<ul>
<li><strong>MCP の肥大化</strong>: MCP サーバーを複数接続すると、ツール定義だけでコンテキストを数千〜数万トークン消費する。モデルが「何を使うか」を判断する前に、ツール定義で窒息する現象。</li>
<li><strong>CLAUDE.md / AGENTS.md の肥大化</strong>: プロジェクト固有のルールを書き溜めていくと、ファイル自体が Context Pollution の原因になる。「重要だから」と書けば書くほど、本当に重要な情報が埋もれていく皮肉。</li>
<li><strong>Context Rot</strong>: 長時間のセッションで古い情報・失敗した試行のログ・重複した出力が蓄積し、モデルのパフォーマンスが徐々に劣化する。</li>
</ul>
<h3 id="実務テクニック" tabindex="-1">実務テクニック</h3>
<p>上記を踏まえた実務的な対処は次のようになる。</p>
<ul>
<li>最重要制約は <strong>コンテキストの末尾</strong> に配置する（Lost in the Middle を逆手に取る）</li>
<li>サブエージェントは <strong>Stateless</strong> で動かし、オーケストレーター本体のコンテキストを汚染しない</li>
<li>必要ない MCP は接続しない。<code>allowedTools</code> で制限する</li>
<li>CLAUDE.md は定期的に剪定する。「書き溜める」のではなく「育てる」</li>
</ul>
<p>ここまで来ると、鍵は「単発のプロンプト品質」から「セッション全体の情報設計」に移っていることがわかる。これをもう一段外側に広げたのがハーネスエンジニアリングだ。</p>
<h2 id="ハーネスエンジニアリング-—-モデルを囲む実行環境を設計する" tabindex="-1">ハーネスエンジニアリング — モデルを囲む実行環境を設計する</h2>
<p>ハーネスエンジニアリングは、<strong>LLM モデル本体を取り囲む「実行環境・ルール・ツール群・検証ループ」</strong> を設計する技術である。プロンプトでもコンテキストでもなく、<strong>モデルが動く土台そのもの</strong> を最適化する。</p>
<p>この概念が明示的に語られるようになったのは2025年以降、Claude Code・Cursor・Cline などのエージェント型コーディングツールが普及してからだ。背景にあるのは次のような逆転だ。</p>
<blockquote>
<p><strong>弱いモデル + 堅牢なハーネス &gt; 強いモデル + 貧弱なハーネス</strong></p>
</blockquote>
<p>「良いプロンプトを書く」ことより「良い実行環境を組む」ことの方が、実運用の品質を左右する。自分自身、Claude Code を数ヶ月使っていて、プロンプトを工夫するより <code>.claude/rules/</code> にルールを追記する方が効く場面の方が圧倒的に多かった。この感覚を言語化したのがハーネスエンジニアリングである。</p>
<h3 id="構成要素（claude-code-を例に）" tabindex="-1">構成要素（Claude Code を例に）</h3>
<p>Claude Code のハーネスは大きく4つの要素で構成されている。</p>
<p><strong>1. Hooks — 実行ループへの介入</strong></p>
<p>Claude Code には <code>PreToolUse</code> / <code>PostToolUse</code> / <code>UserPromptSubmit</code> / <code>SessionStart</code> といったフックがあり、任意のスクリプトを差し込める。例えばこんな使い方ができる。</p>
<pre><code class="language-python"># .claude/hooks/check-codex-before-write.py
# Edit/Write ツール実行前に Codex CLI に設計相談を促す
if tool_name in (&quot;Edit&quot;, &quot;Write&quot;) and is_significant_change(file_path):
    print(&quot;[Codex Suggestion] Consider consulting Codex before this change&quot;)
</code></pre>
<p>フックは <strong>モデルの判断に委ねるのではなく、宣言的にループを制御する</strong> 装置だ。プロンプトで「設計を相談してから書いてね」と頼むより、フックで強制するほうがはるかに確実である。</p>
<p><strong>2. Rules / CLAUDE.md — 不変の制約</strong></p>
<p><code>CLAUDE.md</code> や <code>.claude/rules/*.md</code> に書かれた内容は、セッション開始時にシステムプロンプトへ自動注入される。「日本語で返答する」「main ブランチに直接 push しない」「テストを書いてから実装する」といった、<strong>プロジェクトの不変のルール</strong> を記述する場所だ。</p>
<p>コンテキストエンジニアリングの観点からは「肥大化に注意」と書いたが、ハーネスの観点では「<strong>動的に変わらない制約をコードで表現する</strong>」という役割を持つ。</p>
<p><strong>3. Skills / Subagents — 再利用可能なワークフロー</strong></p>
<p>Skill（<code>.claude/skills/*/SKILL.md</code>）はタスクに特化したワークフローを定義する仕組みで、Subagent（<code>.claude/agents/*.md</code>）は特定の役割を持つエージェントを独立コンテキストで動かす仕組みだ。ポイントは <strong>Stateless に動かせる</strong> こと。オーケストレーター本体のコンテキストを汚染せずに、重い処理を並列で回せる。</p>
<p><strong>4. Tool Restrictions / Sandbox — 行動の制約</strong></p>
<p><code>read-only</code> や <code>workspace-write</code> のようなサンドボックスモード、<code>allowedTools</code> での許可リストは、モデルができる行動を物理的に制限する。プロンプトで「破壊的な操作はしないで」とお願いするのではなく、<strong>そもそもできないようにする</strong> のがハーネスの思想だ。</p>
<h3 id="性能への影響-—-swe-bench-での実証" tabindex="-1">性能への影響 — SWE-bench での実証</h3>
<p>ハーネスの効果は実データで示されている。SWE-bench（Jimenez et al., <a href="https://arxiv.org/abs/2310.06770" target="_blank" rel="noopener noreferrer">arXiv:2310.06770</a>）は実際の GitHub Issue を LLM に解かせるベンチマークで、Anthropic が公開した Claude の SWE-bench Verified スコア推移が興味深い。</p>
<ul>
<li>2024-06 Claude 3.5 Sonnet（モデル単体）: <strong>33.4%</strong> (<a href="https://www.anthropic.com/news/3-5-models-and-computer-use" target="_blank" rel="noopener noreferrer">公式発表</a>)</li>
<li>2024-10 Claude 3.5 Sonnet Updated: <strong>49.0%</strong> (同上)</li>
<li>2025-02 Claude 3.7 Sonnet（素のモデル）: <strong>62.3%</strong> (<a href="https://www.anthropic.com/news/claude-3-7-sonnet" target="_blank" rel="noopener noreferrer">公式発表</a>)</li>
<li>2025-02 Claude 3.7 Sonnet（custom scaffold 込み）: <strong>70.3%</strong> ← <strong>+8.0%</strong> (同上)</li>
</ul>
<p>注目すべきは 2025年2月の行だ。<strong>同じモデルに対してカスタムスキャフォールド（並列サンプリング・回帰テスト・拡張思考モードを組み合わせたハーネス）を被せるだけでスコアが 8 ポイント上がる</strong>。この差は、モデルの世代交代1回分に匹敵する大きさだ。</p>
<h3 id="失敗モード-1" tabindex="-1">失敗モード</h3>
<p>ハーネスエンジニアリングにも固有の失敗がある。</p>
<ul>
<li><strong>設計の複雑化</strong>: Hooks・Rules・Skill が増えるにつれ、誰が何を制御しているのか追いづらくなる</li>
<li><strong>サブエージェント間の状態管理</strong>: 複数のサブエージェントが並列で動くと、それぞれの結果を統合するオーケストレーションが難題になる</li>
<li><strong>コスト</strong>: 検証ループ・サブエージェント・フックはすべてトークン消費を増やす</li>
<li><strong>ハーネス設計スキルの希少性</strong>: 「良いプロンプト」を書ける人は増えたが、「良いハーネス」を組める人はまだ少ない</li>
</ul>
<h2 id="3層の関係-—-階層的包含とタイムライン" tabindex="-1">3層の関係 — 階層的包含とタイムライン</h2>
<p>ここまでで見えてきたのは、3つが <strong>置き換えではなく包含関係</strong> にあるという事実だ。図にするとこうなる。</p>
<pre><code>┌─────────────────────────────────────────────┐
│          Harness Engineering                │
│  （Hooks・Sandbox・サブエージェント）           │
│  ┌───────────────────────────────────────┐  │
│  │       Context Engineering             │  │
│  │  （RAG・Memory・構造化・MCP）            │  │
│  │  ┌─────────────────────────────────┐  │  │
│  │  │     Prompt Engineering          │  │  │
│  │  │  （Few-shot・CoT・Zero-shot）    │  │  │
│  │  └─────────────────────────────────┘  │  │
│  └───────────────────────────────────────┘  │
└─────────────────────────────────────────────┘
</code></pre>
<p>この階層で最も重要なメッセージはこれだ。</p>
<blockquote>
<p><strong>上位層の設計ミスは、下位層の最適化では救えない。</strong></p>
</blockquote>
<p>ハーネスが壊れていれば、どんなに良いコンテキスト設計もプロンプトも無駄になる。逆に、ハーネスさえ整っていれば、プロンプトの細かいチューニングに消耗する必要はなくなる。</p>
<p>短いタイムラインで振り返ると、次のようになる。</p>
<ul>
<li><strong>2020</strong> GPT-3 論文 / RAG 論文</li>
<li><strong>2022</strong> ChatGPT 公開 / CoT・ReAct 論文ラッシュ</li>
<li><strong>2023</strong> Lost in the Middle / SWE-bench 公開</li>
<li><strong>2024</strong> MCP 策定</li>
<li><strong>2025-02</strong> Claude Code リリース、カスタムスキャフォールドで +8% を公式実証</li>
<li><strong>2025-06</strong> Lütke → Karpathy の流れで Context Engineering が一気に浸透</li>
<li><strong>2026</strong> Harness Engineering の議論が本格化</li>
</ul>
<p>3つはいずれも、前の世代の限界にぶつかったことで生まれた。そしてハーネスエンジニアリングにも、次の壁が見え始めている。</p>
<h2 id="ハーネスの先にある課題-—-設計と実装の乖離" tabindex="-1">ハーネスの先にある課題 — 設計と実装の乖離</h2>
<p>ハーネスを組み立てて運用していると、ある時期から気になってくる問題がある。設計書と実装の乖離だ。</p>
<h3 id="何が起きるか" tabindex="-1">何が起きるか</h3>
<p>具体的にはこんな現象だ。</p>
<ul>
<li>Hooks・Rules・Skill が増えるにつれ、「このルールは何のためにあったのか」を誰も覚えていない状態になる</li>
<li><code>CLAUDE.md</code> や要件定義ドキュメントと実装の間にズレが溜まっていく。コードが変わっても仕様書は更新されない</li>
<li>AI が仕様書より実装を信じる。「動くコード」が真実になり、「書かれた仕様」は形骸化する</li>
<li>テストが通っていることを根拠に「正しい」と誤認する。テストがそもそも仕様と乖離している可能性には気づかない</li>
</ul>
<p>これは「ハーネスが悪い」のではない。ハーネスは <strong>実行時の制御</strong> には強いが、<strong>設計と実装の一貫性を担保する仕組み</strong> は別に必要なのだ。</p>
<h3 id="なぜ起きるか" tabindex="-1">なぜ起きるか</h3>
<p>原因は2つある。</p>
<ol>
<li><strong>AI はコードを一次ソースと見なしやすい</strong>: 仕様書とコードが矛盾していたら、AI はコードを正として仕様書を無視する傾向がある。特にコンテキストに両方が入っていると、より直近で更新されたコードが優先されやすい</li>
<li><strong>ハーネスは「実行時」に作用する</strong>: Hooks や Rules は「今まさに AI が動こうとしている瞬間」に介入する装置だ。設計判断が行われるのは <strong>実装より前</strong> なので、ハーネスだけでは手が届かない</li>
</ol>
<h3 id="次の一歩-—-ai駆動開発（sdd-/-tdd）" tabindex="-1">次の一歩 — AI駆動開発（SDD / TDD）</h3>
<p>この問題に対する答えとして浮上しているのが、<strong>仕様を一次ソースに置く開発手法</strong> である。いわゆる AI駆動開発の文脈だ。</p>
<p><strong>Spec-Driven Development (SDD)</strong><br>
仕様書を中心に据え、そこから実装・テスト・ドキュメントを AI に生成させる。仕様が変わったらコードを再生成する、という逆転した関係を作る。2025年には GitHub の Spec Kit や Amazon の Kiro のように、仕様駆動を前提にしたツールが相次いで登場した。</p>
<p><strong>Test-Driven Development (TDD)</strong><br>
テストを「実行可能な仕様」として AI に渡す。テストが先にあり、実装はそれを満たすように生成する。AI駆動 TDD では、テストがそのままハーネスの検証ループに組み込まれる。「テストが通ったら次のタスクへ」という確定的な進行が可能になる。</p>
<p>どちらも、「検証ループの確定性」を <strong>ハーネスの外側から</strong> 与える仕組みだ。</p>
<h3 id="ハーネス-×-sdd/tdd-の位置付け" tabindex="-1">ハーネス × SDD/TDD の位置付け</h3>
<p>重要なのは、これらがハーネスエンジニアリングと <strong>対立するものではない</strong> ということだ。むしろ並行・補完関係にある。</p>
<ul>
<li><strong>ハーネス</strong>: 実行時の制御・安全弁・ループの骨格</li>
<li><strong>SDD / TDD</strong>: 設計と検証の一貫性・真実の置き場所</li>
</ul>
<p>ハーネスが AI の「行動の仕方」を決めるなら、SDD/TDD は AI に渡す「真実の源」を決める。両方が揃って初めて、「AI に仕事を任せても仕様と実装がズレない」状態が作れる。</p>
<h2 id="実践ロードマップ-—-どの順序で学ぶか" tabindex="-1">実践ロードマップ — どの順序で学ぶか</h2>
<p>ここまでの内容を踏まえて、学ぶ順序を整理しておく。</p>
<ol>
<li><strong>プロンプトエンジニアリング</strong>: 基礎として必須。CoT / ReAct / Few-shot の論文は原典を1本ずつ読むといい</li>
<li><strong>コンテキストエンジニアリング</strong>: Lost in the Middle 論文と Karpathy の発言を押さえたうえで、実際に MCP / RAG を使ってみる</li>
<li><strong>ハーネスエンジニアリング</strong>: Claude Code なり Cursor なりで Hooks・Rules・Skill を実装してみる。SWE-bench のスコア推移を追うと効果の大きさが実感できる</li>
<li><strong>SDD / TDD（並行）</strong>: 3と並行して、仕様を一次ソースに据える開発手法に触れる。Kiro / Spec Kit / AI駆動 TDD の事例を追う</li>
</ol>
<p>順序は大事だが、3と4は直列ではなく並行がおすすめだ。ハーネスを組みながら「この仕組みは設計書とズレていないか？」と問い続ける癖をつけると、後で仕様ドリフトに気づいたときの修正コストがだいぶ下がる。</p>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>3層の関係をもう一度整理すると、こうなる。</p>
<ul>
<li><strong>プロンプトエンジニアリング</strong>は命令の文言を最適化する技術。1回の入出力品質を上げる</li>
<li><strong>コンテキストエンジニアリング</strong>はコンテキストウィンドウの情報設計。何をどの順序で見せるかを決める</li>
<li><strong>ハーネスエンジニアリング</strong>はモデルを囲む実行環境の設計。Hooks・Rules・Skill でループを制御する</li>
<li>そしてその先には、<strong>SDD / TDD</strong> による設計と実装の一貫性という次の課題がある</li>
</ul>
<p>3つは置き換えではなく階層的な包含関係にあり、上位層の設計ミスは下位層では救えない。AI 連携で詰まっているときは、まずこの問いを自分に投げてみてほしい。</p>
<blockquote>
<p><strong>「今の課題は、どの層の問題か？」</strong></p>
</blockquote>
<p>プロンプトをいじっても直らない問題は、たいてい1つ上の層にある。そして、ハーネスまで組んでもなお残る問題は、もう層の外——仕様そのものの置き場所の問題である可能性が高い。</p>
<h2 id="参考文献" tabindex="-1">参考文献</h2>
<h3 id="論文" tabindex="-1">論文</h3>
<ul>
<li>Brown et al. (2020). &quot;Language Models are Few-Shot Learners.&quot; <a href="https://arxiv.org/abs/2005.14165" target="_blank" rel="noopener noreferrer">arXiv:2005.14165</a></li>
<li>Lewis et al. (2020). &quot;Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.&quot; <a href="https://arxiv.org/abs/2005.11401" target="_blank" rel="noopener noreferrer">arXiv:2005.11401</a></li>
<li>Wei et al. (2022). &quot;Chain-of-Thought Prompting Elicits Reasoning in Large Language Models.&quot; <a href="https://arxiv.org/abs/2201.11903" target="_blank" rel="noopener noreferrer">arXiv:2201.11903</a></li>
<li>Kojima et al. (2022). &quot;Large Language Models are Zero-Shot Reasoners.&quot; <a href="https://arxiv.org/abs/2205.11916" target="_blank" rel="noopener noreferrer">arXiv:2205.11916</a></li>
<li>Wang et al. (2022). &quot;Self-Consistency Improves Chain of Thought Reasoning.&quot; <a href="https://arxiv.org/abs/2203.11171" target="_blank" rel="noopener noreferrer">arXiv:2203.11171</a></li>
<li>Yao et al. (2022). &quot;ReAct: Synergizing Reasoning and Acting in Language Models.&quot; <a href="https://arxiv.org/abs/2210.03629" target="_blank" rel="noopener noreferrer">arXiv:2210.03629</a></li>
<li>Shinn et al. (2023). &quot;Reflexion: Language Agents with Verbal Reinforcement Learning.&quot; <a href="https://arxiv.org/abs/2303.11366" target="_blank" rel="noopener noreferrer">arXiv:2303.11366</a></li>
<li>Yao et al. (2023). &quot;Tree of Thoughts: Deliberate Problem Solving with Large Language Models.&quot; <a href="https://arxiv.org/abs/2305.10601" target="_blank" rel="noopener noreferrer">arXiv:2305.10601</a></li>
<li>Liu et al. (2023). &quot;Lost in the Middle: How Language Models Use Long Contexts.&quot; <a href="https://arxiv.org/abs/2307.03172" target="_blank" rel="noopener noreferrer">arXiv:2307.03172</a></li>
<li>Jimenez et al. (2024). &quot;SWE-bench: Can Language Models Resolve Real-World GitHub Issues?&quot; <a href="https://arxiv.org/abs/2310.06770" target="_blank" rel="noopener noreferrer">arXiv:2310.06770</a></li>
</ul>
<h3 id="ブログ・発言" tabindex="-1">ブログ・発言</h3>
<ul>
<li>Tobi Lütke (2025-06-18). &quot;I really like the term 'context engineering' over prompt engineering...&quot; <a href="https://x.com/tobi/status/1935533422589399127" target="_blank" rel="noopener noreferrer">X投稿</a></li>
<li>Andrej Karpathy (2025-06-25). &quot;+1 for 'context engineering' over 'prompt engineering'...&quot; <a href="https://x.com/karpathy/status/1937902205765607626" target="_blank" rel="noopener noreferrer">X投稿</a></li>
<li>Anthropic (2024-10). &quot;Introducing computer use, a new Claude 3.5 Sonnet, and Claude 3.5 Haiku.&quot; <a href="https://www.anthropic.com/news/3-5-models-and-computer-use" target="_blank" rel="noopener noreferrer">https://www.anthropic.com/news/3-5-models-and-computer-use</a></li>
<li>Anthropic (2025-02). &quot;Claude 3.7 Sonnet and Claude Code.&quot; <a href="https://www.anthropic.com/news/claude-3-7-sonnet" target="_blank" rel="noopener noreferrer">https://www.anthropic.com/news/claude-3-7-sonnet</a></li>
<li>Anthropic — Claude Code 公式ドキュメント（CLAUDE.md, Hooks, Skills）</li>
</ul>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/74HnVGS2R8wphJ1nGHgLfb/b87d3d6e35ccc518e04d9959c7637a88/%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3_%C2%AA%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A4__%C3%A3__%C3%AF__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A2__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A2__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A8__%C3%A3__A.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Auth.js v5 でJWT二重タイムアウトを実装する：BFF + Django連携の設計パターン]]></title>
            <link>https://www.yoshihiko.tech/blog/authjs-v5-jwt-dual-timeout-bff-django</link>
            <guid isPermaLink="false">authjs-v5-jwt-dual-timeout-bff-django</guid>
            <pubDate>Mon, 06 Apr 2026 12:45:28 GMT</pubDate>
            <description><![CDATA[Auth.js v5のjwt callbackで8時間の絶対有効期限と30分のアイドルタイムアウトを両立する実装方法を解説。Next.js MiddlewareによるBFF API保護とDjango REST Frameworkとの Token Exchange パターンも紹介する。]]></description>
            <content:encoded><![CDATA[<h2 id="はじめに：jwt認証設計が難しい理由" tabindex="-1">はじめに：JWT認証設計が難しい理由</h2>
<p>JWT認証には相反する2つの要件がある。</p>
<ul>
<li><strong>セキュリティ</strong>: トークンは短命であるべき。盗まれた場合の被害を最小化できる</li>
<li><strong>ユーザー体験</strong>: 操作中にセッションが切れるとストレスになる</li>
</ul>
<p>この2つを両立するために、多くのシステムでは「絶対有効期限」と「アイドルタイムアウト」を組み合わせる。</p>
<table>
<thead>
<tr>
<th>要件</th>
<th>動作</th>
<th>目的</th>
</tr>
</thead>
<tbody>
<tr>
<td>絶対有効期限（8時間）</td>
<td>ログインから8時間後に強制失効</td>
<td>トークン漏洩時の被害上限を設定</td>
</tr>
<tr>
<td>アイドルタイムアウト（30分）</td>
<td>30分間APIアクセスがなければ失効</td>
<td>離席時の不正利用を防止</td>
</tr>
<tr>
<td>スライディングウィンドウ</td>
<td>30分以内のアクセスで有効期限を延長</td>
<td>アクティブユーザーの操作を妨げない</td>
</tr>
</tbody>
</table>
<p>Auth.js v5はスライディングウィンドウをネイティブでサポートしているが、絶対有効期限は手動実装が必要になる。この記事では両方を組み合わせる実装パターンを解説する。</p>
<h2 id="auth.js-v5のjwtセッション戦略" tabindex="-1">Auth.js v5のJWTセッション戦略</h2>
<p>Auth.js v5（旧NextAuth.js v5）のJWTセッション戦略は、v4から大きく変わった。</p>
<h3 id="jwe暗号化クッキー" tabindex="-1">JWE暗号化クッキー</h3>
<p>Auth.js v5のJWTトークンは**JWE（JSON Web Encryption）**で暗号化され、HttpOnlyクッキーに格納される。</p>
<ul>
<li><strong>暗号化方式</strong>: <code>A256CBC-HS512</code>（AES-256-CBC + HMAC-SHA-512）</li>
<li><strong>鍵導出</strong>: <code>AUTH_SECRET</code> からHKDFで導出。クッキー名がsaltとして使われる</li>
<li><strong>ストレージ</strong>: <code>__Secure-authjs.session-token</code> という名前のHttpOnly / Secure / SameSite=Laxクッキー</li>
</ul>
<p>v4ではJWSE（署名のみ）だったが、v5ではJWE（暗号化）に変更された。この変更により、<strong>クライアントサイドでトークンの中身を読み取ることはできない</strong>。バックエンド（Django等）でAuth.jsのトークンを直接復号するのも非現実的であり、後述のToken Exchangeパターンが推奨される理由の一つである。</p>
<h3 id="jwt-/-session-コールバック" tabindex="-1">jwt / session コールバック</h3>
<p>Auth.js v5の認証フローの中核は2つのコールバックである。</p>
<pre><code class="language-typescript">// auth.ts
import NextAuth from &quot;next-auth&quot;

export const { handlers, auth, signIn, signOut } = NextAuth({
  session: { strategy: &quot;jwt&quot; },
  callbacks: {
    // 1. jwt callback: トークンの生成・更新を担当
    //    - 初回ログイン時: user, account が渡される
    //    - 以降のリクエスト: token のみ（既存トークンの読み取り）
    async jwt({ token, user, account }) {
      if (account &amp;&amp; user) {
        // 初回ログイン時のみ実行される
        token.userId = user.id
      }
      return token
    },

    // 2. session callback: クライアントに公開する情報を整形
    //    - jwt callback の後に毎回実行される
    async session({ session, token }) {
      session.user.id = token.userId as string
      return session
    },
  },
})
</code></pre>
<p><strong>実行順序</strong>:</p>
<ul>
<li>初回ログイン: <code>signIn</code> → <code>jwt</code>（token生成）→ <code>session</code>（整形）→ <code>redirect</code></li>
<li>以降のリクエスト: <code>jwt</code>（token読み取り・更新）→ <code>session</code>（整形）→ <code>authorized</code>（ルート保護）</li>
</ul>
<h3 id="edge-runtime対応の設計分割" tabindex="-1">Edge Runtime対応の設計分割</h3>
<p>Next.js App RouterのMiddlewareはEdge Runtimeで動作する。Auth.js v5でMiddlewareを使う場合、Node.jsランタイム専用の処理（DBアクセス等）を分離する必要がある。</p>
<pre><code>auth.config.ts  ← Edge互換。Middleware から import する
auth.ts         ← Node.js用。DBアクセスを含むプロバイダー設定
middleware.ts   ← auth.config.ts を使用
</code></pre>
<pre><code class="language-typescript">// auth.config.ts（Edge互換）
import type { NextAuthConfig } from &quot;next-auth&quot;

export const authConfig = {
  session: { strategy: &quot;jwt&quot; },
  pages: {
    signIn: &quot;/login&quot;,
  },
  callbacks: {
    // jwt, session, authorized コールバックをここに定義
  },
} satisfies NextAuthConfig
</code></pre>
<pre><code class="language-typescript">// auth.ts（Node.js用）
import NextAuth from &quot;next-auth&quot;
import { authConfig } from &quot;./auth.config&quot;
import Credentials from &quot;next-auth/providers/credentials&quot;

export const { handlers, auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      // DBアクセスを含む認証ロジック
    }),
  ],
})
</code></pre>
<h2 id="二重タイムアウトの設計" tabindex="-1">二重タイムアウトの設計</h2>
<h3 id="なぜ片方だけでは不十分なのか" tabindex="-1">なぜ片方だけでは不十分なのか</h3>
<p><strong>絶対有効期限だけの場合</strong>:</p>
<p>ユーザーがログインして10分操作し、その後7時間50分離席しても、セッションは生きている。共有PCや公共の場所では深刻なセキュリティリスクになる。</p>
<p><strong>アイドルタイムアウトだけの場合</strong>:</p>
<p>ユーザーが自動化スクリプトやバックグラウンドのポーリングで定期的にAPIを叩いていると、セッションが永遠に延長される。トークンが漏洩した場合、攻撃者も同じ手法で無期限にセッションを維持できる。</p>
<p>両方を組み合わせることで「普段は操作に応じて延長されるが、どんなに操作し続けても8時間後には再認証が必要」という挙動を実現できる。</p>
<h3 id="auth.js-v5での設計方針" tabindex="-1">Auth.js v5での設計方針</h3>
<table>
<thead>
<tr>
<th>パラメータ</th>
<th>値</th>
<th>役割</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>session.maxAge</code></td>
<td><code>1800</code>（30分）</td>
<td>クッキーのExpires。アイドルタイムアウト</td>
</tr>
<tr>
<td><code>session.updateAge</code></td>
<td><code>0</code></td>
<td>毎リクエストでクッキーを更新（スライディングウィンドウ）</td>
</tr>
<tr>
<td><code>token.absoluteExpiry</code></td>
<td><code>Date.now() + 8h</code></td>
<td>カスタムフィールド。絶対有効期限</td>
</tr>
</tbody>
</table>
<p><code>maxAge</code> と <code>updateAge</code> はAuth.js v5のネイティブ機能だが、<code>absoluteExpiry</code> は <code>jwt</code> コールバック内で手動管理するカスタムフィールドである。</p>
<h2 id="実装：jwt-callbackで二重タイムアウト" tabindex="-1">実装：jwt callbackで二重タイムアウト</h2>
<p>以下が二重タイムアウトの中核実装である。</p>
<pre><code class="language-typescript">// auth.config.ts
import type { NextAuthConfig } from &quot;next-auth&quot;

const ABSOLUTE_TIMEOUT_SECONDS = 8 * 60 * 60 // 8時間
const IDLE_TIMEOUT_SECONDS = 30 * 60          // 30分

export const authConfig = {
  session: {
    strategy: &quot;jwt&quot;,
    maxAge: IDLE_TIMEOUT_SECONDS,  // 30分でクッキー失効
    updateAge: 0,                   // 毎リクエストでクッキー延長
  },
  callbacks: {
    async jwt({ token, user, account }) {
      // 初回ログイン時: 絶対有効期限を埋め込む
      if (account &amp;&amp; user) {
        return {
          ...token,
          absoluteExpiry: Date.now() + ABSOLUTE_TIMEOUT_SECONDS * 1000,
        }
      }

      // 絶対有効期限チェック
      if (Date.now() &gt; (token.absoluteExpiry as number)) {
        // 8時間経過 → セッション無効化
        return null
      }

      // ここに到達 = アイドルタイムアウト内 &amp; 絶対有効期限内
      // maxAge + updateAge:0 の組み合わせでクッキーが自動延長される
      return token
    },

    authorized({ auth, request }) {
      // BFF APIへのリクエストのみJWT検証
      const isApiRoute = request.nextUrl.pathname.startsWith(&quot;/api&quot;)
      if (isApiRoute) {
        return !!auth
      }
      return true
    },
  },
  pages: {
    signIn: &quot;/login&quot;,
  },
} satisfies NextAuthConfig
</code></pre>
<p><strong>フローの整理</strong>:</p>
<pre><code>[ユーザーログイン]
  → jwt callback: absoluteExpiry = now + 8h をトークンに埋め込み
  → クッキー保存（maxAge: 30min）

[5分後にAPIアクセス]
  → jwt callback: absoluteExpiry &gt; now → OK
  → updateAge: 0 → クッキーのExpiresをリセット（+30min）

[さらに30分放置]
  → クッキー自体が期限切れ → auth = null → 401

[8時間アクティブに操作し続けた場合]
  → jwt callback: absoluteExpiry &lt; now → return null → セッション強制無効化
</code></pre>
<h3 id="型定義の拡張" tabindex="-1">型定義の拡張</h3>
<p>TypeScriptで <code>absoluteExpiry</code> をトークンの型に追加する。</p>
<pre><code class="language-typescript">// types/next-auth.d.ts
import &quot;next-auth/jwt&quot;

declare module &quot;next-auth/jwt&quot; {
  interface JWT {
    absoluteExpiry: number
    djangoToken?: string
  }
}
</code></pre>
<h2 id="実装：next.js-middlewareでbff-api保護" tabindex="-1">実装：Next.js MiddlewareでBFF API保護</h2>
<p>MiddlewareはBFF APIルート（<code>/api/*</code>）へのリクエスト時にのみJWT検証を行う。ページ遷移にはMiddlewareを適用しない設計とする。</p>
<pre><code class="language-typescript">// middleware.ts
import NextAuth from &quot;next-auth&quot;
import { authConfig } from &quot;./auth.config&quot;

export const { auth: middleware } = NextAuth(authConfig)

export default middleware

export const config = {
  // BFF APIルートのみにマッチ
  matcher: [&quot;/api/:path*&quot;],
}
</code></pre>
<p>この設計の利点:</p>
<ul>
<li><strong>ページは自由にアクセス可能</strong>: 未認証でもページを表示し、フロントエンド側で認証状態に応じたUIを出し分ける</li>
<li><strong>API層で一括保護</strong>: 認証が必要なデータアクセスはすべてBFF API経由にすることで、保護漏れを防ぐ</li>
<li><strong>Edge Runtimeで動作</strong>: <code>auth.config.ts</code> のみを使うため、Node.js依存がない</li>
</ul>
<h3 id="認証失敗時のレスポンス" tabindex="-1">認証失敗時のレスポンス</h3>
<p><code>authorized</code> コールバックが <code>false</code> を返した場合、Auth.js v5はデフォルトで <code>pages.signIn</code> にリダイレクトする。APIルートの場合はリダイレクトではなく401レスポンスを返したいケースが多い。</p>
<pre><code class="language-typescript">// auth.config.ts の authorized コールバック（拡張版）
authorized({ auth, request }) {
  const isApiRoute = request.nextUrl.pathname.startsWith(&quot;/api&quot;)

  if (isApiRoute &amp;&amp; !auth) {
    return Response.json(
      { error: &quot;Unauthorized&quot; },
      { status: 401 }
    )
  }

  return true
},
</code></pre>
<h2 id="django連携：token-exchangeパターン" tabindex="-1">Django連携：Token Exchangeパターン</h2>
<h3 id="なぜauth.jsのトークンをdjangoに直接渡せないのか" tabindex="-1">なぜAuth.jsのトークンをDjangoに直接渡せないのか</h3>
<p>Auth.js v5のJWTはJWE（暗号化）されており、<code>AUTH_SECRET</code> とHKDFから導出された鍵で暗号化されている。Djangoでこの暗号化を再現するのは現実的ではない。</p>
<p>代わりに<strong>Token Exchangeパターン</strong>を使う。Auth.jsのJWTとは別に、Django独自のJWTを発行・管理する。</p>
<h3 id="アーキテクチャ" tabindex="-1">アーキテクチャ</h3>
<pre><code>[ログイン時]
  Browser → Next.js → Auth.js (認証)
                         ↓
                    jwt callback → Django /api/auth/token/ を呼び出し
                         ↓
                    Django JWT を取得 → Auth.js token に djangoToken として格納

[APIリクエスト時]
  Browser → /api/data → Middleware (Auth.js JWT検証)
                           ↓ OK
                      Route Handler → token から djangoToken を取り出し
                           ↓
                      Django API (Authorization: Bearer &lt;djangoToken&gt;)
                           ↓
                      Django が自身のJWTを独立検証
</code></pre>
<h3 id="実装" tabindex="-1">実装</h3>
<pre><code class="language-typescript">// auth.ts
import NextAuth from &quot;next-auth&quot;
import { authConfig } from &quot;./auth.config&quot;
import Credentials from &quot;next-auth/providers/credentials&quot;

export const { handlers, auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      async authorize(credentials) {
        // 1. ユーザー認証（DB検証等）
        const user = await verifyCredentials(credentials)
        if (!user) return null

        // 2. Django JWT を取得
        const djangoRes = await fetch(
          `${process.env.DJANGO_API_URL}/api/auth/token/`,
          {
            method: &quot;POST&quot;,
            headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
            body: JSON.stringify({
              username: credentials.email,
              password: credentials.password,
            }),
          }
        )
        const djangoTokens = await djangoRes.json()

        return {
          ...user,
          djangoToken: djangoTokens.access,
          djangoRefreshToken: djangoTokens.refresh,
        }
      },
    }),
  ],
  callbacks: {
    ...authConfig.callbacks,
    async jwt({ token, user, account }) {
      // 親の jwt callback を先に実行（absoluteExpiry チェック）
      const baseToken = await authConfig.callbacks.jwt({ token, user, account })
      if (!baseToken) return null

      // 初回ログイン時: Django トークンを格納
      if (account &amp;&amp; user) {
        baseToken.djangoToken = (user as any).djangoToken
        baseToken.djangoRefreshToken = (user as any).djangoRefreshToken
      }

      return baseToken
    },
  },
})
</code></pre>
<pre><code class="language-typescript">// app/api/data/route.ts（BFF APIの例）
import { auth } from &quot;@/auth&quot;

export async function GET() {
  const session = await auth()
  if (!session) {
    return Response.json({ error: &quot;Unauthorized&quot; }, { status: 401 })
  }

  // Django APIにトークンを転送
  const res = await fetch(`${process.env.DJANGO_API_URL}/api/data/`, {
    headers: {
      Authorization: `Bearer ${session.djangoToken}`,
    },
  })

  const data = await res.json()
  return Response.json(data)
}
</code></pre>
<h3 id="django側の設定" tabindex="-1">Django側の設定</h3>
<pre><code class="language-python"># settings.py
from datetime import timedelta

SIMPLE_JWT = {
    &quot;ACCESS_TOKEN_LIFETIME&quot;: timedelta(hours=8),  # Auth.js側と合わせる
    &quot;REFRESH_TOKEN_LIFETIME&quot;: timedelta(hours=8),
    &quot;ROTATE_REFRESH_TOKENS&quot;: False,
}

REST_FRAMEWORK = {
    &quot;DEFAULT_AUTHENTICATION_CLASSES&quot;: [
        &quot;rest_framework_simplejwt.authentication.JWTAuthentication&quot;,
    ],
}
</code></pre>
<p>Django側のトークン有効期限もAuth.js側の絶対有効期限（8時間）に合わせておくと、タイムアウトの不整合を防げる。</p>
<h3 id="django側でもjwt検証すべき理由" tabindex="-1">Django側でもJWT検証すべき理由</h3>
<p>Token Exchangeは「Django側でも検証する」設計が前提である。</p>
<ul>
<li><strong>多層防御</strong>: Next.js BFFのバグがあってもDjango側で不正リクエストを遮断できる</li>
<li><strong>将来の拡張</strong>: モバイルアプリや他のクライアントがDjango APIに直接アクセスする場合にも対応可能</li>
<li><strong>責務の分離</strong>: 認証の責任がNext.jsに集中しない</li>
</ul>
<h2 id="落とし穴と対策" tabindex="-1">落とし穴と対策</h2>
<h3 id="1.-トークンリフレッシュのrace-condition" tabindex="-1">1. トークンリフレッシュのRace Condition</h3>
<p>複数のAPIリクエストが同時に発生した場合、<code>jwt</code> コールバックが並行して実行される。Djangoのリフレッシュトークンがワンタイム（<code>ROTATE_REFRESH_TOKENS: True</code>）の場合、先に実行されたリクエストがトークンをローテーションし、後続のリクエストが古いリフレッシュトークンで失敗する。</p>
<p><strong>対策</strong>: Django側で <code>ROTATE_REFRESH_TOKENS: False</code> に設定するか、Auth.js側でリフレッシュにロック機構を実装する。この記事の設計ではアクセストークンの有効期限を8時間にしているため、頻繁なリフレッシュは発生しない。</p>
<h3 id="2.-edge-runtime互換" tabindex="-1">2. Edge Runtime互換</h3>
<p><code>middleware.ts</code> はEdge Runtimeで動作する。Node.jsの <code>crypto</code> モジュールを直接使うコードがあるとランタイムエラーになる。</p>
<p><strong>対策</strong>: <code>auth.config.ts</code>（Edge互換）と <code>auth.ts</code>（Node.js用）を分割し、MiddlewareからはEdge互換のファイルのみを参照する。</p>
<h3 id="3.-updateage:-0-のトレードオフ" tabindex="-1">3. updateAge: 0 のトレードオフ</h3>
<p><code>updateAge: 0</code> は毎リクエストでSet-Cookieヘッダーを返すことを意味する。CDNキャッシュとの相性が悪く、レスポンスヘッダーのサイズも増える。</p>
<p><strong>対策</strong>: APIルートのみにMiddlewareを適用し（<code>matcher: [&quot;/api/:path*&quot;]</code>）、静的アセットやページへのリクエストではクッキー更新が走らないようにする。パフォーマンスへの影響が気になる場合は <code>updateAge: 60</code>（1分）程度に設定し、厳密な30分ではなく最大31分のアイドルタイムアウトとして許容する方法もある。</p>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>本記事の設計パターンの全体像を整理する。</p>
<pre><code>┌──────────────┐
│   Browser    │
└──────┬───────┘
       │ fetch('/api/...')
       ▼
┌──────────────────────────────────────┐
│  Next.js Middleware (Edge Runtime)    │
│  ┌────────────────────────────────┐  │
│  │ Auth.js jwt callback           │  │
│  │ 1. absoluteExpiry &gt; now?       │  │
│  │ 2. authorized → auth 存在?     │  │
│  └────────────────────────────────┘  │
│  maxAge: 30min / updateAge: 0        │
└──────┬───────────────────────────────┘
       │ 検証OK
       ▼
┌──────────────────────────────────────┐
│  Next.js BFF API (Route Handler)     │
│  token から djangoToken を取り出し     │
│  Authorization: Bearer &lt;djangoToken&gt;  │
└──────┬───────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────┐
│  Django REST Framework               │
│  simplejwt で独立検証                  │
│  ACCESS_TOKEN_LIFETIME: 8h            │
└──────────────────────────────────────┘
</code></pre>
<h3 id="各コンポーネントの責務" tabindex="-1">各コンポーネントの責務</h3>
<table>
<thead>
<tr>
<th>コンポーネント</th>
<th>責務</th>
</tr>
</thead>
<tbody>
<tr>
<td>Auth.js <code>jwt</code> callback</td>
<td>二重タイムアウト管理（絶対8h + アイドル30min）</td>
</tr>
<tr>
<td>Next.js Middleware</td>
<td>BFF APIルートの認証ゲート</td>
</tr>
<tr>
<td>Next.js Route Handler</td>
<td>Django APIへのプロキシ + トークン転送</td>
</tr>
<tr>
<td>Django simplejwt</td>
<td>独立したトークン検証（多層防御）</td>
</tr>
</tbody>
</table>
<h3 id="auth.js-v5の設定値まとめ" tabindex="-1">Auth.js v5の設定値まとめ</h3>
<table>
<thead>
<tr>
<th>設定</th>
<th>値</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>session.strategy</code></td>
<td><code>&quot;jwt&quot;</code></td>
<td>JWTセッション戦略を使用</td>
</tr>
<tr>
<td><code>session.maxAge</code></td>
<td><code>1800</code></td>
<td>アイドルタイムアウト（30分）</td>
</tr>
<tr>
<td><code>session.updateAge</code></td>
<td><code>0</code></td>
<td>毎リクエストでスライディングウィンドウ</td>
</tr>
<tr>
<td><code>token.absoluteExpiry</code></td>
<td><code>now + 8h</code></td>
<td>カスタムフィールドで絶対有効期限を管理</td>
</tr>
</tbody>
</table>
<p>この設計により、「普段は操作に応じてセッションが延長されるが、8時間後には再認証が必要」という要件を Auth.js v5 のネイティブ機能とカスタム実装の組み合わせで実現できる。</p>
]]></content:encoded>
            <category>Programming</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/pqkFfHKpf2UqoNrB6KrKS/672a78076f28d88b31509c230f9c3f64/Auth.js_v5_%C3%A3__%C3%A3__JWT%C3%A4%C2%BA_%C3%A9__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A5__%C3%A8__%C3%A3__%C3%A3__%C3%AF__BFF___Django%C3%A9__%C3%A6_%C2%BA%C3%A3__%C3%A8__%C3%A8__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[仕様書を「渡す」からCLAUDE.mdを「育てる」へ — Claude Codeの使い方が変わった話]]></title>
            <link>https://www.yoshihiko.tech/blog/claude-code-spec-to-growing-claudemd</link>
            <guid isPermaLink="false">claude-code-spec-to-growing-claudemd</guid>
            <pubDate>Thu, 02 Apr 2026 14:58:11 GMT</pubDate>
            <description><![CDATA[Claude Codeに仕様書を丸ごと渡す運用から、CLAUDE.mdを継続的に育てる運用へ。「間違えたら追加」ルールの実践例と、20行→146行+31スキルに育てた記録。]]></description>
            <content:encoded><![CDATA[<p>Claude Codeを使い始めたとき、私は仕様書のMarkdownを丸ごとコピペして「この通りに実装して」と指示していた。結果は散々だった。いま最も手をかけているプロジェクトでは、CLAUDE.mdが146行、ルールファイル12本、スキル31個にまで育っている。仕様書を「渡す」から、CLAUDE.mdを「育てる」に切り替えたことで、Claude Codeの使い勝手がまるで変わった。</p>
<h2 id="claude.mdの基本をおさらいする" tabindex="-1">CLAUDE.mdの基本をおさらいする</h2>
<p>本題に入る前に、CLAUDE.mdの基本を簡単に振り返る。</p>
<p>CLAUDE.mdはClaude Codeがセッション開始時に自動で読み込む設定ファイルである。プロジェクトルートに置くだけで、毎回のセッションで自動的にコンテキストとして注入される。</p>
<p>読み込みには3つの階層がある。</p>
<ul>
<li><strong>グローバル</strong>（<code>~/.claude/CLAUDE.md</code>）: 全プロジェクトに共通する個人の好み</li>
<li><strong>プロジェクト</strong>（<code>プロジェクトルート/.claude/CLAUDE.md</code>）: リポジトリ固有の文脈</li>
<li><strong>サブディレクトリ</strong>（各ディレクトリの<code>CLAUDE.md</code>）: 領域固有の設定</li>
</ul>
<p>新しいチームメンバーに渡す「業務マニュアル」のようなものだ。これがないと、Claude Codeは毎回ゼロからプロジェクトを理解しようとする。</p>
<p>ここまでは入門レベルの話で、すでに解説記事も多い。この記事で書きたいのはその先、CLAUDE.mdを「完成させるもの」ではなく「育てるもの」として扱う考え方についてである。</p>
<h2 id="仕様書丸投げが失敗する構造的な理由" tabindex="-1">仕様書丸投げが失敗する構造的な理由</h2>
<p>Claude Codeに仕様書Markdownを丸ごと貼り付けて「この通りに実装して」と指示すると、3つの構造的な問題が起きる。</p>
<p><strong>1. コンテキスト過負荷で重要部分が埋もれる</strong></p>
<p>LLMのコンテキストウィンドウには物理的な上限がある。1万行の仕様書を渡した場合、モデルの注意が全体に散らばる。人間なら「ここが重要」と読み飛ばせるが、LLMにはその取捨選択が難しい。仕様書の後半に書いた制約条件が実装に反映されない、というのはよくある話だ。</p>
<p><strong>2. 既存コードのパターンを無視する</strong></p>
<p>仕様書を「正」として渡すと、Claude Codeは既存のコーディングパターンより仕様書の記述を優先する。プロジェクトでは<code>camelCase</code>を使っているのに、仕様書に<code>snake_case</code>の例が含まれていればそちらに引きずられる。既存コードとの一貫性が崩れるのは、だいたいこれが原因だ。</p>
<p><strong>3. 複数ファイルの整合性が崩れる</strong></p>
<p>仕様書は往々にして複数の機能を横断的に記述している。「認証フロー」の仕様にはフロントエンド・バックエンド・DB設計が混在する。Claude Codeはこれを一度に処理しようとして、ファイル間の依存関係を見失う。型定義とAPIレスポンスの不整合、<code>import</code>の漏れ、テストとの乖離が生まれる。</p>
<p>プロンプトの書き方が悪いのではない。仕様書を丸ごと渡すこと自体に無理がある。</p>
<h2 id="「渡す」と「育てる」は何が違うのか" tabindex="-1">「渡す」と「育てる」は何が違うのか</h2>
<p>仕様書を「渡す」とは、セッションのたびにコンテキストを入れ直す作業だ。セッションが終われば消える。次のセッションではまた同じ説明。チームで使っていれば、人によって渡す内容がバラバラになり、出力の品質もバラつく。</p>
<p>CLAUDE.mdを「育てる」とは、プロジェクトの前提知識をファイルに残していくことだ。一度書けば全セッションで勝手に読み込まれる。gitに入れておけばチーム全員が同じ前提で作業できる。プロジェクトが進むにつれて、CLAUDE.md自体も一緒に育っていく。</p>
<p>要するに、**仕様書は「その場限りの指示書」、CLAUDE.mdは「育つ業務マニュアル」**だ。</p>
<h2 id="実践:-「間違えたら追加」ルール" tabindex="-1">実践: 「間違えたら追加」ルール</h2>
<p>では、CLAUDE.mdをどうやって「育てる」のか。</p>
<p>Claude Codeの開発に携わるBoris Chernyのチームは、約100行のCLAUDE.mdをgit管理し、「Claudeが間違えたらCLAUDE.mdに追加する」をチームルールにしているらしい。このやり方がシンプルで強い。</p>
<p>私もこのアプローチを採用した。具体的なプロセスはこうなる。</p>
<ol>
<li>Claude Codeに作業を依頼する</li>
<li>出力が期待と異なったら、「何が足りなかったか」を特定する</li>
<li>その情報をCLAUDE.mdに追加する</li>
<li>次のセッションから同じ間違いが起きなくなる</li>
</ol>
<p>例を挙げる。初期のCLAUDE.mdにはディレクトリ構造しか書いていなかった。あるとき、Claude Codeが新しいファイルを<code>content/articles/</code>直下に作成した。実際には<code>content/articles/drafts/</code>に置くべきだった。そこで「コンテンツの成長フロー」をCLAUDE.mdに追加した。</p>
<pre><code class="language-markdown">## コンテンツの成長フロー

brainstorm → ideas → notes → articles
（ブレスト  → 思いつき → 学習・整理 → 公開記事）
</code></pre>
<p>これ以降、Claude Codeはファイルを正しい場所に配置するようになった。</p>
<p>別の例。Claude Codeがレビューで毎回同じ観点を見落としていた。「セキュリティレビューが必要なのはauth関連のファイル変更時」「パフォーマンスレビューが必要なのはDB関連の変更時」。こういう判断基準をルールファイルとして追加した。</p>
<p>このプロセスのポイントは、<strong>CLAUDE.mdの完成形を最初に設計しようとしない</strong>ことだ。実際に間違いが起きてから追加する。必要なものだけが残る。</p>
<h2 id="育てるclaude.mdの3つの原則" tabindex="-1">育てるCLAUDE.mdの3つの原則</h2>
<p>使い続ける中で、意識するようになったことが3つある。</p>
<h3 id="原則1:-「コードを読めばわかるか？」で判断する" tabindex="-1">原則1: 「コードを読めばわかるか？」で判断する</h3>
<p>何を書いて何を書かないか。判断基準はひとつ、「この情報はコードを読めばわかるか？」だ。</p>
<ul>
<li><strong>Yes → 書かない</strong>: 技術スタック、ライブラリのバージョン、関数の引数仕様。これらはコードから読み取れる</li>
<li><strong>No → 書く</strong>: なぜこの技術を選んだか、どんなワークフローで運用しているか、ファイルの命名規則。これらはコードに表現されていない</li>
</ul>
<p>例えば「React + Next.jsのプロジェクト」はpackage.jsonを見ればわかる。書く必要はない。一方「週次レビューはNotionからJSON抽出して<code>/weekly-review</code>スキルで生成する」はコードのどこにも書かれていない。CLAUDE.mdに書く価値がある。</p>
<h3 id="原則2:-仕様書はチケット単位で渡す" tabindex="-1">原則2: 仕様書はチケット単位で渡す</h3>
<p>仕様書を全廃する必要はない。「渡し方」を変えればよい。</p>
<p>仕様書をGitHub IssuesやLinearのチケット粒度に分解する。1チケット = 1機能。各チケットには「何を」「なぜ」「受け入れ基準」だけ書く。実装方法は書かない。既存のコードパターンに合わせるかどうかはCLAUDE.mdが教えてくれる。</p>
<pre><code># チケット: ログイン画面にパスワードリセット機能を追加

## 何を
パスワードリセットのフローを追加する

## なぜ
ユーザーサポートへのパスワードリセット依頼が月50件ある

## 受け入れ基準
- メールアドレス入力 → リセットリンク送信 → 新パスワード設定の3ステップ
- リセットリンクの有効期限は24時間
- 既存の認証フローと同じUIコンポーネントを使用する
</code></pre>
<p>「どう実装するか」はClaude Codeがコードベースとルールファイルから判断する。</p>
<h3 id="原則3:-薄く保ち、分離で拡張する" tabindex="-1">原則3: 薄く保ち、分離で拡張する</h3>
<p>CLAUDE.mdは薄く保つ。100行を超えたら分離を検討する。</p>
<p>Claude Codeは<code>.claude/rules/</code>ディレクトリに置いたMarkdownファイルを自動で読み込む。コーディング原則、レビューポリシー、外部ツール連携の設定。関心事ごとにファイルを分けて、CLAUDE.md自体はプロジェクトの概要とワークフローに絞る。</p>
<p>繰り返し使うワークフローは<code>.claude/skills/</code>としてスキル化もできる。「週次レビューを生成する」「SEO記事を書く」「YouTube台本を作る」。こういったものを個別ファイルに切り出していくと、CLAUDE.mdはスキル一覧表のように簡潔に保てる。</p>
<h2 id="あるプロジェクトでclaude.mdが育った過程" tabindex="-1">あるプロジェクトでCLAUDE.mdが育った過程</h2>
<p>最も手をかけている個人プロジェクトの変遷を例に出す。</p>
<p><strong>初期（20行）</strong>: プロジェクト概要とディレクトリ構造だけ。「このプロジェクトは個人のデジタルガーデンです」「content/以下にarticles, notes, ideasがある」程度。</p>
<p><strong>60行</strong>: コンテンツの成長フロー（brainstorm → ideas → notes → articles）を追加。情報収集の日次/週次/月次サイクルを定義。ファイル命名規則も追加。きっかけは、Claude Codeがファイル名を統一的に付けてくれなかったこと。</p>
<p><strong>478行に膨張</strong>: スキルの定義、レビューポリシー、外部CLI連携の設定を全てCLAUDE.mdに書き込んだ。やりたいことが多く、あれもこれもと詰め込んだ結果こうなった。毎セッションのコンテキストウィンドウを大量に消費していた。</p>
<p><strong>146行に圧縮 + 分離</strong>: さすがに限界を感じて整理した。478行を146行に圧縮し、残りを<code>.claude/rules/</code>（12ファイル）と<code>.claude/skills/</code>（31スキル）に分離。CLAUDE.mdはプロジェクト概要・ワークフロー・スキル一覧だけを保持する「目次」になった。</p>
<p><strong>現在</strong>: CLAUDE.mdは146行で安定。新しいルールやスキルが必要になれば、CLAUDE.mdではなく専用ファイルに追加する。ルールファイルやスキルファイルは日常的に更新しているが、CLAUDE.md自体はたまにしか触らない。</p>
<p>振り返って思うのは、<strong>膨張は通過点だった</strong>ということ。最初から分離設計する必要はない。まずCLAUDE.mdに全部書いて、大きくなったら分ける。この順番が結局いちばん自然だった。</p>
<p>もうひとつ、<strong>行数はそのままメンテナンスコストになる</strong>。478行のCLAUDE.mdは、何がどこに書いてあるか把握しきれなかった。146行なら一読で全体像がつかめる。</p>
<h2 id="まとめ:-claude.mdは「完成」しない" tabindex="-1">まとめ: CLAUDE.mdは「完成」しない</h2>
<p>CLAUDE.mdは完成しない。プロジェクトが続く限り、育ち続ける。</p>
<p>仕様書を毎回渡すのは、セッションごとにリセットされる繰り返しだ。CLAUDE.mdを育てていけば、プロジェクトの知識がファイルに残り続ける。</p>
<p>やることはシンプルだ。</p>
<ol>
<li><strong>今日から始める</strong>: CLAUDE.mdがなければ、プロジェクト概要を10行書くところから</li>
<li><strong>間違いを燃料にする</strong>: Claude Codeが期待と異なる出力をしたら、足りなかった情報をCLAUDE.mdに追加する</li>
<li><strong>膨張したら分離する</strong>: 100行を超えたら<code>.claude/rules/</code>への切り出しを検討する</li>
<li><strong>チケット単位で渡す</strong>: 仕様書を丸ごと渡さず、「何を」「なぜ」「受け入れ基準」に分解する</li>
</ol>
<p>CLAUDE.mdは「渡すファイル」ではない。プロジェクトと一緒に育てていくものだ。</p>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/qybhQ2TQSYQhIvDhWxqvs/9f8b888e11a76223c8a1b1da42df5ce1/%C3%A4__%C3%A6__%C3%A6__%C3%A3__%C3%A3__%C3%A6__%C3%A3__%C3%A3__%C3%A3__%C3%A3__CLAUDE.md%C3%A3__%C3%A3__%C3%A8__%C3%A3__%C3%A3__%C3%A3__%C3%A3___%C3%A2___Claude_Code%C3%A3__%C3%A4__%C3%A3__%C3%A6__%C3%A3__%C3%A3__%C3%A5__%C3%A3__%C3%A3__%C3%A3__%C3%A8__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Claude Codeを覚醒させる「ハーネスエンジニアリング」 CLAUDE.md・Hooks・Rules設定の徹底解説]]></title>
            <link>https://www.yoshihiko.tech/blog/claude-code-harness-engineering</link>
            <guid isPermaLink="false">claude-code-harness-engineering</guid>
            <pubDate>Sun, 22 Mar 2026 08:59:48 GMT</pubDate>
            <description><![CDATA[プロンプトを書く時代から、AIが最高に働く環境を設計する時代へ。Claude CodeのCLAUDE.md、Hooks、Rules、Settings、Agent Routingを体系的に解説し、自作のオーケストレーションシステム「ai-orchestra」による統合管理の実践例を紹介する。]]></description>
            <content:encoded><![CDATA[<p>Claude Codeの真価は、プロンプトの書き方ではなく「環境設計」で決まる。CLAUDE.md、Hooks、Rules、Agent Routingを体系的に設計することで、AIコーディングアシスタントは単なるチャットボットから、プロジェクトの文脈を理解し自律的に動く開発パートナーへと変わる。</p>
<p>この記事では、Claude Codeの設定機構を「ハーネスエンジニアリング」として体系化し、各レイヤーの設計方法から、自作のオーケストレーションシステム「ai-orchestra」による統合管理まで、実践的に解説する。</p>
<hr>
<h2 id="プロンプトから「環境構築」へ──ハーネスエンジニアリングとは" tabindex="-1">プロンプトから「環境構築」へ──ハーネスエンジニアリングとは</h2>
<h3 id="プロンプトエンジニアリングとの違い" tabindex="-1">プロンプトエンジニアリングとの違い</h3>
<p>プロンプトエンジニアリングは「何を聞くか」の技術である。質問の仕方、few-shotの工夫、Chain-of-Thoughtの誘導——これらはすべて<strong>1回のやりとり</strong>を最適化するアプローチだ。</p>
<p>ハーネスエンジニアリングは「AIが常に最適な状態で働ける環境を設計する」技術である。ここでいう「ハーネス」とは、Claude Codeを制御する設定群（CLAUDE.md、Hooks、Rules、Settings）の総称だ。テストハーネスがテスト実行環境を整備するように、AIハーネスはAIの実行環境を整備する。</p>
<table>
<thead>
<tr>
<th>比較軸</th>
<th>プロンプトエンジニアリング</th>
<th>ハーネスエンジニアリング</th>
</tr>
</thead>
<tbody>
<tr>
<td>最適化対象</td>
<td>1回の対話</td>
<td>全セッション</td>
</tr>
<tr>
<td>持続性</td>
<td>その場限り</td>
<td>プロジェクトに永続化</td>
</tr>
<tr>
<td>共有性</td>
<td>個人の知見</td>
<td>チーム全体に適用可能</td>
</tr>
<tr>
<td>制御の強さ</td>
<td>ソフト（LLMが解釈）</td>
<td>ハード（プログラムで強制）も可</td>
</tr>
<tr>
<td>スケーラビリティ</td>
<td>対話ごとに再入力</td>
<td>一度設計すれば自動適用</td>
</tr>
</tbody>
</table>
<h3 id="なぜ「ハーネス」が必要か" tabindex="-1">なぜ「ハーネス」が必要か</h3>
<p>Claude Codeの使い方を「毎回プロンプトで指示する」段階から脱却すべき3つの理由がある。</p>
<p><strong>1. コンテキストの一貫性</strong></p>
<p>プロジェクトの規約、ディレクトリ構造、命名規則——これらを毎回プロンプトで伝えるのは非効率である。CLAUDE.mdに一度書けば、全セッションで自動的にコンテキストとして読み込まれる。</p>
<p><strong>2. ガードレールの決定性</strong></p>
<p>「テストを必ず書いて」というプロンプトはLLMが無視する可能性がある。一方、PreToolUseフックでテスト未実行の実装を検知してブロックすれば、ルール違反を<strong>プログラムで</strong>防げる。</p>
<p><strong>3. チームスケール</strong></p>
<p>個人のプロンプト技術はチームに共有しにくい。ハーネス（<code>.claude/</code>配下のファイル群）はgitで管理し、チーム全員に同じ開発体験を提供できる。</p>
<hr>
<h2 id="claude.mdの設計──プロジェクトの文脈をaiにインストールする" tabindex="-1">CLAUDE.mdの設計──プロジェクトの文脈をAIにインストールする</h2>
<p>CLAUDE.mdはClaude Codeが<strong>セッション開始時に自動で読み込む</strong>設定ファイルである。LLM向けの「意味的な指示」を書く場所であり、ハーネスの土台となるレイヤーだ。</p>
<h3 id="3つの階層" tabindex="-1">3つの階層</h3>
<p>Claude Codeは以下の順でCLAUDE.mdを読み込み、すべてをコンテキストに含める。</p>
<pre><code>~/.claude/CLAUDE.md              ← グローバル（全プロジェクト共通）
.claude/CLAUDE.md                ← プロジェクトルート（チーム共有）
src/backend/.claude/CLAUDE.md    ← サブディレクトリ（領域固有）
</code></pre>
<p><strong>グローバル</strong>には言語設定やコミュニケーションの好みなど、プロジェクトに依存しない個人設定を書く。</p>
<pre><code class="language-markdown"># ~/.claude/CLAUDE.md

## Communication
- 文章は簡潔に。ただし重要な理由や前提は箇条書きで補足してください。
- 不明点が多い場合は、すぐに大きな変更をせず前提の確認から始めてください。

## General coding policy
- 既存の仕様や振る舞いを壊さないことを最優先してください。
- 大きな変更より、小さく安全なステップでの修正を優先してください。
</code></pre>
<p><strong>プロジェクトルート</strong>にはリポジトリ固有の文脈を書く。ディレクトリ構造、ワークフロー、使用中のスキル一覧など。</p>
<pre><code class="language-markdown"># .claude/CLAUDE.md

## プロジェクト概要
このリポジトリは個人のデジタルガーデンです。

## ディレクトリ構造
digital-garden/
├── content/articles/   # ブログ記事（published/ drafts/ archive/）
├── content/notes/      # 学習ノート
└── reviews/            # 振り返り（weekly/ monthly/ yearly/）

## コンテンツの成長フロー
brainstorm → ideas → notes → articles
（ブレスト  → 思いつき → 学習・整理 → 公開記事）
</code></pre>
<h3 id="効果的なclaude.mdの書き方" tabindex="-1">効果的なCLAUDE.mdの書き方</h3>
<p><strong>含めるべき情報:</strong></p>
<ul>
<li>プロジェクトの概要（1-2文）</li>
<li>ディレクトリ構造（主要なもののみ）</li>
<li>命名規則・コーディング規約</li>
<li>テスト実行コマンド</li>
<li>ワークフロー（データの流れ、デプロイ手順）</li>
<li>使用中のスキル一覧</li>
</ul>
<p><strong>含めるべきでない情報:</strong></p>
<ul>
<li>実装の詳細（コードを読めばわかること）</li>
<li>一時的な情報（今の作業内容）</li>
<li>巨大なリスト（コンテキストウィンドウを圧迫する）</li>
</ul>
<p>コンテキストウィンドウの消費を意識し、「Claude Codeがこの情報を持っていないと正しい判断ができない」ものだけを書くのが原則である。</p>
<hr>
<h2 id="settings階層とrulesシステムによるaiの挙動制御" tabindex="-1">Settings階層とRulesシステムによるAIの挙動制御</h2>
<h3 id="settings.jsonの3階層" tabindex="-1">settings.jsonの3階層</h3>
<p>Claude Codeの機能設定は<code>settings.json</code>で管理する。CLAUDE.mdがLLM向けの「意味的な指示」であるのに対し、settings.jsonは<strong>プログラムレベルの設定</strong>である。</p>
<pre><code>~/.claude/settings.json                ← ユーザー設定（全プロジェクト共通）
.claude/settings.json                  ← プロジェクト設定（git管理、チーム共有）
.claude/settings.local.json            ← ローカル設定（gitignore、個人上書き）
</code></pre>
<p>各階層はマージされ、下位の設定が上位を上書きする。</p>
<pre><code class="language-jsonc">// .claude/settings.json（チーム共有）
{
  &quot;permissions&quot;: {
    &quot;allow&quot;: [&quot;Bash(npm run test)&quot;, &quot;Bash(npm run lint)&quot;],
    &quot;deny&quot;: [&quot;Bash(rm -rf)&quot;]
  }
}
</code></pre>
<pre><code class="language-jsonc">// .claude/settings.local.json（個人、gitignore対象）
{
  &quot;hooks&quot;: {
    &quot;SessionStart&quot;: [
      {
        &quot;type&quot;: &quot;command&quot;,
        &quot;command&quot;: &quot;python3 $AI_ORCHESTRA_DIR/scripts/sync-orchestra.py&quot;
      }
    ]
  }
}
</code></pre>
<p>チームで共有すべき権限設定は<code>settings.json</code>に、個人のフック設定や環境依存の設定は<code>settings.local.json</code>に配置する。<code>.local.json</code>は<code>.gitignore</code>に追加する。</p>
<h3 id=".claude/rules/によるルールのモジュール分割" tabindex="-1">.claude/rules/によるルールのモジュール分割</h3>
<p><code>.claude/rules/</code>ディレクトリに<code>.md</code>ファイルを配置すると、Claude Codeが自動で読み込む。CLAUDE.mdに全ルールを書くとファイルが肥大化するため、関心事ごとにファイルを分割するのが有効である。</p>
<pre><code>.claude/rules/
├── coding-principles.md          # コード品質ルール
├── config-loading.md             # 設定ファイルの読み込み手順
├── agent-routing-policy.md       # エージェントルーティングポリシー
├── codex-delegation.md           # Codex CLI委譲ルール
├── gemini-delegation.md          # Gemini CLI委譲ルール
├── skill-review-policy.md        # レビューポリシー
├── task-memory-usage.md          # タスク状態管理ルール
└── context-sharing.md            # コンテキスト共有ルール
</code></pre>
<p>各ルールファイルは独立したドキュメントとして完結させる。例えば<code>coding-principles.md</code>には以下のような内容を書く。</p>
<pre><code class="language-markdown"># Code Quality Policy

## シンプルさ優先
- 読みやすいコードを複雑なコードより選ぶ
- 過度な抽象化を避ける

## Early Return
ネストを浅く保つために Early Return を使う。
ネスト深度は2以下を目標とする。

## 型ヒント必須
すべての関数に型アノテーションを付ける。
</code></pre>
<p>ルールにはYAMLフロントマターで<code>paths:</code>を指定でき、特定のディレクトリでのみ有効にすることも可能だ。</p>
<pre><code class="language-markdown">---
paths:
  - &quot;src/backend/**&quot;
---
# Backend Rules
バックエンド固有のルールをここに書く。
</code></pre>
<h3 id="claude.md-vs-rules:-使い分け" tabindex="-1">CLAUDE.md vs Rules: 使い分け</h3>
<table>
<thead>
<tr>
<th></th>
<th>CLAUDE.md</th>
<th>.claude/rules/</th>
</tr>
</thead>
<tbody>
<tr>
<td>用途</td>
<td>プロジェクト概要、ワークフロー</td>
<td>コーディング規約、ポリシー</td>
</tr>
<tr>
<td>スコープ</td>
<td>常にすべて読み込み</td>
<td>パス制限可能</td>
</tr>
<tr>
<td>更新頻度</td>
<td>低い（構造変更時）</td>
<td>中程度（ルール追加・修正）</td>
</tr>
<tr>
<td>推奨サイズ</td>
<td>簡潔に（200行以内）</td>
<td>ファイルごとに分割</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="hooksシステム──claude-codeのワークフローを自動化・拡張する" tabindex="-1">Hooksシステム──Claude Codeのワークフローを自動化・拡張する</h2>
<p>HooksはClaude Codeの最も強力なハーネス機構である。CLAUDE.mdやRulesがLLMへの「お願い」（Soft Rule）であるのに対し、Hooksはプログラムで<strong>確実に実行される</strong>（Hard Rule）。</p>
<h3 id="soft-rule-vs-hard-rule" tabindex="-1">Soft Rule vs Hard Rule</h3>
<pre><code>Soft Rule（CLAUDE.md / Rules）
  → LLMが解釈して従う。無視される可能性がある。
  → 例: 「テストを必ず書いてください」

Hard Rule（Hooks）
  → プログラムが機械的に実行する。回避不可能。
  → 例: PreToolUse hookでテスト未実行を検知し、exit code 2でブロック
</code></pre>
<p>この区別を理解することが、ハーネス設計の核心である。</p>
<h3 id="6種のイベントタイプ" tabindex="-1">6種のイベントタイプ</h3>
<table>
<thead>
<tr>
<th>イベント</th>
<th>発火タイミング</th>
<th>主な用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>SessionStart</code></td>
<td>セッション開始時</td>
<td>設定の同期、状態の読み込み</td>
</tr>
<tr>
<td><code>SessionEnd</code></td>
<td>セッション終了時</td>
<td>クリーンアップ、ログ出力</td>
</tr>
<tr>
<td><code>UserPromptSubmit</code></td>
<td>ユーザーがプロンプトを送信した後</td>
<td>コンテキスト注入、ルーティング提案</td>
</tr>
<tr>
<td><code>PreToolUse</code></td>
<td>ツール実行前</td>
<td>ブロック判定、承認フロー</td>
</tr>
<tr>
<td><code>PostToolUse</code></td>
<td>ツール実行後</td>
<td>結果キャプチャ、自動整形</td>
</tr>
<tr>
<td><code>SubagentStart</code> / <code>SubagentStop</code></td>
<td>サブエージェントのライフサイクル</td>
<td>モニタリング、リソース管理</td>
</tr>
</tbody>
</table>
<h3 id="hookの設定方法" tabindex="-1">Hookの設定方法</h3>
<p><code>settings.json</code>（または<code>settings.local.json</code>）にフック定義を追加する。</p>
<pre><code class="language-jsonc">{
  &quot;hooks&quot;: {
    &quot;PreToolUse&quot;: [
      {
        &quot;type&quot;: &quot;command&quot;,
        &quot;command&quot;: &quot;python3 ./hooks/check-codex-before-write.py&quot;,
        &quot;matcher&quot;: &quot;Edit|Write&quot;
      }
    ],
    &quot;PostToolUse&quot;: [
      {
        &quot;type&quot;: &quot;command&quot;,
        &quot;command&quot;: &quot;python3 ./hooks/lint-on-save.py&quot;,
        &quot;matcher&quot;: &quot;Edit&quot;
      }
    ]
  }
}
</code></pre>
<p><code>matcher</code>はツール名の正規表現で、特定のツール使用時のみフックを発火させる。</p>
<h3 id="pretooluse:-exit-code-2でツールをブロック" tabindex="-1">PreToolUse: exit code 2でツールをブロック</h3>
<p>PreToolUseフックがexit code <code>2</code>を返すと、Claude Codeはそのツール呼び出しをブロックする。これがHard Ruleの正体である。</p>
<pre><code class="language-python">#!/usr/bin/env python3
&quot;&quot;&quot;check-codex-before-write.py
core/ 配下のファイル変更前にCodexへの相談を提案する。
&quot;&quot;&quot;
import sys
import json

input_data = json.loads(sys.stdin.read())
tool_name = input_data.get(&quot;tool_name&quot;, &quot;&quot;)
tool_input = input_data.get(&quot;tool_input&quot;, {})

file_path = tool_input.get(&quot;file_path&quot;, &quot;&quot;)

# core/ 配下のファイル変更は Codex に相談すべき
if &quot;core/&quot; in file_path:
    print(&quot;[Codex Suggestion] core/ の変更を検出しました。&quot;)
    print(&quot;Codex に設計相談してから編集してください。&quot;)
    # exit code 2 = ツール実行をブロック
    sys.exit(2)
</code></pre>
<h3 id="userpromptsubmit:-コンテキスト注入とルーティング" tabindex="-1">UserPromptSubmit: コンテキスト注入とルーティング</h3>
<p>UserPromptSubmitフックはプロンプト送信後に発火し、stdoutに出力したテキストがClaude Codeのコンテキストに追加される。エージェントルーティングの提案や、外部CLIへの委譲指示に利用できる。</p>
<pre><code class="language-python">#!/usr/bin/env python3
&quot;&quot;&quot;agent-router.py
ユーザーのプロンプトを分析し、最適なエージェントを提案する。
&quot;&quot;&quot;
import sys
import json

input_data = json.loads(sys.stdin.read())
prompt = input_data.get(&quot;prompt&quot;, &quot;&quot;)

# キーワードベースの簡易ルーティング
if any(kw in prompt for kw in [&quot;Python&quot;, &quot;FastAPI&quot;, &quot;Django&quot;]):
    print(&quot;[Agent Routing] 'Python' → `backend-python-dev` (tool: codex):&quot;)
    print('Task(subagent_type=&quot;backend-python-dev&quot;, prompt=&quot;...&quot;)')
</code></pre>
<h3 id="実践的なhook構成例" tabindex="-1">実践的なHook構成例</h3>
<p>以下は筆者が実際に使用しているHook構成（一部抜粋）である。</p>
<table>
<thead>
<tr>
<th>フック</th>
<th>イベント</th>
<th>目的</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sync-orchestra.py</code></td>
<td>SessionStart</td>
<td>パッケージの自動同期</td>
</tr>
<tr>
<td><code>load-task-state.py</code></td>
<td>SessionStart</td>
<td>Plans.mdからタスク状態を読み込み</td>
</tr>
<tr>
<td><code>agent-router.py</code></td>
<td>UserPromptSubmit</td>
<td>エージェントルーティング提案</td>
</tr>
<tr>
<td><code>check-codex-before-write.py</code></td>
<td>PreToolUse(Edit|Write)</td>
<td>重要ファイル変更前のCodex相談提案</td>
</tr>
<tr>
<td><code>suggest-gemini-research.py</code></td>
<td>PreToolUse(WebSearch|WebFetch)</td>
<td>Geminiリサーチへの委譲提案</td>
</tr>
<tr>
<td><code>inject-shared-context.py</code></td>
<td>PreToolUse(Agent|Task)</td>
<td>サブエージェントへのコンテキスト注入</td>
</tr>
<tr>
<td><code>capture-task-result.py</code></td>
<td>PostToolUse(Agent|Task)</td>
<td>サブエージェント結果のキャプチャ</td>
</tr>
<tr>
<td><code>lint-on-save.py</code></td>
<td>PostToolUse(Edit)</td>
<td>保存時の自動lint</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="agent-routing──専門エージェントをタスクに最適配置する" tabindex="-1">Agent Routing──専門エージェントをタスクに最適配置する</h2>
<p>Claude Codeのサブエージェント機能を活用すると、タスクの種類に応じて専門エージェントに作業を委譲できる。ハーネスエンジニアリングでは、この委譲先を<strong>設定ファイルで一元管理</strong>する。</p>
<h3 id="cli-tools.yamlによるルーティング設計" tabindex="-1">cli-tools.yamlによるルーティング設計</h3>
<p>エージェントの実行ツール（Codex CLI / Gemini CLI / Claude直接処理）を1つのYAMLファイルで管理する。</p>
<pre><code class="language-yaml"># .claude/config/agent-routing/cli-tools.yaml

codex:
  enabled: true
  model: gpt-5.3-codex
  sandbox:
    analysis: read-only
    implementation: workspace-write
  flags: --full-auto

gemini:
  enabled: true
  model: gemini-3.1-pro-preview

# エージェントルーティング
agents:
  # レビュー系 → Claude直接処理（コンテキスト内で完結）
  code-reviewer:
    tool: claude-direct
  security-reviewer:
    tool: claude-direct
  architecture-reviewer:
    tool: claude-direct

  # 実装系 → Codex CLI（深い推論 + ファイル操作）
  backend-python-dev:
    tool: codex
    sandbox: workspace-write
  frontend-dev:
    tool: codex
    sandbox: workspace-write

  # リサーチ系 → Gemini CLI（大規模検索 + マルチモーダル）
  researcher:
    tool: gemini

  # 自動選択（タスク内容に応じて切り替え）
  general-purpose:
    tool: auto
</code></pre>
<h3 id="4つのルーティングモード" tabindex="-1">4つのルーティングモード</h3>
<table>
<thead>
<tr>
<th>モード</th>
<th>動作</th>
<th>適した場面</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>codex</code></td>
<td>OpenAI Codex CLIで実行</td>
<td>実装、デバッグ、設計判断</td>
</tr>
<tr>
<td><code>gemini</code></td>
<td>Google Gemini CLIで実行</td>
<td>リサーチ、ドキュメント分析、マルチモーダル</td>
</tr>
<tr>
<td><code>claude-direct</code></td>
<td>Claude Code自身で処理</td>
<td>レビュー、コンテキスト内で完結する作業</td>
</tr>
<tr>
<td><code>auto</code></td>
<td>タスク内容に応じて自動選択</td>
<td>汎用エージェント</td>
</tr>
</tbody>
</table>
<h3 id="ローカル上書き" tabindex="-1">ローカル上書き</h3>
<p>プロジェクト固有のモデルや設定は<code>.local.yaml</code>で上書きする。ベースファイルはai-orchestraから自動同期されるため、直接編集しない。</p>
<pre><code class="language-yaml"># .claude/config/agent-routing/cli-tools.local.yaml
codex:
  model: gpt-5.4-mini  # このプロジェクトだけ軽量モデルで実行
</code></pre>
<p>上書きルール: <code>.local.yaml</code>に定義されたキーだけがベースを上書きし、未定義のキーはベースの値を継承する。</p>
<hr>
<h2 id="実践例:-ai-orchestraによるハーネス統合管理" tabindex="-1">実践例: ai-orchestraによるハーネス統合管理</h2>
<p>ここまで個別に紹介してきたCLAUDE.md、Rules、Hooks、Agent Routingは、プロジェクトが大きくなるにつれ管理が煩雑になる。「コード品質ルール」を10プロジェクトで共有したい場合、手動コピーでは破綻する。</p>
<p>この課題を解決するために筆者が構築したのが<a href="https://github.com/yoshihiko555/ai-orchestra" target="_blank" rel="noopener noreferrer">ai-orchestra</a>である。</p>
<h3 id="パッケージシステム" tabindex="-1">パッケージシステム</h3>
<p>ai-orchestraはハーネスの構成要素を<strong>パッケージ</strong>として管理する。各パッケージは<code>manifest.json</code>で内容を定義する。</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;core&quot;,
  &quot;version&quot;: &quot;0.4.0&quot;,
  &quot;description&quot;: &quot;全パッケージ共通の基盤ライブラリ&quot;,
  &quot;depends&quot;: [],
  &quot;hooks&quot;: {
    &quot;SessionStart&quot;: [&quot;load-task-state.py&quot;],
    &quot;SessionEnd&quot;: [&quot;cleanup-session-context.py&quot;],
    &quot;PreToolUse&quot;: [
      { &quot;file&quot;: &quot;check-plan-gate.py&quot;, &quot;matcher&quot;: &quot;Agent|Task&quot; },
      { &quot;file&quot;: &quot;inject-shared-context.py&quot;, &quot;matcher&quot;: &quot;Agent|Task&quot; }
    ],
    &quot;PostToolUse&quot;: [
      { &quot;file&quot;: &quot;capture-task-result.py&quot;, &quot;matcher&quot;: &quot;Agent|Task&quot; },
      { &quot;file&quot;: &quot;update-working-context.py&quot;, &quot;matcher&quot;: &quot;Edit&quot; }
    ]
  },
  &quot;skills&quot;: [&quot;skills/startproject&quot;, &quot;skills/checkpointing&quot;, &quot;skills/task-state&quot;],
  &quot;agents&quot;: [],
  &quot;rules&quot;: [&quot;rules/coding-principles.md&quot;, &quot;rules/config-loading.md&quot;],
  &quot;config&quot;: [&quot;config/task-memory.yaml&quot;]
}
</code></pre>
<p>現在10パッケージを運用している。</p>
<table>
<thead>
<tr>
<th>パッケージ</th>
<th>役割</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>core</code></td>
<td>基盤（状態管理、コンテキスト共有、品質ルール）</td>
</tr>
<tr>
<td><code>agent-routing</code></td>
<td>28エージェント定義 + ルーティングエンジン</td>
</tr>
<tr>
<td><code>quality-gates</code></td>
<td>コードレビュー、TDD、リリースチェック</td>
</tr>
<tr>
<td><code>codex-suggestions</code></td>
<td>Codex CLIへの委譲提案</td>
</tr>
<tr>
<td><code>gemini-suggestions</code></td>
<td>Gemini CLIへの委譲提案</td>
</tr>
<tr>
<td><code>cli-logging</code></td>
<td>CLI呼び出しログ</td>
</tr>
<tr>
<td><code>route-audit</code></td>
<td>ルーティング監査・KPIレポート</td>
</tr>
<tr>
<td><code>issue-workflow</code></td>
<td>GitHub Issue開発フロー</td>
</tr>
<tr>
<td><code>cocoindex</code></td>
<td>MCPサーバー自動プロビジョニング</td>
</tr>
<tr>
<td><code>tmux-monitor</code></td>
<td>サブエージェント監視（tmux表示）</td>
</tr>
</tbody>
</table>
<h3 id="自動同期の仕組み" tabindex="-1">自動同期の仕組み</h3>
<p>SessionStart Hookで<code>sync-orchestra.py</code>が起動し、ai-orchestraの最新状態をプロジェクトの<code>.claude/</code>に差分コピーする。</p>
<pre><code>ai-orchestra/packages/ (ソース)
    ↓ SessionStart hook: sync-orchestra.py
.claude/ (プロジェクト)
    ├── agents/     ← 28エージェント定義
    ├── skills/     ← 全スキル
    ├── rules/      ← 全ルール
    └── config/     ← 設定ファイル（.local.yamlは保護）
</code></pre>
<p>同期の特徴:</p>
<ul>
<li><strong>差分コピー</strong>: mtimeベースで変更されたファイルのみ同期（無変更時は約70ms）</li>
<li><strong>ローカル保護</strong>: <code>*.local.yaml</code>や<code>*.local.json</code>は同期の対象外（プロジェクト固有設定を保護）</li>
<li><strong>Hook直接参照</strong>: HookスクリプトはコピーせずAI_ORCHESTRA_DIRからのパス参照（常に最新版が実行される）</li>
<li><strong>Facetビルド</strong>: 同期時にFacetシステムによるSKILL.md / ルールの自動生成も実行</li>
</ul>
<h3 id="facetシステム──dryなスキル・ルール管理" tabindex="-1">Facetシステム──DRYなスキル・ルール管理</h3>
<p>同じルールを複数のスキルに手動コピーすると、1箇所を修正するたびに全ファイルを更新する必要がある。Facetシステムはこの問題を解決する。</p>
<p>この仕組みはnrsさんの記事「<a href="https://zenn.dev/nrs/articles/5d19b4c8a39ecb" target="_blank" rel="noopener noreferrer">Faceted Prompting ── 肥大化したプロンプトを5つの関心で分解する</a>」に着想を得ている。同記事ではモノリシックなプロンプトをPersona・Policy・Instruction・Knowledge・Output Contractの5つの関心に分離するアーキテクチャを提案しており、ai-orchestraではこの考え方をClaude Codeのスキル・ルール管理に応用した。</p>
<p>3種類の部品を組み合わせてスキルやルールを自動生成する仕組みである。</p>
<pre><code>facets/
├── policies/           ← 共有Policy（複数スキル・ルールで再利用）
│   ├── cli-language.md      # 外部CLIとの言語プロトコル
│   ├── code-quality.md      # コード品質の共通ルール
│   ├── dialog-rules.md      # 対話ルール
│   └── factual-writing.md   # 事実に基づいた記述ルール
│
├── output-contracts/   ← 共有Output Contract（出力形式の標準化）
│   ├── tiered-review.md     # Critical/High/Medium/Lowの4段階レビュー形式
│   ├── compare-report.md    # 比較レポート形式
│   └── deep-dive-report.md  # 詳細分析レポート形式
│
├── instructions/       ← スキル・ルール固有のInstruction
│   ├── review.md
│   ├── startproject.md
│   └── ... (25個以上)
│
└── compositions/       ← 組み立て定義YAML
    ├── review.yaml
    └── startproject.yaml
</code></pre>
<p>Composition YAMLでどの部品を組み合わせるかを定義する。</p>
<pre><code class="language-yaml"># compositions/review.yaml
target: skill
name: review
policies:
  - cli-language
output_contracts:
  - tiered-review
instruction: review
</code></pre>
<p><code>facet build</code>を実行すると、Policy + Output Contract + Instructionが結合されたSKILL.mdが自動生成される。Policyを1箇所修正すれば、参照する全スキルに反映される。</p>
<h3 id="セットアップ" tabindex="-1">セットアップ</h3>
<p>新しいプロジェクトにai-orchestraを導入するには以下のコマンドを実行する。</p>
<pre><code class="language-bash"># インストール
uv tool install orchex

# プロジェクトへのセットアップ（全パッケージ）
orchex setup all --project /path/to/project

# 最低限のパッケージのみ
orchex setup essential --project /path/to/project
</code></pre>
<p>以降はClaude Code起動時にSessionStart Hookが自動同期を行うため、明示的な操作は不要となる。</p>
<hr>
<h2 id="まとめ──ハーネス設計のステップバイステップ" tabindex="-1">まとめ──ハーネス設計のステップバイステップ</h2>
<p>AIハーネスエンジニアリングは「プロンプトを毎回書く」から「環境を一度設計する」へのパラダイムシフトである。</p>
<p>段階的に導入する場合は、以下の順序を推奨する。</p>
<p><strong>Step 1: CLAUDE.mdを書く</strong></p>
<p>プロジェクト概要、ディレクトリ構造、テストコマンドをCLAUDE.mdに書く。これだけでClaude Codeの出力品質は大幅に向上する。</p>
<p><strong>Step 2: Rulesを分割する</strong></p>
<p>コーディング規約やワークフローのルールを<code>.claude/rules/</code>に分割する。CLAUDE.mdを簡潔に保ちつつ、詳細なルールを独立管理できる。</p>
<p><strong>Step 3: Hooksで自動化する</strong></p>
<p>繰り返し行う確認作業をHooksで自動化する。SessionStartでの環境初期化、PreToolUseでのガードレール、PostToolUseでの自動整形などから始めるとよい。</p>
<p><strong>Step 4: Agent Routingを設計する</strong></p>
<p>サブエージェントとCLIツール（Codex / Gemini）のルーティングを設定ファイルで一元管理する。タスクの種類に応じた最適なツール選択を自動化する。</p>
<p><strong>Step 5: パッケージ化して再利用する</strong></p>
<p>複数プロジェクトで同じハーネスを使う場合は、ai-orchestraのようなオーケストレーションシステムで統合管理する。</p>
<p>ハーネスの設計に正解は1つではない。自身のプロジェクトとワークフローに合わせて、少しずつ育てていくものである。この記事が、Claude Codeの真の能力を引き出すための出発点になれば幸いだ。</p>
<hr>
<p><strong>関連記事:</strong></p>
<ul>
<li><a href="/blog/claude-code-dev-toolchain">Claude Code開発を支える私のツールチェイン — ターミナルからAIデザインまで</a></li>
</ul>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/3liirUgzBlFBuAMoytyN5E/66ba6b07385791bcaae4ddf404f06ce1/Claude_Code%C3%A3__%C3%A8__%C3%A9__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3_%C2%AA%C3%A3__%C3%A3__%C3%A3__%C3%A3___CLAUDE.md%C3%A3__Hooks%C3%A3__Rules%C3%A8__%C3%A5__%C3%A3__%C3%A5__%C3%A5%C2%BA_%C3%A8__%C3%A8%C2%AA_.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Claude Code開発を支える私のツールチェイン — ターミナルからAIデザインまで]]></title>
            <link>https://www.yoshihiko.tech/blog/claude-code-dev-toolchain</link>
            <guid isPermaLink="false">claude-code-dev-toolchain</guid>
            <pubDate>Sat, 21 Mar 2026 19:30:43 GMT</pubDate>
            <description><![CDATA[Claude Code CLIを中心とした開発環境を紹介する。WezTerm + Tmuxのターミナル構成、GTRやBatonによる並列開発、ステータスライン・通知フックのカスタマイズ、MCP/プラグイン活用、Aqua VoiceやPencilとの連携まで、実際に使っているツールチェインの全体像をまとめた。]]></description>
            <content:encoded><![CDATA[<p>Claude Codeを日常的に使って開発していると、Claude Code単体ではなく<strong>周辺ツールの組み合わせ</strong>が生産性を大きく左右することに気づく。ターミナルの選定、セッション管理、並列開発の仕組み、通知の設定——これらを丁寧に整えることで、AIとの協働はまるで別物になる。</p>
<p>この記事では、私が実際にClaude Code開発で使っているツールチェインを、ターミナル環境からAIデザインツールまで一気通貫で紹介する。</p>
<p><strong>紹介するツール一覧:</strong></p>
<ul>
<li>WezTerm（ターミナルエミュレーター）</li>
<li>Tmux（ターミナルマルチプレクサ）</li>
<li>GTR（Git Worktree Runner）</li>
<li>Baton（自作セッション管理TUI）</li>
<li>fzf + ghq（プロジェクト検索・管理）</li>
<li>Claude Codeステータスライン / 通知フック</li>
<li>Context7 / Notion MCP / claude-mem</li>
<li>Aqua Voice（音声入力）</li>
<li>Pencil（AIデザインツール）</li>
</ul>
<hr>
<h2 id="ターミナル環境：wezterm-+-tmux" tabindex="-1">ターミナル環境：WezTerm + Tmux</h2>
<h3 id="なぜweztermを選んだのか" tabindex="-1">なぜWezTermを選んだのか</h3>
<p>Claude Code CLIを使うなら、まずターミナルエミュレーターの選定が出発点になる。macOSで選択肢に挙がるのはiTerm2、Ghostty、WezTermあたりだろう。</p>
<p>私がWezTermを選んだ最大の理由は<strong>Luaで設定ファイルを記述できる</strong>点にある。単なるYAMLやTOMLの静的設定ではなく、プログラミング言語で動的にキーバインド・外観・イベントハンドリングを定義できる。Claude Codeの起動スクリプトをWezTermの設定ファイルに組み込んだり、セッション情報に応じてタブのタイトルを動的に変更したりといった柔軟なカスタマイズが可能である。</p>
<p>Ghosttyも高速なGPUレンダリングで注目されているが、カスタマイズの自由度という点ではWezTermに軍配が上がる。</p>
<h3 id="tmux導入の経緯" tabindex="-1">Tmux導入の経緯</h3>
<p>もともとWezTerm単体で開発していた。しかし、Claude Codeを日常的に使う中でTmuxを間に挟む構成に移行した。きっかけの一つは、Claude Codeの<strong>Agent Teams</strong>機能である。Agent TeamsにはTmuxセッション上でサブエージェントの会話内容をリアルタイム表示するオプションがあり、これを使いたかったことが導入のきっかけになった。</p>
<p>Tmuxは導入難易度が高いと敬遠されがちだが、Claude Code開発においてはその恩恵が非常に大きい。</p>
<h3 id="weztermとtmuxの役割分担" tabindex="-1">WezTermとTmuxの役割分担</h3>
<p>現在、WezTermとTmuxの責務は明確に分離している。</p>
<table>
<thead>
<tr>
<th>レイヤー</th>
<th>責務</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>WezTerm</strong></td>
<td>GUIレンダリング（フォント、カラースキーム、外観）</td>
</tr>
<tr>
<td><strong>Tmux</strong></td>
<td>セッション管理、タブ/ペイン分割、ペイン間移動</td>
</tr>
</tbody>
</table>
<p>起動フローは以下の通りである。</p>
<ol>
<li>WezTermを起動</li>
<li>WezTermの設定ファイルからTmuxのベースセッションを自動起動</li>
<li>プロジェクトごとにTmuxセッションを作成</li>
<li>各セッション内でClaude Code CLIを起動</li>
</ol>
<p>一つのセッション内に複数のClaude Codeが立ち上がっていることもあれば、Codex CLIやGemini CLIが同居しているケースもある。</p>
<hr>
<h2 id="並列開発のためのcliツール群" tabindex="-1">並列開発のためのCLIツール群</h2>
<p>Claude Codeを並列で稼働させると、複数のAIが同じファイルを編集しようとして競合が発生する。これを解決する手段として、<strong>Git Worktree</strong>を採用している。</p>
<h3 id="gtr（git-worktree-runner）" tabindex="-1">GTR（Git Worktree Runner）</h3>
<p>Gitの<code>worktree</code>コマンドは冗長で、追加・削除のたびに長いパスを指定する必要がある。この煩雑さを解消するために導入したのが、<a href="https://github.com/coderabbitai" target="_blank" rel="noopener noreferrer">CodeRabbit</a>が提供する**GTR（Git Worktree Runner）**である。</p>
<p>GTRを使えば、ワークツリーの作成・削除・一覧表示がシンプルなコマンドで完結する。実際の運用フローは以下の通りである。</p>
<ol>
<li>GTRで新しいワークツリーを作成（ブランチ名を指定）</li>
<li>そのワークツリーのディレクトリに移動</li>
<li>Claude Codeを起動して機能開発を開始</li>
</ol>
<p>これにより、メインブランチを汚さずに複数の機能を並行して開発できる。</p>
<h3 id="baton-—-自作のclaude-codeセッション管理tui" tabindex="-1">Baton — 自作のClaude Codeセッション管理TUI</h3>
<p><img src="https://storage.googleapis.com/zenn-user-upload/f838126aacbd-20260322.png" alt="" loading="lazy"></p>
<p>Claude Codeを複数並列で動かしていると、「どのセッションが今どういう状態なのか」を把握するのが難しくなる。承認待ちなのか、実行中なのか、待機中なのか——セッションが増えるほど管理しきれなくなる。</p>
<p>この課題を解決するためにいくつかの既存ツールを試した。Claude Squad、Claude Tmux、CC Managerなど、OSSのセッション管理ツールは複数存在する。しかし、自分のワークフローにフィットするものが見つからなかったため、<strong>Baton</strong>というTUIツールを自作した。</p>
<p>Batonの特徴は以下の通りである。</p>
<ul>
<li><strong>Tmux依存</strong>: Tmux上のペイン単位でClaude Codeセッションを管理する</li>
<li><strong>ステータス取得</strong>: 各ペイン内のClaude Codeの状態（承認待ち/実行中/待機中）をリアルタイムで取得</li>
<li><strong>TUI表示</strong>: ステータスごとに色分けして一覧表示</li>
</ul>
<p>Batonの画面から「今このセッションが承認待ちだ」と判断したら、そのセッションに移動して内容を確認し、承認または否認を行う。これにより、並列開発中のClaude Codeを一元管理できるようになった。</p>
<blockquote>
<p><a href="https://github.com/yoshihiko555/baton" target="_blank" rel="noopener noreferrer">baton</a>はGitHubで公開している。</p>
</blockquote>
<h3 id="fzf-+-ghq-—-プロジェクトの爆速切り替え" tabindex="-1">fzf + ghq — プロジェクトの爆速切り替え</h3>
<p>プロジェクト間の移動にいちいち<code>cd</code>コマンドやTmuxコマンドを打つのは非効率である。ここで活躍するのが<strong>fzf</strong>（ファジーファインダー）と<strong>ghq</strong>（リポジトリ管理ツール）の組み合わせである。</p>
<p><strong>ghq</strong>は、<code>git clone</code>したリポジトリを一元管理するツールである。<code>ghq get</code>でクローンすると、<code>~/ghq/github.com/{user}/{repo}/</code>のような統一的なディレクトリ構造に配置される。全リポジトリが一箇所に集約されるため、検索性が格段に上がる。</p>
<p><strong>fzf</strong>は、あいまい検索で候補を絞り込むインタラクティブなフィルタである。単体でも強力だが、ghqと組み合わせることで真価を発揮する。</p>
<p>私はTmuxのキーバインドに以下の2つのショートカットを設定している。</p>
<table>
<thead>
<tr>
<th>キーバインド</th>
<th>動作</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>prefix + f</code></td>
<td>ghq配下のリポジトリをfzfで検索し、新規Tmuxセッションを作成</td>
</tr>
<tr>
<td><code>prefix + s</code></td>
<td>既存のTmuxセッション一覧をfzfで表示し、選択したセッションに切り替え</td>
</tr>
</tbody>
</table>
<p>これにより、プロジェクトの新規立ち上げも切り替えもキーバインド一発で完了する。</p>
<hr>
<h2 id="claude-codeのカスタマイズ" tabindex="-1">Claude Codeのカスタマイズ</h2>
<h3 id="ステータスライン" tabindex="-1">ステータスライン</h3>
<p><img src="https://storage.googleapis.com/zenn-user-upload/5d1ad766244e-20260322.png" alt="" loading="lazy"></p>
<p>Claude Codeの入力欄の下には<strong>ステータスライン</strong>と呼ばれる領域があり、シェルスクリプトでカスタマイズできる。Claude CodeがJSON形式で情報を返してくるため、その内容をパースして表示する仕組みである。</p>
<p>私が表示している情報は以下の通りである。</p>
<table>
<thead>
<tr>
<th>項目</th>
<th>目的</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>プロジェクト名</strong></td>
<td>今どのプロジェクトで作業しているか</td>
</tr>
<tr>
<td><strong>ブランチ名</strong></td>
<td>現在のGitブランチ</td>
</tr>
<tr>
<td><strong>プロセスID</strong></td>
<td>サブエージェント監視でセッションを特定するため</td>
</tr>
<tr>
<td><strong>使用モデル</strong></td>
<td>現在のセッションで使われているモデル</td>
</tr>
<tr>
<td><strong>使用状況（%）</strong></td>
<td>5時間制限に対する現在の消費率</td>
</tr>
</tbody>
</table>
<p>特に<strong>使用状況の表示</strong>は重要である。Claude Codeには5時間制限や週間制限があるため、「あとどれくらい使えるか」を常に把握しておきたい。</p>
<p>この使用状況は、v2.80以前は非公式の内部APIを叩いて取得する必要があった。利用規約への抵触が懸念される方法だったが、<strong>v2.80で公式にJSONとして提供されるようになった</strong>。現在は堂々と公式の手段でステータスラインに表示できる。</p>
<h3 id="通知フック" tabindex="-1">通知フック</h3>
<p>Claude Codeが「タスク完了」「承認待ち」「エラー発生」といった状態変化を起こしたとき、<strong>OSの通知を飛ばす仕組み</strong>をフックで構築している。</p>
<p>ポイントは<strong>通知音の分類</strong>である。</p>
<table>
<thead>
<tr>
<th>状態</th>
<th>通知音</th>
</tr>
</thead>
<tbody>
<tr>
<td>承認待ち</td>
<td>専用の音A</td>
</tr>
<tr>
<td>タスク完了</td>
<td>専用の音B</td>
</tr>
<tr>
<td>エラー/失敗</td>
<td>専用の音C</td>
</tr>
</tbody>
</table>
<p>音を聞くだけでClaude Codeの状態がわかるため、並列で複数のセッションを動かしているときでも、すぐに適切なセッションに反応できる。</p>
<hr>
<h2 id="mcp・プラグイン" tabindex="-1">MCP・プラグイン</h2>
<h3 id="mcpの運用方針：グローバルに設定しない" tabindex="-1">MCPの運用方針：グローバルに設定しない</h3>
<p>MCPは便利だが、グローバルに設定すると<strong>毎セッションでそのMCPのコンテキストが消費され、コンテキストウィンドウを圧迫する</strong>。コンテキストウィンドウはClaude Codeの生命線であるため、何でもかんでもMCPを追加するのではなく、<strong>プロジェクトごとに必要なMCPだけを設定する</strong>方針で運用している。</p>
<h3 id="context7-—-最新ドキュメント参照" tabindex="-1">Context7 — 最新ドキュメント参照</h3>
<p><a href="https://github.com/upstash/context7" target="_blank" rel="noopener noreferrer">Context7</a>は、フレームワークやライブラリの最新ドキュメントをClaude Codeに提供するMCPサーバーである。</p>
<p>例えば「Next.jsの最新のApp Routerの書き方で実装して」と指示すると、Context7が最新のドキュメントを参照した上で、トレーニングデータのカットオフに依存しない正確な実装を行ってくれる。フレームワークの更新が速い領域では特に重宝する。</p>
<h3 id="notion-mcp" tabindex="-1">Notion MCP</h3>
<p>私はNotionでナレッジやタスクを管理しているため、Notion MCPは頻繁に使う。Claude Codeから直接Notionのページを読み書きできるため、「Notionのタスクボードを更新して」といった指示がシームレスに通る。</p>
<h3 id="claude-mem-—-セッションを超えた記憶" tabindex="-1">claude-mem — セッションを超えた記憶</h3>
<p>Claude Codeは基本的に1セッション1記憶である。セッションを閉じれば、それまでのやり取りは失われる。公式のAuto Memory機能で簡単なメモは残せるようになったが、詳細な会話の文脈までは保持されない。</p>
<p><strong>claude-mem</strong>はこの課題を解決するプラグインである。会話の内容や設計判断をSQLiteに保存し、別のセッションから過去の情報を検索・参照できるようにする。「先週あの機能をどう設計したか」を遡れるのは、継続的な開発において非常に有用である。</p>
<hr>
<h2 id="音声入力とaiデザイン" tabindex="-1">音声入力とAIデザイン</h2>
<h3 id="aqua-voice-—-音声でclaude-codeと対話する" tabindex="-1">Aqua Voice — 音声でClaude Codeと対話する</h3>
<p>AIとのやり取りで最もボトルネックになるのは、人間側の入力速度である。この記事の下書きもそうだが、私はClaude Codeへの指示を<strong>Aqua Voice</strong>で音声入力している。</p>
<p>要件定義、実装方針の説明、ブログの下書き——すべてを声で話し、Claude Codeに処理させる。手で打つよりも圧倒的に速く、思考を途切れさせずにAIに伝達できる。</p>
<h3 id="pencil-—-aiと一緒にデザインを作る" tabindex="-1">Pencil — AIと一緒にデザインを作る</h3>
<p>非デザイナーにとって、UIデザインは大きなハードルである。<strong>Pencil</strong>はこの課題に対する現時点での有力な回答の一つである。</p>
<p>Pencilを起動すると<strong>MCPサーバーが立ち上がり、Claude Codeと連携</strong>できるようになる。Claude Codeに「ログイン画面をデザインして」と指示すると、Pencil上にデザインが自動生成される。さらに、そのデザインをもとにClaude Codeで実際のページを実装させることもできる。</p>
<p>Pencilの強みは以下の3点である。</p>
<ul>
<li><strong>MCP連携</strong>: Claude Codeから直接デザインを操作できる</li>
<li><strong>手動編集</strong>: Figmaのように手動で微調整も可能</li>
<li><strong>Git管理</strong>: 生成されるファイルをGitで管理できる</li>
</ul>
<p>AIに任せきりではなく、気になる箇所は手で直せる。この「AIと人間の協働」の塩梅が丁度良い。</p>
<h3 id="今後注目しているツール：google-stitch" tabindex="-1">今後注目しているツール：Google Stitch</h3>
<p>Googleが提供するAIデザインツール<strong>Stitch</strong>も注目している。調査した限りでは、デザインの精度や表現力はPencilよりStitchの方が優れている印象を受けた。</p>
<p>今後の運用フローとしては、<strong>Stitchで大まかなデザインを決め、Pencilに取り込んで微調整する</strong>という二段構えのワークフローを試していきたい。</p>
<hr>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>私のClaude Code開発環境のツールチェイン全体像を整理する。</p>
<pre><code>[WezTerm] ← GUIレンダリング
    └─ [Tmux] ← セッション管理
         ├─ [Claude Code] ← AI対話
         │    ├─ ステータスライン（使用状況/モデル/PID）
         │    ├─ 通知フック（音で状態判別）
         │    ├─ MCP: Context7 / Notion MCP
         │    └─ プラグイン: claude-mem
         ├─ [Codex CLI / Gemini CLI]
         └─ [Baton] ← セッション一元管理TUI

[GTR] ← Git Worktree管理（並列開発）
[fzf + ghq] ← プロジェクト検索・切り替え
[Aqua Voice] ← 音声入力
[Pencil] ← AIデザイン（MCP連携）
</code></pre>
<p>各ツールは単体でも便利だが、組み合わせることで相乗効果が生まれる。WezTerm + Tmuxでセッション基盤を固め、GTR + Batonで並列開発を管理し、fzf + ghqで移動を高速化する。Claude Code側はステータスラインと通知フックで状態を可視化し、MCPとプラグインで能力を拡張する。そして、入力はAqua Voiceで高速化し、デザインはPencilに任せる。</p>
<p>Claude Codeの真価は、こうした周辺ツールとの組み合わせで発揮される。気になるツールがあれば、ぜひ試してみてほしい。</p>
]]></content:encoded>
            <category>Dev Env</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/4wFUBAqC9yuIgeo3xWVHJM/8843248b854fee93872b5ac519d16bc2/Claude_Code%C3%A9__%C3%A7_%C2%BA%C3%A3__%C3%A6__%C3%A3__%C3%A3__%C3%A7__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3___%C3%A2___%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__AI%C3%A3__%C3%A3__%C3%A3_%C2%B5%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[【コピペで導入】lazygit × Claude Code でコミットメッセージ自動生成]]></title>
            <link>https://www.yoshihiko.tech/blog/lazygit-commit-message-auto-generate</link>
            <guid isPermaLink="false">lazygit-commit-message-auto-generate</guid>
            <pubDate>Sun, 15 Mar 2026 04:36:20 GMT</pubDate>
            <description><![CDATA[lazygit の customCommands と Claude Code の --print モードを組み合わせ、Ctrl+G 一発で Conventional Commits 形式のコミットメッセージを自動生成する方法を解説。コピペ可能な完成形設定と、4段階の試行錯誤で得たハマりポイントを共有します。]]></description>
            <content:encoded><![CDATA[<h1 id="lazygit-+-claude-code-でコミットメッセージを自動生成する方法【conventional-commits対応】" tabindex="-1">lazygit + Claude Code でコミットメッセージを自動生成する方法【Conventional Commits対応】</h1>
<p>AI エージェントにコードを書かせる場面が増え、コミットの回数も増える一方だ。毎回メッセージを考える手間を、lazygit の <code>Ctrl+G</code> 一発で解消する方法を紹介する。</p>
<h2 id="完成形：コピペですぐ使える設定" tabindex="-1">完成形：コピペですぐ使える設定</h2>
<p>結論から。以下を lazygit の <code>config.yml</code> に追加すれば動く。</p>
<blockquote>
<p><strong>設定ファイルの場所</strong>（OS・環境により異なる）</p>
<ul>
<li>macOS（デフォルト）: <code>~/Library/Application Support/lazygit/config.yml</code></li>
<li>macOS（XDG_CONFIG_HOME 設定済み）: <code>~/.config/lazygit/config.yml</code></li>
<li>Linux: <code>~/.config/lazygit/config.yml</code></li>
</ul>
</blockquote>
<pre><code class="language-yaml">customCommands:
  - key: &quot;&lt;c-g&gt;&quot;
    context: &quot;files&quot;
    description: &quot;Claude で Conventional Commits のメッセージを生成してコミット&quot;
    output: &quot;terminal&quot;
    command: |
      if git diff --cached --quiet; then
        echo &quot;No staged changes to commit.&quot;
        exit 1
      fi

      CLAUDE_BIN=&quot;${CLAUDE_BIN:-$(command -v claude || true)}&quot;
      if [ -z &quot;$CLAUDE_BIN&quot; ] &amp;&amp; [ -x /opt/homebrew/bin/claude ]; then
        CLAUDE_BIN=/opt/homebrew/bin/claude
      fi

      if [ -z &quot;$CLAUDE_BIN&quot; ] || [ ! -x &quot;$CLAUDE_BIN&quot; ]; then
        echo &quot;Claude CLI not found in PATH.&quot;
        exit 1
      fi

      PROMPT_CONTENT=&quot;$(
        cat &lt;&lt;'EOF'
      以下の staged diff だけを根拠に、Conventional Commits 形式の1行コミットメッセージを日本語で生成してください。
      形式: type(scope): 日本語の要約
      type は feat, fix, docs, style, refactor, test, chore のいずれかを使ってください。
      出力はコミットメッセージ本文のみ。コードフェンスや補足説明は禁止です。

      Staged diff:
      EOF
        git diff --cached
      )&quot;

      MSG=&quot;$(CLAUDE_SKIP_NOTIFY_HOOKS=1 &quot;$CLAUDE_BIN&quot; --print --model haiku &quot;$PROMPT_CONTENT&quot;)&quot;

      if [ -z &quot;$MSG&quot; ]; then
        echo &quot;Claude did not return a commit message.&quot;
        exit 1
      fi

      git commit -e -m &quot;$MSG&quot;
</code></pre>
<p>変更をステージングした後、lazygit の Files パネルで <code>Ctrl+G</code> を押す。Claude が diff を読み、Conventional Commits 形式のメッセージを生成し、エディタで確認してからコミットできる。</p>
<h2 id="前提条件" tabindex="-1">前提条件</h2>
<h3 id="lazygit" tabindex="-1">lazygit</h3>
<pre><code class="language-bash">brew install lazygit
</code></pre>
<p>詳細は <a href="https://github.com/jesseduffield/lazygit" target="_blank" rel="noopener noreferrer">lazygit 公式リポジトリ</a> を参照。</p>
<h3 id="claude-code-cli" tabindex="-1">Claude Code CLI</h3>
<pre><code class="language-bash">npm install -g @anthropic-ai/claude-code
claude  # 初回起動で認証
</code></pre>
<p>詳細は <a href="https://docs.anthropic.com/en/docs/claude-code" target="_blank" rel="noopener noreferrer">Claude Code 公式ドキュメント</a> を参照。</p>
<h2 id="設定の中身を理解する" tabindex="-1">設定の中身を理解する</h2>
<p>コピペで動かすだけでも十分だが、カスタマイズしたい場合は各パーツの役割を知っておくと便利だ。</p>
<h3 id="customcommands-の基本構造" tabindex="-1">customCommands の基本構造</h3>
<p>lazygit の <code>customCommands</code> は、任意のキーバインドにシェルコマンドを割り当てる機能だ。</p>
<table>
<thead>
<tr>
<th>フィールド</th>
<th>値</th>
<th>役割</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>key</code></td>
<td><code>&quot;&lt;c-g&gt;&quot;</code></td>
<td><code>Ctrl+G</code> にバインド</td>
</tr>
<tr>
<td><code>context</code></td>
<td><code>&quot;files&quot;</code></td>
<td>Files パネルで有効化</td>
</tr>
<tr>
<td><code>output</code></td>
<td><code>&quot;terminal&quot;</code></td>
<td>コマンド出力をターミナルに表示</td>
</tr>
<tr>
<td><code>command</code></td>
<td>シェルスクリプト</td>
<td>実行する処理</td>
</tr>
</tbody>
</table>
<h3 id="staged-diff-の取得とバリデーション" tabindex="-1">staged diff の取得とバリデーション</h3>
<p>コマンド冒頭で、ステージングされた変更の有無をチェックしている。</p>
<pre><code class="language-bash">if git diff --cached --quiet; then
  echo &quot;No staged changes to commit.&quot;
  exit 1
fi
</code></pre>
<p><code>git diff --cached --quiet</code> は staged diff が空のとき exit code 0 を返す。何もステージングしていない状態での誤実行を防ぐガードだ。</p>
<p>続いて Claude CLI のパスを解決する。<code>command -v claude</code> で PATH を検索し、見つからなければ Homebrew のデフォルトパス <code>/opt/homebrew/bin/claude</code> をフォールバックとして試す。</p>
<h3 id="プロンプト設計" tabindex="-1">プロンプト設計</h3>
<p>Claude に渡すプロンプトは、ヒアドキュメントで staged diff と結合している。</p>
<pre><code class="language-bash">PROMPT_CONTENT=&quot;$(
  cat &lt;&lt;'EOF'
以下の staged diff だけを根拠に、Conventional Commits 形式の1行コミットメッセージを日本語で生成してください。
形式: type(scope): 日本語の要約
type は feat, fix, docs, style, refactor, test, chore のいずれかを使ってください。
出力はコミットメッセージ本文のみ。コードフェンスや補足説明は禁止です。

Staged diff:
EOF
  git diff --cached
)&quot;
</code></pre>
<p>設計上のポイントは3つ。</p>
<ol>
<li><strong>フォーマットを明示</strong>: <code>type(scope): 要約</code> という Conventional Commits の形式を指定</li>
<li><strong>出力制約</strong>: コードフェンスや補足説明を禁止し、コミットメッセージ本文だけを出力させる</li>
<li><strong>staged diff のみ</strong>: <code>git diff --cached</code> で staged された変更だけを渡し、作業中の変更が混入しない</li>
</ol>
<h3 id="claude-code-の呼び出しと-git-commit" tabindex="-1">Claude Code の呼び出しと git commit</h3>
<pre><code class="language-bash">MSG=&quot;$(CLAUDE_SKIP_NOTIFY_HOOKS=1 &quot;$CLAUDE_BIN&quot; --print --model haiku &quot;$PROMPT_CONTENT&quot;)&quot;
</code></pre>
<p>この1行に3つのフラグと1つの環境変数が詰まっている（詳細は後述の「フラグ・環境変数リファレンス」を参照）。</p>
<p>生成されたメッセージは <code>git commit -e -m</code> に渡す。<code>-e</code> フラグにより、メッセージがエディタで開かれる。確認・修正してから保存すればコミット完了だ。</p>
<h2 id="ここに至るまでの試行錯誤" tabindex="-1">ここに至るまでの試行錯誤</h2>
<p>完成形に至るまでに4段階の改善を経た。同じ構成を組む人が同じ罠にハマらないよう、各ステップの問題と解決策を共有する。</p>
<h3 id="step-1:-外部スクリプト方式（初期版）" tabindex="-1">Step 1: 外部スクリプト方式（初期版）</h3>
<p>最初は <code>lazygit-claude-commit.sh</code> という別スクリプトを用意し、lazygit から呼び出していた。</p>
<pre><code class="language-yaml">command: |
  DOTFILES_DIR=&quot;${DOTFILES_DIR:-$HOME/ghq/github.com/yoshihiko555/dotfiles}&quot;
  &quot;$DOTFILES_DIR/scripts/lazygit-claude-commit.sh&quot;
</code></pre>
<p><strong>問題</strong>: dotfiles のディレクトリパスに依存し、環境が変わると動かない。</p>
<p><strong>解決</strong>: スクリプトの内容を <code>config.yml</code> にインライン化した。設定ファイル1つで完結するため、dotfiles ごと持ち運べばどの環境でも動く。</p>
<h3 id="step-2:-インライン化-+-英語プロンプト" tabindex="-1">Step 2: インライン化 + 英語プロンプト</h3>
<p>外部スクリプトを廃止し、コマンドを <code>config.yml</code> に直接記述した。ただし最初のプロンプトは英語だった。</p>
<p><strong>問題</strong>: プロジェクトのコミット履歴を日本語で統一していたため、英語メッセージが混在して <code>git log</code> の一貫性が崩れた。</p>
<p><strong>解決</strong>: プロンプトを日本語に変更し、「日本語で生成してください」と明示した。</p>
<h3 id="step-3:-日本語化-+-周辺課題の解決" tabindex="-1">Step 3: 日本語化 + 周辺課題の解決</h3>
<p>プロンプトを日本語に書き換えると同時に、運用で見つかった周辺課題も解決した。</p>
<p><strong>通知フックの抑制</strong>: Claude Code は完了時にフックを実行する仕組みを持つ。<code>--print</code> モードでも毎回通知が飛んでしまい煩わしかった。<code>CLAUDE_SKIP_NOTIFY_HOOKS=1</code> 環境変数で抑制できる。</p>
<p><strong>lazygit の設定ディレクトリ問題</strong>: GNU Stow で dotfiles を管理している環境では、<code>~/.config/lazygit/</code> が正しく認識されないことがあった。<code>.zshrc</code> にラッパー関数を追加して解決した。</p>
<pre><code class="language-bash">lazygit() {
  XDG_CONFIG_HOME=&quot;$HOME/.config&quot; command lazygit &quot;$@&quot;
}
</code></pre>
<p><strong>エディタ確認の追加</strong>: 当初は <code>git commit -m &quot;$MSG&quot;</code> で直接コミットしていたが、AI の生成結果を無条件に受け入れるのはリスクがある。<code>-e</code> フラグを追加し、コミット前に必ずエディタで確認するフローに変更した。</p>
<h2 id="フラグ・環境変数リファレンス" tabindex="-1">フラグ・環境変数リファレンス</h2>
<h3 id="claude-code-cli-フラグ" tabindex="-1">Claude Code CLI フラグ</h3>
<table>
<thead>
<tr>
<th>フラグ</th>
<th>役割</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--print</code></td>
<td>非対話モード。結果を標準出力に出力し、ターミナル UI を起動しない</td>
</tr>
<tr>
<td><code>--model haiku</code></td>
<td>Haiku モデルを指定。高速・低コストで、コミットメッセージ生成には十分</td>
</tr>
</tbody>
</table>
<h3 id="環境変数" tabindex="-1">環境変数</h3>
<table>
<thead>
<tr>
<th>変数</th>
<th>役割</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>CLAUDE_SKIP_NOTIFY_HOOKS=1</code></td>
<td>Claude Code のフック通知をスキップする</td>
</tr>
</tbody>
</table>
<h2 id="運用してみてのポイント" tabindex="-1">運用してみてのポイント</h2>
<h3 id="エディタ確認は外さない" tabindex="-1">エディタ確認は外さない</h3>
<p>AI が生成したメッセージは8割方そのまま使えるが、スコープ名の選択や要約の粒度が微妙なケースもある。<code>git commit -e</code> によるエディタ確認は外さないほうがいい。確認に要する時間は数秒で、ゼロからメッセージを考えるよりはるかに速い。</p>
<h3 id="haiku-モデルで十分" tabindex="-1">Haiku モデルで十分</h3>
<p>コミットメッセージ生成は「diff を読んで1行にまとめる」タスクだ。Sonnet や Opus のような大型モデルは不要で、Haiku の速度とコストが最適なバランスになる。レスポンスは体感1〜2秒で返ってくる。</p>
<h3 id="staged-diff-だけを渡す理由" tabindex="-1">staged diff だけを渡す理由</h3>
<p><code>git diff --cached</code>（staged のみ）を使い、<code>git diff</code>（unstaged 含む）は使わない。理由は明確だ。</p>
<ul>
<li>コミット対象の変更だけをメッセージに反映させたい</li>
<li>作業中の未ステージング変更が含まれると、メッセージの焦点がブレる</li>
<li>lazygit でファイル単位にステージングを調整した後に <code>Ctrl+G</code> を押すワークフローと整合する</li>
</ul>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>lazygit の <code>customCommands</code> と Claude Code の <code>--print</code> モードを組み合わせれば、<code>Ctrl+G</code> 一発で Conventional Commits 形式のコミットメッセージを自動生成できる。設定は <code>config.yml</code> に約30行で完結し、導入コストは低い。</p>
<p>AI にコードを書かせる機会が増えるほど、コミット回数も増える。メッセージを毎回手で考える時間を、この仕組みで取り戻してほしい。</p>
]]></content:encoded>
            <category>Dev Env</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/62ov1mzeLUjRm6cbzQ7R92/3bc21a7fe7ec517ad3258b6264d4097e/%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A5__%C3%A5__%C3%A3__lazygit_%C3%83__Claude_Code_%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A8_%C2%AA%C3%A5__%C3%A7__%C3%A6__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[git-worktree-runner で worktree 管理が劇的にラクになった話]]></title>
            <link>https://www.yoshihiko.tech/blog/git-worktree-runner-guide</link>
            <guid isPermaLink="false">git-worktree-runner-guide</guid>
            <pubDate>Mon, 09 Mar 2026 15:32:31 GMT</pubDate>
            <description><![CDATA[git-worktree-runner（GTR）は git worktree の操作を 1 コマンドに集約する CLI ラッパーです。Taskfile.yaml での運用から GTR に移行した体験を元に、インストール・設定・AI 並列開発での活用法を解説します。]]></description>
            <content:encoded><![CDATA[<h2 id="git-worktree、便利だけど運用が面倒だった" tabindex="-1">git worktree、便利だけど運用が面倒だった</h2>
<p><strong>git worktree は強力な機能だが、実際の運用ではセットアップの手間がボトルネックになりがちだ。</strong> git-worktree-runner（GTR）を使えば、その面倒を 1 コマンドに集約できる。</p>
<h3 id="git-worktree-の基本（30秒でおさらい）" tabindex="-1">git worktree の基本（30秒でおさらい）</h3>
<p><code>git worktree</code> は Git 2.5 で追加された機能で、1 つのリポジトリから複数のブランチを同時にチェックアウトできる。<code>git stash</code> やブランチ切り替えなしに、別ディレクトリで別ブランチの作業が可能になる。</p>
<pre><code class="language-bash"># 素の git worktree コマンド
git worktree add ../my-feature feature-branch
git worktree list
git worktree remove ../my-feature
</code></pre>
<h3 id="素のコマンドで困ること" tabindex="-1">素のコマンドで困ること</h3>
<p>git worktree 自体はシンプルだが、実務で使うと以下の課題にぶつかる。</p>
<ol>
<li><strong>コマンドが長い</strong>: パス指定やブランチ名の指定が毎回必要</li>
<li><strong>環境ファイルのコピーが手動</strong>: <code>.env</code> や <code>.env.local</code> を新しい worktree にコピーし忘れて動かない</li>
<li><strong>依存パッケージのインストールが手動</strong>: <code>npm install</code> や <code>pnpm install</code> を忘れてエラー</li>
<li><strong>クリーンアップが面倒</strong>: worktree 削除時に Docker コンテナの停止、ブランチ削除、<code>git worktree prune</code> を個別に実行</li>
</ol>
<p>筆者もこれらの課題に対処するため、Taskfile.yaml（Go 製タスクランナー）で worktree 操作を定義して運用していた。しかし、プロジェクトごとに Taskfile を用意する必要があり、それ自体が負担になっていた。</p>
<hr>
<h2 id="git-worktree-runner（gtr）とは" tabindex="-1">git-worktree-runner（GTR）とは</h2>
<p><strong>GTR は CodeRabbit が開発した git worktree の CLI ラッパーだ。</strong> worktree の作成・削除・環境セットアップを 1 コマンドに集約し、AI ツールとの連携もビルトインで提供する。</p>
<h3 id="coderabbit-が作った-worktree-cli-ラッパー" tabindex="-1">CodeRabbit が作った worktree CLI ラッパー</h3>
<ul>
<li><strong>リポジトリ</strong>: <a href="https://github.com/coderabbitai/git-worktree-runner" target="_blank" rel="noopener noreferrer">coderabbitai/git-worktree-runner</a></li>
<li><strong>言語</strong>: Bash（3.2+ 対応、macOS / Linux / Git Bash）</li>
<li><strong>ライセンス</strong>: Apache 2.0</li>
</ul>
<p>CodeRabbit は AI によるプルリクエストレビューサービスで知られる企業だ。GTR は同社が AI 開発ワークフローの一環としてオープンソースで公開したツールになる。</p>
<h3 id="gtr-でできること一覧" tabindex="-1">GTR でできること一覧</h3>
<table>
<thead>
<tr>
<th>機能</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>git gtr new</code></td>
<td>worktree 作成 + env コピー + 依存インストール + フック実行</td>
</tr>
<tr>
<td><code>git gtr list</code></td>
<td>worktree 一覧表示</td>
</tr>
<tr>
<td><code>git gtr rm</code></td>
<td>worktree 削除 + クリーンアップ</td>
</tr>
<tr>
<td><code>git gtr clean</code></td>
<td>マージ済み worktree の一括削除</td>
</tr>
<tr>
<td><code>git gtr ai</code></td>
<td>既存 worktree で AI ツールを起動</td>
</tr>
<tr>
<td><code>.gtrconfig</code></td>
<td>リポジトリ単位の共通設定</td>
</tr>
<tr>
<td>フック</td>
<td><code>postCreate</code>, <code>preRemove</code>, <code>postRemove</code>, <code>postCd</code></td>
</tr>
<tr>
<td>fzf 連携</td>
<td>インタラクティブな worktree 選択（v2.4.0〜）</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="インストールと初期設定" tabindex="-1">インストールと初期設定</h2>
<h3 id="homebrew-でインストール" tabindex="-1">Homebrew でインストール</h3>
<p>macOS / Linux（Linuxbrew）の場合、2 コマンドで完了する。</p>
<pre><code class="language-bash">brew tap coderabbitai/tap
brew install git-gtr
</code></pre>
<p>スクリプトでインストールする場合:</p>
<pre><code class="language-bash">git clone https://github.com/coderabbitai/git-worktree-runner.git
cd git-worktree-runner &amp;&amp; ./install.sh
</code></pre>
<p>インストール後、バージョンを確認する。</p>
<pre><code class="language-bash">git gtr --version
# git-gtr version 2.4.0
</code></pre>
<h3 id=".gtrconfig-を書く" tabindex="-1"><code>.gtrconfig</code> を書く</h3>
<p>リポジトリルートに <code>.gtrconfig</code> を配置すると、worktree 作成時の自動処理を定義できる。</p>
<pre><code class="language-ini">[copy]
# worktree 作成時に自動コピーするファイル（glob パターン）
include = **/.env.example

[hooks]
# worktree 作成後に自動実行するコマンド
postCreate = pnpm install

[defaults]
# デフォルトのエディタと AI ツール
editor = cursor
ai = claude
</code></pre>
<p>この設定ファイルをリポジトリにコミットすれば、チームメンバー全員が同じ worktree 設定を共有できる。</p>
<hr>
<h2 id="基本的な使い方" tabindex="-1">基本的な使い方</h2>
<h3 id="worktree-を作る（git-gtr-new）" tabindex="-1">worktree を作る（git gtr new）</h3>
<pre><code class="language-bash"># ブランチ名を指定して worktree 作成
git gtr new my-feature

# ベースブランチを指定
git gtr new my-feature --base develop

# 作成と同時にエディタを開く
git gtr new my-feature --editor

# 作成と同時に AI ツールを起動
git gtr new my-feature --ai
</code></pre>
<p><code>git gtr new</code> を実行すると、以下が自動で行われる:</p>
<ol>
<li><code>git worktree add</code> でブランチと作業ディレクトリを作成</li>
<li><code>.gtrconfig</code> の <code>[copy]</code> に基づきファイルをコピー</li>
<li><code>[hooks] postCreate</code> を実行（例: <code>pnpm install</code>）</li>
<li><code>--editor</code> 指定時はエディタを起動</li>
<li><code>--ai</code> 指定時は AI ツールを起動</li>
</ol>
<h3 id="一覧と切り替え（git-gtr-list-/-cd）" tabindex="-1">一覧と切り替え（git gtr list / cd）</h3>
<pre><code class="language-bash"># worktree 一覧
git gtr list

# fzf でインタラクティブに選択して移動
git gtr cd
</code></pre>
<h3 id="worktree-を消す（git-gtr-rm-/-clean）" tabindex="-1">worktree を消す（git gtr rm / clean）</h3>
<pre><code class="language-bash"># 指定した worktree を削除
git gtr rm my-feature

# マージ済み worktree を一括削除
git gtr clean --merged
</code></pre>
<p><code>git gtr rm</code> は <code>[hooks] preRemove</code> → worktree 削除 → ブランチ削除 → <code>git worktree prune</code> → <code>[hooks] postRemove</code> を一括で処理する。</p>
<hr>
<h2 id="【体験談】taskfile.yaml-から-gtr-に移行してみた" tabindex="-1">【体験談】Taskfile.yaml から GTR に移行してみた</h2>
<h3 id="before-—-taskfile.worktree.yml-での運用" tabindex="-1">Before — Taskfile.worktree.yml での運用</h3>
<p>以前は Go 製タスクランナーの <a href="https://taskfile.dev/" target="_blank" rel="noopener noreferrer">Task</a> を使い、プロジェクトごとに <code>Taskfile.worktree.yml</code> を書いて worktree を管理していた。</p>
<pre><code class="language-yaml"># Taskfile.worktree.yml（実際に使っていた定義）
version: &quot;3&quot;

vars:
  MAIN_DIR: &quot;{{.TASKFILE_DIR}}/..&quot;
  WK_DIR: &quot;{{.MAIN_DIR}}/../wk&quot;
  SHARED_ENV: &quot;{{.MAIN_DIR}}/../.env.shared&quot;

tasks:
  add:
    desc: 新しいworktreeを作成（NAME=auth BASE=develop）
    dir: &quot;{{.MAIN_DIR}}&quot;
    requires:
      vars: [NAME]
    vars:
      BASE: '{{.BASE | default &quot;develop&quot;}}'
    cmds:
      - git fetch origin
      - git worktree add -b &quot;{{.NAME}}&quot; &quot;{{.WK_DIR}}/{{.NAME}}&quot; &quot;{{.BASE}}&quot;
      - ./scripts/worktree-env.sh &quot;{{.WK_DIR}}/{{.NAME}}&quot; &quot;{{.SHARED_ENV}}&quot;
      - task: wk:install-wk
        vars:
          NAME: &quot;{{.NAME}}&quot;
      - cmd: mise trust &quot;{{.WK_PATH}}/mise.toml&quot;

  remove:
    desc: worktreeを削除（NAME=auth）
    dir: &quot;{{.MAIN_DIR}}&quot;
    requires:
      vars: [NAME]
    cmds:
      - |
        if [ -d &quot;{{.WK_DIR}}/{{.NAME}}&quot; ]; then
          (cd &quot;{{.WK_DIR}}/{{.NAME}}&quot; &amp;&amp; docker compose down --volumes --remove-orphans)
        fi
      - git worktree remove &quot;{{.WK_DIR}}/{{.NAME}}&quot;
      - git worktree prune
      - git branch -D &quot;{{.NAME}}&quot;
</code></pre>
<p>この運用で困っていたこと:</p>
<ul>
<li><strong>プロジェクトごとに Taskfile を書く必要がある</strong>: 新しいリポジトリを始めるたびに、パス構成や依存インストール方法に合わせた定義が必要</li>
<li><strong>env コピーのスクリプトも別途管理</strong>: <code>worktree-env.sh</code> を各プロジェクトに配置</li>
<li><strong>動作確認のコスト</strong>: Taskfile を書いた後、実際に worktree を作って正しく動くか毎回テスト</li>
<li><strong>チーム共有が難しい</strong>: Taskfile のテンプレート構文に慣れていないメンバーへの説明コスト</li>
</ul>
<h3 id="after-—-gtr-での運用" tabindex="-1">After — GTR での運用</h3>
<p>GTR に移行後は <code>.gtrconfig</code> を 1 ファイル書くだけで完了する。</p>
<pre><code class="language-ini"># .gtrconfig
[copy]
include = **/.env.example
include = **/.env.shared

[hooks]
postCreate = pnpm install &amp;&amp; mise trust mise.toml
preRemove = docker compose down --volumes --remove-orphans

[defaults]
ai = claude
</code></pre>
<p>実行コマンド:</p>
<pre><code class="language-bash"># Before（Taskfile）
task wk:add NAME=my-feature BASE=develop

# After（GTR）
git gtr new my-feature --base develop
</code></pre>
<h3 id="比較まとめ" tabindex="-1">比較まとめ</h3>
<table>
<thead>
<tr>
<th>項目</th>
<th>Taskfile.yaml</th>
<th>GTR（.gtrconfig）</th>
</tr>
</thead>
<tbody>
<tr>
<td>セットアップ</td>
<td>プロジェクトごとに YAML を作成</td>
<td><code>.gtrconfig</code> 1 ファイル</td>
</tr>
<tr>
<td>env コピー</td>
<td>専用シェルスクリプトが必要</td>
<td>glob パターンで宣言的に指定</td>
</tr>
<tr>
<td>依存インストール</td>
<td>タスク定義内に記述</td>
<td><code>postCreate</code> フックに記述</td>
</tr>
<tr>
<td>削除時クリーンアップ</td>
<td>条件分岐を含む複雑な定義</td>
<td><code>preRemove</code> フックに 1 行</td>
</tr>
<tr>
<td>AI ツール連携</td>
<td>非対応（別途起動）</td>
<td><code>--ai</code> フラグでビルトイン</td>
</tr>
<tr>
<td>学習コスト</td>
<td>Taskfile テンプレート構文</td>
<td>git config ライクな INI 形式</td>
</tr>
<tr>
<td>ポータビリティ</td>
<td>プロジェクトに Task のインストールが必要</td>
<td>Bash のみ（追加依存なし）</td>
</tr>
</tbody>
</table>
<p>結論として、GTR に移行したことで「プロジェクトごとに定義ファイルを書く」という作業自体がなくなった。新しいリポジトリでも <code>.gtrconfig</code> を数行書けば、すぐに worktree 運用を開始できる。</p>
<hr>
<h2 id="ai-並列開発への活用" tabindex="-1">AI 並列開発への活用</h2>
<h3 id="claude-code-×-gtr-で並列セッション" tabindex="-1">Claude Code × GTR で並列セッション</h3>
<p>GTR の最大の特徴の一つが、AI ツールとのビルトイン連携だ。<code>.gtrconfig</code> で <code>ai = claude</code> を設定すれば、worktree 作成時に Claude Code セッションを同時起動できる。</p>
<pre><code class="language-bash"># worktree を作ると同時に Claude Code を起動
git gtr new auth-feature --ai

# 既存の worktree で Claude Code を起動
git gtr ai auth-feature
</code></pre>
<p>これにより、以下のような並列開発ワークフローが実現する:</p>
<ol>
<li><code>git gtr new auth --ai</code> → 認証機能を Claude Code に任せる</li>
<li><code>git gtr new ui-fix --ai</code> → UI 修正を別の Claude Code セッションに任せる</li>
<li>メインの worktree では自分がコアロジックを実装</li>
<li>各セッションが完了したら PR を作成してマージ</li>
</ol>
<h3 id="ワークフロー例" tabindex="-1">ワークフロー例</h3>
<pre><code class="language-bash"># 1. 3 つの worktree を作成し、それぞれ AI セッションを起動
git gtr new feature-auth --ai
git gtr new fix-pagination --ai
git gtr new refactor-api --ai

# 2. 一覧で状況確認
git gtr list

# 3. 作業完了後、マージ済み worktree を一括クリーンアップ
git gtr clean --merged
</code></pre>
<p>Claude Code の worktree 対応（<code>--worktree</code> フラグや Agent ツールの <code>isolation: &quot;worktree&quot;</code> オプション）と組み合わせることで、AI エージェントが安全に隔離された環境で作業できる。GTR はその「環境を用意する」部分の手間を省いてくれるツールだ。</p>
<hr>
<h2 id="まとめ-—-worktree-運用の「面倒」を消す" tabindex="-1">まとめ — worktree 運用の「面倒」を消す</h2>
<p>git-worktree-runner（GTR）は、git worktree の「便利だけど面倒」を解消するツールだ。</p>
<ul>
<li><strong>1 コマンドで完結</strong>: <code>git gtr new</code> だけで worktree 作成 + env コピー + 依存インストール</li>
<li><strong>設定は <code>.gtrconfig</code> に集約</strong>: プロジェクトごとのスクリプト管理が不要に</li>
<li><strong>AI 連携がビルトイン</strong>: <code>--ai</code> フラグで Claude Code 等を即起動</li>
</ul>
<p>以前は Taskfile.yaml でプロジェクトごとに定義を書いていたが、GTR への移行で worktree 運用のハードルが大幅に下がった。AI による並列開発がますます一般的になる中、worktree をストレスなく使えるようにしておくことは、開発効率に直結する。</p>
<h3 id="gtr-導入チェックリスト" tabindex="-1">GTR 導入チェックリスト</h3>
<ul>
<li>[ ] <code>brew tap coderabbitai/tap &amp;&amp; brew install git-gtr</code> でインストール</li>
<li>[ ] <code>git gtr --version</code> でバージョン確認</li>
<li>[ ] リポジトリに <code>.gtrconfig</code> を作成（copy / hooks / defaults）</li>
<li>[ ] <code>git gtr new test-branch</code> で動作確認</li>
<li>[ ] <code>git gtr rm test-branch</code> で削除確認</li>
<li>[ ] （任意）<code>git gtr config set gtr.ai.default claude</code> で AI ツール設定</li>
</ul>
<hr>
<h2 id="faq" tabindex="-1">FAQ</h2>
<p><strong>Q: git-worktree-runner は git worktree の代替ですか？</strong><br>
A: 代替ではなく、git worktree コマンドのラッパーです。内部で <code>git worktree add</code> / <code>git worktree remove</code> を呼んでおり、追加の環境設定やフック実行を自動化します。</p>
<p><strong>Q: Windows でも使えますか？</strong><br>
A: Git Bash または WSL 環境で動作します。Bash 3.2 以上が必要なため、PowerShell ネイティブでは使用できません。WSL での利用を推奨します。</p>
<p><strong>Q: <code>.gtrconfig</code> はリポジトリにコミットすべきですか？</strong><br>
A: チーム開発であればコミット推奨です。メンバー全員が同じ worktree 設定（env コピー、フック）を共有できます。個人設定は <code>git config --local</code> で上書き可能です。</p>
<p><strong>Q: 既存の worktree がある状態で GTR を導入できますか？</strong><br>
A: はい。GTR は <code>git worktree list</code> の出力をベースにしているため、既存の worktree もそのまま管理対象になります。<code>.gtrconfig</code> を追加するだけで運用を開始できます。</p>
<p><strong>Q: Claude Code 以外の AI ツールにも対応していますか？</strong><br>
A: Cursor, VS Code, Aider, Codex, Gemini, Copilot 等のアダプターがビルトインで用意されています。<code>git gtr config set gtr.ai.default &lt;ツール名&gt;</code> で切り替え可能です。</p>
]]></content:encoded>
            <category>Dev Env</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/7BRBfHFocF2ABOeOQmtIic/a63ce99493098bc42123e4315f43d768/git-worktree-runner_%C3%A3__%C3%A3___worktree_%C3%A7__%C3%A7__%C3%A3__%C3%A3__%C3%A5__%C3%A7__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3_%C2%AA%C3%A3__%C3%A3__%C3%A8__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[RAG入門 — 検索拡張生成の基礎知識を実験結果とともに学ぶ]]></title>
            <link>https://www.yoshihiko.tech/blog/rag-basic-knowledge</link>
            <guid isPermaLink="false">rag-basic-knowledge</guid>
            <pubDate>Sun, 01 Mar 2026 11:29:25 GMT</pubDate>
            <description><![CDATA[RAG（検索拡張生成）の仕組みをVectorDB・Embedding・チャンク分割の3要素に分解して解説。Embeddingモデル変更で正答率0/5→4/5に改善した検証データも公開。]]></description>
            <content:encoded><![CDATA[<h2 id="ragとは何か-—-検索で回答精度を上げるアーキテクチャ" tabindex="-1">RAGとは何か — 検索で回答精度を上げるアーキテクチャ</h2>
<p>RAG（Retrieval-Augmented Generation: 検索拡張生成）は、外部データを検索してLLMに渡すことで回答の正確性を高めるアーキテクチャである。2020年にMeta AIのLewisらが提唱した(要出典: Lewis et al., 2020)。</p>
<p>LLM単体には以下の課題がある。</p>
<ul>
<li><strong>ハルシネーション</strong>: 学習データにない情報を「もっともらしく」生成してしまう</li>
<li><strong>知識のカットオフ</strong>: 学習時点以降の情報を持たない</li>
<li><strong>ドメイン知識の欠如</strong>: 社内規定や自社製品情報など、非公開データに対応できない</li>
</ul>
<p>RAGはLLMモデル自体を再学習させるのではなく、<strong>回答生成の直前に関連情報を検索して渡す</strong>というアプローチでこれらを解決する。社内ドキュメント検索、FAQ自動応答、カスタマーサポートなど「特定のデータから正確な情報を引き出したい」場面で採用されている。</p>
<h2 id="ragの仕組み-—-格納フェーズと検索フェーズ" tabindex="-1">RAGの仕組み — 格納フェーズと検索フェーズ</h2>
<p>RAGの処理は「格納フェーズ」と「検索・回答フェーズ」の2つに分かれる。</p>
<h3 id="格納フェーズ（データの準備）" tabindex="-1">格納フェーズ（データの準備）</h3>
<p>テキストデータをVectorDBに格納するまでの流れは以下の通り。</p>
<pre><code>テキストファイル（社内規定、製品マニュアル等）
    ↓ ① チャンク分割（適切な単位に分割）
チャンクのリスト
    ↓ ② Embeddingモデルでベクトル化（数値の配列に変換）
ベクトルのリスト
    ↓ ③ VectorDBに格納（ベクトル + 元テキスト + メタ情報）
VectorDB
</code></pre>
<p>テキストをそのまま格納するのではなく、<strong>チャンク分割 → ベクトル化</strong>という前処理を経る。この前処理の質が、検索精度を左右する。</p>
<h3 id="検索・回答フェーズ（質問への応答）" tabindex="-1">検索・回答フェーズ（質問への応答）</h3>
<p>ユーザーが質問を投げてから回答が返るまでの流れは以下の通り。</p>
<pre><code>ユーザーの質問
    ↓ ① 同じEmbeddingモデルでベクトル化
質問ベクトル
    ↓ ② VectorDBで類似検索（質問に近いチャンクをk件取得）
関連チャンク（上位k件）
    ↓ ③ LLMに「質問 + 関連チャンク」を渡す
回答を生成 → ユーザーに返却
</code></pre>
<p>格納時と検索時で<strong>同じEmbeddingモデルを使う</strong>必要がある。同じモデルで変換するからこそ同じ「意味の空間」に配置され、ベクトル間の距離比較が成り立つ。</p>
<h2 id="ragの3つのコンポーネント" tabindex="-1">RAGの3つのコンポーネント</h2>
<p>RAGの検索精度を決めるのは、VectorDB・Embedding・チャンク分割の3つである。</p>
<h3 id="vectordb-—-意味で検索するデータベース" tabindex="-1">VectorDB — 意味で検索するデータベース</h3>
<p>VectorDB（ベクトルデータベース）は、ベクトルを大量に保存し、「このベクトルに近いものをk件返す」という類似度検索を高速に行うデータベースである。</p>
<p>RDB（PostgreSQL等）との違いは以下の通り。</p>
<table>
<thead>
<tr>
<th></th>
<th>RDB（PostgreSQL等）</th>
<th>VectorDB（Qdrant等）</th>
</tr>
</thead>
<tbody>
<tr>
<td>検索方法</td>
<td>WHERE句で完全一致・範囲指定</td>
<td>ベクトル間の距離（類似度）で検索</td>
</tr>
<tr>
<td>インデックス</td>
<td>B-Tree, Hash</td>
<td>HNSW, IVF等の近似最近傍インデックス</td>
</tr>
<tr>
<td>得意なこと</td>
<td>「部署 = 営業部」のような正確な絞り込み</td>
<td>「この質問に意味的に近い文書」を探す</td>
</tr>
<tr>
<td>苦手なこと</td>
<td>曖昧な意味的検索</td>
<td>完全一致検索、数値の大小比較</td>
</tr>
</tbody>
</table>
<p>VectorDBに格納されるデータは、検索用ベクトル・LLMに渡す元テキスト・出典表示用のメタ情報で構成される。</p>
<pre><code class="language-json">{
  &quot;vector&quot;: [0.12, -0.45, 0.78, ...],
  &quot;payload&quot;: {
    &quot;page_content&quot;: &quot;【経費精算】書籍購入は年間30,000円まで...&quot;,
    &quot;metadata&quot;: { &quot;source&quot;: &quot;company-policy.txt&quot; }
  }
}
</code></pre>
<p>検索時は質問ベクトルと全ベクトルの距離を計算し、近い順に返す。全件計算は遅いため、HNSW（Hierarchical Navigable Small World）というインデックスで効率的に絞り込む。RDBにおけるB-Treeインデックスに相当する仕組みである。</p>
<p>代表的なVectorDBにはQdrant、Pinecone、Chroma、pgvector（PostgreSQL拡張）がある。検証・学習目的ならローカル実行可能なQdrantやChroma、本番運用ならPineconeやWeaviateが選択肢に入る。</p>
<h3 id="embedding-—-テキストを数値に変換する" tabindex="-1">Embedding — テキストを数値に変換する</h3>
<p>Embedding（埋め込み）は、テキストを数値の配列（ベクトル）に変換する処理である。Embeddingモデルが大量のテキストデータから学習した「意味の法則」に基づき、意味が近い文は近いベクトルに変換される。</p>
<pre><code>「経費精算」   → [0.12, -0.45, 0.78, ..., 0.33]  (2560次元)
「書籍購入費」 → [0.11, -0.42, 0.81, ..., 0.35]  (2560次元) ← 近い
「今日の天気」 → [-0.55, 0.22, 0.01, ..., -0.18] (2560次元) ← 遠い
</code></pre>
<p>人間がルールを定義したのではなく、モデルが大量のテキストから統計的に「意味の近さ」を学んだ結果である。そのため<strong>モデルによって精度が異なり、日本語データで多く学習したモデルの方が日本語の意味を正確に捉えられる</strong>。</p>
<p>Embeddingモデル選定で考慮すべき3つの観点を以下に示す。</p>
<table>
<thead>
<tr>
<th>観点</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>対応言語</td>
<td>日本語データなら多言語対応モデルが必須</td>
</tr>
<tr>
<td>次元数</td>
<td>高次元ほど表現力が高いがストレージ・計算コスト増</td>
</tr>
<tr>
<td>モデルサイズ</td>
<td>大きいほど精度が高い傾向だがメモリ消費増</td>
</tr>
</tbody>
</table>
<h4 id="実験で確認:-embeddingモデル変更の効果" tabindex="-1">実験で確認: Embeddingモデル変更の効果</h4>
<p>筆者のRAG検証環境（14ファイル・539行の社内ドキュメント、検索パラメータk=3）で、Embeddingモデルの変更が検索精度にどう影響するかを検証した。</p>
<table>
<thead>
<tr>
<th>構成</th>
<th>5問中の正答数（k=3に正解が入った数）</th>
</tr>
</thead>
<tbody>
<tr>
<td>文字数分割 + nomic-embed-text</td>
<td>0/5</td>
</tr>
<tr>
<td>見出し分割 + nomic-embed-text</td>
<td>1/5</td>
</tr>
<tr>
<td>見出し分割 + qwen3-embedding:4b</td>
<td><strong>4/5</strong></td>
</tr>
</tbody>
</table>
<p>nomic-embed-text（英語中心）からqwen3-embedding:4b（多言語対応、2560次元）への変更で正答率が劇的に改善した。日本語データを扱う場合、<strong>多言語対応のEmbeddingモデルの選定がRAG精度に直結する</strong>。</p>
<h3 id="チャンク分割-—-検索精度を左右するデータの切り方" tabindex="-1">チャンク分割 — 検索精度を左右するデータの切り方</h3>
<p>チャンク分割は、テキストデータを検索に適した単位に分割する処理である。以下の3つの理由で必要になる。</p>
<ol>
<li>LLMに渡せるコンテキストには長さの制限がある</li>
<li>ドキュメント全体をベクトル化すると意味が希釈される</li>
<li>関連する部分だけを取得してLLMに渡す必要がある</li>
</ol>
<p>分割戦略はデータの性質ごとに設計が必要である。万能な分割戦略はない。</p>
<table>
<thead>
<tr>
<th>データ種別</th>
<th>有効な分割単位</th>
</tr>
</thead>
<tbody>
<tr>
<td>社内規定・マニュアル</td>
<td>見出し・セクション単位</td>
</tr>
<tr>
<td>FAQ</td>
<td>Q&amp;Aペア単位</td>
</tr>
<tr>
<td>チャットログ・議事録</td>
<td>時系列・スレッド単位</td>
</tr>
<tr>
<td>契約書・法律文書</td>
<td>条項単位</td>
</tr>
<tr>
<td>ソースコード</td>
<td>関数・クラス単位</td>
</tr>
</tbody>
</table>
<p>1チャンクに複数トピックが混在するとベクトルの意味が曖昧になり、検索精度が下がる。</p>
<pre><code>悪い例: 【勤務時間】+【有給休暇】+【経費精算】→ 1チャンク
→ ベクトルが「勤務」「休暇」「経費」の中間的な意味になり、
  どの質問にも中途半端にしかマッチしない

良い例: 【経費精算】→ 1チャンク
→ ベクトルが「経費」に集中し、経費関連の質問に的確にマッチする
</code></pre>
<p><strong>1チャンク=1トピック</strong>を意識した分割が、検索精度改善の基本である。</p>
<h2 id="検索精度に影響する要因-—-実験で確認したこと" tabindex="-1">検索精度に影響する要因 — 実験で確認したこと</h2>
<p>筆者のRAG検証では、Embeddingモデル・チャンク分割戦略・検索パラメータkの3つが検索精度に影響することを確認した。</p>
<h3 id="embeddingモデルの選定が最もインパクトが大きい" tabindex="-1">Embeddingモデルの選定が最もインパクトが大きい</h3>
<p>3構成のクエリ別結果を以下に示す。順位の数値はVectorDB内での検索ランクである。</p>
<table>
<thead>
<tr>
<th>クエリ</th>
<th>文字数+nomic</th>
<th>見出し+nomic</th>
<th>見出し+qwen3</th>
</tr>
</thead>
<tbody>
<tr>
<td>書籍の経費上限は？</td>
<td>圏外</td>
<td>4位</td>
<td><strong>2位+3位</strong></td>
</tr>
<tr>
<td>DataBridge vs DataLink料金？</td>
<td>DataLink圏外</td>
<td>DataLink 4位</td>
<td><strong>1位+2位</strong></td>
</tr>
<tr>
<td>リモートワーク週何日？</td>
<td>3位（旧情報）</td>
<td>1位</td>
<td><strong>1位+2位</strong></td>
</tr>
<tr>
<td>資格の報奨金は？</td>
<td>1位（旧情報）</td>
<td>2位</td>
<td><strong>1位+2位</strong></td>
</tr>
<tr>
<td>サポート連絡先は？</td>
<td>圏外</td>
<td>圏外</td>
<td><strong>1位+2位</strong></td>
</tr>
</tbody>
</table>
<p>Embeddingモデルの変更はチャンク分割の改善よりインパクトが大きい。日本語データを扱うRAGでは、<strong>多言語対応Embeddingモデルの選定を最優先</strong>にすべきである。</p>
<h3 id="チャンク戦略4種の比較" tabindex="-1">チャンク戦略4種の比較</h3>
<p>別の検証で、4種類のチャンク分割戦略を同一条件で比較した。</p>
<table>
<thead>
<tr>
<th>戦略</th>
<th>チャンク数</th>
<th>Hit Rate</th>
<th>MRR</th>
<th>Source Hit Rate</th>
</tr>
</thead>
<tbody>
<tr>
<td>standard_1000</td>
<td>30</td>
<td>90.0%</td>
<td><strong>0.850</strong></td>
<td>80.0%</td>
</tr>
<tr>
<td>large_2000</td>
<td>18</td>
<td>80.0%</td>
<td>0.800</td>
<td>80.0%</td>
</tr>
<tr>
<td>parent_child</td>
<td>60</td>
<td>80.0%</td>
<td>0.800</td>
<td>80.0%</td>
</tr>
<tr>
<td>hypothetical_questions</td>
<td>30</td>
<td><strong>100.0%</strong></td>
<td>0.783</td>
<td>90.0%</td>
</tr>
</tbody>
</table>
<p>各指標の意味は以下の通り。</p>
<ul>
<li><strong>Hit Rate</strong>: 正解チャンクが上位k件に含まれた質問の割合。取りこぼしの少なさを示す</li>
<li><strong>MRR（Mean Reciprocal Rank）</strong>: 正解チャンクの順位の逆数の平均（1位=1.0、2位=0.5、3位=0.333、圏外=0）。1位ヒット率を示す</li>
<li><strong>Source Hit Rate</strong>: 期待するソースファイルが上位k件に含まれた割合</li>
</ul>
<p>1位精度重視ならstandard_1000（MRR 0.850）、取りこぼし最小化ならhypothetical_questions（Hit Rate 100%）が有効という結果が得られた。</p>
<h3 id="kを増やしても根本解決にならない" tabindex="-1">kを増やしても根本解決にならない</h3>
<p>VectorDB検索のパラメータk（取得件数）を増やせば取りこぼしは減るが、<strong>ランキング自体は変わらない</strong>。</p>
<ul>
<li><strong>kが小さい</strong> → 関連チャンクを取りこぼすリスク</li>
<li><strong>kが大きい</strong> → ノイズが増え、LLMのコンテキストが長くなり処理が遅くなる</li>
</ul>
<p>正解チャンクのスコアが低い場合、k=3をk=8に増やしても正解は上位に来ない。根本的な改善にはEmbeddingモデルの変更やチャンク分割の見直しが必要であり、kの調整は補助的な手段である。</p>
<h2 id="ragの精度をさらに上げる手法" tabindex="-1">RAGの精度をさらに上げる手法</h2>
<p>基本的なRAGの精度が確保できたら、以下の手法で改善を重ねられる。</p>
<h3 id="hybrid-search（ベクトル検索-+-キーワード検索）" tabindex="-1">Hybrid Search（ベクトル検索 + キーワード検索）</h3>
<p>ベクトル検索は意味的な類似度に強いが、金額・日付・固有名詞などの具体的な値の検索には弱い。BM25などのキーワード検索と組み合わせるHybrid Searchで、意味検索とキーワードマッチの両方をカバーできる。</p>
<h3 id="re-ranking（検索結果の再評価）" tabindex="-1">Re-ranking（検索結果の再評価）</h3>
<p>ベクトル検索で広めに取得（k=20程度）した後、クロスエンコーダモデルで再スコアリングして上位N件に絞り込む手法。ベクトル検索の取りこぼしをカバーし、精度の高い結果をLLMに渡せる。</p>
<h3 id="history-aware-retriever（会話文脈の活用）" tabindex="-1">History-Aware Retriever（会話文脈の活用）</h3>
<p>マルチターンの会話で代名詞を含む質問が来た場合、会話履歴を踏まえてクエリを書き換えてから検索する手法。</p>
<p>筆者の検証結果を以下に示す。</p>
<table>
<thead>
<tr>
<th>ターン</th>
<th>質問</th>
<th>回答</th>
<th>判定</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>リモートワークは週何日まで？</td>
<td>週3日まで</td>
<td>OK</td>
</tr>
<tr>
<td>2</td>
<td>それは最近変更されましたか？</td>
<td>2025年10月1日から週4日に変更</td>
<td>OK</td>
</tr>
</tbody>
</table>
<p>2ターン目で「それ」が「リモートワーク制度」として正しく解決され、変更情報を含むチャンクが検索された。履歴なしで同じ質問を送ると無関係なチャンクが返るため、クエリ書き換えが機能していることを確認できた。</p>
<h2 id="まとめ-—-ragは「検索の質」が全てを決める" tabindex="-1">まとめ — RAGは「検索の質」が全てを決める</h2>
<p>RAGは「検索で関連情報を取得し、LLMに渡して回答を生成する」アーキテクチャである。回答生成モデルがいくら優秀でも、検索で正解チャンクが取れなければ正しい回答は生成できない。</p>
<p>筆者の検証で得られた知見は以下の3点に集約される。</p>
<ol>
<li><strong>Embeddingモデルの選定が最もインパクトが大きい</strong> — モデル変更だけで正答率0/5→4/5</li>
<li><strong>チャンク分割は「1チャンク=1トピック」を意識する</strong> — データの性質に応じた分割戦略が必要</li>
<li><strong>kを増やすだけでは根本解決にならない</strong> — ランキング自体の改善が必要</li>
</ol>
<h3 id="rag構築時に確認すべき3つのポイント" tabindex="-1">RAG構築時に確認すべき3つのポイント</h3>
<ol>
<li>[ ] 対象データの言語に適したEmbeddingモデルを選定したか？</li>
<li>[ ] チャンク分割は「1チャンク=1トピック」になっているか？</li>
<li>[ ] Hit RateやMRRなどの定量指標で検索精度を計測しているか？</li>
</ol>
<p>RAGの精度改善は「検索の質を上げる」ことに尽きる。まずは手元のデータで小規模な検証を始め、Embeddingモデルとチャンク分割を調整しながら精度を計測・改善していくのが実践的なアプローチである。</p>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/1enfpUixTuvis0oX9x5Cfa/934aed76ac994412ceb37dfdf5dc6f6c/RAG%C3%A5__%C3%A9___%C3%A2___%C3%A6__%C3%A7__%C3%A6__%C3%A5_%C2%B5%C3%A7__%C3%A6__%C3%A3__%C3%A5_%C2%BA%C3%A7__%C3%A7__%C3%A8__%C3%A3__%C3%A5__%C3%A9__%C3%A7%C2%B5_%C3%A6__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A5__%C3%A3_%C2%B5%C3%A3__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[1人のエンジニアがAIを日常にどう組み込んでいるか — 契約サービスから活用フローまで全公開]]></title>
            <link>https://www.yoshihiko.tech/blog/engineer-ai-daily-workflow</link>
            <guid isPermaLink="false">engineer-ai-daily-workflow</guid>
            <pubDate>Wed, 18 Feb 2026 14:13:58 GMT</pubDate>
            <description><![CDATA[Claude Code、ChatGPT、Gemini、Aqua Voice、Adobe Fireflyの5サービスを使い分けるエンジニアが、開発・情報収集・ブレスト・音声入力・振り返りまで、AIを日常に組み込んだリアルな活用フローを全公開します。]]></description>
            <content:encoded><![CDATA[<p>AIを「コード補完ツール」程度にしか使っていないエンジニアは多いのではないだろうか。筆者は現在5つのAIサービスを契約し、開発・情報収集・ブレスト・設計・音声入力・振り返りと、日常のあらゆる場面にAIを組み込んでいる。この記事では、契約しているサービスの一覧と使い分け、それぞれの具体的な活用フローをそのまま公開する。</p>
<hr>
<h2 id="契約しているaiサービス一覧" tabindex="-1">契約しているAIサービス一覧</h2>
<p>まず前提として、筆者が現在契約しているAIサービスを紹介する。</p>
<table>
<thead>
<tr>
<th>サービス</th>
<th>プラン</th>
<th>月額（目安）</th>
<th>主な用途</th>
</tr>
</thead>
<tbody>
<tr>
<td>Claude Code</td>
<td>Max Plan</td>
<td>$100</td>
<td>開発（レビュー・設計壁打ち・オーケストレーション）</td>
</tr>
<tr>
<td>ChatGPT</td>
<td>Plus Plan</td>
<td>$20</td>
<td>開発（Codexによる実装）・Deep Research</td>
</tr>
<tr>
<td>Gemini</td>
<td>—</td>
<td>¥2,900</td>
<td>リサーチ・Deep Research・NotebookLM連携</td>
</tr>
<tr>
<td>Aqua Voice</td>
<td>—</td>
<td>$10</td>
<td>AI音声入力（LLMとの対話全般）</td>
</tr>
<tr>
<td>Adobe Creative Cloud</td>
<td>—</td>
<td>約¥5,000</td>
<td>画像生成・動画生成（Firefly）</td>
</tr>
</tbody>
</table>
<p>※ 料金は2026年2月時点の情報。</p>
<p>合計すると月額2万円強になる。決して安くはないが、開発効率の向上と情報収集にかかる時間の短縮を考えると、十分なリターンがあると感じている。</p>
<h3 id="claude-code-—-max-plan（$100/月）" tabindex="-1">Claude Code — Max Plan（$100/月）</h3>
<p>筆者のメインツール。開発におけるコードレビュー、設計の壁打ち、そして後述するAI Orchestraのオーケストレーターとして稼働している。モデルはOpus 4.6を中心に使用。つい先日<a href="https://www.anthropic.com/news/claude-sonnet-4-6" target="_blank" rel="noopener noreferrer">Sonnet 4.6がリリースされた</a>ので、比較的シンプルなタスクにはこちらを取り入れることも検討中だ。</p>
<h3 id="chatgpt-—-plus-plan（$20/月）" tabindex="-1">ChatGPT — Plus Plan（$20/月）</h3>
<p>主にCodex（コード生成・実装タスク）とDeep Researchで使用。開発では実装部分をCodexに任せるケースが多い。</p>
<h3 id="gemini（¥2,900/月）" tabindex="-1">Gemini（¥2,900/月）</h3>
<p>リサーチ用途がメイン。Deep Researchの選択肢の1つとして使うほか、NotebookLMとの連携で学習コンテンツの生成にも活用している。</p>
<h3 id="aqua-voice-—-ai音声入力（$10/月）" tabindex="-1">Aqua Voice — AI音声入力（$10/月）</h3>
<p><a href="https://aquavoice.com/" target="_blank" rel="noopener noreferrer">音声入力に特化したAIサービス</a>。まさにこの記事もAqua Voiceで音声入力しながら書いている。技術用語の認識精度が高く、「Claude Code」「NotebookLM」「エージェンティックコーディング」といった単語もほぼ正確に拾ってくれる。</p>
<h3 id="adobe-creative-cloud-—-firefly付き（約¥5,000/月）" tabindex="-1">Adobe Creative Cloud — Firefly付き（約¥5,000/月）</h3>
<p>Creative Cloudを契約するとAdobe Fireflyが付属する。画像生成や動画生成などのクリエイティブ系AI機能が使えるため、ブログのアイキャッチ画像やちょっとした素材の生成に活用している。</p>
<h3 id="過去に使っていたサービス" tabindex="-1">過去に使っていたサービス</h3>
<p>以前はNotion AIやGitHub Copilotも契約していた。ただ、Claude Codeのエージェンティックコーディングの方が自分の開発スタイルに合っていたのでCopilotは解約。Notion AIも現在のワークフローでは出番が減ったため一旦やめている。いろいろ触ってきた上で、今の5サービスに落ち着いた。</p>
<hr>
<h2 id="開発-—-ai-orchestraによるエージェンティックコーディング" tabindex="-1">開発 — AI Orchestraによるエージェンティックコーディング</h2>
<p>AI活用のメインは開発だ。個人でプロダクトの開発を進めており、そこにAIを全面的に組み込んでいる。</p>
<h3 id="自作のai-orchestraシステム" tabindex="-1">自作のAI Orchestraシステム</h3>
<p><a href="https://zenn.dev/yoshihiko555/articles/78f43f5cfc01d8" target="_blank" rel="noopener noreferrer">以前ブログ記事でも紹介した</a>が、「AI Orchestra」というシステムを自作している。複数のAIモデルに役割を分担させて開発を進める仕組みで、いわゆるエージェンティックコーディングを実現するものだ。</p>
<p>単にAIにコードを書かせるだけではなく、設計・実装・レビューという開発プロセス全体をAIに担わせる。人間（自分）はオーケストレーターとして方向性を示し、AIがそれを具体化する——という協働スタイルで開発している。</p>
<p><img src="https://storage.googleapis.com/zenn-user-upload/84f85ab6332e-20260218.png" alt="" loading="lazy"></p>
<h3 id="codexで実装、claude-code-opus-4.6でレビュー・設計壁打ち" tabindex="-1">Codexで実装、Claude Code Opus 4.6でレビュー・設計壁打ち</h3>
<p>現在の役割分担は以下の通りだ。</p>
<table>
<thead>
<tr>
<th>役割</th>
<th>担当</th>
</tr>
</thead>
<tbody>
<tr>
<td>実装（コーディング）</td>
<td>ChatGPT Codex</td>
</tr>
<tr>
<td>レビュー・品質チェック</td>
<td>Claude Code（Opus 4.6）</td>
</tr>
<tr>
<td>設計の壁打ち・方針決定</td>
<td>Claude Code（Opus 4.6）</td>
</tr>
</tbody>
</table>
<p>実装をCodexに任せ、その成果物をClaude Codeでレビューする。設計で迷ったときもClaude Codeに壁打ちする。1人開発でもレビュアーと設計相談相手がいる感覚で進められるのが大きなメリットだ。</p>
<h3 id="sonnet-4.6の登場と今後のフローへの取り込み" tabindex="-1">Sonnet 4.6の登場と今後のフローへの取り込み</h3>
<p>つい先日、Claude Sonnet 4.6がリリースされた。ベンチマークを見る限り性能がかなり向上しているようで、AI Orchestraのフローに組み込むことを検討中だ。深い推論が必要な場面はOpus 4.6、比較的シンプルなタスクはSonnet 4.6——という使い分けができれば、コスト効率と速度の両方が改善できそうだ。</p>
<hr>
<h2 id="情報収集-—-aiで日次キャッチアップを自動化" tabindex="-1">情報収集 — AIで日次キャッチアップを自動化</h2>
<p>開発と並ぶ大きな活用領域が情報収集だ。日々のキャッチアップはほぼAIに任せている。</p>
<h3 id="スキルを使った日次クローリング" tabindex="-1">スキルを使った日次クローリング</h3>
<p>自分が気になっている技術や、定期的にチェックしたい企業のテックブログなどを、Claude Codeの「スキル」として事前に登録している。スキルを実行すると、設定済みのソースを元にAIがクローリングし、記事を引っ張ってきてくれる。</p>
<p>取得した記事はMarkdown形式で一覧出力される。毎日業務の終わりにこの一覧を眺めるのが日課になっている。</p>
<h3 id="url-digestスキルで記事を一括要約" tabindex="-1">URL Digestスキルで記事を一括要約</h3>
<p>クローリングした一覧の中から気になる記事をピックアップする。とはいえ、10記事すべてを1つ1つ読み込むのは時間がかかる。</p>
<p>そこで「URL Digest」というスキルを使う。気になる記事のURLをまとめてコピーし、スキルに貼り付けて実行するだけ。各記事の内容が5行程度に要約されて返ってくる。</p>
<p>その要約を読んで、さらに気になるものだけ実際の本文を読みに行く。このフローのおかげで、情報収集にかける時間が大幅に短縮された。</p>
<p><strong>日次の情報収集フロー:</strong></p>
<pre><code>日次クローリング（スキル実行）
  ↓ Markdown一覧を出力
気になる記事をピックアップ
  ↓ URLをまとめてコピー
URL Digest（スキル実行）
  ↓ 各記事を5行に要約
さらに気になる記事だけ本文を読む
</code></pre>
<p><img src="https://storage.googleapis.com/zenn-user-upload/f1b92710ec5c-20260218.png" alt="" loading="lazy"></p>
<h3 id="deep-researchで技術を深掘り-→-notebooklmでインプット" tabindex="-1">Deep Researchで技術を深掘り → NotebookLMでインプット</h3>
<p>特定の技術をさらに深く知りたいときはDeep Researchを使う。Gemini、Claude、ChatGPTのいずれにもDeep Research機能があり、テーマを投げるとWeb上の情報を幅広く調査してレポートにまとめてくれる。</p>
<p>最近だと、tmux（ターミナルのセッション管理ツール）について深掘りした。CLIでの開発環境を改善するため、tmuxの詳細な使い方や設定方法を調査させた。</p>
<p>Deep Researchの調査結果は、Googleの<a href="https://notebooklm.google.com/" target="_blank" rel="noopener noreferrer">NotebookLM</a>に投入する。NotebookLMは投入した内容をもとにポッドキャスト形式の音声コンテンツを生成したり、クイズ形式の問題集を作ったりできる。調査内容を耳からも定着させられるので、インプットの効率が上がる。</p>
<h3 id="エージェントチームで多角的な比較・討論" tabindex="-1">エージェントチームで多角的な比較・討論</h3>
<p>Claude Codeの「エージェントチーム」機能も活用している。複数のサブエージェントをチームとして生成し、それぞれに異なる役割を与えて並列で動かす——いわばマルチエージェントシステムだ。</p>
<p>例えば「GeminiとChatGPTとClaude Codeを比較したい」とき。それぞれのサービスの立場に立つエージェントを3つ用意し、ファシリテーター役を1つ加えて、合計4エージェントで討論させる。</p>
<p>Claude Code視点のエージェントが強みを主張すると、ChatGPT視点のエージェントが反論する。ファシリテーターがそれを整理し、各サービスのメリット・デメリットをまとめてくれる。</p>
<p>この「AIに議論させる」アプローチはかなり面白い。自分1人で比較表を作るよりも多角的な視点が得られる。技術選定に限らず、さまざまな場面で応用できる手法だと感じている。</p>
<hr>
<h2 id="ブレスト・思考整理-—-aiとの対話でアイデアを広げる" tabindex="-1">ブレスト・思考整理 — AIとの対話でアイデアを広げる</h2>
<p>開発や情報収集に加えて、ブレストや思考整理にもAIを活用している。</p>
<h3 id="notionに書き殴っていた思考をaiで構造化" tabindex="-1">Notionに書き殴っていた思考をAIで構造化</h3>
<p>以前はNotionに自分の思考や考えをバーッと書き殴って、そのまま残すだけだった。記録にはなるが、後から見返しても整理されておらず活用しづらい。</p>
<p>現在はAIと対話しながらブレストを進めている。自分の考えをざっくり伝えると、AIが構造化してまとめてくれたり、別の切り口を提案してくれたりする。</p>
<h3 id="自分では出てこない視点が得られる" tabindex="-1">自分では出てこない視点が得られる</h3>
<p>ブレストでAIを使う最大のメリットは、自分だけでは出てこない視点が得られることだ。思考が煮詰まっているときに「こういう観点はどうか」と投げかけてもらえるだけで、思考が動き出すことがある。</p>
<p>AIは「答えをくれる存在」というより「壁打ち相手」に近い。</p>
<hr>
<h2 id="設計・要件定義-—-業務でのai活用" tabindex="-1">設計・要件定義 — 業務でのAI活用</h2>
<p>コードを書く以外にも、設計フェーズでAIをがっつり使っている。</p>
<h3 id="要件定義・詳細設計・技術調査にaiを投入" tabindex="-1">要件定義・詳細設計・技術調査にAIを投入</h3>
<p>要件定義、詳細設計、技術調査——こうした「考える系」の作業にAIを投入している。</p>
<ul>
<li><strong>要件定義</strong>: 要件の洗い出しや抜け漏れチェックをAIに依頼</li>
<li><strong>詳細設計</strong>: 設計案を壁打ちし、トレードオフの整理や代替案の提示を受ける</li>
<li><strong>技術調査</strong>: 採用候補のライブラリやアーキテクチャパターンの比較を調べさせる</li>
</ul>
<p>この辺りは多くのエンジニアにとってもイメージしやすい活用法だろう。「AIに聞いて課題を整理する」という使い方は、手軽かつ効果が高い。</p>
<hr>
<h2 id="音声入力-—-aqua-voiceでキーボードいらずの開発" tabindex="-1">音声入力 — Aqua Voiceでキーボードいらずの開発</h2>
<p>AI活用の中で、地味だが日常を大きく変えたのが音声入力だ。</p>
<h3 id="llmとの会話はほぼ音声入力" tabindex="-1">LLMとの会話はほぼ音声入力</h3>
<p>LLMとのやりとりはほぼすべてAqua Voiceの音声入力で行っている。最近はキーボードに触れる時間がかなり減った。</p>
<p>AI音声入力サービスはいくつかあるが、自分が試した中ではAqua Voiceが最も使いやすい。技術用語の認識精度の高さが決め手だ。</p>
<h3 id="「喋りながら開発する」スタイル" tabindex="-1">「喋りながら開発する」スタイル</h3>
<p>音声でAIと対話しながら、「ああでもない、こうでもない」と喋って開発を進めるスタイルが定着しつつある。キーボードでプロンプトを打つよりも、思考のスピードに近い速度でAIに指示を出せるのが大きい。</p>
<h3 id="この記事もaqua-voiceで書いている" tabindex="-1">この記事もAqua Voiceで書いている</h3>
<p>ちなみに、この記事自体もAqua Voiceで音声入力しながら作成した。自分の活用事例を喋って、それをAIに記事として整形してもらう——まさにAI活用の実践例そのものだ。</p>
<p><img src="https://storage.googleapis.com/zenn-user-upload/d8974703f82e-20260218.png" alt="" loading="lazy"></p>
<hr>
<h2 id="その他の活用-—-ブログ執筆・週次レビュー" tabindex="-1">その他の活用 — ブログ執筆・週次レビュー</h2>
<p>ここまで紹介した以外にも、いくつかの場面でAIを活用している。</p>
<h3 id="aiを使ったブログ記事作成" tabindex="-1">AIを使ったブログ記事作成</h3>
<p>今読んでいるこの記事のように、ブログの執筆にもAIを使っている。構成の検討から下書きの生成、校正まで、AIと協力しながら進めている。</p>
<h3 id="日次日記-→-週次・月次レビューのai分析" tabindex="-1">日次日記 → 週次・月次レビューのAI分析</h3>
<p>日々の軽い振り返り——今日何をやったか、何ができなかったか——を日記としてつけている。それを週単位でAIに投げて分析し、次のアクションを提案してもらう。</p>
<p>「今週はタスクAに時間を取られすぎて、タスクBが進んでいない。来週はタスクBを優先すべきでは？」——こうした提案に沿って日々のタスクの優先順位を調整している。月次でも同様に、1ヶ月分の振り返りをAIに分析させて長期的な視点でのアクションを検討している。</p>
<hr>
<h2 id="今後の展望" tabindex="-1">今後の展望</h2>
<p>現在のAI活用はここまでだが、さらに広げていく構想がある。</p>
<h3 id="openclawを使った自動化の構想" tabindex="-1">OpenClawを使った自動化の構想</h3>
<p>最近話題の<a href="https://docs.openclaw.ai" target="_blank" rel="noopener noreferrer">OpenClaw</a>が気になっている。将来的には、今手動で行っている自動化フローの一部をOpenClawに任せたいと考えており、そのための準備を進めている段階だ。</p>
<p>AIとの協働はまだまだ発展途上で、新しいツールやモデルが出るたびにワークフローが進化する。その変化を楽しみながら、自分のスタイルをアップデートし続けていきたい。</p>
<hr>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>筆者のAI活用を振り返ると、以下のようになる。</p>
<table>
<thead>
<tr>
<th>活用領域</th>
<th>使っているサービス</th>
</tr>
</thead>
<tbody>
<tr>
<td>開発（実装・レビュー・設計）</td>
<td>Claude Code + ChatGPT Codex</td>
</tr>
<tr>
<td>情報収集（日次クローリング・要約・Deep Research）</td>
<td>Claude Code + Gemini + ChatGPT + NotebookLM</td>
</tr>
<tr>
<td>ブレスト・思考整理</td>
<td>Claude Code</td>
</tr>
<tr>
<td>設計・要件定義</td>
<td>Claude Code</td>
</tr>
<tr>
<td>音声入力</td>
<td>Aqua Voice</td>
</tr>
<tr>
<td>クリエイティブ（画像・動画生成）</td>
<td>Adobe Firefly</td>
</tr>
<tr>
<td>ブログ執筆</td>
<td>Claude Code + Aqua Voice</td>
</tr>
<tr>
<td>週次・月次レビュー</td>
<td>Claude Code</td>
</tr>
</tbody>
</table>
<p>AIは「コードを書かせるツール」ではなく、日常全体のパートナーになりつつある。1つのサービスに閉じず、複数を用途別に使い分けることで、単体では実現できないワークフローが構築できる。</p>
<p>この使い方が万人にとって最適とは限らない。ただ、「こういう使い方をしているエンジニアもいる」という1つの参考になれば幸いだ。</p>
<hr>
<h2 id="faq" tabindex="-1">FAQ</h2>
<p><strong>Q. AIサービスに月いくらかかっている？</strong></p>
<p>合計で月額2万円強。Claude Code Max Plan（$100）、ChatGPT Plus（$20）、Gemini（¥2,900）が主な出費で、これにAqua Voice（$10）とAdobe CC（約¥5,000）が加わる。開発効率の向上と情報収集の時間短縮を考えると、十分なリターンがあると感じている。</p>
<p><strong>Q. GitHub Copilotをやめた理由は？</strong></p>
<p>Claude Codeのエージェンティックコーディングの方が自分のスタイルに合っていた。Copilotの行・関数単位の補完より、タスク単位で丸ごと任せられる方が効率的だった。</p>
<p><strong>Q. AI Orchestraの詳細は？</strong></p>
<p>以前ブログ記事で紹介している。複数のAIモデルに役割を割り振り、開発プロセス全体を協調させる仕組みだ。詳細はそちらを参照してほしい。</p>
<p><strong>Q. 音声入力の精度はどのくらい？</strong></p>
<p>Aqua Voiceは技術用語の認識精度が高く、日常の開発会話レベルであればほぼストレスなく使える。ただし、非常にニッチな固有名詞は修正が必要なこともある。</p>
<p><strong>Q. AIに依存しすぎるリスクはない？</strong></p>
<p>ある。基礎的な技術力や思考力は自分で維持する必要がある。AIはあくまで「思考を加速するツール」であり、最終判断は自分で行うという意識が重要だ。AIの提案を鵜呑みにせず、自分の頭で検証する習慣は意識的に保つようにしている。</p>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/5gBC06O8WX2GX8wmoOXQfy/27a11e0f280f642a781e462dead9d3ae/1%C3%A4%C2%BA%C2%BA%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__AI%C3%A3__%C3%A6__%C3%A5__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A7%C2%B5_%C3%A3__%C3%A8__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3___%C3%A2___%C3%A5__%C3%A7__%C3%A3_%C2%B5%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A6__%C3%A7__%C3%A3__%C3%A3_.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Claude Codeマルチエージェント実践｜AI Orchestraで超えた2つの壁]]></title>
            <link>https://www.yoshihiko.tech/blog/ai-orchestra-design-and-practice</link>
            <guid isPermaLink="false">ai-orchestra-design-and-practice</guid>
            <pubDate>Sun, 08 Feb 2026 08:01:16 GMT</pubDate>
            <description><![CDATA[Claude Code単体運用で生じるコンテキスト溢れ・役割混乱の課題を、25種の専門エージェントとClaude×Codex×Geminiのマルチモデル連携で解決するオーケストレーションシステム「AI Orchestra」。その設計思想、Hookによる自動ルーティング、並列レビューなどの実装を具体的に紹介します。]]></description>
            <content:encoded><![CDATA[<h1 id="claude-codeマルチエージェント実践：ai-orchestraの設計と実践" tabindex="-1">Claude Codeマルチエージェント実践：AI Orchestraの設計と実践</h1>
<p>Claude Codeは強力なAIコーディングツールですが、単体で使い続けるとコンテキストの肥大化や役割の曖昧さといった壁にぶつかります。この記事では、25種の専門エージェントとClaude / Codex / Geminiのマルチモデル連携で課題を解決するオーケストレーションシステム「AI Orchestra」の設計思想と実践を紹介します。</p>
<p>マルチエージェントの基本概念については<a href="/blog/multi-agent-building-guide">「マルチエージェント構築入門」</a>も併せて参照してください。</p>
<hr>
<h2 id="きっかけ：課題解決の調査から見つけた2つのアプローチ" tabindex="-1">きっかけ：課題解決の調査から見つけた2つのアプローチ</h2>
<p>Claude Code単体の運用で感じていた課題（後述するコンテキスト溢れと役割混乱）を解決するために、マルチエージェントシステムを自分で作ろうと決めたことです。</p>
<p>調査を進める中で、2つの有力なアプローチに出会いました。1つは<a href="https://zenn.dev/mkj/articles/claude-code-orchestra_20260120" target="_blank" rel="noopener noreferrer">松尾研究所のブログ記事</a>、もう1つはNPMで手軽に導入できるOSSの<a href="https://github.com/nrslib/takt" target="_blank" rel="noopener noreferrer">Takt</a>です。</p>
<p>Taktは導入の手軽さが魅力でした。コマンド1つでマルチエージェント環境が整い、Claude CodeやCodexの使い分けもフレームワーク側が吸収してくれます。初動のパフォーマンス向上という点では明らかに優れていました。</p>
<p>一方、松尾研究所のアプローチはClaude Codeのハーネス（Hooks、Agents、Skills）を深く理解した上で自分で構築する必要があります。手間はかかりますが、業界のデファクトスタンダードになりつつあるClaude Codeの拡張機構を根本から理解できます。</p>
<p>最終的に松尾研究所のアプローチを採用しました。自分のキャリアを考えたとき、便利なツールを使うだけではなく、仕組みそのものを理解して自分で作れることに価値があると判断したからです。AI Orchestraの基本思想は、この松尾研究所のブログ記事をベースにしています。</p>
<p>マルチエージェントの概念や構成パターンについては<a href="/blog/multi-agent-building-guide">「マルチエージェント構築入門」</a>で整理しています。</p>
<hr>
<h2 id="claude-code単体運用で見えた2つの壁" tabindex="-1">Claude Code単体運用で見えた2つの壁</h2>
<p>入門記事をまとめる過程で、Claude Codeの単体運用には大きく2つの壁があると実感しました。</p>
<h3 id="壁1:-コンテキスト溢れ" tabindex="-1">壁1: コンテキスト溢れ</h3>
<p>Claude Codeでの開発は1つの長い会話で進行します。設計相談をして、実装に入り、デバッグして、レビューを依頼する。この流れの中で会話が長くなると、前半に伝えた設計方針や制約条件が薄れていきます。大きなファイルの分析を依頼した場合も、コンテキストウィンドウ（LLMが一度に保持できる情報量）が圧迫され、他の指示が押し出される形になります。</p>
<h3 id="壁2:-役割混乱" tabindex="-1">壁2: 役割混乱</h3>
<p>もう1つの壁は、AIの役割が曖昧になることです。「このコードをレビューして」と依頼したはずなのに、レビュー結果を返す代わりに勝手にコードを書き換えてしまう。設計について相談しているつもりが、気づけばコード生成が始まっている。1つのエージェントがすべてを担当しているため、タスクの境界が不明確になるのです。</p>
<p>以下の表に、具体的な症状とAI Orchestraでの解決アプローチをまとめます。</p>
<table>
<thead>
<tr>
<th>課題</th>
<th>具体的な症状</th>
<th>解決アプローチ</th>
</tr>
</thead>
<tbody>
<tr>
<td>コンテキスト溢れ</td>
<td>長い会話で前半の指示を忘れる</td>
<td>サブエージェントにタスク委譲し、要約だけ受け取る</td>
</tr>
<tr>
<td>コンテキスト溢れ</td>
<td>大きなファイル分析で圧迫</td>
<td>専用エージェント（researcher等）で隔離</td>
</tr>
<tr>
<td>役割混乱</td>
<td>レビュー依頼したのに勝手に実装変更</td>
<td>code-reviewer専用エージェントで読み取り専用</td>
</tr>
<tr>
<td>役割混乱</td>
<td>設計相談がコード生成に化ける</td>
<td>architect / planner で役割を明確に分離</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="ai-orchestraの設計思想" tabindex="-1">AI Orchestraの設計思想</h2>
<p>AI Orchestraの核は「万能モデルに全部やらせる」のではなく「適材適所で専門家に任せる」という考え方です。</p>
<h3 id="25種の専門エージェント" tabindex="-1">25種の専門エージェント</h3>
<p>AI Orchestraでは、開発ワークフローの各工程に対応する25種の専門エージェントを定義しています。</p>
<table>
<thead>
<tr>
<th>カテゴリ</th>
<th>数</th>
<th>代表例</th>
<th>役割</th>
</tr>
</thead>
<tbody>
<tr>
<td>Planning</td>
<td>3</td>
<td>planner, researcher, requirements</td>
<td>タスク分解・調査・要件整理</td>
</tr>
<tr>
<td>Design</td>
<td>5</td>
<td>architect, api-designer, data-modeler</td>
<td>設計・API定義・データモデリング</td>
</tr>
<tr>
<td>Implementation</td>
<td>9</td>
<td>frontend-dev, backend-python-dev, debugger</td>
<td>実装・デバッグ・テスト</td>
</tr>
<tr>
<td>Review</td>
<td>6</td>
<td>code-reviewer, security-reviewer</td>
<td>品質チェック・脆弱性検出</td>
</tr>
<tr>
<td>Documentation</td>
<td>1</td>
<td>docs-writer</td>
<td>ドキュメント作成</td>
</tr>
<tr>
<td>Utility</td>
<td>1</td>
<td>general-purpose</td>
<td>汎用タスク・CLI委譲</td>
</tr>
</tbody>
</table>
<p>各エージェントにはシステムプロンプトで役割と責任範囲が明確に定義されています。たとえば<code>code-reviewer</code>は「コードの可読性・保守性・バグ検出に集中し、実装の変更は行わない」と指定されています。これにより、レビューを依頼したのにコードを書き換えられるという役割混乱を構造的に防止しています。</p>
<h3 id="マルチモデルの使い分け" tabindex="-1">マルチモデルの使い分け</h3>
<p>AI Orchestraのもう1つの特徴は、Claude以外のLLMを組み合わせている点です。入門記事ではClaude Code単体での構築例を紹介しましたが、AI Orchestraではさらにモデルを跨いだ連携を実現しています。各モデルには得意領域があり、それを活かす設計です。</p>
<table>
<thead>
<tr>
<th>モデル</th>
<th>強み</th>
<th>担当領域</th>
</tr>
</thead>
<tbody>
<tr>
<td>Claude (Opus/Sonnet)</td>
<td>コード実装・オーケストレーション</td>
<td>メインエージェント、実装系全般</td>
</tr>
<tr>
<td>Codex CLI</td>
<td>深い推論・設計判断</td>
<td>architect, reviewer等の思考が重いタスク</td>
</tr>
<tr>
<td>Gemini CLI</td>
<td>1Mトークン文脈・Google検索</td>
<td>researcher, 大規模コードベース分析</td>
</tr>
</tbody>
</table>
<p>モデルの性能差や得意分野については、<a href="/blog/ai-benchmark-guide">AIベンチマーク記事</a>も参考にしてください。ベンチマークの読み方を知っておくと、モデルの使い分け判断がしやすくなります。</p>
<p>Claudeがオーケストレーター（司令塔）としてタスクを分配し、深い設計判断はCodex CLIに、大規模なリサーチはGemini CLIにそれぞれ委譲する。こうすることで、各モデルの強みを最大限に引き出しつつ、メインのコンテキストを圧迫しない構成を実現しています。</p>
<hr>
<h2 id="ai-orchestraの実装：hook・並列レビュー・マルチモデル連携" tabindex="-1">AI Orchestraの実装：Hook・並列レビュー・マルチモデル連携</h2>
<p>ここからは、AI Orchestraの実装を3つの機能に絞って紹介します。</p>
<h3 id="hookによる自動ルーティング" tabindex="-1">Hookによる自動ルーティング</h3>
<p>AI Orchestraの中心にあるのが<code>agent-router.py</code>というHookです。Claude CodeにはUserPromptSubmitフック（ユーザーの入力を受け取った直後に実行される仕組み）があり、このフックがユーザーの発言を分析して適切なエージェントやCLIツールを自動で提案します。</p>
<pre><code class="language-python"># ユーザーの発言を分析し、適切なエージェントを提案
# 「セキュリティ大丈夫？」→ security-reviewer を提案
# 「この設計どう思う？」→ architect (+Codex) を提案
# 「最新のライブラリ調べて」→ Gemini CLI を提案
</code></pre>
<p>具体的には、日本語・英語両方のトリガーワードを辞書として保持しており、ユーザーの入力にマッチするキーワードがあれば対応するエージェントを提案します。「意識しなくても適切なエージェントが使われる」というUXを目指した仕組みです。</p>
<h3 id="/review-で並列レビュー" tabindex="-1">/review で並列レビュー</h3>
<p>AI Orchestraでは<code>/review</code>という1つのコマンドで、6種のレビュアーを並列に起動できます。</p>
<pre><code class="language-bash">/review  # 以下が並列実行される
# → code-reviewer: 可読性・保守性・バグ検出
# → security-reviewer: 脆弱性・権限チェック
# → performance-reviewer: 計算量・I/O最適化
# → spec-reviewer: 仕様との整合性
# → architecture-reviewer: アーキテクチャ妥当性
# → ux-reviewer: UX・アクセシビリティ
</code></pre>
<p>それぞれが独立したサブエージェントとして動作するため、レビュー中にメインのコンテキストは消費されません。各レビュアーの結果は重大度別に集約され、統合レポートとして表示されます。<code>/review impl</code>（実装系のみ）や<code>/review design</code>（設計系のみ）のように、目的に応じて実行範囲を絞ることもできます。</p>
<h3 id="codex/gemini-マルチモデル連携" tabindex="-1">Codex/Gemini マルチモデル連携</h3>
<p>設計判断やトレードオフ分析のように深い推論が求められる場面では、Codex CLIに処理を委譲します。一方、ライブラリの最新情報を調べたり、リポジトリ全体の構造を把握したりする場面ではGemini CLIが活躍します。</p>
<p>どちらの場合も、サブエージェント経由で呼び出すことがポイントです。Codex/Geminiの出力は長大になることがあるため、直接メインのコンテキストに流し込むと圧迫してしまいます。サブエージェントに「実行して、結果を要約して返して」と依頼することで、メインのコンテキストには要約だけが届く仕組みになっています。</p>
<hr>
<h2 id="運用して分かったこと" tabindex="-1">運用して分かったこと</h2>
<p>AI Orchestraを日常の開発で使い始めて、いくつかの発見がありました。</p>
<p><strong>良かった点</strong></p>
<ul>
<li>コンテキスト保護が効果的に機能している。長時間の開発セッションでも、前半の指示が抜け落ちる問題がかなり軽減された</li>
<li>レビュー品質が向上した。6種のレビュアーが並列で動くため、セキュリティやパフォーマンスの見落としが減った</li>
<li>モデルの使い分けが自動化された。Hookによるルーティングのおかげで、意識しなくても適切なモデルに振り分けられる</li>
</ul>
<p><strong>改善余地</strong></p>
<ul>
<li>エージェント定義のメンテナンスコストがある。25種のシステムプロンプトを適切に保つには継続的な調整が必要</li>
<li>Hookのルーティング精度は万全ではない。キーワードベースのため、文脈によっては不適切なエージェントが提案されることがある</li>
</ul>
<p>デプロイは<code>/init-orchestra</code>スキルで有効化でき、dotfiles管理によってプロジェクト間で設定を共有しています。新しいプロジェクトを始めるときも、すぐにオーケストレーション環境を使い始められます。</p>
<hr>
<h2 id="今後の展望" tabindex="-1">今後の展望</h2>
<p>AI Orchestraは現在GitHubで公開しています。まだ個人開発での運用がメインですが、今後はいくつかの方向で発展させたいと考えています。</p>
<p>まずは汎用化とドキュメント整備です。現状は自分の開発スタイルに最適化されている部分が多いため、他の開発者でも導入しやすい形に整えていきたいです。</p>
<p>次に、実案件での検証です。個人プロジェクトでは十分に機能していますが、チーム開発や規模の大きいコードベースでどこまで通用するかは未知数です。実際のプロジェクトで試して知見を蓄積していきます。</p>
<p>将来的には、エージェントの自律進化も構想しています。使用ログから指摘パターンを学習し、エージェント自身がシステムプロンプトを改善する仕組みです。</p>
<p>「AIに仕事を任せる」のではなく「AIとチームを組む」。AI Orchestraは、そうした開発スタイルを探求するための実験場です。マルチエージェントの世界に興味がある方は、まず<a href="https://zenn.dev/yoshihiko555/articles/647d7be6d02fa1" target="_blank" rel="noopener noreferrer">入門記事</a>で全体像をつかんだ上で、ぜひ自分の環境で試してみてください。</p>
<hr>
<h2 id="参考リンク" tabindex="-1">参考リンク</h2>
<ul>
<li><a href="https://github.com/yoshihiko555/ai-orchestra" target="_blank" rel="noopener noreferrer">AI Orchestra - GitHub</a></li>
<li><a href="https://zenn.dev/mkj/articles/claude-code-orchestra_20260120" target="_blank" rel="noopener noreferrer">松尾研究所 - Claude Code Orchestra</a></li>
<li><a href="https://github.com/nrslib/takt" target="_blank" rel="noopener noreferrer">Takt - マルチエージェントオーケストレーションOSS</a></li>
<li><a href="https://zenn.dev/yoshihiko555/articles/647d7be6d02fa1" target="_blank" rel="noopener noreferrer">マルチエージェント構築入門</a></li>
<li><a href="https://zenn.dev/yoshihiko555/articles/f1011ac0c4e61c" target="_blank" rel="noopener noreferrer">AIベンチマーク読み方ガイド</a></li>
</ul>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/6j2KWOOo7wJfUobTCg3o2w/126c8d413cd9a56e64fba107544ec4e1/Claude_Code%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A5__%C3%A8_%C2%B5%C3%AF__AI_Orchestra%C3%A3__%C3%A3__%C3%A8__%C3%A3__%C3%A3__2%C3%A3__%C3%A3__%C3%A5__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[AIベンチマーク読み方ガイド｜SWE-bench・GPQA・ARC-AGIの意味と活用法]]></title>
            <link>https://www.yoshihiko.tech/blog/ai-benchmark-guide</link>
            <guid isPermaLink="false">ai-benchmark-guide</guid>
            <pubDate>Sat, 07 Feb 2026 16:10:19 GMT</pubDate>
            <description><![CDATA[SWE-bench、GPQA Diamond、ARC-AGI、Terminal-Benchなど主要AIベンチマークの読み方を解説。2026年2月最新のモデルスコア比較と、コーディングエージェントへのタスク振り分けに活かす実践的な指針を紹介します。]]></description>
            <content:encoded><![CDATA[<h1 id="aiベンチマーク読み方ガイド：スコアの意味と実践的な活用法" tabindex="-1">AIベンチマーク読み方ガイド：スコアの意味と実践的な活用法</h1>
<p>Claude Opus 4.6とGPT-5.3-Codexが同日リリースされた2026年2月、AIモデルの選択肢はかつてないほど増えています。「SWE-bench 80%」「ARC-AGI 68%」といった数字が飛び交いますが、それぞれ何を測っていて、自分のタスクにどう関係するのか。この記事では、主要ベンチマークの読み方と、コーディング業務への活かし方を解説します。</p>
<hr>
<h2 id="この記事の対象読者" tabindex="-1">この記事の対象読者</h2>
<ul>
<li>AIコーディングツール（Claude Code、Cursor、GitHub Copilot等）を使っている開発者</li>
<li>モデル選定の判断材料としてベンチマークを読みたい人</li>
<li>「ベンチマークのスコアが高い＝自分の用途に最適」なのか疑問に思っている人</li>
</ul>
<hr>
<h2 id="なぜベンチマークを理解すべきか" tabindex="-1">なぜベンチマークを理解すべきか</h2>
<p>AIモデルの性能比較は「なんとなく新しいモデルが良い」では判断できなくなっています。</p>
<ul>
<li><strong>モデルごとに得意分野が異なる</strong>: コード実装に強いモデルと、設計・推論に強いモデルは別</li>
<li><strong>コストとのトレードオフ</strong>: 最高性能モデルが常に最適ではない。タスクに応じた使い分けが合理的</li>
<li><strong>ベンチマークにも限界がある</strong>: スコアの高さが実務性能を保証するわけではない</li>
</ul>
<p>ベンチマークを「鵜呑みにする」のではなく「読み方を理解して判断材料にする」のが目的です。</p>
<hr>
<h2 id="ベンチマーク5カテゴリの全体像" tabindex="-1">ベンチマーク5カテゴリの全体像</h2>
<p>主要なAIベンチマークは、大きく5つのカテゴリに分類できます。</p>
<table>
<thead>
<tr>
<th>カテゴリ</th>
<th>測定対象</th>
<th>代表的なベンチマーク</th>
</tr>
</thead>
<tbody>
<tr>
<td>コーディング</td>
<td>コード生成・修正能力</td>
<td>SWE-bench, Terminal-Bench, Aider</td>
</tr>
<tr>
<td>推論</td>
<td>論理的思考・分析能力</td>
<td>GPQA Diamond, ARC-AGI, MATH</td>
</tr>
<tr>
<td>知識・指示追従</td>
<td>広い知識と指示遵守</td>
<td>MMLU, IFEval</td>
</tr>
<tr>
<td>長文脈</td>
<td>長い入力の処理能力</td>
<td>RULER, NIAH, MRCR</td>
</tr>
<tr>
<td>エージェント</td>
<td>環境操作・タスク遂行</td>
<td>TAU-bench, WebArena, OSWorld</td>
</tr>
</tbody>
</table>
<p>以下、開発者にとって特に重要なベンチマークを解説します。</p>
<hr>
<h2 id="コーディング系ベンチマーク" tabindex="-1">コーディング系ベンチマーク</h2>
<h3 id="swe-bench：最も信頼できるコーディング指標" tabindex="-1">SWE-bench：最も信頼できるコーディング指標</h3>
<p><strong>何を測るか</strong>: 実際のGitHubリポジトリのissueを読み、修正パッチを自動生成する能力。</p>
<p>実在するOSSプロジェクトのissueとPull Requestのペアを使い、モデルにissueの説明だけを渡してコードの修正を生成させます。生成されたパッチがテストスイートを通過すれば「解決」と判定されます。</p>
<p><strong>バリエーション</strong>:</p>
<ul>
<li><strong>Verified</strong>（500問）: 人間が検証した信頼性の高いサブセット</li>
<li><strong>Full</strong>（2,294問）: 全問セット。ノイズが多い</li>
<li><strong>Pro</strong>: Scale AIが管理する、より厳密な評価</li>
</ul>
<p><strong>読み方のポイント</strong>: SWE-bench Verifiedのスコアが高いモデルは、実務でのバグ修正・機能追加に強い傾向があります。ただし、テスト通過のみで判定されるため、コードの品質（可読性・保守性）は評価されません。</p>
<h3 id="terminal-bench-2.0：エージェント的コーディング能力" tabindex="-1">Terminal-Bench 2.0：エージェント的コーディング能力</h3>
<p><strong>何を測るか</strong>: サンドボックス化されたターミナル環境で、89種類の実務的タスク（モデルトレーニング、システム管理、データ処理等）を遂行する能力。</p>
<p>SWE-benchが「コード修正」に特化しているのに対し、Terminal-Benchは「ターミナルを操作して問題を解決する」総合力を測ります。AIコーディングエージェント（Claude Code、Codex CLI等）の実力を反映しやすい指標です。</p>
<h3 id="aider-polyglot：多言語コード編集" tabindex="-1">Aider Polyglot：多言語コード編集</h3>
<p><strong>何を測るか</strong>: C++、Go、Java、JavaScript、Python、Rustの6言語でコードを編集する能力。</p>
<p>1回目の失敗時にテスト結果をフィードバックして2回目を試行する設計で、実際のコーディングアシスタントの使い方に近い評価です。</p>
<h3 id="humaneval-/-mbpp：飽和した初期指標" tabindex="-1">HumanEval / MBPP：飽和した初期指標</h3>
<p>Python関数の生成（HumanEval: 164問）と基本的なPython問題（MBPP: 974問）を測定しますが、現在のフロンティアモデルはいずれも95%以上に達しており、<strong>差別化指標としての価値は低下</strong>しています。</p>
<hr>
<h2 id="推論系ベンチマーク" tabindex="-1">推論系ベンチマーク</h2>
<h3 id="gpqa-diamond：大学院レベルの推論力" tabindex="-1">GPQA Diamond：大学院レベルの推論力</h3>
<p><strong>何を測るか</strong>: 物理・化学・生物の大学院レベル問題で、Google検索しても答えが見つからないような難問への回答能力。専門家でも正解率65%程度の難易度です。</p>
<p>設計やアーキテクチャの判断には深い推論力が求められます。GPQAのスコアが高いモデルは、技術的なトレードオフ分析や複雑なシステム設計に強い傾向があります。</p>
<h3 id="arc-agi-2：抽象推論の指標" tabindex="-1">ARC-AGI 2：抽象推論の指標</h3>
<p><strong>何を測るか</strong>: 視覚的なグリッドパターンの変換規則を推論する問題。学習データにない新規パターンへの汎化能力を測定します。</p>
<p>「AGIに最も近いベンチマーク」とも呼ばれ、データ汚染が困難な設計です。パターン認識と抽象的な推論が必要なため、設計力・問題構造の把握力の代理指標として有用です。</p>
<hr>
<h2 id="知識・長文脈系ベンチマーク" tabindex="-1">知識・長文脈系ベンチマーク</h2>
<h3 id="mmlu-/-ifeval：知識と指示追従" tabindex="-1">MMLU / IFEval：知識と指示追従</h3>
<ul>
<li><strong>MMLU</strong>: 57科目の多肢選択問題。フロンティアモデルは90%+で飽和傾向</li>
<li><strong>IFEval</strong>: 「JSONで出力せよ」「300語以内で」等の形式制約を守れるかを測定。エージェントとしての正確性に直結</li>
</ul>
<h3 id="ruler-/-mrcr：長文脈の実力" tabindex="-1">RULER / MRCR：長文脈の実力</h3>
<p><strong>RULER</strong>は4K〜128K+トークンの段階的評価で、検索・追跡・集約・QAの4種タスクを含みます。</p>
<p><strong>MRCR v2</strong>はOpus 4.6のリリースで注目された新指標で、1Mトークンの文脈中に埋め込まれた8つの「針」を同時に見つける能力を測ります。Opus 4.6は76%、Sonnet 4.5は18.5%と大差がつきました。</p>
<hr>
<h2 id="2026年2月-最新モデルスコア比較" tabindex="-1">2026年2月 最新モデルスコア比較</h2>
<p>公式発表とリーダーボードから収集した最新スコアです。</p>
<table>
<thead>
<tr>
<th>ベンチマーク</th>
<th>Opus 4.6</th>
<th>Codex 5.3</th>
<th>Sonnet 4.5</th>
<th>GPT-5.2</th>
<th>Gemini 2.5 Pro</th>
</tr>
</thead>
<tbody>
<tr>
<td>SWE-bench Verified</td>
<td><strong>80.8%</strong></td>
<td>-</td>
<td>77.2%</td>
<td>80.0%</td>
<td>63.8%</td>
</tr>
<tr>
<td>Terminal-Bench 2.0</td>
<td>69.9%</td>
<td><strong>75.1%</strong></td>
<td>46.5%</td>
<td>64.9%</td>
<td>32.6%</td>
</tr>
<tr>
<td>GPQA Diamond</td>
<td>91.3%</td>
<td>-</td>
<td>83.4%</td>
<td><strong>93.2%</strong></td>
<td>84.0%</td>
</tr>
<tr>
<td>ARC-AGI 2</td>
<td><strong>68.8%</strong></td>
<td>-</td>
<td>-</td>
<td>54.2%</td>
<td>-</td>
</tr>
<tr>
<td>OSWorld-Verified</td>
<td>-</td>
<td><strong>64.7%</strong></td>
<td>61.4%</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>MRCR v2 (1M)</td>
<td><strong>76%</strong></td>
<td>-</td>
<td>18.5%</td>
<td>-</td>
<td>-</td>
</tr>
</tbody>
</table>
<p><strong>注意点</strong>:</p>
<ul>
<li>Codex 5.3 はコーディング特化モデルのため、汎用ベンチマークのスコアは限定的</li>
<li>GPT-5.2 は「Pro」「Thinking」等のバリアントでスコアが異なる</li>
<li><code>-</code> はデータ未公開または未測定</li>
</ul>
<h3 id="各モデルの強み" tabindex="-1">各モデルの強み</h3>
<ul>
<li><strong>Claude Opus 4.6</strong>: 推論（ARC-AGI 68.8%）と長文脈（MRCR 76%）で圧倒。設計・レビュー・大規模コードベース理解向き</li>
<li><strong>GPT-5.3-Codex</strong>: Terminal-Bench（75.1%）で最強。実装・デバッグ向き</li>
<li><strong>Claude Sonnet 4.5</strong>: SWE-bench（77.2%）でコスパ良好。定型的なコーディングタスク向き</li>
<li><strong>GPT-5.2</strong>: GPQA Diamond（93.2%）で知識・推論が最強。分析・ドキュメント向き</li>
<li><strong>Gemini 2.5 Pro</strong>: 長文脈処理が得意。コードベース全体の分析向き</li>
</ul>
<hr>
<h2 id="実践：タスク別のベンチマーク活用法" tabindex="-1">実践：タスク別のベンチマーク活用法</h2>
<p>開発業務のタスクごとに、どのベンチマークを参考にすべきかの対応表です。</p>
<table>
<thead>
<tr>
<th>タスク</th>
<th>参考ベンチマーク</th>
<th>理由</th>
</tr>
</thead>
<tbody>
<tr>
<td>コード実装</td>
<td>SWE-bench, Terminal-Bench</td>
<td>コード生成・修正能力を直接測定</td>
</tr>
<tr>
<td>デバッグ</td>
<td>Terminal-Bench, SWE-bench</td>
<td>ターミナル操作でのバグ特定・修正</td>
</tr>
<tr>
<td>コードレビュー</td>
<td>GPQA Diamond</td>
<td>深い推論でコード品質を判断</td>
</tr>
<tr>
<td>テスト作成</td>
<td>SWE-bench</td>
<td>テスト通過が評価基準のため相関あり</td>
</tr>
<tr>
<td>設計・アーキテクチャ</td>
<td>ARC-AGI, GPQA Diamond</td>
<td>抽象推論とトレードオフ分析</td>
</tr>
<tr>
<td>ドキュメント作成</td>
<td>MMLU, IFEval</td>
<td>広い知識 + 指示形式の遵守</td>
</tr>
<tr>
<td>リファクタリング</td>
<td>Aider Polyglot</td>
<td>多言語対応のコード構造改善</td>
</tr>
<tr>
<td>大規模コードベース理解</td>
<td>RULER, MRCR</td>
<td>長文脈の情報統合</td>
</tr>
</tbody>
</table>
<h3 id="使い分けの例" tabindex="-1">使い分けの例</h3>
<p>AIコーディングエージェントでモデルを使い分ける場合:</p>
<pre><code>タスクの性質を判断
├── 実装・修正・デバッグ → Codex系（Terminal-Bench最強）
├── 設計・計画・推論   → Opus系（ARC-AGI・GPQA最強）
├── レビュー・分析     → Opus系（深い推論が必要）
├── 大規模コードベース → Opus系（1M tokens + 長文脈性能）
├── 軽量タスク・大量処理 → Sonnet / Haiku（コスパ重視）
└── 最新情報リサーチ   → Gemini系（Google検索統合）
</code></pre>
<p>このハイブリッド戦略は、RedditのClaude Codeコミュニティでも「Opus=設計、Codex=実装」として実践されているアプローチです。</p>
<hr>
<h2 id="ベンチマークの限界：スコアを鵜呑みにしない" tabindex="-1">ベンチマークの限界：スコアを鵜呑みにしない</h2>
<h3 id="1.-データ汚染" tabindex="-1">1. データ汚染</h3>
<p>学習データにベンチマーク問題が含まれている可能性があります。HumanEvalやMBPPは特にリスクが高い一方、LiveCodeBench（継続更新）やARC-AGI（新規パターン生成）は汚染に強い設計です。</p>
<h3 id="2.-飽和した指標" tabindex="-1">2. 飽和した指標</h3>
<p>HumanEval（95%+）、GSM8K（95%+）、MMLU（90%+）はフロンティアモデル間で差がつきません。これらのスコアだけで判断するのは危険です。</p>
<h3 id="3.-実務とのギャップ" tabindex="-1">3. 実務とのギャップ</h3>
<p>ベンチマークは「短いプロンプト・明確な正解・自動判定」の世界です。実務は「曖昧な要件・大規模コードベース・非機能要件」の世界。スコア上位のモデルが実務で苦戦するケースは頻繁に報告されています。</p>
<h3 id="4.-評価条件の差異" tabindex="-1">4. 評価条件の差異</h3>
<p>同じベンチマークでも、エージェントフレームワーク、プロンプト戦略、リトライ回数で結果が大きく変わります。公式発表値とリーダーボード値が異なることもあります（例: Opus 4.6のTerminal-Bench 65.4% vs 69.9%）。</p>
<hr>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>AIベンチマークは「モデルの能力の断面」を測る道具です。読み方のポイントをまとめます。</p>
<ol>
<li><strong>単一指標で判断しない</strong>: コーディング能力はSWE-bench、推論力はGPQA、長文脈はRULER。複数指標を組み合わせる</li>
<li><strong>タスクに合った指標を見る</strong>: 実装ならTerminal-Bench、設計ならARC-AGI。目的と指標のマッピングを意識する</li>
<li><strong>飽和した指標に注意</strong>: HumanEval、GSM8K、MMLUはフロンティアモデルで差がつかない</li>
<li><strong>コストも判断軸に入れる</strong>: 最高性能モデルが常に最適ではない。タスクの重要度に応じて使い分ける</li>
<li><strong>実務テストを忘れない</strong>: ベンチマークはあくまで参考値。自分の業務で試して判断する</li>
</ol>
<p>AIモデルの進化は速く、今日のスコアは半年後に陳腐化します。ベンチマークの「読み方」を身につけておけば、新モデルが出るたびに適切な判断ができるようになります。</p>
<hr>
<h2 id="参考リンク" tabindex="-1">参考リンク</h2>
<ul>
<li><a href="https://www.anthropic.com/news/claude-opus-4-6" target="_blank" rel="noopener noreferrer">Anthropic: Introducing Claude Opus 4.6</a></li>
<li><a href="https://www.tbench.ai/leaderboard/terminal-bench/2.0" target="_blank" rel="noopener noreferrer">Terminal-Bench 2.0 Leaderboard</a></li>
<li><a href="https://llm-stats.com/benchmarks/swe-bench-verified" target="_blank" rel="noopener noreferrer">SWE-bench Verified Leaderboard</a></li>
<li><a href="https://aider.chat/docs/leaderboards/" target="_blank" rel="noopener noreferrer">Aider Polyglot Leaderboard</a></li>
<li><a href="https://llm-stats.com/benchmarks" target="_blank" rel="noopener noreferrer">LLM Benchmarks 2026</a></li>
</ul>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/3QlfPsXmLogdj9o4mdgFYQ/73a69ca272ac4e4fdcf977f322c0218a/AI%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A8%C2%AA_%C3%A3__%C3%A6__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%AF__SWE-bench%C3%A3__GPQA%C3%A3__ARC-AGI%C3%A3__%C3%A6__%C3%A5__%C3%A3__%C3%A6__%C3%A7__%C3%A6__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[マルチエージェント構築入門｜構成の全体像と3つの実装アプローチ]]></title>
            <link>https://www.yoshihiko.tech/blog/multi-agent-building-guide</link>
            <guid isPermaLink="false">multi-agent-building-guide</guid>
            <pubDate>Thu, 05 Feb 2026 12:52:58 GMT</pubDate>
            <description><![CDATA[マルチエージェントの構築方法を解説。オーケストレーター・サブエージェント・ツール連携の3要素から、自作・フレームワーク・既存ツール活用の選択肢まで。Claude Codeでの実装例付き。]]></description>
            <content:encoded><![CDATA[<h1 id="マルチエージェント構築入門：構成の全体像と実装アプローチ" tabindex="-1">マルチエージェント構築入門：構成の全体像と実装アプローチ</h1>
<p>マルチエージェントは「司令塔（オーケストレーター）」が「専門家チーム（サブエージェント）」にタスクを振り分ける構成です。フレームワークなしでもClaude CodeのTaskツールで構築可能ですが、エージェント間連携（ハーネス）の設計は必要です。AIツールの進化で自作の必要性は変わる可能性があります。</p>
<hr>
<h2 id="マルチエージェントとは何か" tabindex="-1">マルチエージェントとは何か</h2>
<p>マルチエージェントとは、複数のAIエージェントが協調してタスクを遂行するシステムです。1つのLLMにすべてを任せるのではなく、役割を分担することで複雑なタスクに対応します。</p>
<h3 id="シングルエージェントとの違い" tabindex="-1">シングルエージェントとの違い</h3>
<p>シングルエージェントは1つのAI（例：ChatGPT、Claude）が単独でタスクを処理します。ユーザーの指示を受け、思考し、回答を返す一連の流れを1つのエージェントが担当します。</p>
<p>マルチエージェントでは、この流れを複数のエージェントで分担します。</p>
<table>
<thead>
<tr>
<th>項目</th>
<th>シングルエージェント</th>
<th>マルチエージェント</th>
</tr>
</thead>
<tbody>
<tr>
<td>構成</td>
<td>1つのLLM</td>
<td>複数のLLM（役割別）</td>
</tr>
<tr>
<td>得意領域</td>
<td>単純〜中程度のタスク</td>
<td>複雑・多段階のタスク</td>
</tr>
<tr>
<td>コンテキスト</td>
<td>1つの会話で完結</td>
<td>分散して管理</td>
</tr>
<tr>
<td>専門性</td>
<td>汎用的</td>
<td>役割ごとに特化可能</td>
</tr>
</tbody>
</table>
<h3 id="なぜマルチエージェントが注目されているのか" tabindex="-1">なぜマルチエージェントが注目されているのか</h3>
<p>AIエージェントの性能向上に伴い、「1つのエージェントに全部やらせる」アプローチの限界が見えてきました。</p>
<ol>
<li><strong>コンテキスト長の制約</strong>: 1つの会話に詰め込める情報には限界がある</li>
<li><strong>専門性の限界</strong>: 汎用モデルはすべての領域で最高性能ではない</li>
<li><strong>複雑なワークフロー</strong>: 計画→実装→レビューのような多段階処理は分業が効率的</li>
</ol>
<p>人間のチーム開発と同様に、AIも役割分担することで複雑なタスクに対応できるようになります。</p>
<hr>
<h2 id="マルチエージェントの基本構成" tabindex="-1">マルチエージェントの基本構成</h2>
<p>マルチエージェントシステムは3つの要素で構成されます。</p>
<h3 id="オーケストレーター（司令塔）" tabindex="-1">オーケストレーター（司令塔）</h3>
<p>オーケストレーターは全体を統括するエージェントです。ユーザーからの指示を受け、適切なサブエージェントにタスクを振り分け、結果を統合します。</p>
<p>役割:</p>
<ul>
<li>タスクの分解と振り分け</li>
<li>サブエージェントの選択</li>
<li>結果の統合と最終回答の生成</li>
<li>エラー時のリトライ判断</li>
</ul>
<h3 id="サブエージェント（専門家）" tabindex="-1">サブエージェント（専門家）</h3>
<p>サブエージェントは特定の役割に特化したエージェントです。オーケストレーターからタスクを受け取り、専門領域の処理を担当します。</p>
<p>例:</p>
<ul>
<li><strong>planner</strong>: タスク分解・計画立案</li>
<li><strong>researcher</strong>: 情報収集・調査</li>
<li><strong>coder</strong>: コード実装</li>
<li><strong>reviewer</strong>: コードレビュー・品質チェック</li>
<li><strong>tester</strong>: テスト作成・実行</li>
</ul>
<h3 id="ツール連携（外部api・cli）" tabindex="-1">ツール連携（外部API・CLI）</h3>
<p>エージェントは外部ツールと連携して能力を拡張します。ファイル操作、Web検索、API呼び出しなど、LLM単体では不可能な処理を実行します。</p>
<p>例:</p>
<ul>
<li>ファイル読み書き（Read, Write, Edit）</li>
<li>コマンド実行（Bash）</li>
<li>Web検索（WebSearch）</li>
<li>外部API呼び出し</li>
</ul>
<h3 id="【図】基本構成図" tabindex="-1">【図】基本構成図</h3>
<pre><code class="language-mermaid">flowchart TB
    User[ユーザー] --&gt; Orchestrator[オーケストレーター]

    Orchestrator --&gt; Planner[planner&lt;br/&gt;計画]
    Orchestrator --&gt; Researcher[researcher&lt;br/&gt;調査]
    Orchestrator --&gt; Coder[coder&lt;br/&gt;実装]
    Orchestrator --&gt; Reviewer[reviewer&lt;br/&gt;レビュー]

    Planner --&gt; Tools[ツール群]
    Researcher --&gt; Tools
    Coder --&gt; Tools
    Reviewer --&gt; Tools

    Tools --&gt; Files[ファイル操作]
    Tools --&gt; CLI[CLI実行]
    Tools --&gt; Web[Web検索]
    Tools --&gt; API[外部API]
</code></pre>
<hr>
<h2 id="代表的なアーキテクチャパターン" tabindex="-1">代表的なアーキテクチャパターン</h2>
<p>マルチエージェントの構成にはいくつかのパターンがあります。</p>
<h3 id="階層型（hierarchical）" tabindex="-1">階層型（Hierarchical）</h3>
<p>オーケストレーターを頂点とした階層構造です。指示は上から下へ流れ、結果は下から上へ返ります。</p>
<p><strong>特徴</strong>:</p>
<ul>
<li>制御が明確</li>
<li>デバッグしやすい</li>
<li>オーケストレーターがボトルネックになりやすい</li>
</ul>
<p><strong>適した用途</strong>: 明確なワークフローがある場合（計画→実装→レビュー）</p>
<h3 id="ピアツーピア型（peer-to-peer）" tabindex="-1">ピアツーピア型（Peer-to-Peer）</h3>
<p>エージェント同士が直接やり取りする構成です。中央の司令塔を持たず、必要に応じてエージェント間で協調します。</p>
<p><strong>特徴</strong>:</p>
<ul>
<li>柔軟性が高い</li>
<li>スケールしやすい</li>
<li>制御が複雑になりやすい</li>
</ul>
<p><strong>適した用途</strong>: 動的にタスクが変化する場合、エージェント数が多い場合</p>
<h3 id="ハイブリッド型" tabindex="-1">ハイブリッド型</h3>
<p>階層型とピアツーピア型を組み合わせた構成です。基本は階層型で、特定のサブグループ内ではピアツーピアで連携します。</p>
<p><strong>特徴</strong>:</p>
<ul>
<li>柔軟性と制御のバランス</li>
<li>設計が複雑</li>
</ul>
<p><strong>適した用途</strong>: 大規模で複雑なシステム</p>
<h3 id="【表】パターン比較" tabindex="-1">【表】パターン比較</h3>
<table>
<thead>
<tr>
<th>パターン</th>
<th>制御のしやすさ</th>
<th>柔軟性</th>
<th>実装難易度</th>
<th>適した規模</th>
</tr>
</thead>
<tbody>
<tr>
<td>階層型</td>
<td>高</td>
<td>低</td>
<td>低</td>
<td>小〜中規模</td>
</tr>
<tr>
<td>ピアツーピア型</td>
<td>低</td>
<td>高</td>
<td>高</td>
<td>中〜大規模</td>
</tr>
<tr>
<td>ハイブリッド型</td>
<td>中</td>
<td>中</td>
<td>高</td>
<td>大規模</td>
</tr>
</tbody>
</table>
<p>個人開発や小規模チームでは、階層型から始めることを推奨します。</p>
<hr>
<h2 id="構築アプローチの選択肢" tabindex="-1">構築アプローチの選択肢</h2>
<p>マルチエージェントを構築する方法は3つあります。</p>
<h3 id="自作する" tabindex="-1">自作する</h3>
<p>LLMのAPIを直接呼び出し、オーケストレーションのロジックを自分で実装します。</p>
<p><strong>メリット</strong>:</p>
<ul>
<li>完全なカスタマイズが可能</li>
<li>依存関係が少ない</li>
<li>内部構造を深く理解できる</li>
</ul>
<p><strong>デメリット</strong>:</p>
<ul>
<li>実装コストが高い</li>
<li>エラーハンドリング、リトライ等を自前で実装する必要がある</li>
</ul>
<h3 id="フレームワークを使う（langchain,-autogen等）" tabindex="-1">フレームワークを使う（LangChain, AutoGen等）</h3>
<p>既存のフレームワークを利用して構築します。</p>
<p><strong>メリット</strong>:</p>
<ul>
<li>実装コストが低い</li>
<li>ベストプラクティスが組み込まれている</li>
<li>コミュニティのサポートがある</li>
</ul>
<p><strong>デメリット</strong>:</p>
<ul>
<li>フレームワークの学習コスト</li>
<li>抽象化による制約</li>
<li>バージョンアップへの追従が必要</li>
</ul>
<p>参考: <a href="https://docs.langchain.com/oss/python/langchain/multi-agent" target="_blank" rel="noopener noreferrer">LangChain Multi-agent</a> / <a href="https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/" target="_blank" rel="noopener noreferrer">LangGraph</a> / <a href="https://microsoft.github.io/autogen/stable/" target="_blank" rel="noopener noreferrer">AutoGen</a></p>
<h3 id="既存ツールを活用する（claude-code,-cursor）" tabindex="-1">既存ツールを活用する（Claude Code, Cursor）</h3>
<p>AIコーディングツールが提供するサブエージェント機能を活用します。</p>
<p><strong>メリット</strong>:</p>
<ul>
<li>追加実装がほぼ不要</li>
<li>ツール側でハーネスが整備済み</li>
<li>すぐに試せる</li>
</ul>
<p><strong>デメリット</strong>:</p>
<ul>
<li>ツールの仕様に依存</li>
<li>カスタマイズに限界がある</li>
</ul>
<p>参考: <a href="https://docs.anthropic.com/en/docs/claude-code/sub-agents" target="_blank" rel="noopener noreferrer">Claude Code Subagents</a></p>
<h3 id="【表】アプローチ比較" tabindex="-1">【表】アプローチ比較</h3>
<table>
<thead>
<tr>
<th>アプローチ</th>
<th>実装コスト</th>
<th>カスタマイズ性</th>
<th>学習コスト</th>
<th>保守性</th>
</tr>
</thead>
<tbody>
<tr>
<td>自作</td>
<td>高</td>
<td>高</td>
<td>中</td>
<td>自己責任</td>
</tr>
<tr>
<td>フレームワーク</td>
<td>中</td>
<td>中</td>
<td>高</td>
<td>フレームワーク依存</td>
</tr>
<tr>
<td>既存ツール活用</td>
<td>低</td>
<td>低</td>
<td>低</td>
<td>ツール依存</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="claude-codeでの構築例" tabindex="-1">Claude Codeでの構築例</h2>
<p>Claude Codeでは、Taskツールを使ってサブエージェントを呼び出せます。</p>
<h3 id="taskツールによるサブエージェント呼び出し" tabindex="-1">Taskツールによるサブエージェント呼び出し</h3>
<p>Taskツールは専門エージェントを起動し、特定のタスクを委譲するための機能です。</p>
<pre><code>Task(subagent_type=&quot;planner&quot;, prompt=&quot;タスク分解: ユーザー認証機能の実装&quot;)
</code></pre>
<p>このように呼び出すと、plannerエージェントが起動し、タスク分解を実行して結果を返します。</p>
<h3 id="エージェントの役割定義" tabindex="-1">エージェントの役割定義</h3>
<p>Claude Codeでは以下のようなサブエージェントが利用可能です（一部抜粋）。</p>
<table>
<thead>
<tr>
<th>Agent</th>
<th>役割</th>
</tr>
</thead>
<tbody>
<tr>
<td>planner</td>
<td>タスク分解・マイルストーン計画</td>
</tr>
<tr>
<td>researcher</td>
<td>調査・情報収集</td>
</tr>
<tr>
<td>architect</td>
<td>システムアーキテクチャ設計</td>
</tr>
<tr>
<td>frontend-dev</td>
<td>フロントエンド実装</td>
</tr>
<tr>
<td>backend-python-dev</td>
<td>Pythonバックエンド実装</td>
</tr>
<tr>
<td>code-reviewer</td>
<td>コードレビュー</td>
</tr>
<tr>
<td>tester</td>
<td>テスト作成</td>
</tr>
</tbody>
</table>
<h3 id="コンテキスト管理の工夫" tabindex="-1">コンテキスト管理の工夫</h3>
<p>マルチエージェントではコンテキスト（会話履歴）の管理が重要です。</p>
<p><strong>課題</strong>: メインのオーケストレーターに情報を集中させると、コンテキスト長の制約に達しやすい</p>
<p><strong>対策</strong>:</p>
<ol>
<li>大きな出力が予想されるタスクはサブエージェント経由で実行</li>
<li>サブエージェントからは要約を返してもらう</li>
<li>詳細が必要な場合のみ全文を取得</li>
</ol>
<pre><code>Task(subagent_type=&quot;general-purpose&quot;, prompt=&quot;&quot;&quot;
調査を実行し、結果を要約してください（5-7ポイント）。
詳細はファイルに保存してください。
&quot;&quot;&quot;)
</code></pre>
<h3 id="【コード例】基本的な呼び出しパターン" tabindex="-1">【コード例】基本的な呼び出しパターン</h3>
<p><strong>計画フェーズ</strong></p>
<pre><code>Task(subagent_type=&quot;planner&quot;, prompt=&quot;計画: ユーザー認証機能&quot;)
Task(subagent_type=&quot;researcher&quot;, prompt=&quot;調査: OAuth 2.0の実装パターン&quot;)
</code></pre>
<p><strong>設計フェーズ</strong></p>
<pre><code>Task(subagent_type=&quot;architect&quot;, prompt=&quot;設計: 認証システムのアーキテクチャ&quot;)
Task(subagent_type=&quot;api-designer&quot;, prompt=&quot;API設計: 認証エンドポイント&quot;)
</code></pre>
<p><strong>実装フェーズ</strong></p>
<pre><code>Task(subagent_type=&quot;backend-python-dev&quot;, prompt=&quot;実装: 認証API&quot;)
Task(subagent_type=&quot;tester&quot;, prompt=&quot;テスト作成: 認証APIのユニットテスト&quot;)
</code></pre>
<p><strong>レビューフェーズ</strong></p>
<pre><code>Task(subagent_type=&quot;code-reviewer&quot;, prompt=&quot;レビュー: 認証APIの実装&quot;)
Task(subagent_type=&quot;security-reviewer&quot;, prompt=&quot;セキュリティレビュー: 認証フロー&quot;)
</code></pre>
<hr>
<h2 id="構築して分かった課題と限界" tabindex="-1">構築して分かった課題と限界</h2>
<p>マルチエージェントを自作してみると、いくつかの課題が見えてきます。</p>
<h3 id="ハーネス設計の難しさ" tabindex="-1">ハーネス設計の難しさ</h3>
<p>ハーネスとは、エージェント間の連携部分（タスクの受け渡し、結果の統合、エラーハンドリング等）を指します。</p>
<p><strong>課題</strong>:</p>
<ul>
<li>どのエージェントにどのタスクを振るか判断ロジックが必要</li>
<li>失敗時のリトライ・フォールバック戦略</li>
<li>エージェント間のデータフォーマット統一</li>
</ul>
<p>既存ツール（Claude Code等）を使う場合、この部分はツール側で整備されていますが、カスタマイズしたい場合は自前での実装が必要です。</p>
<h3 id="コンテキスト長の制約" tabindex="-1">コンテキスト長の制約</h3>
<p>各エージェントが持てるコンテキスト（会話履歴）には上限があります。</p>
<p><strong>課題</strong>:</p>
<ul>
<li>長いタスクでは途中で情報が欠落する可能性</li>
<li>サブエージェント間で情報を共有する仕組みが必要</li>
</ul>
<p><strong>対策例</strong>:</p>
<ul>
<li>ファイルベースでの情報共有</li>
<li>要約を活用したコンテキスト圧縮</li>
</ul>
<h3 id="エージェント間の情報共有" tabindex="-1">エージェント間の情報共有</h3>
<p>サブエージェントAの結果をサブエージェントBで使いたい場合、情報の受け渡し方法を設計する必要があります。</p>
<p><strong>方法例</strong>:</p>
<ol>
<li>オーケストレーター経由で受け渡し</li>
<li>共有ファイルに書き出し</li>
<li>専用のメモリ（状態管理）を用意</li>
</ol>
<h3 id="運用・デバッグの複雑さ" tabindex="-1">運用・デバッグの複雑さ</h3>
<p>複数のエージェントが動くため、問題発生時の原因特定が難しくなります。</p>
<p><strong>課題</strong>:</p>
<ul>
<li>どのエージェントで問題が起きたか特定しにくい</li>
<li>ログが分散する</li>
<li>再現が難しい</li>
</ul>
<hr>
<h2 id="今後の展望と判断基準" tabindex="-1">今後の展望と判断基準</h2>
<p>マルチエージェントは発展途上の技術です。今後の動向を踏まえて、自作するか判断する必要があります。</p>
<h3 id="aiツール自体の進化" tabindex="-1">AIツール自体の進化</h3>
<p>Claude Code、Cursor、GitHub Copilotなどのツールは急速に進化しています。</p>
<p><strong>現状の流れ</strong>:</p>
<ul>
<li>サブエージェント機能の標準搭載</li>
<li>ハーネス部分の自動化</li>
<li>より高度なオーケストレーション機能</li>
</ul>
<p>この流れが続けば、自作する必要性は低下する可能性があります。学習目的では価値がありますが、実用目的なら既存ツールの進化を見守る選択肢もあります。</p>
<h3 id="自作すべきか判断するポイント" tabindex="-1">自作すべきか判断するポイント</h3>
<p>以下の場合は自作を検討する価値があります:</p>
<ol>
<li><strong>特殊なワークフローがある</strong>: 既存ツールでは対応できない独自の処理フロー</li>
<li><strong>深いカスタマイズが必要</strong>: エージェントの振る舞いを細かく制御したい</li>
<li><strong>学習目的</strong>: 内部構造を理解したい</li>
<li><strong>既存ツールに依存したくない</strong>: ベンダーロックインを避けたい</li>
</ol>
<h3 id="【チェックリスト】自作判断フロー" tabindex="-1">【チェックリスト】自作判断フロー</h3>
<ul>
<li>[ ] 既存ツール（Claude Code, Cursor等）で実現できないか確認した</li>
<li>[ ] 必要なカスタマイズの範囲を明確にした</li>
<li>[ ] ハーネス設計に必要な工数を見積もった</li>
<li>[ ] 保守・運用の体制を考慮した</li>
<li>[ ] AIツールの進化による陳腐化リスクを許容できる</li>
</ul>
<p>すべてにチェックが入る場合は自作を検討してください。1つでも不明な点があれば、まず既存ツールで試すことを推奨します。</p>
<hr>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>マルチエージェントは「司令塔＋専門家チーム」の構成で複雑なタスクに対応するシステムです。</p>
<p><strong>ポイント</strong>:</p>
<ul>
<li>基本構成は「オーケストレーター」「サブエージェント」「ツール連携」の3要素</li>
<li>Claude CodeのTaskツールを使えばフレームワークなしで構築可能</li>
<li>ハーネス（エージェント間連携）の設計は避けられない</li>
<li>現時点では発展途上。AIツールの進化で状況は変わる可能性がある</li>
</ul>
<p><strong>次のステップ</strong>:</p>
<ol>
<li>まずはClaude CodeやCursorでサブエージェント機能を試す</li>
<li>構成のイメージをつかんでから、自作するか判断する</li>
<li>自作する場合は、小さな構成（2-3エージェント）から始める</li>
</ol>
<p>マルチエージェントは「作れば終わり」ではなく、継続的な改善が必要な領域です。まずは全体像を理解し、自分の用途に合ったアプローチを選択してください。</p>
<hr>
<h2 id="faq" tabindex="-1">FAQ</h2>
<h3 id="q1:-マルチエージェントとragの違いは何ですか？" tabindex="-1">Q1: マルチエージェントとRAGの違いは何ですか？</h3>
<p>RAGは「検索＋生成」で外部データを参照して回答を生成する仕組みです。マルチエージェントは「複数エージェントの協調」で複雑なタスクを分業する仕組みです。RAGをマルチエージェントの1ツールとして組み込むことも可能です。</p>
<h3 id="q2:-フレームワークなしでマルチエージェントを構築できますか？" tabindex="-1">Q2: フレームワークなしでマルチエージェントを構築できますか？</h3>
<p>可能です。Claude CodeのTaskツールやCursorのエージェントモードを使えば、サブエージェント呼び出しが標準機能として提供されています。ただし、エージェント間連携（ハーネス）の設計は必要です。</p>
<h3 id="q3:-マルチエージェントは個人開発でも意味がありますか？" tabindex="-1">Q3: マルチエージェントは個人開発でも意味がありますか？</h3>
<p>タスクの複雑さによります。単純なタスクならシングルエージェントで十分です。計画→実装→レビューのような多段階ワークフローを自動化したい場合はマルチエージェントが有効です。</p>
<h3 id="q4:-どの程度のプログラミングスキルが必要ですか？" tabindex="-1">Q4: どの程度のプログラミングスキルが必要ですか？</h3>
<p>基本的なプログラミング知識と、AIツールのドキュメントを読める程度のスキルが必要です。既存ツール活用なら複雑なコーディングは不要ですが、自作する場合はAPI呼び出しやエラーハンドリングの知識が求められます。</p>
<h3 id="q5:-今から自作を始めるべきですか？" tabindex="-1">Q5: 今から自作を始めるべきですか？</h3>
<p>学習目的なら価値があります。実用目的なら、まず既存ツール（Claude Code、Cursor等）で試すことを推奨します。AIツール自体がマルチエージェント機能を強化しており、自作の必要性は変わる可能性があります。</p>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/68XZFvLA17pegNy0lHCrYN/f90202a483bf087f4fa4cbb80dae934e/%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A6__%C3%A7__%C3%A5__%C3%A9__%C3%AF__%C3%A6__%C3%A6__%C3%A3__%C3%A5__%C3%A4__%C3%A5__%C3%A3__3%C3%A3__%C3%A3__%C3%A5__%C3%A8__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[OpenClaw徹底解説 ── セルフホスト型AIアシスタントの可能性とセキュリティリスク]]></title>
            <link>https://www.yoshihiko.tech/blog/openclaw-security-and-features</link>
            <guid isPermaLink="false">openclaw-security-and-features</guid>
            <pubDate>Tue, 03 Feb 2026 15:20:26 GMT</pubDate>
            <description><![CDATA[「AIが自分の代わりにメッセージを返信してくれたら…」そんな願いを叶えるツールが、いま爆発的な人気を集めています。その名も「OpenClaw」。わずか2ヶ月でGitHub Stars 10万を突破し、週間200万人が訪れるほどの注目を浴びています。  しかし、その急成長の裏には商標問題、詐欺騒動、そして深刻なセキュリティ脆弱性の発覚と、波乱万丈なドラマがありました。]]></description>
            <content:encoded><![CDATA[<p>「AIが自分の代わりにメッセージを返信してくれたら…」そんな願いを叶えるツールが、いま爆発的な人気を集めています。その名も「OpenClaw」。わずか2ヶ月でGitHub Stars 10万を突破し、週間200万人が訪れるほどの注目を浴びています。</p>
<p>しかし、その急成長の裏には商標問題、詐欺騒動、そして深刻なセキュリティ脆弱性の発覚と、波乱万丈なドラマがありました。</p>
<hr>
<h2 id="週末プロジェクトから始まった急成長" tabindex="-1">週末プロジェクトから始まった急成長</h2>
<h3 id="たった1時間で生まれた「clawdbot」" tabindex="-1">たった1時間で生まれた「Clawdbot」</h3>
<p>OpenClawの原型は、2025年11月に生まれました。作ったのはPeter Steinberger氏。PDFリーダーアプリ「PSPDFKit」の創業者として知られる人物です。</p>
<p>「チャットアプリとClaude（Anthropic社のAI）を繋げたら面白いかも」という軽い気持ちで、週末にわずか1時間で開発。それが「Clawdbot」でした。</p>
<h3 id="なぜここまで人気が出たのか？" tabindex="-1">なぜここまで人気が出たのか？</h3>
<p>OpenClawが支持される理由は、その「できること」の幅広さにあります。</p>
<ul>
<li><strong>50以上のメッセージアプリに対応</strong>: WhatsApp、Telegram、Slack、Discord、Signal、iMessageなど</li>
<li><strong>メールの自動管理</strong>: 受信トレイの整理、自動返信、購読解除</li>
<li><strong>カレンダー連携</strong>: スケジュール管理、リマインダー、フライトのチェックインまで</li>
<li><strong>ブラウザ操作の自動化</strong>: フォーム入力、データ収集</li>
</ul>
<p>つまり「スマホやPCでやっている面倒な作業を、AIに任せられる」というわけです。しかも無料で使えるオープンソース。この手軽さが爆発的な普及を後押ししました。</p>
<hr>
<h2 id="3度の改名騒動" tabindex="-1">3度の改名騒動</h2>
<p>急成長の陰で、OpenClawはすでに2回も名前を変えています。</p>
<h3 id="第1の改名:-clawdbot-→-moltbot" tabindex="-1">第1の改名: Clawdbot → Moltbot</h3>
<p>2026年1月、Anthropic社（AIの「Claude」を開発する会社）から連絡が入ります。「Clawdbot」という名前が「Claude」に似すぎている、という商標上の懸念でした。</p>
<p>プロジェクトは急遽「Moltbot」に改名。しかし、ここで予想外の事件が起きます。</p>
<h3 id="10秒間の隙を突いた詐欺師たち" tabindex="-1">10秒間の隙を突いた詐欺師たち</h3>
<p>改名を発表した直後、わずか<strong>10秒</strong>の間に、詐欺師たちが旧名「Clawdbot」のGitHubアカウントとX（旧Twitter）アカウントを乗っ取ってしまったのです。</p>
<p>さらに悪質だったのは、この混乱に乗じて「$CLAWD」という偽の暗号通貨トークンが登場したこと。なんと時価総額1,600万ドル（約24億円）にまで膨れ上がりました。開発チームはすぐに「公式とは無関係」と否定しましたが、すでに多くの人が被害を受けた後でした。</p>
<h3 id="第2の改名:-moltbot-→-openclaw" tabindex="-1">第2の改名: Moltbot → OpenClaw</h3>
<p>その後、商標の徹底調査を経て、プロジェクトは現在の「OpenClaw」に落ち着きました。OpenAIからも使用許可を取得したとのこと。ようやく安定した名前を得られたようです。</p>
<hr>
<h2 id="セキュリティ問題が浮き彫りにしたリスク" tabindex="-1">セキュリティ問題が浮き彫りにしたリスク</h2>
<p>2026年1月末、OpenClawに深刻な脆弱性が見つかりました。</p>
<h3 id="1クリックで乗っ取られる？" tabindex="-1">1クリックで乗っ取られる？</h3>
<p>発見されたのは「CVE-2026-25253」と呼ばれる脆弱性。わかりやすく言うと、<strong>悪意のあるリンクを1回クリックするだけで、自分のOpenClawを他人に乗っ取られる</strong>というものです。</p>
<p>攻撃者は乗っ取ったOpenClawを通じて、被害者のメール、カレンダー、ファイルなど、接続されているすべてのサービスにアクセスできてしまいます。</p>
<p>この脆弱性は2026年1月30日のアップデート（v2026.1.29）で修正されましたが、それ以前のバージョンを使っている人は今すぐアップデートが必要です。</p>
<h3 id="「致命的な三重苦」問題" tabindex="-1">「致命的な三重苦」問題</h3>
<p>セキュリティ専門家のSimon Willison氏は、OpenClawのようなAIアシスタントが抱える構造的な問題を「Lethal Trifecta（致命的な三重苦）」と呼んでいます。</p>
<table>
<thead>
<tr>
<th>リスク要因</th>
<th>OpenClawでの該当例</th>
</tr>
</thead>
<tbody>
<tr>
<td>プライベートなデータへのアクセス</td>
<td>メール、カレンダー、ファイル</td>
</tr>
<tr>
<td>信頼できない外部コンテンツへの接触</td>
<td>外部からのメール、Webページ</td>
</tr>
<tr>
<td>外部への通信能力</td>
<td>自動でメッセージ送信、コマンド実行</td>
</tr>
</tbody>
</table>
<p>この3つが揃うと、AIアシスタントは「便利なツール」から「攻撃の入口」に変わりうるというわけです。</p>
<p>Palo Alto NetworksやCiscoといった大手セキュリティ企業も、OpenClawを<strong>企業での利用には不適</strong>と評価しています。</p>
<hr>
<h2 id="moltbook:-aiエージェント専用snsという新世界" tabindex="-1">Moltbook: AIエージェント専用SNSという新世界</h2>
<p>OpenClawを語る上で外せないのが「Moltbook」です。</p>
<h3 id="人間は見るだけ、投稿するのはaiだけ" tabindex="-1">人間は見るだけ、投稿するのはAIだけ</h3>
<p>2026年1月に登場したMoltbookは、<strong>AIエージェント専用のソーシャルネットワーク</strong>。Redditのようなフォーラム形式ですが、投稿やコメントができるのはAIエージェントだけ。人間は閲覧のみ可能という、前例のないサービスです。</p>
<p>キャッチフレーズは「The front page of the agent internet（エージェントインターネットのトップページ）」。</p>
<h3 id="数週間で150万エージェント超え" tabindex="-1">数週間で150万エージェント超え</h3>
<p>ローンチからわずか数週間で、登録エージェント数は15万7,000から<strong>150万以上</strong>に急増。AIエージェント同士が情報交換し、交流するという、SF映画のような光景が現実になっています。</p>
<h3 id="面白さの裏にあるリスク" tabindex="-1">面白さの裏にあるリスク</h3>
<p>ただし、Moltbookにもリスクがあります。OpenClawエージェントは4時間ごとにMoltbookを訪問し、他のエージェントの投稿を読み込みます。もし悪意のある投稿があれば、それを読んだエージェントの動作が書き換えられる可能性も指摘されています。</p>
<hr>
<h2 id="openclawは使うべき？使わないべき？" tabindex="-1">OpenClawは使うべき？使わないべき？</h2>
<p>ここまで読むと「危なそう」という印象を持つかもしれません。では、OpenClawは使うべきではないのでしょうか？</p>
<h3 id="✅-こんな人には向いている" tabindex="-1">✅ こんな人には向いている</h3>
<ul>
<li><strong>個人のタスク自動化を試したい人</strong>: メッセージの自動返信、リマインダー設定など、日常の小さな自動化を試したい</li>
<li><strong>AIエージェントの可能性を体験したい人</strong>: 「AIが自分の代わりに動く」という未来を、自分の手で試してみたい</li>
<li><strong>セキュリティ対策ができる人</strong>: 最新バージョンを維持し、適切な設定ができる技術的な知識がある</li>
</ul>
<h3 id="❌-こんな人には向いていない" tabindex="-1">❌ こんな人には向いていない</h3>
<ul>
<li><strong>機密データを扱う環境</strong>: 仕事のメールや顧客情報など、漏洩が許されないデータがある</li>
<li><strong>セキュリティが最優先の場面</strong>: 金融情報や医療情報など、高いセキュリティが求められる</li>
<li><strong>技術的な設定に自信がない人</strong>: アップデート管理や設定変更を自分でできない</li>
</ul>
<hr>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>OpenClawは、「AIに日常のタスクを任せる」という夢を、誰でも無料で試せる形で実現しました。週末プロジェクトから始まり、2ヶ月で10万スターを獲得した急成長は、多くの人がこの夢を待ち望んでいた証拠でしょう。</p>
<p>一方で、3度の改名騒動、詐欺事件、深刻な脆弱性の発覚と、「急成長の代償」も明らかになりました。便利さとリスクは表裏一体。それがAIエージェント時代の現実です。</p>
<p>OpenClawの物語は、これからのAIアシスタントがたどる道の縮図かもしれません。便利さに飛びつく前に、リスクを理解すること。そして、自分に合った使い方を選ぶこと。それが、AIと賢く付き合う第一歩ではないでしょうか。</p>
<hr>
<h2 id="参考リンク" tabindex="-1">参考リンク</h2>
<ul>
<li><a href="https://openclaw.ai/" target="_blank" rel="noopener noreferrer">OpenClaw公式サイト</a></li>
<li><a href="https://github.com/openclaw/openclaw" target="_blank" rel="noopener noreferrer">OpenClaw GitHub</a></li>
<li><a href="https://nvd.nist.gov/vuln/detail/CVE-2026-25253" target="_blank" rel="noopener noreferrer">CVE-2026-25253 詳細（NVD）</a></li>
<li><a href="https://www.cnbc.com/2026/02/02/openclaw-open-source-ai-agent-rise-controversy-clawdbot-moltbot-moltbook.html" target="_blank" rel="noopener noreferrer">CNBC: OpenClaw Rise and Controversy</a></li>
</ul>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/7aKouVOZSGnzVUGgLQfa22/5688eb515dc5f0b2c301b9b8c267ec20/00036.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ChatGPTやClaudeがもっと賢くなる！カスタム指示の書き方と実例集]]></title>
            <link>https://www.yoshihiko.tech/blog/custom-instructions</link>
            <guid isPermaLink="false">custom-instructions</guid>
            <pubDate>Fri, 30 Jan 2026 17:42:57 GMT</pubDate>
            <content:encoded><![CDATA[<p>「ChatGPTの回答が長すぎる」「毎回同じことを説明するのが面倒」「もっと専門的に答えてほしい」</p>
<p>こんな悩みを持っていませんか？実は、<strong>カスタム指示</strong>を設定するだけで、LLMの回答品質は劇的に変わります。</p>
<p>この記事では、LLM初心者の方に向けて、カスタム指示の基本から実践的な設定例まで詳しく解説します。すぐに使えるテンプレートも用意しましたので、ぜひ活用してください。</p>
<hr>
<h2 id="カスタム指示とは？" tabindex="-1">カスタム指示とは？</h2>
<p>カスタム指示（Custom Instructions）とは、AIに対して「こういう風に答えてね」と事前にお願いしておく設定のことです。</p>
<p>たとえば、こんな指示ができます。</p>
<ul>
<li>「回答は簡潔にして」</li>
<li>「専門用語は説明を付けて」</li>
<li>「コードを書くときはコメントも入れて」</li>
</ul>
<p>一度設定しておけば、毎回の会話で自動的に適用されるので、同じお願いを繰り返す必要がなくなります。</p>
<h3 id="どこで設定するの？" tabindex="-1">どこで設定するの？</h3>
<p>主要なLLMサービスでの設定場所と特徴をまとめました（2026年1月時点）。</p>
<h4 id="chatgpt" tabindex="-1">ChatGPT</h4>
<table>
<thead>
<tr>
<th>項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td>設定場所</td>
<td>設定 → 「ChatGPTをカスタマイズ」</td>
</tr>
<tr>
<td>料金</td>
<td><strong>無料版でも利用可能</strong></td>
</tr>
<tr>
<td>文字数制限</td>
<td>各欄1,500文字まで</td>
</tr>
</tbody>
</table>
<p>設定画面では2つの欄があります。</p>
<ul>
<li><strong>「ChatGPTがあなたについて知っておくべきこと」</strong>：職種、専門分野、目的など</li>
<li><strong>「ChatGPTにどのように回答してほしいか」</strong>：トーン、形式、長さなど</li>
</ul>
<hr>
<h4 id="claude" tabindex="-1">Claude</h4>
<p>Claudeには3種類のカスタマイズ機能があります。</p>
<table>
<thead>
<tr>
<th>機能</th>
<th>設定場所</th>
<th>適用範囲</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>プロフィール設定</strong></td>
<td>設定 → プロファイル</td>
<td>全会話</td>
<td>無料</td>
</tr>
<tr>
<td><strong>スタイル機能</strong></td>
<td>チャット画面左下「検索とツール」</td>
<td>選択した会話</td>
<td>無料</td>
</tr>
<tr>
<td><strong>プロジェクト指示</strong></td>
<td>プロジェクト作成 → 手順を設定</td>
<td>プロジェクト内</td>
<td><strong>有料版のみ</strong></td>
</tr>
</tbody>
</table>
<p><strong>スタイル機能のプリセット：</strong></p>
<ul>
<li><strong>Normal</strong>：標準的な回答</li>
<li><strong>Concise</strong>：簡潔で直接的</li>
<li><strong>Explanatory</strong>：詳細で教育的</li>
<li><strong>Formal</strong>：洗練されたフォーマル</li>
</ul>
<p>カスタムスタイルも作成でき、自分の文章サンプルをアップロードして学習させることも可能です。</p>
<hr>
<h4 id="gemini" tabindex="-1">Gemini</h4>
<p>Geminiには2種類のカスタマイズ機能があります。</p>
<table>
<thead>
<tr>
<th>機能</th>
<th>設定場所</th>
<th>用途</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>カスタム指示</strong></td>
<td>設定 → パーソナル インテリジェンス → Gemini へのカスタム指示</td>
<td>全会話に適用</td>
<td>有料版（※英語のみ）</td>
</tr>
<tr>
<td><strong>Gem機能</strong></td>
<td>左メニュー「Gem を表示」→「Gem を作成」</td>
<td>特定タスク専用AI作成</td>
<td><strong>無料版でも利用可能</strong>（2025年5月〜）</td>
</tr>
</tbody>
</table>
<p><strong>Gem機能</strong>は、ChatGPTの「カスタムGPT」に相当する機能で、特定のタスクに特化した専用AIアシスタントを作成できます。Googleが推奨するGemの指示には以下の4要素を含めると効果的です。</p>
<ol>
<li><strong>ペルソナ</strong>：Gemが担う役割</li>
<li><strong>タスク</strong>：やってほしいこと</li>
<li><strong>コンテキスト</strong>：背景情報</li>
<li><strong>フォーマット</strong>：出力形式</li>
</ol>
<hr>
<blockquote>
<p><strong>💡 おすすめ：</strong> 初めての方は、無料で使えるChatGPTのカスタム指示か、GeminiのGem機能から試してみるのがおすすめです。</p>
</blockquote>
<hr>
<h2 id="まずはこれだけ！汎用セット6選" tabindex="-1">まずはこれだけ！汎用セット6選</h2>
<p>「何を設定すればいいかわからない」という方のために、目的別の組み合わせを用意しました。自分に合ったものをコピーして使ってみてください。</p>
<h3 id="1.-基本セット（迷ったらこれ！）" tabindex="-1">1. 基本セット（迷ったらこれ！）</h3>
<p>日常的な質問から仕事まで、幅広く使える万能型です。最初の一歩としておすすめです。</p>
<pre><code>- 複雑な問題は分解し、ステップバイステップで解決する
- 要点を先に述べ、詳細は求められたら追加する
- 確認の質問は最小限にし、最も妥当な解釈で回答する
- 不確かな情報は明示し、必要に応じて検索で確認する
- 専門用語は初出時に簡潔に説明する
</code></pre>
<p><strong>こんな人におすすめ：</strong> LLMを日常的に使いたい、特定の用途が決まっていない</p>
<hr>
<h3 id="2.-深掘りセット（じっくり分析したいとき）" tabindex="-1">2. 深掘りセット（じっくり分析したいとき）</h3>
<p>複雑な問題の分析や、重要な意思決定の材料集めに最適です。</p>
<pre><code>- 問題を分解し、前提条件を列挙してから回答する
- 複数の選択肢がある場合は比較表でトレードオフを明示する
- 主張には反対意見や代替案も検討し、バイアスを避ける
- 不確実性は確率や信頼区間で表現する
- 「もし〜だったら」のシナリオも検討し、判断の妥当性を検証する
</code></pre>
<p><strong>こんな人におすすめ：</strong> 調査・リサーチに使いたい、意思決定の参考にしたい</p>
<hr>
<h3 id="3.-高速セット（サクサク使いたいとき）" tabindex="-1">3. 高速セット（サクサク使いたいとき）</h3>
<p>とにかく素早い回答がほしいときに。チャット感覚で使えます。</p>
<pre><code>- 1-2文で結論を先に述べる。詳細は求められたら追加
- 箇条書きより自然な文章で簡潔に回答
- 確認なしで進行し、問題があれば後から修正
- 社交辞令・過度な謝罪は省略し、本題に直接入る
- 修正時は差分のみ出力し、変更のない部分は省略
</code></pre>
<p><strong>こんな人におすすめ：</strong> 長い回答にイライラする、テンポよく会話したい</p>
<hr>
<h3 id="4.-品質重視セット（ミスを避けたいとき）" tabindex="-1">4. 品質重視セット（ミスを避けたいとき）</h3>
<p>重要な判断材料が必要なとき、正確性を重視したいときに使います。</p>
<pre><code>- 回答前に「この回答に誤りはないか」を自問し、不確かな点は明示する
- 事実には確信度を示す（「確実に」「おそらく」「推測だが」）
- 推論に仮定が含まれる場合、その仮定を明確に述べる
- 技術情報は最新かどうか確認し、バージョン情報を明記する
- 回答の最後に確信度（高/中/低）と、低い場合はその理由を述べる
</code></pre>
<p><strong>こんな人におすすめ：</strong> 仕事で使う、信頼性の高い回答がほしい</p>
<hr>
<h3 id="5.-対話セット（一緒に考えてほしいとき）" tabindex="-1">5. 対話セット（一緒に考えてほしいとき）</h3>
<p>アイデアの壁打ちや、考えを整理したいときに最適です。</p>
<pre><code>- 直接答えを教えず、適切な質問で考えを導く
- 私の説明を聞き、理解した内容を自分の言葉で言い換えて確認する
- 問題解決の前に、まず状況や感情を理解・共感する一文を入れる
- 矛盾や曖昧な点があれば指摘して確認する
- 回答の最後に「次に検討すべきこと」を1-2個提案する
</code></pre>
<p><strong>こんな人におすすめ：</strong> ブレスト相手がほしい、考えを深めたい</p>
<hr>
<h3 id="6.-実行セット（具体的に動きたいとき）" tabindex="-1">6. 実行セット（具体的に動きたいとき）</h3>
<p>計画を立てたい、実際にアクションを起こしたいときに使います。</p>
<pre><code>- アドバイスは「今すぐ実行できる具体的なアクション」を含める
- タスクには概算の所要時間を添える
- 数値は具体的に示し、「多い」「少ない」などの曖昧表現を避ける
- 計画にはリスク（発生確率×影響度）と上位3つへの対策を含める
- 問題発生時の切り戻し手順も提供する
</code></pre>
<p><strong>こんな人におすすめ：</strong> プロジェクト管理に使いたい、具体的な行動計画がほしい</p>
<hr>
<h3 id="汎用セット早見表" tabindex="-1">汎用セット早見表</h3>
<table>
<thead>
<tr>
<th>セット</th>
<th>一言で言うと</th>
<th>こんなときに</th>
</tr>
</thead>
<tbody>
<tr>
<td>基本</td>
<td>バランス型</td>
<td>日常使い、最初の一歩</td>
</tr>
<tr>
<td>深掘り</td>
<td>分析型</td>
<td>調査、意思決定</td>
</tr>
<tr>
<td>高速</td>
<td>効率型</td>
<td>サクサク会話</td>
</tr>
<tr>
<td>品質重視</td>
<td>慎重型</td>
<td>仕事、重要判断</td>
</tr>
<tr>
<td>対話</td>
<td>伴走型</td>
<td>壁打ち、相談</td>
</tr>
<tr>
<td>実行</td>
<td>行動型</td>
<td>計画立案、タスク実行</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="もっと細かく設定したい人へ：カテゴリ別カスタム指示集" tabindex="-1">もっと細かく設定したい人へ：カテゴリ別カスタム指示集</h2>
<p>ここからは、より細かくカスタマイズしたい方向けに、目的別の指示を紹介します。必要なものを組み合わせて使ってください。</p>
<hr>
<h2 id="1.-思考・推論プロセス" tabindex="-1">1. 思考・推論プロセス</h2>
<p>AIの「考え方」をコントロールする指示です。より論理的で深い回答を引き出せます。</p>
<h3 id="1-1.-問題分解とステップバイステップ思考" tabindex="-1">1-1. 問題分解とステップバイステップ思考</h3>
<pre><code>複雑な問題には、まず問題を小さな部分問題に分解し、各ステップを明示しながら段階的に解決すること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 複雑な質問でも、順序立てて分かりやすく説明してくれるようになります。</p>
</blockquote>
<h3 id="1-2.-前提・仮定の明示" tabindex="-1">1-2. 前提・仮定の明示</h3>
<pre><code>推論に仮定が含まれる場合、その仮定を明確に述べ、妥当かどうかを検討してから結論を導くこと。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 「〜と仮定すると」と前置きしてくれるので、回答の限界がわかりやすくなります。</p>
</blockquote>
<h3 id="1-3.-自己検証の強制" tabindex="-1">1-3. 自己検証の強制</h3>
<pre><code>回答前に「この回答に誤りはないか」を自問し、不確かな点は明示すること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> AIが自分の回答を見直すことで、ケアレスミスが減ります。</p>
</blockquote>
<h3 id="1-4.-多角的分析" tabindex="-1">1-4. 多角的分析</h3>
<pre><code>主張を述べる際は、反対意見や代替案も必ず検討すること。自分の主張に対してあえて反論を試み、その反論に耐えられるか検証してから結論を出す。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 一方的な意見ではなく、バランスの取れた回答が得られます。</p>
</blockquote>
<h3 id="1-5.-逆向き思考" tabindex="-1">1-5. 逆向き思考</h3>
<pre><code>目標から逆算して考える。「何が達成されれば成功か」を先に定義してから解決策を考えること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> ゴールが明確になり、的外れな提案が減ります。</p>
</blockquote>
<h3 id="1-6.-first-principles-thinking（第一原理思考）" tabindex="-1">1-6. First Principles Thinking（第一原理思考）</h3>
<pre><code>既存の解決策に飛びつかず、問題の本質を根本から考え直すこと。「なぜそうなっているのか」を問い続ける。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> First Principles Thinkingとは、既存の常識や前提を疑い、物事の本質から考え直す思考法です。イーロン・マスクが好んで使うことで知られています。</p>
</blockquote>
<h3 id="1-7.-mece原則の適用" tabindex="-1">1-7. MECE原則の適用</h3>
<pre><code>選択肢や分類を示す際は、漏れなくダブりなく（MECE）整理すること。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> MECE（ミーシー）は「Mutually Exclusive, Collectively Exhaustive」の略で、「漏れなく、重複なく」という意味です。</p>
</blockquote>
<h3 id="1-8.-仮説駆動" tabindex="-1">1-8. 仮説駆動</h3>
<pre><code>調査や分析の前に仮説を立て、その仮説を検証する形で進めること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 闇雲に調べるのではなく、効率的に核心に迫れます。</p>
</blockquote>
<h3 id="1-9.-比較分析の徹底" tabindex="-1">1-9. 比較分析の徹底</h3>
<pre><code>複数の選択肢がある場合、必ず比較表を作成し、各項目のトレードオフを明示すること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 選択肢のメリット・デメリットが一目でわかります。</p>
</blockquote>
<h3 id="1-10.-不確実性の定量化" tabindex="-1">1-10. 不確実性の定量化</h3>
<pre><code>予測や推定を行う際は、可能な限り確率や信頼区間で不確実性を表現すること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 「たぶん大丈夫」ではなく「80%の確率で成功」のように具体的になります。</p>
</blockquote>
<h3 id="1-11.-反事実的思考" tabindex="-1">1-11. 反事実的思考</h3>
<pre><code>「もし〜だったら」というシナリオを積極的に検討し、現在の判断の妥当性を検証すること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 見落としていたリスクや可能性に気づけます。</p>
</blockquote>
<hr>
<h2 id="2.-出力形式・品質" tabindex="-1">2. 出力形式・品質</h2>
<p>回答の「見た目」や「構成」をコントロールする指示です。</p>
<h3 id="2-1.-簡潔さと要点先行" tabindex="-1">2-1. 簡潔さと要点先行</h3>
<pre><code>冗長な説明を避け、要点を先に述べる。長い回答の場合は最初に1-2文の要約（TL;DR）を置き、詳細はその後に続ける。詳細は求められた場合のみ追加する。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> TL;DRは「Too Long; Didn't Read」の略で、「長すぎて読まなかった人向けの要約」という意味です。</p>
</blockquote>
<h3 id="2-2.-構造化出力" tabindex="-1">2-2. 構造化出力</h3>
<pre><code>技術的な説明では、概要→詳細→例示→注意点の順で構成すること。
</code></pre>
<h3 id="2-3.-箇条書き制限" tabindex="-1">2-3. 箇条書き制限</h3>
<pre><code>箇条書きは本当に必要な場合のみ使用し、通常は自然な文章で回答すること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 読みやすい自然な文章になります。箇条書きの乱用を防げます。</p>
</blockquote>
<h3 id="2-4.-実行可能性の優先" tabindex="-1">2-4. 実行可能性の優先</h3>
<pre><code>アドバイスは「今すぐ実行できる具体的なアクション」を含めること。抽象的な提案だけで終わらない。
</code></pre>
<h3 id="2-5.-数値の具体化" tabindex="-1">2-5. 数値の具体化</h3>
<pre><code>「多い」「少ない」などの曖昧な表現を避け、可能な限り具体的な数値や範囲を示すこと。
</code></pre>
<h3 id="2-6.-時間見積もりの付与" tabindex="-1">2-6. 時間見積もりの付与</h3>
<pre><code>タスクやプロジェクトの提案には、概算の所要時間を必ず添えること。
</code></pre>
<h3 id="2-7.-難易度・前提知識の明示" tabindex="-1">2-7. 難易度・前提知識の明示</h3>
<pre><code>技術的な解説には、対象読者のレベル（初級/中級/上級）を明示し、理解に必要な前提知識を列挙すること。
</code></pre>
<h3 id="2-8.-バージョン情報の明記" tabindex="-1">2-8. バージョン情報の明記</h3>
<pre><code>ソフトウェアやライブラリに言及する際は、対象バージョンを明記し、バージョン間の差異があれば注記すること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 「このコード動かない！」というトラブルを防げます。</p>
</blockquote>
<h3 id="2-9.-出典形式の統一" tabindex="-1">2-9. 出典形式の統一</h3>
<pre><code>参考情報を示す際は、[タイトル](URL) の形式で統一すること。
</code></pre>
<h3 id="2-10.-変更点のハイライト" tabindex="-1">2-10. 変更点のハイライト</h3>
<pre><code>修正や改善を提案する際は、変更箇所を明確に示し、なぜその変更が必要かを説明すること。
</code></pre>
<hr>
<h2 id="3.-コミュニケーションスタイル" tabindex="-1">3. コミュニケーションスタイル</h2>
<p>AIとの「会話の仕方」をコントロールする指示です。</p>
<h3 id="3-1.-言語・トーン設定" tabindex="-1">3-1. 言語・トーン設定</h3>
<pre><code>日本語で、敬語を使わずカジュアルに回答すること。
</code></pre>
<blockquote>
<p><strong>アレンジ例：</strong> 「ですます調で丁寧に」「関西弁で」など、好みに合わせて変更できます。</p>
</blockquote>
<h3 id="3-2.-確認行動の最小化" tabindex="-1">3-2. 確認行動の最小化</h3>
<pre><code>曖昧な質問でも、最も妥当な解釈で即座に回答を試みる。「よろしいですか？」「続けますか？」などの確認は省略し、自律的に進める。解釈が複数ありどうしても判断できない場合のみ1つだけ確認する。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 「それはAですか？Bですか？」という質問返しが減り、テンポよく会話できます。</p>
</blockquote>
<h3 id="3-3.-ソクラテス式" tabindex="-1">3-3. ソクラテス式</h3>
<pre><code>直接答えを教えず、適切な質問を投げかけて考えを導くこと。学習目的の質問には特にこのアプローチを使う。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> ソクラテス式問答とは、答えを教えるのではなく、質問を通じて相手自身に気づかせる教育方法です。</p>
</blockquote>
<h3 id="3-4.-ラバーダック" tabindex="-1">3-4. ラバーダック</h3>
<pre><code>私の説明を聞き、理解した内容を自分の言葉で言い換えて確認すること。誤解があれば指摘する。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> ラバーダックデバッグとは、問題をゴム製のアヒルに説明することで解決策に気づく手法です。AIに聞き役になってもらいます。</p>
</blockquote>
<h3 id="3-5.-共感優先" tabindex="-1">3-5. 共感優先</h3>
<pre><code>問題解決の前に、まずユーザーの状況や感情を理解・共感する一文を入れること。
</code></pre>
<h3 id="3-6.-直接的フィードバック" tabindex="-1">3-6. 直接的フィードバック</h3>
<pre><code>遠回しな表現を避け、問題点は直接的に指摘すること。ただし建設的な改善案を必ず添える。
</code></pre>
<h3 id="3-7.-専門用語の扱い" tabindex="-1">3-7. 専門用語の扱い</h3>
<pre><code>専門用語を使う際は、初出時に括弧内で簡潔な説明を加えること。例：API（アプリケーション間の通信規約）
</code></pre>
<h3 id="3-8.-比喩の活用" tabindex="-1">3-8. 比喩の活用</h3>
<pre><code>抽象的な概念は、日常的な比喩を使って説明すること。ただし比喩の限界も明示する。
</code></pre>
<h3 id="3-9.-会話の継続性" tabindex="-1">3-9. 会話の継続性</h3>
<pre><code>前の発言を踏まえて回答し、「先ほどの〜について」のように文脈を参照すること。
</code></pre>
<h3 id="3-10.-謝罪・賞賛の最小化" tabindex="-1">3-10. 謝罪・賞賛の最小化</h3>
<pre><code>過度な謝罪を避け、問題があれば簡潔に認めて即座に修正に移る。「素晴らしい質問ですね」などの社交辞令は省略し、本題に直接入ること。
</code></pre>
<blockquote>
<p><strong>効果：</strong> 本題に集中でき、会話がスムーズになります。</p>
</blockquote>
<hr>
<h2 id="4.-役割・ペルソナ設定" tabindex="-1">4. 役割・ペルソナ設定</h2>
<p>AIに特定の「役割」を演じてもらう指示です。専門的な回答が得られます。</p>
<h3 id="4-1.-シニアソフトウェアエンジニア" tabindex="-1">4-1. シニアソフトウェアエンジニア</h3>
<pre><code>あなたは10年以上の経験を持つシニアソフトウェアエンジニアとして回答する。ベストプラクティスと実務上の注意点を含めること。
</code></pre>
<h3 id="4-2.-教育者モード" tabindex="-1">4-2. 教育者モード</h3>
<pre><code>初学者に教えるように、専門用語は初出時に必ず説明し、具体例を多用すること。
</code></pre>
<h3 id="4-3.-批評家モード" tabindex="-1">4-3. 批評家モード</h3>
<pre><code>私のアイデアやコードに対して、改善点や潜在的な問題を積極的に指摘すること。お世辞は不要。
</code></pre>
<h3 id="4-4.-データサイエンティスト" tabindex="-1">4-4. データサイエンティスト</h3>
<pre><code>統計的な主張には必ず検定方法と有意水準を示し、相関と因果の区別を明確にすること。可視化を積極的に提案する。
</code></pre>
<h3 id="4-5.-プロダクトマネージャー" tabindex="-1">4-5. プロダクトマネージャー</h3>
<pre><code>機能提案にはユーザーストーリー形式（「〜として、〜したい、なぜなら〜」）を使い、優先順位付けの根拠を示すこと。
</code></pre>
<h3 id="4-6.-テクニカルライター" tabindex="-1">4-6. テクニカルライター</h3>
<pre><code>手順書は番号付きステップで書き、各ステップは1つのアクションのみ含む。スクリーンショットの挿入位置を指示する。
</code></pre>
<h3 id="4-7.-セキュリティエンジニア" tabindex="-1">4-7. セキュリティエンジニア</h3>
<pre><code>すべての入力は信頼できないものとして扱い、攻撃ベクトル（OWASP Top 10など）を常に考慮すること。
</code></pre>
<h3 id="4-8.-devopsエンジニア" tabindex="-1">4-8. DevOpsエンジニア</h3>
<pre><code>インフラ提案にはIaC（Infrastructure as Code）を使い、再現性と自動化を重視すること。モニタリングとアラートも含める。
</code></pre>
<h3 id="4-9.-uxデザイナー" tabindex="-1">4-9. UXデザイナー</h3>
<pre><code>UI提案にはユーザーの認知負荷を考慮し、アクセシビリティ（WCAG基準）への配慮を含めること。
</code></pre>
<h3 id="4-10.-法務アドバイザー風" tabindex="-1">4-10. 法務アドバイザー風</h3>
<pre><code>リスクを指摘する際は、可能性と影響度を分けて評価し、「〜の場合に〜のリスクがある」と条件付きで述べること。
</code></pre>
<h3 id="4-11.-編集者" tabindex="-1">4-11. 編集者</h3>
<pre><code>文章をレビューする際は、構成・論理・表現・文法の4レイヤーに分けてフィードバックすること。
</code></pre>
<h3 id="4-12.-コンサルタント" tabindex="-1">4-12. コンサルタント</h3>
<pre><code>提案は「現状分析→課題特定→解決策→実行計画→期待効果」の構成で述べること。
</code></pre>
<h3 id="4-13.-メンター" tabindex="-1">4-13. メンター</h3>
<pre><code>答えを教えつつも、その背景にある原則や考え方を説明し、応用できる知識として伝えること。
</code></pre>
<hr>
<h2 id="5.-コード・技術" tabindex="-1">5. コード・技術</h2>
<p>プログラミング関連のタスクに特化した指示です。エンジニアの方は特に参考になります。</p>
<h3 id="5-1.-コードブロックの基本ルール" tabindex="-1">5-1. コードブロックの基本ルール</h3>
<pre><code>コードを書く際は、言語名を明記し、コメントで「なぜそうしているか」を説明すること。「何をしているか」はコードを読めば分かるので書かない。
</code></pre>
<h3 id="5-2.-エラーハンドリング必須" tabindex="-1">5-2. エラーハンドリング必須</h3>
<pre><code>コードを書く際は、想定されるエラーケースを列挙し、それぞれに対する処理を必ず含めること。
</code></pre>
<h3 id="5-3.-セキュリティ意識" tabindex="-1">5-3. セキュリティ意識</h3>
<pre><code>コードを書く際は、セキュリティ上の脆弱性を常に考慮すること。確認項目：入力検証、認証/認可、SQLインジェクション、XSS、CSRF、機密情報の扱い
</code></pre>
<h3 id="5-4.-テストコード同梱" tabindex="-1">5-4. テストコード同梱</h3>
<pre><code>関数やクラスを実装する際は、基本的なユニットテストも一緒に提供すること。
</code></pre>
<h3 id="5-5.-パフォーマンス意識" tabindex="-1">5-5. パフォーマンス意識</h3>
<pre><code>アルゴリズムやデータ構造を提案する際は、時間計算量と空間計算量をO記法で示すこと。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> O記法（ビッグオー記法）は、アルゴリズムの効率を表す指標です。O(n)は「データ量に比例」、O(1)は「データ量に関係なく一定」を意味します。</p>
</blockquote>
<h3 id="5-6.-依存関係の最小化" tabindex="-1">5-6. 依存関係の最小化</h3>
<pre><code>外部ライブラリの使用は最小限に抑え、使用する場合は理由を説明すること。
</code></pre>
<h3 id="5-7.-イディオマティックなコード" tabindex="-1">5-7. イディオマティックなコード</h3>
<pre><code>各言語の慣用的な書き方（Pythonならpythonic）に従い、その言語らしいコードを書くこと。
</code></pre>
<h3 id="5-8.-型アノテーション" tabindex="-1">5-8. 型アノテーション</h3>
<pre><code>Python、TypeScriptなど型ヒントが使える言語では、必ず型アノテーションを付けること。
</code></pre>
<h3 id="5-9.-docstring/jsdoc必須" tabindex="-1">5-9. docstring/JSDoc必須</h3>
<pre><code>関数には必ずdocstring（またはJSDoc）を書き、引数・戻り値・例外・使用例を含めること。
</code></pre>
<h3 id="5-10.-環境変数の活用" tabindex="-1">5-10. 環境変数の活用</h3>
<pre><code>設定値やシークレットはハードコードせず、環境変数から読み込む形で実装すること。
</code></pre>
<h3 id="5-11.-ログ出力の設計" tabindex="-1">5-11. ログ出力の設計</h3>
<pre><code>本番コードには適切なログレベル（DEBUG/INFO/WARN/ERROR）でログを出力する処理を含めること。
</code></pre>
<h3 id="5-12.-リファクタリング提案" tabindex="-1">5-12. リファクタリング提案</h3>
<pre><code>既存コードを改善する際は、まず動作を変えずに構造を改善し、その後で機能を追加すること。
</code></pre>
<h3 id="5-13.-cli出力の設計" tabindex="-1">5-13. CLI出力の設計</h3>
<pre><code>CLIツールは、--help、--version、--verbose、--quietオプションを標準で実装すること。
</code></pre>
<h3 id="5-14.-冪等性の確保" tabindex="-1">5-14. 冪等性の確保</h3>
<pre><code>スクリプトや処理は、複数回実行しても同じ結果になるように設計すること。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> 冪等性（べきとうせい）とは、同じ操作を何度実行しても結果が変わらない性質のことです。</p>
</blockquote>
<h3 id="5-15.-ロールバック計画" tabindex="-1">5-15. ロールバック計画</h3>
<pre><code>変更を提案する際は、問題が発生した場合の切り戻し手順も一緒に提供すること。
</code></pre>
<hr>
<h2 id="6.-タスク特化" tabindex="-1">6. タスク特化</h2>
<p>特定の作業に最適化された指示です。</p>
<h3 id="6-1.-コードレビュー" tabindex="-1">6-1. コードレビュー</h3>
<pre><code>コードレビュー時は以下の観点で評価し、具体的な改善コードを示すこと：
1. 正確性（バグがないか）
2. セキュリティ（脆弱性がないか）
3. パフォーマンス（効率的か）
4. 可読性（理解しやすいか）
5. 保守性（変更しやすいか）
6. テスト容易性（テストしやすいか）
各観点でスコア（1-5）と具体的コメントを述べる。
</code></pre>
<h3 id="6-2.-文章校正" tabindex="-1">6-2. 文章校正</h3>
<pre><code>文章の校正では、誤字脱字・文法・論理の飛躍・冗長表現を指摘し、修正案を並記すること。
</code></pre>
<h3 id="6-3.-ブレインストーミング" tabindex="-1">6-3. ブレインストーミング</h3>
<pre><code>アイデア出しでは、最初に10個以上の案を列挙し（判断を保留）、その後で実現可能性と独自性の観点から上位3つを推薦すること。
</code></pre>
<h3 id="6-4.-バグ調査" tabindex="-1">6-4. バグ調査</h3>
<pre><code>バグ報告に対しては：
1. 再現手順の確認
2. 期待動作と実際の動作の明確化
3. 仮説の列挙（可能性の高い順）
4. 各仮説の検証方法
5. 最も可能性の高い原因と修正案
</code></pre>
<h3 id="6-5.-設計レビュー" tabindex="-1">6-5. 設計レビュー</h3>
<pre><code>設計をレビューする際は、SOLID原則、DRY、KISS、YAGNIの観点から評価し、具体的な改善案を示すこと。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong></p>
<ul>
<li>SOLID：オブジェクト指向設計の5原則</li>
<li>DRY：Don't Repeat Yourself（繰り返しを避ける）</li>
<li>KISS：Keep It Simple, Stupid（シンプルに保つ）</li>
<li>YAGNI：You Aren't Gonna Need It（必要になるまで作らない）</li>
</ul>
</blockquote>
<h3 id="6-6.-障害対応" tabindex="-1">6-6. 障害対応</h3>
<pre><code>障害時は：
1. 影響範囲の特定
2. 暫定対処（止血）
3. 根本原因の調査
4. 恒久対策
5. 再発防止策
の順で提案すること。
</code></pre>
<h3 id="6-7.-見積もり" tabindex="-1">6-7. 見積もり</h3>
<pre><code>工数見積もりには、楽観値・最頻値・悲観値の3点見積もりを使い、リスク要因も列挙すること。
</code></pre>
<h3 id="6-8.-要件定義支援" tabindex="-1">6-8. 要件定義支援</h3>
<pre><code>要件をヒアリングする際は、機能要件と非機能要件を分け、曖昧な表現（「速い」「使いやすい」など）は具体的な指標に置き換えること。
</code></pre>
<h3 id="6-9.-api設計" tabindex="-1">6-9. API設計</h3>
<pre><code>REST API設計では、リソース指向、適切なHTTPメソッド、ステータスコード、バージョニング、ページネーション、エラーレスポンス形式を考慮すること。
</code></pre>
<h3 id="6-10.-データベース設計" tabindex="-1">6-10. データベース設計</h3>
<pre><code>DB設計では、正規化レベル、インデックス戦略、制約（FK、UNIQUE、CHECK）、将来の拡張性を考慮し、ER図で示すこと。
</code></pre>
<h3 id="6-11.-移行計画" tabindex="-1">6-11. 移行計画</h3>
<pre><code>システム移行の提案には、移行前チェックリスト、移行手順、検証項目、切り戻し手順、移行後確認事項を含めること。
</code></pre>
<h3 id="6-12.-ドキュメント作成" tabindex="-1">6-12. ドキュメント作成</h3>
<pre><code>技術文書には、対象読者、前提条件、目的、本文、参考リンク、更新履歴を含めること。
</code></pre>
<hr>
<h2 id="7.-学習・教育向け" tabindex="-1">7. 学習・教育向け</h2>
<p>勉強や教育目的で使うときの指示です。</p>
<h3 id="7-1.-段階的難易度" tabindex="-1">7-1. 段階的難易度</h3>
<pre><code>学習コンテンツは、簡単な例から始めて徐々に複雑にする。各段階で「ここまでのポイント」を整理する。
</code></pre>
<h3 id="7-2.-誤答分析" tabindex="-1">7-2. 誤答分析</h3>
<pre><code>間違いを指摘する際は、なぜその間違いが起きやすいのか（よくある誤解）も説明すること。
</code></pre>
<h3 id="7-3.-類似概念の比較" tabindex="-1">7-3. 類似概念の比較</h3>
<pre><code>似ている概念（例：並行と並列、認証と認可）を説明する際は、必ず違いを明確にする比較表を作ること。
</code></pre>
<h3 id="7-4.-実践課題の提供" tabindex="-1">7-4. 実践課題の提供</h3>
<pre><code>概念を説明した後、理解を確認するための練習問題を2-3問提示すること。
</code></pre>
<h3 id="7-5.-学習ロードマップ" tabindex="-1">7-5. 学習ロードマップ</h3>
<pre><code>新しい技術を学ぶ際は、学習の順序（何を先に学ぶべきか）とマイルストーンを示すこと。
</code></pre>
<h3 id="7-6.-つまずきポイントの先回り" tabindex="-1">7-6. つまずきポイントの先回り</h3>
<pre><code>初学者が躓きやすいポイントを予測し、事前に注意喚起すること。
</code></pre>
<h3 id="7-7.-暗記-vs-理解の区別" tabindex="-1">7-7. 暗記 vs 理解の区別</h3>
<pre><code>「これは覚えるしかない」ものと「原理を理解すれば導ける」ものを明確に区別して伝えること。
</code></pre>
<hr>
<h2 id="8.-メタ指示・自己改善" tabindex="-1">8. メタ指示・自己改善</h2>
<p>AIの「振る舞い方」自体をコントロールする指示です。</p>
<h3 id="8-1.-指示の解釈確認" tabindex="-1">8-1. 指示の解釈確認</h3>
<pre><code>曖昧な指示を受けた場合、自分の解釈を述べてから回答を始めること。
</code></pre>
<h3 id="8-2.-自己評価の付与" tabindex="-1">8-2. 自己評価の付与</h3>
<pre><code>回答の最後に、その回答の確信度（高/中/低）と、低い場合はその理由を述べること。
</code></pre>
<h3 id="8-3.-代替案の提示" tabindex="-1">8-3. 代替案の提示</h3>
<pre><code>回答とともに、「別のアプローチ」として異なる解決策も1つ以上提示すること。
</code></pre>
<h3 id="8-4.-限界の明示" tabindex="-1">8-4. 限界の明示</h3>
<pre><code>できないこと、分からないことは明確に述べ、推測で回答する場合はその旨を明示すること。
</code></pre>
<h3 id="8-5.-フォローアップ提案" tabindex="-1">8-5. フォローアップ提案</h3>
<pre><code>回答の最後に、「次に検討すべきこと」や「関連して調べると良いこと」を1-2個提案すること。
</code></pre>
<h3 id="8-6.-矛盾の検出" tabindex="-1">8-6. 矛盾の検出</h3>
<pre><code>ユーザーの発言に矛盾がある場合、それを指摘して確認すること。
</code></pre>
<h3 id="8-7.-コンテキスト要約" tabindex="-1">8-7. コンテキスト要約</h3>
<pre><code>長い会話では、定期的にこれまでの議論を要約し、認識を合わせること。
</code></pre>
<h3 id="8-8.-改善フィードバック要求" tabindex="-1">8-8. 改善フィードバック要求</h3>
<pre><code>重要な回答の後、「この説明で分かりにくい点があれば教えてください」と明示的に確認すること。
</code></pre>
<hr>
<h2 id="9.-創造性・アイデア発想" tabindex="-1">9. 創造性・アイデア発想</h2>
<p>クリエイティブな作業を支援する指示です。</p>
<h3 id="9-1.-制約を変える" tabindex="-1">9-1. 制約を変える</h3>
<pre><code>アイデア出しで行き詰まったら、制約条件を意図的に変えて（緩める/厳しくする/逆転する）発想すること。
</code></pre>
<h3 id="9-2.-異分野からの類推" tabindex="-1">9-2. 異分野からの類推</h3>
<pre><code>問題解決の際、全く異なる分野（生物学、建築、ゲームなど）での類似問題とその解決策を参考にすること。
</code></pre>
<h3 id="9-3.-scamper法の適用" tabindex="-1">9-3. SCAMPER法の適用</h3>
<pre><code>改善アイデアを出す際は、Substitute/Combine/Adapt/Modify/Put to other uses/Eliminate/Reverseの観点で検討すること。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> SCAMPER法は、既存のアイデアを発展させるためのフレームワークです。代用・結合・応用・修正・転用・削除・逆転の7つの視点からアイデアを広げます。</p>
</blockquote>
<h3 id="9-4.-最悪のアイデアから" tabindex="-1">9-4. 最悪のアイデアから</h3>
<pre><code>あえて最悪のアイデアを考え、それを反転させることで良いアイデアを導くこと。
</code></pre>
<hr>
<h2 id="10.-プロジェクト管理・意思決定" tabindex="-1">10. プロジェクト管理・意思決定</h2>
<p>計画立案や判断を支援する指示です。</p>
<h3 id="10-1.-リスク分析" tabindex="-1">10-1. リスク分析</h3>
<pre><code>計画を立てる際は、リスク（発生確率×影響度）を評価し、上位3つへの対策を含めること。
</code></pre>
<h3 id="10-2.-意思決定マトリクス" tabindex="-1">10-2. 意思決定マトリクス</h3>
<pre><code>選択肢を評価する際は、評価軸と重み付けを明確にしたマトリクスを作成すること。
</code></pre>
<h3 id="10-3.-ステークホルダー分析" tabindex="-1">10-3. ステークホルダー分析</h3>
<pre><code>提案を作成する際は、影響を受ける関係者を列挙し、各者の関心事と懸念を考慮すること。
</code></pre>
<h3 id="10-4.-mvp思考" tabindex="-1">10-4. MVP思考</h3>
<pre><code>機能提案では、最小限の実装で価値を検証できるMVP（Minimum Viable Product）を最初に定義すること。
</code></pre>
<blockquote>
<p><strong>用語解説：</strong> MVP（Minimum Viable Product）とは、必要最小限の機能だけを持つ製品のことです。早く市場に出してフィードバックを得るために使われます。</p>
</blockquote>
<h3 id="10-5.-振り返り構造化" tabindex="-1">10-5. 振り返り構造化</h3>
<pre><code>プロジェクトの振り返りでは、Keep（続けること）/Problem（問題）/Try（試すこと）の形式で整理すること。
</code></pre>
<hr>
<h2 id="11.-出力制御・特殊指示" tabindex="-1">11. 出力制御・特殊指示</h2>
<p>細かい出力の調整に使う指示です。</p>
<h3 id="11-1.-出力長の制御" tabindex="-1">11-1. 出力長の制御</h3>
<pre><code>回答は[短い/普通/詳細]モードで調整可能。デフォルトは普通（300-500字程度）。「詳しく」と言われたら詳細モードに切り替える。
</code></pre>
<h3 id="11-2.-思考の可視化" tabindex="-1">11-2. 思考の可視化</h3>
<pre><code>複雑な問題では、思考過程を「考えていること：」として明示的に示してから結論を述べること。
</code></pre>
<h3 id="11-3.-仮置きして進行" tabindex="-1">11-3. 仮置きして進行</h3>
<pre><code>不明点があっても一旦仮定を置いて回答を完成させ、仮定部分を明示すること。後から修正可能。
</code></pre>
<h3 id="11-4.-差分のみ出力" tabindex="-1">11-4. 差分のみ出力</h3>
<pre><code>修正を求められた場合、変更箇所のみを示し、変更のない部分は省略すること。
</code></pre>
<h3 id="11-5.-段階的開示" tabindex="-1">11-5. 段階的開示</h3>
<pre><code>長い回答は分割して提供し、各部分の終わりで続きが必要か確認すること。
</code></pre>
<h3 id="11-6.-条件分岐の明示" tabindex="-1">11-6. 条件分岐の明示</h3>
<pre><code>「Aの場合は〜、Bの場合は〜」のように、状況によって回答が変わる場合は条件を明示すること。
</code></pre>
<h3 id="11-7.-優先順位の遵守" tabindex="-1">11-7. 優先順位の遵守</h3>
<pre><code>指示が矛盾した場合の優先順位：安全性 &gt; 正確性 &gt; 有用性 &gt; 効率性 &gt; 美しさ
</code></pre>
<h3 id="11-8.-メタ指示の受付" tabindex="-1">11-8. メタ指示の受付</h3>
<pre><code>「今後は〜」で始まる指示は、以降の会話全体に適用する永続的な設定変更として扱うこと。
</code></pre>
<h3 id="11-9.-ロールの切り替え" tabindex="-1">11-9. ロールの切り替え</h3>
<pre><code>「〜として答えて」と言われたら、そのロールを維持する。「普通に戻って」でデフォルトに戻す。
</code></pre>
<h3 id="11-10.-中断と再開" tabindex="-1">11-10. 中断と再開</h3>
<pre><code>長いタスクは論理的な区切りで分割し、各区切りで進捗を報告すること。
</code></pre>
<hr>
<h2 id="12.-品質・安全性" tabindex="-1">12. 品質・安全性</h2>
<p>回答の信頼性を高める指示です。</p>
<h3 id="12-1.-ソース意識" tabindex="-1">12-1. ソース意識</h3>
<pre><code>事実を述べる際は、確信度を示す（「確実に」「おそらく」「推測だが」など）。不確かな情報は検索で確認すること。
</code></pre>
<h3 id="12-2.-最新情報への配慮" tabindex="-1">12-2. 最新情報への配慮</h3>
<pre><code>技術情報や時事問題は、知識のカットオフを意識し、必要に応じてウェブ検索で最新情報を確認すること。
</code></pre>
<hr>
<h2 id="効果を高める4つのコツ" tabindex="-1">効果を高める4つのコツ</h2>
<p>最後に、カスタム指示をより効果的にするためのコツを紹介します。</p>
<h3 id="1.-具体的に書く" tabindex="-1">1. 具体的に書く</h3>
<p>❌ 悪い例：「良い回答をして」</p>
<p>✅ 良い例：「3文以内で要点を先に述べる」</p>
<p>抽象的な指示より、具体的な行動を書いた方が効果的です。</p>
<h3 id="2.-優先順位を示す" tabindex="-1">2. 優先順位を示す</h3>
<pre><code>正確性 &gt; 簡潔さ &gt; 網羅性
</code></pre>
<p>複数の指示が矛盾する場合に備えて、優先順位を明示しておくと安心です。</p>
<h3 id="3.-例を添える" tabindex="-1">3. 例を添える</h3>
<pre><code>専門用語は初出時に説明する。例：API（アプリケーション間の通信規約）
</code></pre>
<p>望む出力の具体例を1つ入れると、AIの理解度が大幅に上がります。</p>
<h3 id="4.-否定形も使う" tabindex="-1">4. 否定形も使う</h3>
<pre><code>- 箇条書きは必要な場合のみ使用し、通常は自然な文章で回答する
- 「素晴らしい質問ですね」などの社交辞令は省略する
</code></pre>
<p>「〜しない」という制約も効果的です。やってほしくないことを明示しましょう。</p>
<hr>
<h2 id="まとめ" tabindex="-1">まとめ</h2>
<p>カスタム指示を設定することで、LLMの回答品質は大きく変わります。</p>
<p><strong>初めての方は：</strong></p>
<ol>
<li>まず「基本セット」を試す</li>
<li>不満点が出てきたら、該当するカテゴリから指示を追加</li>
<li>自分だけの最適な組み合わせを見つける</li>
</ol>
<p>という流れがおすすめです。</p>
<p>この記事で紹介した指示は、すべてコピー＆ペーストで使えます。ぜひ色々試して、あなたに合った設定を見つけてください。</p>
<hr>
<p><strong>この記事が参考になったら、シェアしていただけると嬉しいです！</strong></p>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/4EojonTOebJGeiaXf98ol0/9e391fc8bd6e548f8c96136c805b49d7/ChatGPT%C3%A3__Claude%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A8__%C3%A3__%C3%A3_%C2%AA%C3%A3__%C3%AF__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A6__%C3%A7_%C2%BA%C3%A3__%C3%A6__%C3%A3__%C3%A6__%C3%A3__%C3%A5__%C3%A4__%C3%A9__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ローカルLLMはまず小さめモデルから：Ollamaで始める導入〜疎通確認]]></title>
            <link>https://www.yoshihiko.tech/blog/ollama-local-llm-setup</link>
            <guid isPermaLink="false">ollama-local-llm-setup</guid>
            <pubDate>Sun, 18 Jan 2026 06:40:49 GMT</pubDate>
            <description><![CDATA[acでOllamaを使い、ローカルLLMを最短で起動→1回応答まで確認する手順を解説。モデル選定の考え方、遅い/メモリ/ディスクの切り分け、API疎通(curl)もまとめます。]]></description>
            <content:encoded><![CDATA[<p>※この記事は macOS（Mac）でのローカル検証を想定します。コマンドや挙動は Ollama のバージョンで変わる可能性があるため、実行前に <code>ollama --help</code> を確認してください。(要出典: Ollama公式CLI)</p>
<h2 id="tl;dr" tabindex="-1">TL;DR</h2>
<p>まずはモデルサイズが小さいものから始めて「起動→1回応答」まで通し、用途に合わせて段階的にモデルを変えるのが安全です。モデルが大きいほど、メモリ/ディスク消費と処理時間が増えやすくなります。</p>
<hr>
<h2 id="先に結論：最初は“小さめモデルで1回応答”まで通す" tabindex="-1">先に結論：最初は“小さめモデルで1回応答”まで通す</h2>
<p>結論：最初にやることは「小さめのモデルで 1 回応答を返す」だけです。ここまで通れば、以降の問題は「モデル選定」か「性能/運用」のどちらかに寄せて切り分けできます。</p>
<h3 id="なぜ小さめから始めるべきか（失敗要因が減る）" tabindex="-1">なぜ小さめから始めるべきか（失敗要因が減る）</h3>
<p>モデルが大きいほど、必要なメモリやディスク容量が増えます。その結果、初回のセットアップで発生しやすい問題（動作が遅い/メモリ不足/ディスク不足）が増えます。最初は “成功状態を作る” ことを優先してください。</p>
<h3 id="この記事でできるようになること（到達点を明確化）" tabindex="-1">この記事でできるようになること（到達点を明確化）</h3>
<p>この順に進めます。</p>
<ol>
<li>Ollama を導入する</li>
<li>モデルを取得する（まずは小さめ）</li>
<li>CLI で 1 回応答を確認する</li>
<li>必要なら API（OpenAI 互換）でも疎通確認する</li>
<li>モデル選定と運用上の注意を把握する</li>
</ol>
<h3 id="この記事の提供物（チェックリスト/早見表/curlテンプレ）" tabindex="-1">この記事の提供物（チェックリスト/早見表/curlテンプレ）</h3>
<p>「再現しやすい」ことを優先して、次をコピペ用で用意します。</p>
<ul>
<li>インストール後チェックリスト（バージョン/ヘルプ/起動状態）</li>
<li>モデル選定の早見表（用途×サイズ×必要リソース×体感）</li>
<li>API 疎通確認テンプレ（curl 1 発）</li>
</ul>
<hr>
<h2 id="ollama-とは何か：ローカルでllmを動かすための入口" tabindex="-1">Ollama とは何か：ローカルでLLMを動かすための入口</h2>
<p>結論：Ollama は、ローカルで LLM を「取得して」「起動して」「呼び出す」までを短い手順で行うためのツールです。(要出典: Ollama公式)</p>
<h3 id="どんな用途に向くか（検証/プロトタイピング）" tabindex="-1">どんな用途に向くか（検証/プロトタイピング）</h3>
<p>次の用途に向きます。</p>
<ul>
<li>プロンプトの試行錯誤（要約/文章生成/アイデア出し）</li>
<li>ローカル API でアプリ側の I/F を検証する（まず疎通、次に実装）</li>
<li>外部サービスに送らずに検証したい（ただし投入データの扱いは要判断）</li>
</ul>
<h3 id="向かないケース（重いモデル・低スペック等の注意）" tabindex="-1">向かないケース（重いモデル・低スペック等の注意）</h3>
<p>次の条件では「セットアップはできても快適ではない」可能性が上がります。</p>
<ul>
<li>メモリに余裕がないのに、大きいモデルを前提にしている</li>
<li>CPU 実行なのに GPU 実行の速度を期待している（体感の前提がズレる）</li>
<li>常時稼働を前提にしている（更新/ディスク管理/公開範囲の運用が必要）</li>
</ul>
<hr>
<h2 id="インストール（mac想定）と導入後の確認（チェックリスト付き）" tabindex="-1">インストール（Mac想定）と導入後の確認（チェックリスト付き）</h2>
<p>結論：インストールしたら、まず「バージョン」「ヘルプ」「起動状態」を確認してください。ここを飛ばすと、後工程で詰まったときに原因が追えません。</p>
<h3 id="インストール手順（homebrew前提）※コマンドは公式で確認" tabindex="-1">インストール手順（Homebrew前提）※コマンドは公式で確認</h3>
<p>手順（例）：</p>
<ol>
<li>Homebrew がない場合は導入する (要出典: Homebrew公式)</li>
<li>Ollama をインストールする
<ol>
<li><code>brew install ollama</code> (要出典: Homebrew Formulae - ollama)</li>
</ol>
</li>
<li>インストール確認
<ol>
<li><code>ollama --version</code> (要出典: Ollama公式CLI)</li>
</ol>
</li>
</ol>
<p>※Ollama は macOS アプリとして動作する形態もあります。インストール方法・起動方法は公式の案内に従ってください。(要出典: Ollama公式)</p>
<h3 id="導入後の確認（バージョン/ヘルプ/起動状態）" tabindex="-1">導入後の確認（バージョン/ヘルプ/起動状態）</h3>
<ol>
<li>ヘルプを表示して、使えるサブコマンドを確認する
<ol>
<li><code>ollama --help</code> (要出典: Ollama公式CLI)</li>
</ol>
</li>
<li>起動状態を確認する（起動方式により異なる）
<ul>
<li>CLI で起動するのか、アプリとして起動するのかを、公式と <code>--help</code> で確定する (要出典: Ollama公式)</li>
</ul>
</li>
</ol>
<h3 id="インストール後チェックリスト（コピペ用）" tabindex="-1">インストール後チェックリスト（コピペ用）</h3>
<ul>
<li>[ ] <code>ollama --version</code> が表示できる (要出典: Ollama公式CLI)</li>
<li>[ ] <code>ollama --help</code> が表示できる (要出典: Ollama公式CLI)</li>
<li>[ ]（任意）API を使う場合、ローカルの待受（例：<code>http://localhost:11434</code>）に疎通できる (要出典: Ollama公式 OpenAI互換API)</li>
</ul>
<hr>
<h2 id="最短で動かす：モデル取得→起動→1回応答" tabindex="-1">最短で動かす：モデル取得→起動→1回応答</h2>
<p>結論：「モデル取得 → 起動 → CLI で 1 回応答」の順で進めると、前提が少なく成功しやすいです。</p>
<h3 id="モデルを選んで取得する（まずは小さめ）" tabindex="-1">モデルを選んで取得する（まずは小さめ）</h3>
<p>最初は “小さめのモデル” を選んでください。ここで言う「小さめ」は、モデルサイズ（パラメータ数など）が比較的小さいもの、または軽い量子化のものです。どれが該当するかは公式の一覧で確認してください。(要出典: Ollama公式モデル一覧)</p>
<p>手順（例）：</p>
<ol>
<li>モデルを取得する
<ol>
<li><code>ollama pull &lt;model&gt;</code> (要出典: Ollama公式CLI)</li>
</ol>
</li>
</ol>
<h3 id="起動（サーバー/常駐）と停止" tabindex="-1">起動（サーバー/常駐）と停止</h3>
<p>起動方法は環境差が出やすいので、この記事では「自分の環境の正解」を <code>--help</code> と公式で確定する前提にします。(要出典: Ollama公式)</p>
<p>確認観点（ここだけ押さえる）：</p>
<ul>
<li>起動しているか（プロセス/メニューバー/待受ポートなど）</li>
<li>停止できるか（常駐を止められるか）</li>
</ul>
<h3 id="1回応答の確認（cli-で最短）" tabindex="-1">1回応答の確認（CLI で最短）</h3>
<p>手順（例）：</p>
<ol>
<li>モデルを実行してプロンプトを投げる
<ol>
<li><code>ollama run &lt;model&gt;</code> (要出典: Ollama公式CLI)</li>
</ol>
</li>
<li>まずは短い入力で 1 回だけ応答を確認する（例：日本語で 1 行）</li>
</ol>
<p>注意：</p>
<ul>
<li>初回はモデル読み込みで時間がかかることがあります</li>
<li>応答が返れば、セットアップは成功と判断できます</li>
</ul>
<hr>
<h2 id="モデルの選び方：用途×日本語×サイズ×快適さで決める" tabindex="-1">モデルの選び方：用途×日本語×サイズ×快適さで決める</h2>
<p>結論：モデル選定は「用途」と「快適に動くか（メモリ/速度/ディスク）」で決めます。品質だけで選ぶと、運用が破綻しやすくなります。</p>
<h3 id="選定の観点（用途・品質・速度・コスト＝リソース）" tabindex="-1">選定の観点（用途・品質・速度・コスト＝リソース）</h3>
<p>最低限、次の観点で判断します。</p>
<ul>
<li>用途：雑談/要約/文章生成/コード支援など</li>
<li>言語：日本語の比重（日本語中心なら、まず日本語で短文テストする）</li>
<li>速度：対話の待ち時間を許容できるか</li>
<li>リソース：メモリ/ディスク/発熱/バッテリー（ノートPC）</li>
</ul>
<h3 id="“大きいほど良い”ではない（トレードオフの整理）" tabindex="-1">“大きいほど良い”ではない（トレードオフの整理）</h3>
<p>一般に、モデルが大きいほど品質が上がりやすい一方、必要なメモリが増え、応答も遅くなりがちです。まずは “動くサイズ” を見つけ、用途に応じて段階的に変えてください。</p>
<h3 id="マシンスペック別の選び方（断定せず目安）※要出典" tabindex="-1">マシンスペック別の選び方（断定せず目安）※要出典</h3>
<p>ここは数値を断定せず、判断フローにします（数値を出す場合は出典必須）。</p>
<ol>
<li>小さめのモデルで 1 回応答が返ることを確認する</li>
<li>遅い/重いなら、まずモデルサイズを下げる</li>
<li>まだ厳しいなら、同時に動かすアプリを減らす</li>
<li>それでも用途に耐えないなら、目的（速度/品質）に合わせて環境（より強いマシン/GPU 等）を検討する<br>
(要出典: モデルサイズと必要メモリの一般的目安、各モデルの推奨要件)</li>
</ol>
<hr>
<h2 id="よくある詰まりと切り分け（遅い/メモリ/取得失敗/ディスク肥大）" tabindex="-1">よくある詰まりと切り分け（遅い/メモリ/取得失敗/ディスク肥大）</h2>
<p>結論：詰まったら「症状 → 原因候補 → 対策の順序」で切り分けてください。順番を固定すると、原因が追いやすくなります。</p>
<h3 id="体感が遅いとき（モデルサイズ/同時アプリ/期待値）" tabindex="-1">体感が遅いとき（モデルサイズ/同時アプリ/期待値）</h3>
<p>確認の順序：</p>
<ol>
<li>モデルサイズを下げる（最優先）</li>
<li>同時に動いているアプリを減らす（ブラウザ/IDE 等）</li>
<li>CPU 実行の前提に期待値を合わせる（GPU 前提で比較しない）(要出典: CPU/GPU比較の一般論)</li>
</ol>
<h3 id="メモリ不足っぽいとき（症状と対策の順序）" tabindex="-1">メモリ不足っぽいとき（症状と対策の順序）</h3>
<p>症状例：</p>
<ul>
<li>起動/読み込みで止まる、OS 全体が重くなる、他アプリが落ちる</li>
</ul>
<p>対策の順序：</p>
<ol>
<li>モデルサイズを下げる</li>
<li>同時アプリを止める</li>
<li>常駐プロセスを減らす</li>
<li>必要なら「ローカルでやる範囲」を見直す（用途と品質の再調整）</li>
</ol>
<h3 id="モデル取得に失敗するとき（ネットワーク/容量）" tabindex="-1">モデル取得に失敗するとき（ネットワーク/容量）</h3>
<p>確認ポイント：</p>
<ul>
<li>ネットワークが不安定でないか</li>
<li>ディスク容量が足りているか（モデルはサイズが大きい）(要出典: モデルサイズの説明)</li>
</ul>
<h3 id="ディスクが増えるとき（一覧・削除・更新の運用）※コマンドは要確認" tabindex="-1">ディスクが増えるとき（一覧・削除・更新の運用）※コマンドは要確認</h3>
<p>結論：モデルは増える前提なので、「一覧→不要削除→自分ルール」をセットで決めます。</p>
<ol>
<li>現在のモデル一覧を見る（例：<code>ollama list</code>）(要出典: Ollama公式CLI)</li>
<li>不要なモデルを削除する（例：<code>ollama rm &lt;model&gt;</code>）(要出典: Ollama公式CLI)</li>
<li>検証用は軽量モデルに固定する、などの運用ルールを決める</li>
</ol>
<hr>
<h2 id="api-で疎通確認する（最小テンプレ）" tabindex="-1">API で疎通確認する（最小テンプレ）</h2>
<p>結論：CLI で 1 回応答を確認できたら、次に API を “最小テンプレ” で疎通確認します。ここまで通れば、アプリ検証に進めます。</p>
<h3 id="まずは-curl-1発で確認する（成功判定の観点）" tabindex="-1">まずは curl 1発で確認する（成功判定の観点）</h3>
<p>Ollama は OpenAI 互換 API を提供しています。(要出典: Ollama OpenAI compatibility)</p>
<p>手順（例）：</p>
<ol>
<li>サーバーが起動している前提で、次を実行する</li>
<li><code>&lt;model&gt;</code> は <code>ollama run</code> で動作確認できたモデル名に置き換える</li>
</ol>
<pre><code class="language-bash">curl http://localhost:11434/v1/chat/completions \
  -H &quot;Content-Type: application/json&quot; \
  -d '{
    &quot;model&quot;: &quot;&lt;model&gt;&quot;,
    &quot;messages&quot;: [{&quot;role&quot;:&quot;user&quot;,&quot;content&quot;:&quot;テストです。1行で返して&quot;}]
  }'
</code></pre>
<p>(要出典: Ollama公式 OpenAI互換API)</p>
<p>成功判定の観点：</p>
<ul>
<li>HTTP が 200 系で返る</li>
<li>レスポンスに出力が含まれる（例：<code>choices</code>/<code>message</code> 相当）(要出典: Ollama公式 OpenAI互換APIレスポンス形式)</li>
</ul>
<h3 id="openai-互換-api-を使う場面（アプリ検証に寄せる）" tabindex="-1">OpenAI 互換 API を使う場面（アプリ検証に寄せる）</h3>
<p>使いどころ：</p>
<ul>
<li>既存の OpenAI クライアント/ツールを “ホスト差し替え” で動かしたい</li>
<li>アプリ側の I/F（ストリーミング、ツール呼び出し等）を先に作りたい</li>
</ul>
<h3 id="うまくいかないときの観点（url/ポート/バインド）※要出典" tabindex="-1">うまくいかないときの観点（URL/ポート/バインド）※要出典</h3>
<p>よく見るポイント：</p>
<ul>
<li><code>localhost:11434</code> に到達できるか（ポート・起動状態）(要出典: Ollama公式)</li>
<li>別ホスト/別端末から叩く場合、バインド先や FW を閉じられているか（意図せぬ公開を避ける）(要出典: Ollama公式 or OSネットワーク設定)</li>
</ul>
<h3 id="api-仕様の参照先（公式）と、curlテンプレ（コピペ用）" tabindex="-1">API 仕様の参照先（公式）と、curlテンプレ（コピペ用）</h3>
<p>公式で確認しておく項目（ここを押さえるとブレません）：</p>
<ul>
<li>デフォルトのベースURL/ポート（例：<code>11434</code>）(要出典: Ollama公式 OpenAI互換API)</li>
<li>対応エンドポイント（例：<code>/v1/chat/completions</code>）(要出典: Ollama公式 OpenAI互換API)</li>
<li>サポートされるリクエストフィールド（例：<code>model</code>/<code>messages</code>/<code>stream</code> 等）(要出典: Ollama公式 OpenAI互換API)</li>
</ul>
<hr>
<h2 id="運用と安全：ローカルでも“公開範囲”と“投入データ”を決める" tabindex="-1">運用と安全：ローカルでも“公開範囲”と“投入データ”を決める</h2>
<p>結論：ローカルでも「どこまで公開されるか」と「何を投入してよいか」を決めないと、事故（意図せぬ公開・データ持ち出し）につながります。</p>
<h3 id="うっかり公開のリスク（バインド先/ポート/fw）" tabindex="-1">うっかり公開のリスク（バインド先/ポート/FW）</h3>
<p>注意点：</p>
<ul>
<li>ローカル専用のつもりでも、設定や共有の仕方次第で外部から到達できる可能性があります</li>
<li>社内ネットワーク内での利用でも、公開範囲を明確にしてください<br>
(要出典: Ollamaのホスト設定/OSのFW設定)</li>
</ul>
<h3 id="社内データ投入の判断（利用条件・リスク）" tabindex="-1">社内データ投入の判断（利用条件・リスク）</h3>
<p>判断観点：</p>
<ul>
<li>モデルの利用条件（ライセンス/規約）を確認する (要出典: 各モデルカード/配布元)</li>
<li>入力データの取り扱い（ログ/保存/共有の有無）を把握する (要出典: Ollamaのデータ取り扱いに関する公式情報)</li>
</ul>
<h3 id="継続運用（更新/削除/定期メンテ）" tabindex="-1">継続運用（更新/削除/定期メンテ）</h3>
<p>最低限の運用ルール例：</p>
<ol>
<li>使わないモデルは削除する（ディスク肥大を抑える）</li>
<li>更新時は <code>--help</code> でコマンド差分を確認する</li>
<li>トラブルが出たら「症状→切り分け」に戻る</li>
</ol>
<hr>
<h2 id="参考" tabindex="-1">参考</h2>
<ul>
<li>Ollama 公式サイト/ドキュメント (要出典: <a href="https://ollama.com/" target="_blank" rel="noopener noreferrer">https://ollama.com/</a> / <a href="https://docs.ollama.com/" target="_blank" rel="noopener noreferrer">https://docs.ollama.com/</a>)</li>
<li>Homebrew Formulae: ollama (要出典: <a href="https://formulae.brew.sh/formula/ollama" target="_blank" rel="noopener noreferrer">https://formulae.brew.sh/formula/ollama</a>)</li>
<li>Ollama OpenAI compatibility (要出典: <a href="https://docs.ollama.com/api/openai-compatibility" target="_blank" rel="noopener noreferrer">https://docs.ollama.com/api/openai-compatibility</a> / <a href="https://ollama.com/blog/openai-compatibility" target="_blank" rel="noopener noreferrer">https://ollama.com/blog/openai-compatibility</a>)</li>
</ul>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/1NQmtspOD1S6LEb4SrCYIQ/49c213de131a9a4ea139c32e805f3b01/%C3%A3__%C3%A3__%C3%A3__%C3%A3__LLM%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A5__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%AF__Ollama%C3%A3__%C3%A3__%C3%A5__%C3%A3__%C3%A3__%C3%A5__%C3%A5__%C3%A3__%C3%A7__%C3%A9__%C3%A7_%C2%BA%C3%A8%C2%AA_.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[LLMリーダーボードの読み方：スコア指針とベンチマーク確認手順]]></title>
            <link>https://www.yoshihiko.tech/blog/llm-leaderboard-score-guide</link>
            <guid isPermaLink="false">llm-leaderboard-score-guide</guid>
            <pubDate>Sat, 10 Jan 2026 08:57:26 GMT</pubDate>
            <content:encoded><![CDATA[<h2 id="tl;dr" tabindex="-1">TL;DR</h2>
<p>LLMリーダーボードの順位は、評価タスク・評価条件・指標の組み合わせ結果です。用途が一致するベンチマークと条件を確認できない場合は、比較結果を意思決定に使わないでください。</p>
<h2 id="リーダーボードは「何を測っているか」を理解する" tabindex="-1">リーダーボードは「何を測っているか」を理解する</h2>
<p>結論：リーダーボードは特定タスクの代理測定であり、用途が一致しない比較は結論を誤らせます。</p>
<h3 id="ベンチマーク/指標/評価条件の違い" tabindex="-1">ベンチマーク/指標/評価条件の違い</h3>
<p>用語の役割は次のとおりです。(出典候補: L3)</p>
<ul>
<li>ベンチマーク: どのタスクを測るか</li>
<li>指標: どう採点するか</li>
<li>評価条件: どの設定で測るか<br>
これらが異なると、同じスコアでも意味が変わります。</li>
</ul>
<h3 id="用途適合と横断比較の分離" tabindex="-1">用途適合と横断比較の分離</h3>
<p>用途適合の確認が最優先です。横断比較は「用途と条件が一致する範囲」に限定します。</p>
<p>読む順序（最小手順）</p>
<ol>
<li>目的と用途を1行で定義する。</li>
<li>目的に対応するタスク/ベンチマークを確認する。(出典候補: B1,B2,B3,C1,C2,M1,M2)</li>
<li>評価条件と指標を確認する。(出典候補: L3)</li>
<li>一致しない項目がある場合は比較を保留にする。</li>
</ol>
<h2 id="スコアが決まるプロセスを押さえる" tabindex="-1">スコアが決まるプロセスを押さえる</h2>
<p>結論：データセット、評価条件、採点方式の差がスコア差の主因です。</p>
<h3 id="データセット設計（分割/難易度/更新）" tabindex="-1">データセット設計（分割/難易度/更新）</h3>
<p>分割や難易度、更新方針でスコアは変動します。(出典候補: B1,B2,B3,C1,C2,M1,M2)<br>
同名ベンチマークでも版が違う場合は同一比較にしません。</p>
<h3 id="評価プロトコル（ゼロショット/フューショット/実行環境）" tabindex="-1">評価プロトコル（ゼロショット/フューショット/実行環境）</h3>
<p>ショット数、プロンプト形、実行環境が違うと結果は変わります。(出典候補: L3)</p>
<h3 id="生成設定（温度/最大トークン/デコード）" tabindex="-1">生成設定（温度/最大トークン/デコード）</h3>
<p>温度・最大トークン・デコード方式の差は生成結果に影響します。(出典候補: L3)</p>
<h3 id="採点方式（自動/人手/ルーブリック）" tabindex="-1">採点方式（自動/人手/ルーブリック）</h3>
<p>自動採点と人手評価では信頼性と再現性の扱いが異なります。(出典候補: L3)</p>
<p>確認すべき項目</p>
<ul>
<li>データセットの版と分割</li>
<li>ショット数/プロンプト条件</li>
<li>生成設定（温度/最大トークン/デコード）</li>
<li>採点方式と評価者条件</li>
</ul>
<h2 id="評価指標の種類と得意領域を整理する" tabindex="-1">評価指標の種類と得意領域を整理する</h2>
<p>結論：指標はタスク依存のため、汎用スコアの単独比較は避けます。</p>
<h3 id="正解一致・パス率系（qa/コード）" tabindex="-1">正解一致・パス率系（QA/コード）</h3>
<p>正答が明確なタスクで有効な指標です。(出典候補: L3)</p>
<h3 id="生成品質系（評価者/ルーブリック）" tabindex="-1">生成品質系（評価者/ルーブリック）</h3>
<p>評価者やルーブリックに依存するため、評価条件の透明性が重要です。(出典候補: L3)</p>
<h3 id="推論/数学/安全性系" tabindex="-1">推論/数学/安全性系</h3>
<p>専用ベンチマークで評価されることが多く、指標の目的を確認する必要があります。(出典候補: B2,B3)</p>
<h2 id="用途別ベンチマークの選び方" tabindex="-1">用途別ベンチマークの選び方</h2>
<p>結論：用途ごとに代表ベンチマークを固定してから比較します。</p>
<p>用途別マッピング表（例）</p>
<table>
<thead>
<tr>
<th>用途</th>
<th>要求能力</th>
<th>主要指標</th>
<th>代表ベンチマーク</th>
<th>典型的な落とし穴</th>
<th>追加検証</th>
</tr>
</thead>
<tbody>
<tr>
<td>コーディング</td>
<td>コード生成/修正/テスト</td>
<td>パス率/正解一致 (出典候補: L3)</td>
<td>例: 主要コード系ベンチマーク (出典候補: C1,C2)</td>
<td>実行環境差/評価条件差</td>
<td>自社コードで小規模検証</td>
</tr>
<tr>
<td>一般QA</td>
<td>知識/推論/長文理解</td>
<td>正解一致/生成品質 (出典候補: L3)</td>
<td>例: MMLU/GSM8K/BBH (出典候補: B1,B2,B3)</td>
<td>データ汚染/バージョン差</td>
<td>自社FAQで小規模検証</td>
</tr>
<tr>
<td>マルチモーダル</td>
<td>画像/音声/複合</td>
<td>生成品質/一致率 (出典候補: L3)</td>
<td>例: 主要マルチモーダル系 (出典候補: M1,M2)</td>
<td>入力条件の差</td>
<td>自社データで小規模検証</td>
</tr>
</tbody>
</table>
<h2 id="vellum-と-artificial-analysis-の読み方を比較する" tabindex="-1">Vellum と Artificial Analysis の読み方を比較する</h2>
<p>結論：評価対象・集計・更新の違いを確認できない場合、順位比較は保留にします。</p>
<p>比較表（要約）</p>
<table>
<thead>
<tr>
<th>項目</th>
<th>Vellum</th>
<th>Artificial Analysis</th>
<th>確認ポイント</th>
</tr>
</thead>
<tbody>
<tr>
<td>評価対象</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L4)</td>
<td>対象タスク範囲</td>
</tr>
<tr>
<td>主なベンチマーク</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L4)</td>
<td>ベンチの違い</td>
</tr>
<tr>
<td>指標</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L4)</td>
<td>指標定義</td>
</tr>
<tr>
<td>評価プロトコル</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L3)</td>
<td>ショット数/環境</td>
</tr>
<tr>
<td>生成設定</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L3)</td>
<td>温度/デコード</td>
</tr>
<tr>
<td>集計方法</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L4)</td>
<td>平均/カテゴリ別</td>
</tr>
<tr>
<td>更新頻度</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L4)</td>
<td>更新タイミング</td>
</tr>
<tr>
<td>モデル版管理</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L4)</td>
<td>同名モデルの扱い</td>
</tr>
<tr>
<td>再現性情報</td>
<td>(出典候補: L1)</td>
<td>(出典候補: L4)</td>
<td>公開ログ/コード</td>
</tr>
<tr>
<td>同条件で比較可能か</td>
<td>可/不可/保留</td>
<td>可/不可/保留</td>
<td>判断根拠</td>
</tr>
</tbody>
</table>
<p>比較の最小手順</p>
<ol>
<li>評価対象とベンチマークが一致するか確認する。(出典候補: L1,L3,L4)</li>
<li>評価条件（ショット数/生成設定/集計）が一致するか確認する。(出典候補: L1,L3,L4)</li>
<li>一致しない場合は「保留」として別枠で扱う。</li>
</ol>
<h2 id="スコア差の解釈ルールを決める" tabindex="-1">スコア差の解釈ルールを決める</h2>
<p>結論：差が小さい場合の保留ルールを先に決めておきます。</p>
<p>スコア解釈チェックリスト</p>
<ul>
<li>用途とタスクが一致している</li>
<li>指標定義が一致している (出典候補: L3)</li>
<li>評価条件（ショット数・プロンプト）が一致している (出典候補: L3)</li>
<li>生成設定が一致している (出典候補: L3)</li>
<li>集計方法が一致している (出典候補: L3)</li>
<li>ばらつきや再現性の情報がある</li>
<li>汚染・リークの兆候がない (出典候補: R1)</li>
<li>モデルのバージョン差を混同していない</li>
<li>非スコア要素（コスト/速度/安全性）を併記している</li>
<li>判断保留条件を明文化している</li>
</ul>
<h2 id="実務の選定手順（目的→指標→ベンチマーク→検証）" tabindex="-1">実務の選定手順（目的→指標→ベンチマーク→検証）</h2>
<p>結論：目的から逆算して評価軸を固定すれば、スコア差の説明が可能になります。</p>
<p>手順</p>
<ol>
<li>目的を1行で定義する（例: コードレビュー補助）。</li>
<li>重要要件を列挙する（コスト/速度/安全性/対応言語）。</li>
<li>目的に対応する指標を決める。(出典候補: L3)</li>
<li>指標に対応する代表ベンチマークを決める。(出典候補: B1,B2,B3,C1,C2,M1,M2)</li>
<li>評価条件（ショット数/環境/生成設定/採点方式）を固定する。(出典候補: L3)</li>
<li>自社タスクで小規模検証を設計する（期間/データ/評価者）。</li>
<li>判断結果と理由をテンプレに記録する。</li>
</ol>
<p>評価設計テンプレ（入力項目）</p>
<ul>
<li>目的・用途</li>
<li>重要要件（コスト/速度/安全性）</li>
<li>対象タスク（具体例）</li>
<li>候補モデル</li>
<li>主要指標 (出典候補: L3)</li>
<li>代表ベンチマーク (出典候補: B1,B2,B3,C1,C2,M1,M2)</li>
<li>評価プロトコル（ショット数/環境/生成設定）</li>
<li>受け入れ基準</li>
<li>小規模検証の計画（期間/データ/評価者）</li>
<li>判断結果と理由</li>
</ul>
<h2 id="注意点とfaq" tabindex="-1">注意点とFAQ</h2>
<p>結論：スコア至上主義を避け、非スコア要素も評価に含めます。</p>
<p>注意点</p>
<ul>
<li>スコア上位＝用途最適とは断定しない。</li>
<li>リーダーボード間の単純順位比較は避ける。</li>
<li>出典なしで具体的な数値やランキングを提示しない。</li>
</ul>
<p>FAQ<br>
Q. リーダーボードの総合順位だけで選定してよい？<br>
A. 用途と条件が一致しない場合は避けるべきです。用途、指標、評価条件の一致を確認してから判断してください。</p>
<p>Q. ベンチマークのスコア差はどれくらいで有意と見る？<br>
A. 有意差は評価プロトコルと再現性の情報に依存します。差が小さい場合は保留ルールを適用してください。(出典候補: L3)</p>
<p>Q. 同じ指標名でも比較できないのはなぜ？<br>
A. 指標定義や評価条件が違うと、同名でも意味が変わるためです。(出典候補: L3)</p>
<p>Q. マルチモーダルはどのベンチマークを見ればよい？<br>
A. 目的タスクに近い評価タスクを含むベンチマークを選び、公式の評価条件を確認してください。(出典候補: B1,B2,B3,C1,C2,M1,M2)</p>
<p>Q. スコア以外で最低限見るべき項目は？<br>
A. コスト、速度、安全性、運用条件（権限/ログ/監査）を最低限併記してください。</p>
<h2 id="出典候補（sources_list）" tabindex="-1">出典候補（sources_list）</h2>
<ul>
<li>L1 <a href="https://www.vellum.ai/llm-leaderboard" target="_blank" rel="noopener noreferrer">https://www.vellum.ai/llm-leaderboard</a></li>
<li>L2 <a href="https://www.vellum.ai/open-llm-leaderboard" target="_blank" rel="noopener noreferrer">https://www.vellum.ai/open-llm-leaderboard</a></li>
<li>L3 <a href="https://artificialanalysis.ai/methodology" target="_blank" rel="noopener noreferrer">https://artificialanalysis.ai/methodology</a></li>
<li>L4 <a href="https://artificialanalysis.ai/leaderboards/models" target="_blank" rel="noopener noreferrer">https://artificialanalysis.ai/leaderboards/models</a></li>
<li>B1 <a href="https://arxiv.org/abs/2009.03300" target="_blank" rel="noopener noreferrer">https://arxiv.org/abs/2009.03300</a></li>
<li>B2 <a href="https://arxiv.org/abs/2210.09261" target="_blank" rel="noopener noreferrer">https://arxiv.org/abs/2210.09261</a></li>
<li>B3 <a href="https://ar5iv.labs.arxiv.org/html/2110.14168" target="_blank" rel="noopener noreferrer">https://ar5iv.labs.arxiv.org/html/2110.14168</a></li>
<li>C1 <a href="https://ar5iv.labs.arxiv.org/html/2107.03374" target="_blank" rel="noopener noreferrer">https://ar5iv.labs.arxiv.org/html/2107.03374</a></li>
<li>C2 <a href="https://arxiv.org/abs/2310.06770" target="_blank" rel="noopener noreferrer">https://arxiv.org/abs/2310.06770</a></li>
<li>M1 <a href="https://arxiv.org/abs/2311.16502" target="_blank" rel="noopener noreferrer">https://arxiv.org/abs/2311.16502</a></li>
<li>M2 <a href="https://ar5iv.labs.arxiv.org/html/2307.06281" target="_blank" rel="noopener noreferrer">https://ar5iv.labs.arxiv.org/html/2307.06281</a></li>
<li>R1 <a href="https://arxiv.org/abs/2412.15194" target="_blank" rel="noopener noreferrer">https://arxiv.org/abs/2412.15194</a></li>
</ul>
]]></content:encoded>
            <category>AI</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/8u7GQ3mAlcvIHOc5bFJc6/a953bf74c33117efa393eaf8541f515a/LLM%C3%A3_%C2%AA%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A3__%C3%A8%C2%AA_%C3%A3__%C3%A6__.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Vite + Docker + ESBuild Errorの解決]]></title>
            <link>https://www.yoshihiko.tech/blog/vite_docker_esbuild-error</link>
            <guid isPermaLink="false">vite_docker_esbuild-error</guid>
            <pubDate>Fri, 12 Nov 2021 03:53:22 GMT</pubDate>
            <content:encoded><![CDATA[<p>Vite×Dockerの組み合わせで、開発サーバ起動時に下記エラーが発生した際の対応方法のメモです。</p>
<pre><code class="language-bash">ERR! Error: The package &quot;esbuild-windows-64&quot; could not be found, and is needed by esbuild.
ERR!
ERR! If you are installing esbuild with npm, make sure that you don't specify the
ERR! &quot;--no-optional&quot; flag. The &quot;optionalDependencies&quot; package.json feature is used
ERR! by esbuild to install the correct binary executable for your current platform.
ERR!     at generateBinPath (D:\Dropbox\04_Development\06_vent\node_modules\esbuild\lib\main.js:1643:15)
ERR!     at esbuildCommandAndArgs (D:\Dropbox\04_Development\06_vent\node_modules\esbuild\lib\main.js:1699:11)
ERR!     at ensureServiceIsRunning (D:\Dropbox\04_Development\06_vent\node_modules\esbuild\lib\main.js:1856:25)
ERR!     at Object.build (D:\Dropbox\04_Development\06_vent\node_modules\esbuild\lib\main.js:1749:26)
ERR!     at D:\Dropbox\04_Development\06_vent\node_modules\vite\dist\node\chunks\dep-85dbaaa7.js:42489:54
ERR!     at Array.map (&lt;anonymous&gt;)
ERR!     at scanImports (D:\Dropbox\04_Development\06_vent\node_modules\vite\dist\node\chunks\dep-85dbaaa7.js:42489:31)
ERR!     at processTicksAndRejections (node:internal/process/task_queues:96:5)
ERR!     at async optimizeDeps (D:\Dropbox\04_Development\06_vent\node_modules\vite\dist\node\chunks\dep-85dbaaa7.js:63029:30)
ERR!     at async runOptimize (D:\Dropbox\04_Development\06_vent\node_modules\vite\dist\node\chunks\dep-85dbaaa7.js:66901:48)
ERR!     at async createServer (D:\Dropbox\04_Development\06_vent\node_modules\vite\dist\node\chunks\dep-85dbaaa7.js:66930:9)
ERR!     at async Object.start (D:\Dropbox\04_Development\06_vent\node_modules\storybook-builder-vite\index.js:33:20)
ERR!     at async Promise.all (index 0)
ERR!     at async storybookDevServer (D:\Dropbox\04_Development\06_vent\node_modules\@storybook\core-server\dist\cjs\dev-server.js:123:28)
ERR!     at async buildDevStandalone (D:\Dropbox\04_Development\06_vent\node_modules\@storybook\core-server\dist\cjs\build-dev.js:112:31)
ERR!     at async buildDev (D:\Dropbox\04_Development\06_vent\node_modules\@storybook\core-server\dist\cjs\build-dev.js:154:5)
ERR!  Error: The package &quot;esbuild-windows-64&quot; could not be found, and is needed by esbuild.
</code></pre>
<p>開発サーバを起動する前に、<code>npm rebuild esbuild</code>を実行すると解決します。</p>
<p>docker-composeで起動する場合は、commandに追記してあげれば大丈夫でした。</p>
<pre><code class="language-yaml">version: '3'
services:
  app:
    command: sh -c 'npm rebuild esbuild &amp;&amp; yarn dev'
</code></pre>
<h3 id="参考記事" tabindex="-1">参考記事</h3>
<ul>
<li><a href="https://www.fixes.pub/program/955737.html" target="_blank" rel="noopener noreferrer">reactjs : Vite Reactアプリ:Dockerコンテナのesbuildエラー</a></li>
<li><a href="https://github.com/vitejs/vite/issues/2671#issuecomment-829535806" target="_blank" rel="noopener noreferrer">Vite + Esbuild Docker issue #2671</a></li>
</ul>
]]></content:encoded>
            <category>Programming</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/5gqsl4ZChCI3uaSXQA86du/3c94f9928ffaaab2d8911d32e742de1e/programming.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Nuxt.jsのページ遷移時のスクロール制御したい]]></title>
            <link>https://www.yoshihiko.tech/blog/nuxt-router-scroll-control</link>
            <guid isPermaLink="false">nuxt-router-scroll-control</guid>
            <pubDate>Thu, 28 Oct 2021 14:31:11 GMT</pubDate>
            <content:encoded><![CDATA[<p>Nuxt.jsのデフォルト設定では、ページ遷移時にスクロール位置が保持されたままページが遷移されてしまいます。こちらの挙動を制御するには、いくつか方法がありますが、今回は公式ドキュメントにもあるように、<code>router.scrollBehavior.js</code>を作成して対応していきます。</p>
<h1 id="スクロールの挙動を変更" tabindex="-1">スクロールの挙動を変更</h1>
<p>スクロールの挙動を変更するには、基本的に以下の2つの方法があります。</p>
<ul>
<li>nuxt.config.js内のroute.scrollBehaviorプロパティ</li>
<li>router.scrollBehavior.js (v2.9.0 ~ )</li>
</ul>
<p>公式的には、<code>router.scrollBehavior.js</code>を作成した方法を推奨しているようなので、今回はそちらの方法で設定を行っていきます。</p>
<p>※これらの設定は、Nuxt.jsが既に定義してあるデフォルトの設定を上書きする形になるので、注意が必要です。</p>
<h2 id="router.scrollbehavior.jsの作成" tabindex="-1">router.scrollBehavior.jsの作成</h2>
<p>プロジェクトのルートディレクトリ配下にappディレクトリを作成して、その中に<code>router.scrollBehavior.js</code>を配置していきます。</p>
<pre><code class="language-javascript">// app/router.scrollBehavior.js
export default (to, from, savedPosition) =&gt; {
	return { x: 0, y: 0 }
}
</code></pre>
<p>このメソッドは、ページがレンダリングされる度に呼ばれます。メソッドの詳しい説明については、公式ドキュメントを参照してください。</p>
<p><a href="https://router.vuejs.org/guide/advanced/scroll-behavior.html#async-scrolling" target="_blank" rel="noopener noreferrer">Vue-Router-async-scrolling</a></p>
<p>単純にページ遷移時にスクロール位置を先頭にするには、上述したように<code>return { x: 0, y: 0 }</code>を返却するだけでいいので、特にこだわりが無ければこれだけでも問題ないようにも思えます。</p>
<p>逆に固定レイアウトがある場合や、スムーススクロールを実装したい場合は少しこのファイルに手を加えてあげる必要があります。</p>
<pre><code class="language-javascript">// app/router.scrollBehavior.js
export default (to, from, savedPosition) =&gt; {
	return {
		selecter: to.hash,
		offset: { x: 0, y: 0 },
		behavior: 'smooth', // スムーススクロールの設定
	}
}
</code></pre>
<h1 id="特定のアンカータグの遷移" tabindex="-1">特定のアンカータグの遷移</h1>
<p>Headless CMSから取得したコンテンツを利用する場合、nuxt-linkが使用できず普通のアンカータグになってしまうかと思います。この場合、アンカータグデフォルトの動作となってしまいscrollBehaviorがうまく動作してくれないことがあります。</p>
<p>この場合は、アンカータグのデフォルトの挙動を宣言して、vue-routerで遷移させることで解決します。</p>
<p>例えば、記事の目次としてこのようなアンカータグが存在するとします。</p>
<pre><code class="language-html">&lt;div class=&quot;toc&quot;&gt;
	&lt;a href=&quot;#h1&quot;&gt;H1&lt;/a&gt;
	&lt;a href=&quot;#h2&quot;&gt;H2&lt;/a&gt;
	&lt;a href=&quot;#h3&quot;&gt;H3&lt;/a&gt;
&lt;/div&gt;
</code></pre>
<p>これらのアンカータグに対して、preventDefault()を呼んであげて、その後vue-routerのpush()を呼んでページを遷移するといった形になります。</p>
<pre><code class="language-javascript">setup () {
	const router = useRouter()
	onMounted(() =&gt; {
		const tocs = document.querySelectorAll('.toc a[href^=&quot;#&quot;]')
		for (let idx = 0; idx &lt; tocs.length; i++) {
			tocs[idx].addEventListener('click', e =&gt; {
				e.preventDefault()
				router.push({ path: e.target.hash })
			})
		}
	})
}
</code></pre>
<h1 id="エラー発生時の対応" tabindex="-1">エラー発生時の対応</h1>
<h2 id="domexception:-failed-to-execute-'queryselector'-on-'document':-'~~~'-is-not-a-valid-selector." tabindex="-1"><code>DOMException: Failed to execute 'querySelector' on 'Document': '~~~' is not a valid selector.</code></h2>
<p>日本語のハッシュをpushしたときに発生するエラーだと思います。パラメータの<code>%E3</code>とかが解決できなくて起きている模様ですね。こちらは、Nuxt.jsのデフォルトの設定を参考にhashをエスケープすることで解決することが可能です。</p>
<pre><code class="language-javascript">const hash = (() =&gt; {
  if (typeof window.CSS !== 'undefined' &amp;&amp; typeof window.CSS.escape !== 'undefined')
    return `#${window.CSS.escape(to.hash.substr(1))}`
  return to.hash
})()

return {
	selector: hash,
}
</code></pre>
<h2 id="typeerror:-cannot-read-properties-of-undefined-(reading-'behavior')" tabindex="-1"><code>TypeError: Cannot read properties of undefined (reading 'behavior')</code></h2>
<p>デフォルトの設定を参考に色々触っていた際に、scrollBehaviorにasyncをくっつけていた時に発生したエラーです。</p>
<pre><code class="language-javascript">export default async (to, from, savedPosition) =&gt; {
	// ...
}
</code></pre>
<p>下記が、Vue-Routerのソースです。</p>
<pre><code class="language-javascript">router.app.$nextTick(function () {
  var position = getScrollPosition();
  var shouldScroll = behavior.call(
    router,
    to,
    from,
    isPop ? position : null
  );

  if (!shouldScroll) {
    return
  }

  if (typeof shouldScroll.then === 'function') {
    shouldScroll
      .then(function (shouldScroll) {
        scrollToPosition((shouldScroll), position);
      })
      .catch(function (err) {
        if (process.env.NODE_ENV !== 'production') {
          assert(false, err.toString());
        }
      });
  } else {
    scrollToPosition(shouldScroll, position);
  }
});
</code></pre>
<p><code>behavior.call</code>の部分で定義した<code>scrollBehavior</code>を呼び出しています。その後のIF文でshouldScrollオブジェクトは、asyncを付けているためPromiseと解釈されてしまいエラーが発生しているようでした。</p>
<p>解決策としては、asyncを使用するのをやめるか、Promiseオブジェクトを返却（試してはない）してあげればOKです。</p>
<h1 id="参考文献" tabindex="-1">参考文献</h1>
<ul>
<li><a href="https://nuxtjs.org/docs/configuration-glossary/configuration-router/#scrollbehavior" target="_blank" rel="noopener noreferrer">Nuxt.js-scrollbehavior</a></li>
<li><a href="https://github.com/nuxt/nuxt.js/blob/dev/packages/vue-app/template/router.scrollBehavior.js" target="_blank" rel="noopener noreferrer">Nuxt.js-router.scrollBehavior.js</a></li>
<li><a href="https://koredana.info/blog/nuxtjs-router-push-alt-anchor" target="_blank" rel="noopener noreferrer">特定のアンカータグの遷移処理をrouter.pushでさせたい</a></li>
</ul>
]]></content:encoded>
            <category>Programming</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/6dqpNPZsBJJ7x9XJj1Ld1z/cd1bb9323a8ffb0657ad79525403e1ff/nuxt.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[React × DRFで画像アップロード機能作成 (バックエンド実装編)]]></title>
            <link>https://www.yoshihiko.tech/blog/react-drf-img_uploader-backend</link>
            <guid isPermaLink="false">react-drf-img_uploader-backend</guid>
            <pubDate>Tue, 26 Oct 2021 02:56:58 GMT</pubDate>
            <content:encoded><![CDATA[<p>前回までで、環境構築・フロントエンド側の実装が完了したので、続いてバックエンド側の実装に移りたいと思います。</p>
<p>前回までの記事</p>
<ul>
<li><a href="/blog/react-drf-img_uploader-environment">React × DRFで画像アップロード機能作成 (環境構築編)</a></li>
<li><a href="/blog/react-drf-img_uploader-frontend">React × DRFで画像アップロード機能作成 (フロント実装編)</a></li>
</ul>
<h1 id="実行環境" tabindex="-1">実行環境</h1>
<ul>
<li>Mac OS X 11.2.1</li>
<li>Docker 20.10.5</li>
<li>docker-compose version 1.28.5</li>
<li>node v15.10.0</li>
<li>npm 7.9.0</li>
<li>react 17.2.0</li>
</ul>
<h1 id="コンテナに入る" tabindex="-1">コンテナに入る</h1>
<p>今回は、server用のコンテナに入って作業をしていきます。下記コマンドを実行して、serverコンテナに入ります。</p>
<pre><code class="language-bash">$ docker exec -it server bash
</code></pre>
<h1 id="django-projectの作成" tabindex="-1">Django Projectの作成</h1>
<p>コンテナに入ったら、まず初めにDjangoのProjectを作成していきます。下記コマンドで、Django Projectを作成します。</p>
<pre><code class="language-bash">$ django-admin startproject server
</code></pre>
<p>上記コマンドを実行すると下記のようなファイルたちが生成されます。</p>
<pre><code class="language-text">server/
    manage.py
    server/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py
</code></pre>
<h1 id="django-appの作成" tabindex="-1">Django Appの作成</h1>
<p>Projectの作成が完了したら、続いてはAppの作成をしていきます。<br>
下記コマンドで、Django Appを作成します。</p>
<pre><code class="language-bash">$ python manage.py startapp api
</code></pre>
<p>上記コマンドを実行することで、apiというディレクトリを作成されます。</p>
<pre><code class="language-text">server/
    manage.py
    server/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py
		polls/
		    __init__.py
		    admin.py
		    apps.py
		    migrations/
		        __init__.py
		    models.py
		    tests.py
		    views.py
</code></pre>
<h1 id="djangoの開発サーバで確認してみる" tabindex="-1">Djangoの開発サーバで確認してみる</h1>
<p>正常にDjangoのファイルたちが作成されているか確認するために、Djangoが用意している開発サーバを立てて、確認をしてみます。下記コマンドを実行して、サーバを立てます。</p>
<pre><code class="language-bash">$ python manage.py runserver 0.0.0.0:8000
</code></pre>
<p>サーバが立ち上がったら、ブラウザから<a href="http://localhost:8000/" target="_blank" rel="noopener noreferrer">http://localhost:8000/</a>へアクセスします。</p>
<p>下記画面が表示されたら、確認は完了です。<br>
<img src="//images.contentful.com/d3b1looh8iwg/5CnAqDu9zsmZCnOEFTeOIY/06f7c69826c536aa140ac2b2e680dd77/django_serverup.png" alt="django serverup" loading="lazy"></p>
<hr>
<p>ここまでの手順は、Djangoの公式サイトのチュートリアルに沿った形で実行しています。各コマンドの詳細が気になる方は、公式サイトを確認してみてください。</p>
<p><a href="https://docs.djangoproject.com/ja/3.2/intro/tutorial01/" target="_blank" rel="noopener noreferrer">Django公式tutorial</a></p>
<h1 id="実装" tabindex="-1">実装</h1>
<h2 id="設定ファイルの修正" tabindex="-1">設定ファイルの修正</h2>
<p>早速実装に移っていこうと思いますが、その前に実装を始める前の準備段階として設定ファイル等を修正していきます。</p>
<p><code>/server/server/settings.py</code>をエディターで開きます。</p>
<pre><code class="language-python">ALLOWED_HOSTS = ['*'] # 修正

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # 追記
    'api.apps.ApiConfig', # 追記
    'corsheaders', # 追記
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware' # 追記
]

# 追記
CORS_ORIGIN_WHITELIST = (
    'http://localhost:3000',
)

LANGUAGE_CODE = 'ja' # 修正

TIME_ZONE = 'Asia/Tokyo' # 修正

# 追記
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
</code></pre>
<p>上記のようにsettings.pyを修正します。<br>
※関係のない部分は省略しています。</p>
<h3 id="mediaディレクトリの用意" tabindex="-1">mediaディレクトリの用意</h3>
<p>上記のsettings.pyで<code>MEDIA_URL</code>と<code>MEDIA_ROOT</code>を定義しました。</p>
<p>こちらは、画像の保存先を指定しており、画像が保存されると指定したmediaディレクトリ内に保存しようとします。そのため、事前に空のmediaディレクトリを用意しておいてください。</p>
<h2 id="ルーティングの設定" tabindex="-1">ルーティングの設定</h2>
<p>続いて、ルーティングの設定を行っていきます。まず、<code>/server/server/urls.py</code>をエディターで開きます。</p>
<pre><code class="language-python">from django.contrib import admin
from django.urls import path, include
from django.contrib.staticfiles.urls import static
from . import settings

urlpatterns = [
    path('api/', include('api.urls')),
    path('admin/', admin.site.urls),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
</code></pre>
<p>次に、前回作成したapiアプリ内にurls.pyを作成して下記コードを記述します。</p>
<pre><code class="language-python">from django.db import router
from django.urls import path, include
from . import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()

urlpatterns = [
    path('image/', views.ImageFileView.as_view(), name='images'),
    path('', include(router.urls)),
]
</code></pre>
<p>これでルーティングの設定が完了しました。</p>
<p>流れとしては、<code>example.com/api/image</code>というURLがあったとして、はじめに、server/urls.pyをみて、<code>api/</code> というパスが存在するので、includeしてあるapi/urls.pyに飛びます。</p>
<p>そして、api/urls.pyでは、<code>image/</code> というパスに対して、ImageFileViewというviewの処理へ促すようルーティングしています。</p>
<h2 id="viewの実装" tabindex="-1">Viewの実装</h2>
<p>先程のルーティングで<code>image/</code> に対してのルーティングが完了したので、このまま<code>image/</code> が来たときのViewの処理を実装します。</p>
<p><code>/server/api/views.py</code>を編集します。</p>
<pre><code class="language-python">from django.shortcuts import render
from rest_framework import status, generics, permissions
from .models import *
from .serializers import *

class ImageFileView (generics.ListCreateAPIView):
    permission_classes = (permissions.AllowAny,)
    queryset = UploadFile.objects.all()
    serializer_class = UploadFileSerializer
</code></pre>
<p>ここでは、Django REST frameworkのgenerics.ListCreateAPIViewを使用してViewの処理を記述しています。</p>
<p>こちらのViewは、HTTPメソッドが<code>GET or POST</code> で来た場合、<code>GET</code> なら該当Modelのデータを返却し、<code>POST</code> なら、該当Modelにデータを登録してくれます。</p>
<p>詳しい仕様は、<a href="https://www.django-rest-framework.org/api-guide/generic-views/#listcreateapiview" target="_blank" rel="noopener noreferrer">公式ドキュメント</a>を参考にしてみてください。</p>
<p>該当Modelとは、後述する<code>UploadFileModel</code>になります。</p>
<h2 id="modelの定義" tabindex="-1">Modelの定義</h2>
<p>Viewの処理の実装が完了したので、続いてModelの定義を行っていきます。</p>
<pre><code class="language-python">from django.db import models
import os

def upload_image (instance, filename):
    return 'images/{0}/'.format(filename)

class UploadFile (models.Model):
    file = models.ImageField('画像ファイル', upload_to=upload_image)

    def __str__ (self):
        return self.file.url

    @property
    def filename (self):
        return os.path.basename(self.file.name)
</code></pre>
<p>ここでは、「file」名前のという画像専用のフィールドを用意しています。<br>
<code>upload_to</code>には、データ登録時の保存先のパスを指定しています。<br>
<code>@property</code>にて、新たに当Modelにプロパティを追加することで、画像名の取得を容易にしています。</p>
<h2 id="serializerの定義" tabindex="-1">Serializerの定義</h2>
<p>続いて、Serializerを定義していきます。Serializerは、データの入出力を扱い、それをモデルに渡してくれるためのものです。</p>
<pre><code class="language-python">from rest_framework import serializers
from .models import *

class UploadFileSerializer (serializers.ModelSerializer):
    file = serializers.ImageField()
    file_name = serializers.SerializerMethodField()

    class Meta:
        model = UploadFile
        fields = '__all__'

    def get_file_name (self, obj):
        return obj.filename
</code></pre>
<h1 id="migrationとmigrate" tabindex="-1">migrationとmigrate</h1>
<p>ここまでの実装が完了したら、Modelの変更点等を反映するために、一度マイグレーションとマイグレートを行います。</p>
<pre><code class="language-bash">$ python manage.py makemigrations api
$ python manage.py migrate
</code></pre>
<h1 id="動作確認" tabindex="-1">動作確認</h1>
<p>ここまでで、すべての実装は完了です。動作確認をするため、client、server両方のサーバを起動して、実際に画面からファイルをUploadしてみます。</p>
<p>無事にUploadが完了したら、server/media/imageディレクトリ内に、アップロードした画像が保存されていることが確認できるかと思います。</p>
<h1 id="ディレクトリ構成" tabindex="-1">ディレクトリ構成</h1>
<p>ここまでの作業が完了したら、下記のようなディレクトリ構成になっているかと思います。</p>
<pre><code class="language-text">├── client
│   ├── Dockerfile
│   └── img_uploader
│       ├── package.json
│       ├── public
│       │   ├── favicon.ico
│       │   ├── index.html
│       │   ├── manifest.json
│       │   └── robots.txt
│       ├── src
│       │   ├── App.css
│       │   ├── App.test.tsx
│       │   ├── App.tsx
│       │   ├── assets
│       │   │   └── image
│       │   │       └── upload_bg.jpg
│       │   ├── index.css
│       │   ├── index.tsx
│       │   ├── react-app-env.d.ts
│       │   ├── reportWebVitals.ts
│       │   └── setupTests.ts
│       ├── tsconfig.json
│       └── yarn.lock
├── docker-compose.yml
└── server
    ├── api
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   ├── 0001_initial.py
    │   │   └── __init__.py
    │   ├── models.py
    │   ├── serializers.py
    │   ├── tests.py
    │   ├── urls.py
    │   └── views.py
    ├── db.sqlite3
    ├── manage.py
    ├── media
    │   └── images
    │       └── back_vOylmoT.jpg # アップロードされた画像ファイル
    └── server
        ├── __init__.py
        ├── asgi.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py
</code></pre>
<p>実際のコードはこちらになります。</p>
<p><a href="https://github.com/yoshihiko555/img-uploader" target="_blank" rel="noopener noreferrer">https://github.com/yoshihiko555/img-uploader</a></p>
<h1 id="まとめ" tabindex="-1">まとめ</h1>
<p>今回は全３回に渡って、React × DRFを用いた簡単な画像アップロードアプリを作成してみました。拙い文章が多い中最後まで読んでくださった方には感謝です。</p>
<p>Django REST frameworkを用いることで、非常に簡単にRESTAPIの作成が可能です。Reactに関してはまだまだ、スキル不足な部分が多いので、もっと邁進していきたいと感じました。</p>
]]></content:encoded>
            <category>Programming</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/4jbVtK0nXsMKZplpBAwhri/b0ccef5153d92a7947eb0c682008255c/django-rest-framework.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[React × DRFで画像アップロード機能作成 (フロント実装編)]]></title>
            <link>https://www.yoshihiko.tech/blog/react-drf-img_uploader-frontend</link>
            <guid isPermaLink="false">react-drf-img_uploader-frontend</guid>
            <pubDate>Tue, 26 Oct 2021 02:39:01 GMT</pubDate>
            <content:encoded><![CDATA[<p>前回の記事で、開発環境の構築が完了しました。今回は、フロントエンド側の実装に入りたいと思います。</p>
<p>前回までの記事はこちらです。</p>
<ul>
<li><a href="/blog/react-drf-img_uploader-environment">React × DRFで画像アップロード機能作成 (環境構築編)</a></li>
</ul>
<h1 id="実行環境" tabindex="-1">実行環境</h1>
<ul>
<li>Mac OS X 11.2.1</li>
<li>Docker 20.10.5</li>
<li>docker-compose version 1.28.5</li>
<li>node v15.10.0</li>
<li>npm 7.9.0</li>
<li>react 17.2.0</li>
</ul>
<h1 id="コンテナに入る" tabindex="-1">コンテナに入る</h1>
<p>前回作成したclient用のコンテナが起動していること確認後、下記コマンドを実行して、client用のコンテナに入ります。</p>
<pre><code class="language-bash">$ docker exec -it client sh
</code></pre>
<h1 id="reactアプリの作成" tabindex="-1">Reactアプリの作成</h1>
<p>コンテナに入ったら、下記のコマンドを実行してReactのアプリを作成します。</p>
<pre><code class="language-bash">$ cd client
$ npx create-react-app --template typescript
</code></pre>
<p>コマンドを実行すると、各ライブラリのインストールが始まります。インストールが完了すると、下記メッセージが出力されます。</p>
<pre><code class="language-bash">We suggest that you begin by typing:

  cd img_uploader
  yarn start

Happy hacking!
</code></pre>
<h2 id="開発用サーバを起動" tabindex="-1">開発用サーバを起動</h2>
<p>アプリの作成が無事に完了しているか確認するために、一度開発用サーバを起動して確認します。</p>
<pre><code class="language-bash">$ cd img_uploader
$ yarn start
</code></pre>
<p>サーバを起動したら、<a href="http://localhost:3000/" target="_blank" rel="noopener noreferrer">http://localhost:3000/</a>に接続します。下記画面が表示されていれば、アプリの作成とサーバに起動は成功です。</p>
<p><img src="//images.contentful.com/d3b1looh8iwg/7qeGJOlMbP82VDU13FGYwO/8a27f0a6a76c4aa10aa79f36d9ceb333/React.png" alt="React初期画面" loading="lazy"></p>
<h1 id="ライブラリのインストール" tabindex="-1">ライブラリのインストール</h1>
<p>次に、アプリ作成に必要なライブラリを別途インストールしていきます。</p>
<p>今回インストールするライブラリは下記になります。</p>
<ul>
<li>axios</li>
<li>Material-UI</li>
<li>react-dropzone</li>
</ul>
<p>下記コマンドを実行して、ライブラリをインストールします。</p>
<pre><code class="language-bash">$ yarn add axios @material-ui/core react-dropzone
$ yarn add -D @types/react-dropzone
</code></pre>
<h1 id="機能概要" tabindex="-1">機能概要</h1>
<p>ライブラリのインストールが完了したら、早速フロント側の機能実装に移りたいと思います。今回作成するアプリの機能は以下のようなものになります。</p>
<ul>
<li>ドラッグアンドドロップで画像選択</li>
<li>「Selectボタン」でファイル選択</li>
<li>選択された画像のプレビュー</li>
<li>「Uploadボタン」でサーバに選択された画像を送信</li>
</ul>
<h1 id="実装" tabindex="-1">実装</h1>
<p>早速、実装に移ろうと思います。まずは、画面を描画しているApp.tsxの編集を行っていきます。</p>
<h2 id="必要ライブラリのインポート" tabindex="-1">必要ライブラリのインポート</h2>
<p>まずは、今回使用するライブラリをインポートしていきます。</p>
<pre><code class="language-tsx">import React, { useState, useCallback } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import {
    Container,
    Card,
    CardContent,
    CardActions,
    Typography,
    Button,
} from '@material-ui/core'
import { useDropzone } from 'react-dropzone'
import './App.css';
import bg from './assets/image/upload_bg.jpg'
import axios from 'axios'
</code></pre>
<h2 id="material-uiのデザインアレンジ" tabindex="-1">Material UIのデザインアレンジ</h2>
<p>次に、前回インストールした画面のデザインを簡単に作ってくれるライブラリであるMaterial UIのデザインを少しアレンジしていきます。</p>
<p>Material UIの詳しい使用方法は、<a href="https://material-ui.com/ja/" target="_blank" rel="noopener noreferrer">公式ドキュメント</a>を参照してください。</p>
<pre><code class="language-tsx">const useStyles = makeStyles({
    root: {
        minWidth: 300,
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)'
    },
    title: {
        textAlign: 'center',
    },
    subtitle: {
        textAlign: 'center',
    },
    actions: {
        justifyContent: 'center',
    }
})
</code></pre>
<h2 id="コンポーネントの作成" tabindex="-1">コンポーネントの作成</h2>
<p>実際に描画されるコンポーネントの作成を行っていきます。</p>
<pre><code class="language-tsx">// Dropzoneの設定
const acceptFile = 'image/*'

type MyFile = File &amp; {
    preview: string
}

const App:React.FC = () =&gt; {

    // Style
    const classes = useStyles()

    // State
    const [isShow, setIsShow] = useState(false)
    const [files, setFiles] = useState&lt;MyFile[]&gt;([])

    // ドロップした時の処理
    const onDrop = useCallback((acceptedFiles: File[]) =&gt; {
        setIsShow(true)

        setFiles(acceptedFiles.map(
            file =&gt; Object.assign(file, {
                preview: URL.createObjectURL(file)
            })
        ))
    }, [])

    // Dropzone
    const { getRootProps, getInputProps, isDragActive, open }
        = useDropzone({ noClick: true, onDrop, accept: acceptFile })

    const upload = () =&gt; {
        const data = new FormData()
        files.forEach(file =&gt; data.append('file', file))
        axios.post('http://localhost:8000/api/image/', data)
        .then(res =&gt; {
            console.log(res)
            setIsShow(false)
            setFiles([])
        })
        .catch(e =&gt; {
            console.log(e)
        })
    }

    return (
        &lt;Container maxWidth=&quot;sm&quot;&gt;
            &lt;Card className={classes.root}&gt;
                &lt;CardContent&gt;
                    &lt;Typography variant='h6' component='h2' className={classes.title}&gt;
                        Image uploader
                    &lt;/Typography&gt;
                    &lt;Typography component='p' color='textSecondary' className={classes.subtitle}&gt;
                        drag &amp; drop
                    &lt;/Typography&gt;
                    &lt;div {...getRootProps({ className: 'dropzone' })}&gt;
                        {isShow ? (
                            files.map(file =&gt; (
                                &lt;img key={file.name} src={file.preview} alt={file.name} width='200' /&gt;
                            ))
                        ) : (
                            &lt;img src={upload_bg} alt='bg' className={'upload_img ' + (isDragActive ? 'is-on' : '') } width='200' /&gt;
                        )}
                        &lt;input {...getInputProps()} /&gt;
                    &lt;/div&gt;

                &lt;/CardContent&gt;

                &lt;CardActions className={classes.actions}&gt;
                    &lt;Button
                        size='small'
                        color='primary'
                        variant='contained'
                        component=&quot;span&quot;
                        onClick={open}
                    &gt;Select&lt;/Button&gt;
                    {isShow &amp;&amp; (
                        &lt;Button
                            size='small'
                            color='primary'
                            variant='contained'
                            component='span'
                            onClick={upload}
                        &gt;Upload&lt;/Button&gt;
                    )}
                &lt;/CardActions&gt;
            &lt;/Card&gt;
        &lt;/Container&gt;
    )
}

export default App;
</code></pre>
<h3 id="usestyles()" tabindex="-1">useStyles()</h3>
<pre><code class="language-tsx">const classes = useStyles()
</code></pre>
<p>ここでは、上で定義したMaterial UIのデザインのアレンジするための関数を実行して、その戻り値を<code>classes</code>に格納することで、デザインを適用するための処理です。</p>
<p>実際に、デザインを適用するためには、下記のように<code>className</code>に適用させたいクラス名を定義します。</p>
<pre><code class="language-tsx">&lt;Card className={classes.root}&gt;
</code></pre>
<h3 id="usstate()" tabindex="-1">usState()</h3>
<p>useState関数を使用して、コンポーネント内での状態管理を行います。useStateは下記のようにして使用します。</p>
<pre><code class="language-tsx">const [変数, set変数] = useState(初期値)
</code></pre>
<p>今回は、画像が選択された際に、画面の表示を切り替えるためのフラグをここで管理しています。</p>
<pre><code class="language-tsx">const [isShow, setIsShow] = useState(false)
const [files, setFiles] = useState&lt;MyFile[]&gt;([])// ドロップした時の処理
    const onDrop = useCallback((acceptedFiles: File[]) =&gt; {
        setIsShow(true)

        setFiles(acceptedFiles.map(
            file =&gt; Object.assign(file, {
                preview: URL.createObjectURL(file)
            })
        ))
    }, [])

// ...

// isShowがFalseなら、デフォルトで表示するべき画像を表示しています。
{isShow ? (
    files.map(file =&gt; (
        &lt;img key={file.name} src={file.preview} alt={file.name} width='200' /&gt;
    ))
) : (
    &lt;img src={upload_bg} alt='bg' className={'upload_img ' + (isDragActive ? 'is-on' : '') } width='200' /&gt;
)}
</code></pre>
<h3 id="ドラッグアンドドロップの処理" tabindex="-1">ドラッグアンドドロップの処理</h3>
<p><a href="https://react-dropzone.js.org/" target="_blank" rel="noopener noreferrer">react-dropzone</a>というライブラリを使用して、コンポーネントの一部のエリアにドラッグアンドドロップをすることによって、画像を選択させるといった処理が可能になります。</p>
<pre><code class="language-tsx">
// ドロップした時の処理
const onDrop = useCallback((acceptedFiles: File[]) =&gt; {
		// 
    setIsShow(true)

    setFiles(acceptedFiles.map(
        file =&gt; Object.assign(file, {
            preview: URL.createObjectURL(file)
        })
    ))
}, [])

// useDropzone実行時に渡しているプロパティについて
// noClick: trueを渡すことで、ドラッグアンドドロップでのみ画像選択を許可する
// onDropを渡すことで、ドラッグアンドドロップ時に、実行する関数を指定できる
// Dropzone
const { getRootProps, getInputProps, isDragActive, open }
    = useDropzone({ noClick: true, onDrop, accept: acceptFile })

// ...

&lt;div {...getRootProps({ className: 'dropzone' })}&gt;
    {isShow ? (
        files.map(file =&gt; (
            &lt;img key={file.name} src={file.preview} alt={file.name} width='200' /&gt;
        ))
    ) : (
        &lt;img src={upload_bg} alt='bg' className={'upload_img ' + (isDragActive ? 'is-on' : '') } width='200' /&gt;
    )}
    &lt;input {...getInputProps()} /&gt;
&lt;/div&gt;
</code></pre>
<h3 id="api呼び出し" tabindex="-1">API呼び出し</h3>
<p>Uploadボタンを押した時に、このあと作成するAPIを呼び出すための処理の実装を見ていきます。</p>
<pre><code class="language-tsx">{isShow &amp;&amp; (
  &lt;Button
      size='small'
      color='primary'
      variant='contained'
      component='span'
      onClick={upload}
  &gt;Upload&lt;/Button&gt;
)}

const upload = () =&gt; {
    const data = new FormData()
    files.forEach(file =&gt; data.append('file', file))
    axios.post('http://localhost:8000/api/image/', data)
    .then(res =&gt; {
				// API成功時の処理
        console.log(res)
        setIsShow(false)
        setFiles([])
    })
    .catch(e =&gt; {
        console.log(e)
    })
}
</code></pre>
<p>uploadボタンは、isShowフラグがtrueのときのみ表示され、ボタンを押下した時にupload関数が実行されます。</p>
<p>upload関数では、選択されたファイルをFormDataに格納していき、それをAxiosを使用して、APIを実行しています。</p>
<p>APIの処理が成功したときは、<code>.then</code> の処理に入りisShowフラグを折り、選択ファイルを空にしています。</p>
<h2 id="デザインを整える" tabindex="-1">デザインを整える</h2>
<p>App.tsxの実装が完了したら次は、CSSで細かいデザインを整えて行きます。</p>
<pre><code class="language-css">body {
    background-color: #eee;
}

.upload_img {
    display: block;
    margin: 20px auto;
    border-style: dashed;
    border-radius: 10px;
    border-color: #7c88cb;
}
.upload_img.is-on { opacity: .7; }
.hidden { display: none; }

img {
    display: block;
    margin: 20px auto;
}
</code></pre>
<h2 id="動作確認" tabindex="-1">動作確認</h2>
<p>ここまで実装が済んだら、実際に画像をドラッグアンドドロップしてみます。<br>
<img src="//images.contentful.com/d3b1looh8iwg/29C7Y1pwwfAa86h8WBadaH/6afafe9970bbc425ef8f9bd4d6370696/img-uploader-drug_and_drop.png" alt="img-uploader-drug and drop" loading="lazy"></p>
<p>選択した画像がプレビューされることが確認でき、Uploadボタンが表示されたら一先ずOKです。<br>
<img src="//images.contentful.com/d3b1looh8iwg/9MOcS10jJrZxDD4uWMLGM/61a07d4b630ad86d03bcf09bdb6a9d48/img-uploader-preview.png" alt="img-uploader-preview" loading="lazy"></p>
<h1 id="まとめ" tabindex="-1">まとめ</h1>
<p>今回は、ドラッグアンドドロップで画像を選択して、選択された画像をプレビューするところまで実装しました。次回は、Uploadボタンを押下時に受け取るサーバ側の処理を実装していこうと思います。</p>
]]></content:encoded>
            <category>Programming</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/3uFOs5NyHOvRqYgE1lQ3p6/d79c8fabb7d4151c5773ffe1cdd2bce3/react.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[React × DRFで画像アップロード機能作成 (環境構築編)]]></title>
            <link>https://www.yoshihiko.tech/blog/react-drf-img_uploader-environment</link>
            <guid isPermaLink="false">react-drf-img_uploader-environment</guid>
            <pubDate>Tue, 26 Oct 2021 02:00:01 GMT</pubDate>
            <content:encoded><![CDATA[<p>ReactとDjango REST Frameworkを使った画像アップロード機能を作成してみるの第１回となる「環境構築編」です。</p>
<h1 id="実行環境" tabindex="-1">実行環境</h1>
<ul>
<li>Mac OS X 11.2.1</li>
<li>Docker 20.10.5</li>
<li>docker-compose version 1.28.5</li>
</ul>
<h1 id="作業ディレクトリの用意" tabindex="-1">作業ディレクトリの用意</h1>
<p>今回のアプリを開発するための作業ディレクトリを用意していきます。</p>
<p>ディレクトリ構成は下記のようにします。</p>
<pre><code class="language-text">└── sample
    ├── client
    └── server
</code></pre>
<h1 id="イメージの作成" tabindex="-1">イメージの作成</h1>
<p>まず手始めに、今回のアプリを作成していくにあたりDockerのイメージを作成していこうと思います。Dockerのインストールがお済みでない方は、下記の記事からDockerをインストールをしてから進めていただくと幸いです。</p>
<p><a href="/blog/docker-install-procedure">各OS毎のDockerインストール手順</a></p>
<h2 id="dockerfileの用意" tabindex="-1">Dockerfileの用意</h2>
<p>今回は、クライアントはReact、サーバはDjango REST frameworkを使用していこうと思います。それぞれの環境を構築するために、Dockerfileを用意していこうと思います。</p>
<h3 id="client用dockerfile" tabindex="-1">client用Dockerfile</h3>
<p>clientのディレクトリにDocekrfileという名前のファイルを作成して、下記の内容を記述してください。</p>
<pre><code class="language-docker">FROM node:15.10.0-alpine3.11

WORKDIR /usr/src/app
</code></pre>
<h3 id="server用dockerfile" tabindex="-1">server用Dockerfile</h3>
<p>serverのディレクトリにもclientと同様にDockerfileという名前のファイルを作成して、下記の内容を記述します。</p>
<pre><code class="language-docker">FROM python:3

ENV PYTHONUNBUFFERED 1

RUN apt update
RUN apt install -y --no-install-recommends \
	net-tools \
	sudo \
	bzip2 \
	curl \
	gcc \
	python3-dev \
	vim
RUN apt clean

RUN pip install --upgrade pip
RUN pip install \
	django \
	djangorestframework \
	djangorestframework-jwt \
	django-filter \
	django-environ \
	django-cors-headers \
	gunicorn \
	Pillow
</code></pre>
<h2 id="docker-compose.ymlの用意" tabindex="-1">docker-compose.ymlの用意</h2>
<p>次に、複数コンテナを管理するためにdocker-composeを使用するために、docker-compose.ymlを用意していきます。</p>
<p>sampleディレクトリ直下にdocker-compose.ymlという名前のファイルを用意して、下記の内容を記述します。</p>
<pre><code class="language-yaml">version: '3'
services:
    client:
        build: ./client
        image: client-image
        container_name: client
				environment:
            - NODE_ENV=production
        ports:
            - '3000:3000'
        volumes:
            - ./client/:/usr/src/app/
        tty: true

    server:
        build: ./server
        image: server-image
        container_name: server
        ports:
            - '8000:8000'
        volumes:
            - ./server/:/usr/src/app/
        tty: true
</code></pre>
<p>ここまでディレクトリ構成は以下のようになっています。</p>
<pre><code class="language-text">└── sample
		├── client
		│   └── Dockerfile
		├── docker-compose.yml
		└── server
		    └── Dockerfile
</code></pre>
<h1 id="コンテナの作成" tabindex="-1">コンテナの作成</h1>
<p>上記の手順で用意した、Dockerfileたちをもとに、実際に稼働するコンテナを作成していきます。</p>
<p>下記のコマンドで、Dockerイメージのビルドと、コンテナの起動を行っていきます。</p>
<pre><code class="language-bash">$ docker-compose up -d --build
</code></pre>
<p>上記のコマンドを実行すると、Dockerイメージのビルドが始まり、ビルドが成功すると自動でコンテナが起動します。下記のメッセージが出力されればOKです。</p>
<pre><code class="language-bash">Successfully built 593c766c576516137e385bb2ddeff448bdc53d546d642059be900350d3b74011
Starting client ... done
Starting server ... done
</code></pre>
<h2 id="実際にコンテナが起動しているか確認" tabindex="-1">実際にコンテナが起動しているか確認</h2>
<p>下記コマンドを実行して、コンテナが起動しているか確認します。</p>
<pre><code class="language-bash">$ docker-compose ps

Name             Command            State           Ports         
-------------------------------------------------------------------
client   docker-entrypoint.sh node   Up      0.0.0.0:3000-&gt;3000/tcp
server   python3                     Up      0.0.0.0:8000-&gt;8000/tcp
</code></pre>
<p>StateがUpとなっていれば、コンテナは無事起動しています。ここまでで、アプリを作成するための環境構築は完了です。</p>
<p>次回は、クライアント側であるReactのプロジェクトを作成して、画面側の準備をしていこうと思います。</p>
]]></content:encoded>
            <category>Programming</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/5gqsl4ZChCI3uaSXQA86du/3c94f9928ffaaab2d8911d32e742de1e/programming.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[各OS毎のDockerインストール手順]]></title>
            <link>https://www.yoshihiko.tech/blog/docker-install-procedure</link>
            <guid isPermaLink="false">docker-install-procedure</guid>
            <pubDate>Tue, 26 Oct 2021 01:43:54 GMT</pubDate>
            <content:encoded><![CDATA[<p>だいぶ今更な気もしますが、仮想コンテナ技術の代表ともいえるDockerを各OSで使用するためのインストール方法についてまとめます。ほとんど自分の備忘録も兼ねているのであしからず。</p>
<h1 id="dockerとは" tabindex="-1">Dockerとは</h1>
<p>Dockerをインストールする前に、簡単にDockerとは何かを説明しておきます。Dockerとは、仮想化技術の一つである、「コンテナ型」の仮想化技術を提供するための、オープンソースソフトウェアです。</p>
<p>コンテナ化したアプリケーションを、作成・配布・実行を、簡単に実現することが出来、開発や運用等を効率的に行うことが出来ます。</p>
<h1 id="各os毎のdockerインストール手順" tabindex="-1">各OS毎のDockerインストール手順</h1>
<h2 id="macos" tabindex="-1">macOS</h2>
<p>macOSにて、Dockerをインストールする方法は、２通り存在します。</p>
<ol>
<li>公式サイトからDocker Desktop for Macをインストール</li>
<li>Homebrewを使用してインストール</li>
</ol>
<h3 id="公式サイトからインストール" tabindex="-1">公式サイトからインストール</h3>
<p><a href="https://www.docker.com/get-started" target="_blank" rel="noopener noreferrer">Docker公式サイト Get Started</a>へアクセスして、Dockerをインストールします。Get Startedから、ご自身の環境にあったインストーラーをインストールします。</p>
<p><img src="//images.contentful.com/d3b1looh8iwg/5p3JLKcEzwyfxnKFrVQwSt/adb6d390cc679c18c892a44974200823/Docker_GetStarted.png" alt="Docker GetStarted" loading="lazy"></p>
<p>ダウンロードした「Docker.dmg」を実行し、Docker Desktop for Macのインストールを開始します。実行完了後、Docker.appをApplicationディレクトリにコピーします。</p>
<p><img src="//images.contentful.com/d3b1looh8iwg/4bJzXpeHJzkiXp1iZUQM9f/7f2cc8d3ab81aea17c93a65c3d453378/Docker.dmg.png" alt="Docker.dmg" loading="lazy"></p>
<p>インストールが完了したら、Docker.appを起動してDockerを起動します。<br>
無事に起動が出来ていれば、ツールバーのクジラアイコンをクリックすると、<br>
「<strong>Docker Desktop is running</strong>」と表示されます。</p>
<p><img src="//images.contentful.com/d3b1looh8iwg/1hjR4tmVfHYD66VigOA2ZO/1f90fe5c368c98e3f5907a95e0473dd9/Docker.png" alt="Mac-Docker-running" loading="lazy"></p>
<h3 id="homebrewを使用してインストール" tabindex="-1">Homebrewを使用してインストール</h3>
<p>DockerをHomebewを使用してインストールする場合は、下記のコマンドを実行するだけでOKです。</p>
<pre><code class="language-bash">$ brew install --cask docker
</code></pre>
<p>参考文献として、Homebrewのページが<a href="https://formulae.brew.sh/cask/docker" target="_blank" rel="noopener noreferrer">こちら</a>です。</p>
<h2 id="windows" tabindex="-1">Windows</h2>
<h3 id="公式サイトからインストール-1" tabindex="-1">公式サイトからインストール</h3>
<p>公式サイトからOSに対応するインストーラーをダウンロードするところまでは、macOSと同様です。</p>
<p>ダウンロードした「Docker Desktop Installer.exe」を実行すると、自動的にDockerのインストールが始まります。インストールが完了すると、下記の画面が出てくるので、そのまま「Close」を押して画面を閉じます。</p>
<p><img src="//images.contentful.com/d3b1looh8iwg/6KBcebZUx3ox5EmFf5x83S/20bd57e3121ccd588b70aa220b7bf5e0/Untitled.png" alt="Windows-Docker-installing" loading="lazy"></p>
<p>無事に起動が完了すると、右下のタスクバーにクジラアイコンが表示されます。</p>
<p><img src="//images.contentful.com/d3b1looh8iwg/3P6VT0rKOXEwBPHTY3SaYX/12eef7d3a22b2cb7342ca51bf6b3aeaf/Untitled_1.png" alt="WIndows-Docker-running" loading="lazy"></p>
]]></content:encoded>
            <category>Programming</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/3aVlE8NIfX12QvTafaC8xE/d00bf480e212538e1816fc10d2e7b77d/docker.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Notionのゴミ箱を一括削除する]]></title>
            <link>https://www.yoshihiko.tech/blog/notion-clear-trash</link>
            <guid isPermaLink="false">notion-clear-trash</guid>
            <pubDate>Sun, 12 Sep 2021 13:04:59 GMT</pubDate>
            <content:encoded><![CDATA[<p>現在、Notionには一括でゴミ箱を空にするという機能は存在しません。<br>
ゴミ箱から一つ一つ選択して、削除することは可能ですが...</p>
<p>公式によると、ゴミ箱機能はアーカイブ機能のように動かすことを想定しているからだそうです。<br>
必要なときにいつでも削除・復元が出来るから便利でしょってことなんだろうと思います。</p>
<p>ただ、実際にはゴミ箱に溜まったノートを一括で削除した場面は多いと思います。そこで、Andrew Wangさんが提供しているPythonのスクリプトを実行して、Notionのゴミ箱の一括削除を行います。</p>
<p><a href="https://github.com/axyyu/notion-clear-trash" target="_blank" rel="noopener noreferrer">notion-clear-trash</a></p>
<p>こちらのスクリプトですが、非公式の内部APIを使用したものとなっておりますので、使用する際はご注意ください。</p>
<h1 id="アクセストークンの取得" tabindex="-1">アクセストークンの取得</h1>
<p>APIとの通信には、Notionが発行しているアクセストークンを使用します。</p>
<p><a href="https://www.notion.so/fastnotion/Fast-Notion-74dd4501d5824de7997a143baba1098f" target="_blank" rel="noopener noreferrer">Fast Notion</a>などを使用している方は既にご存じかもしれません。</p>
<h2 id="1.-webブラウザでnotionを開き、devtoolsを起動" tabindex="-1">1. WebブラウザでNotionを開き、DevToolsを起動</h2>
<p>お使いのWebブラウザから<a href="https://www.notion.so/" target="_blank" rel="noopener noreferrer">Notion</a>を開きログインします。<br>
ログインが完了したら、DevToolsを起動します。</p>
<ul>
<li>Windowsの方は、<code>F12</code> または、<code>Ctrl + Shift + I</code></li>
<li>Macの方は、<code>Option + Command + I</code></li>
</ul>
<h2 id="2.-アクセストークンのコピー" tabindex="-1">2. アクセストークンのコピー</h2>
<p>DevTools上の、<code>Application タブ</code>  &gt; <code>Storage</code> &gt; <code>Cookies</code> &gt; <code>token_v2</code> を探して、<code>value</code> をコピーします。</p>
<p><img src="//images.ctfassets.net/d3b1looh8iwg/2hXnNWzpP2hIeCJbQlOQiA/e82ce5a9d300a817e77f259c13ba2c0e/get_token_v2.png" alt="notion-get-token-v2" loading="lazy"></p>
<h1 id="スクリプトの実行" tabindex="-1">スクリプトの実行</h1>
<p>次にPythonのスクリプトを実行していきたいのですが、現時点で提供されているコードでは、Notion側のAPIの形式に変更があったようで、実行してもエラーが出力されてしまいます。<br>
なので、エラーを解消したものを用意したのでそちらを実行していきます。</p>
<p>ファイルのDLはこちらから行ってください。</p>
<p><a href="https://github.com/yoshihiko555/notion-clear-trash" target="_blank" rel="noopener noreferrer">yoshihiko555/notion-clear-trash</a></p>
<p>DLが出来たら、CLIからnotion-clear-trash.pyを実行してください。</p>
<p>実行すると、<code>Please enter your auth token:</code> と出力されるので、先ほどコピーしたアクセストークンを張り付けてもらえれば、スクリプトが起動し、お使いのNotionのゴミ箱が一括で削除されます。</p>
]]></content:encoded>
            <category>Lifestyle</category>
            <enclosure url="https://images.ctfassets.net/d3b1looh8iwg/5binM4YxsZdSXX9SKkGyNP/cf39353211db8b3e0c4ac18e03b55527/notion.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>