<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>ミツエーリンクス X-tech Blog</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/" />
    <link rel="self" type="application/atom+xml" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/atom.xml" />
    <id>tag:www.mitsue.co.jp,2025-06-30:/knowledge/blog/x-tech/28</id>
    <updated>2026-09-10T12:06:21+09:00</updated>
    
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type</generator>

<entry>
    <title>New Relicのブラウザー監視でCSP違反を収集する</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202609/10_1134.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.3043</id>

    <published>2026-09-10T11:34:46+09:00</published>
    <updated>2026-09-10T12:06:21+09:00</updated>

    <summary>Content Security Policy（CSP）は、許可していないJav...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p>Content Security Policy（CSP）は、許可していないJavaScriptの実行やリソースの読み込みをブラウザー側で制限する仕組みです。Cross-Site Scripting（XSS）対策として導入するサイトが増えており、<a href="https://almanac.httparchive.org/ja/2025/security#%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC">2025 Web Almanacのセキュリティ章</a>によると21.9%のサイトで指定されているとのことです。</p>

<p>一方、CSPの設定不備やJavaScriptの記述不備などにより、本来は許可されるべきJavaScriptがCSP違反としてブロックされることがあります。その場合、サイトの機能が正常に動作しなくなります。早期にCSP違反を検出し、解消することが重要です。</p>

<p>この記事では、New Relicのブラウザー監視を導入済みのサイトにおいて、CSP違反を収集・分析する方法を紹介します。</p>
]]>
        <![CDATA[<h2>CSP違反の収集</h2>

<p>2026年4月に提供開始されたブラウザーエージェントv1.313.0以降を導入していれば、すでにCSP違反は収集されています（<a href="https://docs.newrelic.com/docs/release-notes/new-relic-browser-release-notes/browser-agent-release-notes/browser-agent-v1.313.0/">v1.313.0のリリースノート</a>）。</p>

<p>ブラウザーはCSP違反が発生すると、<code>securitypolicyviolation</code>イベントを発火します。New Relicのブラウザーエージェントは、このイベントを待ち受けて、内容をSecurityPolicyViolationイベントとして送信しています。そのため、CSP違反収集用のHTTPエンドポイントを用意せずに収集できます。</p>

<p>CSP違反として収集される固有の情報は、<code>securitypolicyviolation</code>イベントで得られる情報（<a href="https://developer.mozilla.org/en-US/docs/Web/API/SecurityPolicyViolationEvent"><code>SecurityPolicyViolationEvent</code></a>）とほぼ同じです。New Relicが収集する他のイベントと同様に、セッションやユーザーエージェントの情報なども収集されます。収集される情報の一覧は<a href="https://docs.newrelic.com/attribute-dictionary/?event=SecurityPolicyViolation">属性辞書</a>で確認できます。</p>

<h2>ダッシュボードの作成</h2>

<p>CSP違反として収集されたSecurityPolicyViolationイベントは、記事執筆時点では、New Relicの標準的なUIには表示されません。例えば、Errors Inboxには表示されません。</p>

<p>そのため、自分でダッシュボードを作成して内容を確認します。NRQLの例をいくつか示します。</p>

<h3>一覧表示</h3>

<p>SecurityPolicyViolationイベントを一覧表示します。</p>

<pre class="hljs-pre"><code>FROM SecurityPolicyViolation
SELECT *
SINCE 1 week ago</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260907_01.png" width="1853" height="348" alt=""></div>
</div>

<h3>違反した指令・ポリシーごとの違反件数</h3>

<p>違反した指令・ポリシーごとの違反件数を一覧表示します。違反が多い指令やポリシーを確認できます。</p>

<pre class="hljs-pre"><code>FROM SecurityPolicyViolation
SELECT count(*)
FACET effectiveDirective, originalPolicy
SINCE 1 week ago</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260907_02.png" width="545" height="197" alt=""></div>
</div>

<h3>ブロックされたURLごとの違反件数</h3>

<p>ブロックされたURLごとの違反件数を一覧表示します。ブロック回数の多いURLを確認できます。</p>

<pre class="hljs-pre"><code>FROM SecurityPolicyViolation
SELECT count(*)
FACET blockedUri
SINCE 1 week ago</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260907_03.png" width="544" height="276" alt=""></div>
</div>

<h3>違反発生元ごとの違反件数</h3>

<p>違反発生元のURL、行、列ごとの違反件数を一覧表示します。発生元として多いURLなどを確認できます。</p>

<pre class="hljs-pre"><code>FROM SecurityPolicyViolation
SELECT count(*)
FACET sourceFile, lineNumber, columnNumber
SINCE 1 week ago</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260907_04.png" width="683" height="310" alt=""></div>
</div>

<p>なお、SecurityPolicyViolationイベントではスタックトレースを取得できません。New RelicのブラウザーエージェントはブラウザーがCSP違反として報告する情報を収集していますが、ブラウザーはスタックトレースを報告しないためです。</p>

<p>例えば、違反発生元が<code>https://www.googletagmanager.com/gtm.js</code>の5行目、33列目であった場合、Google Tag Managerの<code>eval</code>ラッパー（<code>window.google_tag_manager.e</code>関数）で違反が発生していることがわかります。しかし、この関数を疑似的に書くと次のようになり、固有の処理は持っていません。</p>

<pre class="hljs-pre"><code>window['google_tag_manager'].e = function (s) { return eval(s); };</code></pre>

<p>CSP違反の調査では、window.google<em>tag</em>manager.eを呼び出している処理を特定する必要があります。しかし、New Relicが収集している情報（ブラウザーが提供している情報）からはわかりません。別途、ブラウザーの開発者ツールなどでの調査が必要になります。</p>

<p>なお、スタックトレースを取得できないという制約は、CSP違反をHTTPエンドポイントで受け取っている場合にも生じます。</p>

<h2>CSP違反発生セッション</h2>

<p>サイト側でCSPの設定を調整しても、違反件数をゼロにすることは難しいです。例えば、ブラウザー拡張機能が注入したスクリプトがブロックされた場合、サイト側での調整は困難です。</p>

<p>また、ブラウザー拡張機能が注入したスクリプトに起因する場合、ユーザーがページ遷移するたびに違反が発生する傾向があります。違反件数が増えていると思っても、単にユーザーが多くのページを閲覧しただけ、ということもあります。</p>

<p>そのため、違反件数ではなく、違反が起きたセッション数を確認することもあります。</p>

<pre class="hljs-pre"><code>FROM SecurityPolicyViolation
SELECT uniqueCount(session)
SINCE 1 week ago</code></pre>

<p>違反が起きたセッション数と、ページビューのセッション数の比率は、次のように確認できます。</p>

<pre class="hljs-pre"><code>FROM PageView, SecurityPolicyViolation
SELECT (
  filter(uniqueCount(session), WHERE eventType() = 'SecurityPolicyViolation')
  /
  filter(uniqueCount(session), WHERE eventType() = 'PageView')
) * 100 AS 'CSP違反セッション比'
SINCE 1 week ago
TIMESERIES</code></pre>

<h2>まとめ</h2>

<p>最近のNew Relicブラウザーエージェントは、追加の設定をしなくても、CSP違反を収集できます。</p>

<p>ダッシュボードを自分で用意する必要はありますが、New Relicがすでに収集している情報や、既存の機能と組み合わせて分析できます。</p>

<p>まずは、現状を簡単に把握するところから始めてみてはいかがでしょうか。</p>
]]>
    </content>
</entry>

<entry>
    <title>Figma Motionで作ったアニメーションを、Figma MCPで取得する</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202609/03_1730.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.3034</id>

    <published>2026-09-03T17:30:00+09:00</published>
    <updated>2026-09-03T17:30:58+09:00</updated>

    <summary>以前、私が書いた「AIフレンドリーなFigmaデータとは？」では、Figma M...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p>以前、私が書いた<a href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202601/27_1025.html">「AIフレンドリーなFigmaデータとは？」</a>では、Figma MCP Serverの<code>get_design_context</code>を通して、見た目だけでなく「どう作られているか」がコード品質に効くことを見てきました。
ただし<code>get_design_context</code>が返すのは、レイアウトやスタイルといった静的な構造です。アニメーションに関する情報は含まれません。</p>

<p>2026年6月24日、Figmaは<a href="https://www.figma.com/blog/introducing-figma-motion/">Figma Motion</a>を発表しました。Figmaのキャンバス上にタイムラインを追加し、アニメーションをデザインできる機能です。本稿執筆時点ではベータ版として提供されています。</p>

<p>そのアニメーションデータを取り出すのが、Figma MCP Serverの<code>get_motion_context</code>です。キーフレーム、イージング、タイミングに加え、CSS <code>@keyframes</code> や <a href="https://motion.dev/">Motion</a>向けのコード断片まで返ってくるため、デザイン上の動きを実装へ落としやすくなります。</p>

<p>本記事では、Figma Motionでアニメーションを作り、<code>get_motion_context</code>でデータを取得し、実装につながる流れを紹介します。</p>
]]>
        <![CDATA[<h2>Figma Motionでアニメーションを設定する</h2>

<p>今回はサンプルで作った架空のコーポレートサイトのヒーローバナーエリアに、Figma Motionで簡易アニメーションを設定してみました。</p>

<div class="c-media -fullWidth">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260824_01.gif" width="1080" height="840" alt=""></div>
</div>

<h2>Figma MCPでアニメーションデータを取得する</h2>

<p>次に、同じヒーローバナーエリアに対してFigma MCP Serverの2つのツールを実行しました。</p>

<ul>
<li><code>get_design_context</code>：レイアウトやスタイルなど、静的な構造を取得する</li>
<li><code>get_motion_context</code>：キーフレームやイージングなど、アニメーションに関する情報を取得する</li>
</ul>

<p>どちらも同じノードを指定しています。</p>

<h3>get_design_context で取得できるもの</h3>

<p><code>get_design_context</code>では、React + Tailwind形式のコードが返ってきます。見出しやボタン、右側のビジュアルなど、ヒーローバナーエリアの構造がそのままコード化されます。</p>

<p>Figma Motionでアニメーションを設定した要素は、<code>motion.div</code> として出力されます。ただし中身のアニメーション値は含まれません。</p>

<p>今回取得したコードの見出し部分を抜粋すると、次のようになります。<code>motion.div</code> でラップされているものの、<code>initial</code> や <code>animate</code> などのプロパティは付いていません。</p>

<pre class="hljs-pre"><code>&lt;motion.div
  className="..."
  data-node-id="2:477"
&gt;
  &lt;p className="leading-[84px] mb-0"&gt;変化を、&lt;/p&gt;
  &lt;p className="leading-[84px]"&gt;次の標準へ。&lt;/p&gt;
&lt;/motion.div&gt;</code></pre>

<p><code>get_design_context</code>で取得したレスポンスの注意書きにも、次のように書かれています。</p>

<blockquote>
  <p>IMPORTANT: This design contains animated nodes. You MUST get the animation data via get_motion_context with the same node ID and file key.</p>
</blockquote>

<p>つまり、静的な構造は <code>get_design_context</code>、アニメーションに関する情報は <code>get_motion_context</code> と、役割が分かれています。</p>

<h3>get_motion_context で取得できるもの</h3>

<p><code>get_motion_context</code>（<code>recursive: true</code>）を実行すると、アニメーション対象のノード一覧と、ノードごとのコード断片が返ってきます。</p>

<p>今回のヒーローバナーでは10個の要素がアニメーション対象で、2.5秒のループ（<code>durationMs: 2500</code>, <code>loopMode: "loop"</code>）としてまとまっていました。</p>

<p>各ノードには <code>nodeId</code> と <code>nodeName</code> に加え、<code>codeSnippets</code> としてMotion向けのコードと CSS <code>@keyframes</code> の両方が含まれます。10要素すべてにコードが返ってきますが、ここでは2つのMotion向けコードを紹介します。</p>

<h4>見出しのフェードイン</h4>

<p>見出しには、下方向からフェードインするMotion向けのコードが返ってきました。<code>times</code> の値を見ると、他の要素より少し遅れて登場するようタイミングがずらされています。</p>

<pre class="hljs-pre"><code>&lt;motion.div
  initial={{ opacity: 0, y: 28 }}
  animate={{ opacity: [0, 0, 1, 1], y: [28, 28, 0, 0] }}
  transition={{
    opacity: {
      duration: 2.5,
      times: [0, 0.048, 0.288, 1],
      ease: ["linear", [0.16, 1, 0.3, 1], "linear"],
      repeat: Infinity,
    },
    y: {
      duration: 2.5,
      times: [0, 0.048, 0.288, 1],
      ease: ["linear", [0.16, 1, 0.3, 1], "linear"],
      repeat: Infinity,
    },
  }}
/&gt;</code></pre>

<h4>中央の円の回転・スケール</h4>

<p>ヒーローバナー右側の円形オブジェクトの中央にある黒い円には、透明度・回転・スケールを同時にアニメーションするコードが返ってきました。</p>

<pre class="hljs-pre"><code>&lt;motion.div
  initial={{ opacity: 0, rotate: 30, scaleX: 0.8, scaleY: 0.8 }}
  animate={{
    opacity: [0, 0, 1, 1],
    rotate: [30, 30, 0, 0],
    scaleX: [0.8, 0.8, 1, 1],
    scaleY: [0.8, 0.8, 1, 1],
  }}
  transition={{
    opacity: { duration: 2.5, times: [0, 0.06, 0.34, 1], ease: ["linear", [0.16, 1, 0.3, 1], "linear"], repeat: Infinity },
    rotate: { duration: 2.5, times: [0, 0.06, 0.86, 1], ease: ["linear", [0.16, 1, 0.3, 1], "linear"], repeat: Infinity },
    scaleX: { duration: 2.5, times: [0, 0.06, 0.34, 1], ease: ["linear", [0.16, 1, 0.3, 1], "linear"], repeat: Infinity },
    scaleY: { duration: 2.5, times: [0, 0.06, 0.34, 1], ease: ["linear", [0.16, 1, 0.3, 1], "linear"], repeat: Infinity },
  }}
/&gt;</code></pre>

<h2>アノテーションでは伝わらなかったことが、データとして取れる</h2>

<p>Figma Motionがない場合は、アニメーションの意図をアノテーションとして、テキストベースで書くしかありませんでした。「0.3秒でフェードイン」「下から少しスライド」といった説明を、実装者が読み取って解釈し、コードに落とし込む必要がありました。</p>

<p>Figma Motionでアニメーションを設定し、<code>get_motion_context</code>で取得すると、duration や easing、キーフレームの値まで具体的なデータとして渡せます。見出しが何秒後に登場するか、円が何度分回転するかといった内容も、感覚的な伝え方ではなく数値として伝わるようになりました。</p>

<h2>まとめ</h2>

<p><a href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202601/27_1025.html">「AIフレンドリーなFigmaデータとは？」</a>で触れた<code>get_design_context</code>は、AIにレイアウトやスタイルといった静的なデザイン構造を渡す入口になってきました。Figma Motionと<code>get_motion_context</code>の組み合わせは、動的な情報の詳細までAIに渡せるようになりました。</p>

<p>アノテーションを使うことと比べると、タイミング・イージング・キーフレームが数値やコードとして取得できるため、AIが動きを推測せずに実装へ反映しやすくなります。</p>

<p>Figma Motionでアニメーションをデザインすることで、アニメーションの情報を数値化された情報としてAIに渡すことができ、より効率的なAI駆動開発につながります。</p>
]]>
    </content>
</entry>

<entry>
    <title>「制作会社のためのCMS選定サミット」に明日登壇するので、そこで話したいかもしれない内容を</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202608/27_0936.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.3024</id>

    <published>2026-08-27T09:36:00+09:00</published>
    <updated>2026-08-27T10:53:39+09:00</updated>

    <summary>明日8月28日（金）、Web Designing主催のこのイベントで、株式会社G...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p>明日8月28日（金）、Web Designing主催のこのイベントで、株式会社GIG 代表取締役の岩上貴洋氏とトークセッションに登壇します。テーマは「CMSを求められる現場で、本当のところ何を選べばいいのか」です。対談なのでどう進むかはわかりませんが、今日はそこで話したいかも、と思っている内容を少し書いていきたいと思います。</p>

<p>WordPressやDrupalといった従来型から、SaaS型、Headless CMSまで、選択肢は多々あります。ただ最近感じているのは、選択肢が増えたこと以上に、問いの立て方のほうが古びてきたのではないか、ということです。「どのCMSを選ぶべきか」だけで、本当に足りるのでしょうか。</p>

<h2>今までの機能比較表は書き換えないといけないかもしれない</h2>

<p>機能の有無、編集しやすさ、コスト、カスタマイズ性、サポート体制、どれも不要になったわけではありません。ただ、比較表に並ぶのはAI以前のCMSに求められていた機能や性質であって、その思考のバージョンを上げないままでいることのほうが今はリスクなのではないでしょうか。</p>

<p>わかりやすいのが、脆弱性への対応です。</p>

<p>かつては、深刻な脆弱性が公表されても、情報を確認し、検証環境で試し、次のメンテナンスウィンドウで適用する、という段取りが成り立っていました。今は、公表から実際にスキャンや攻撃が始まるまでの時間が明らかに短くなっています。公開されたパッチの差分から攻撃手順を組み立てる作業自体が自動化され、そこにAIの支援も加わりました。ゼロデイはもちろん、パッチが出たあとのゼロ・アワーの数時間がそのまま危険な時間帯になりえる状況です。</p>

<p>そうなると、「脆弱性が出たらすぐ当てる」という運用の速さだけでは追いつきません。効いてくるのは、当てるまでの間にどれだけ晒されているか、そもそも晒す必要があったのか、のほうです。管理画面をインターネットに置かず、公開側にアプリケーションの実行環境を残さない、そして、更新の責任をサービス提供者に持たせるといった攻撃面を減らす構成そのものが対策になります。</p>

<p>私自身、CMSの脆弱性が出るたびに複数のクライアントサイトへの対応を展開する側にいますが、その重さはここ1、2年で確実に増えました。</p>

<p>このように単純なCMS製品選定というより、そのWebサイトをどんなアーキテクチャで運用していくのか？という設計判断がより重要であると捉えています。</p>

<h2>AIは、CMSを不要にするのか</h2>

<p>ここまでは、増え続ける運用の重さを誰がどう引き受けるか、という話でした。ところが最近は、AIの登場でその前提の置き方そのものを考え直さないといけない状況になってきています。</p>

<p>制作と運用の両方にAIが入り込むことで、これから数年のうちにCMSが置かれる状況は大きく変わると私は考えています。</p>

<p>「この製品の導入事例を公開して、英語版も作って、公開前に表記ルールをチェックして」。そう指示すると、AI AgentがCMSや周辺システムを操作し、変更を確認した上でWebサイトへ反映する、つまり、管理画面を人が操作する世界から意図を伝える世界へと変貌します。</p>

<p>ただ、意図で動く相手ほど、動いてよい範囲が決まっていないと任せられません。どのフィールドを触ってよく、何が必須で、誰の承認を経て公開されるのか、といった構造とワークフローとして明示されたルールが必要となります。そして、それを制御しているのがCMSです。</p>

<p>AIに任せる範囲が広がるほど、ガバナンスの器としてのコンテンツ管理機能の価値が上がると思います。</p>

<h2>読み手も、人間だけではなくなる</h2>

<p>これからはAIやAI Agentも企業のWebサイトを訪れ、製品情報、FAQ、事例を読み取って利用します。これはSEOの延長線上にありつつも新たな側面がある話です。人間はレイアウトや前後の文脈で意味を補えますが、より短い時間で探索を行う機械は補わないことも多いです。そのため、より機械にフレンドリーな構造化されたデータと明示された関係性を読み取る傾向にあります。製品と仕様、事例と製品がデータとして結びついているか？を明示してあげることで、よりAIに引用されやすくなる可能性があります。</p>

<h2>明日の対談</h2>

<p>さて、CMSやAIのことを書き始めると終わりがないのですが、ここらで明日のイベントに戻りましょう。</p>

<p>当日は、CMS選定の実際、SaaS型、Headless CMS、セキュリティ、AI、公開後の運用まで、かなり率直に話す予定です。「結局、どのCMSを選べばいいのか」から始めて、最後は「そもそも、これからCMSは何のために存在するのか」まで行ければ、と思っています。皆様お忙しいとは思いますが、お時間のある方はぜひご参加ください。明日、銀座でお会いできるのを楽しみにしております。</p>

<h3>開催概要</h3>

<ul>
<li><strong>イベント名</strong>：制作会社のためのCMS選定サミット</li>
<li><strong>日時</strong>：2026年8月28日（金）16:00〜19:40（トークセッションは18:20〜／19:00より懇親会）</li>
<li><strong>会場</strong>：マイナビPLACE（東京都中央区銀座・歌舞伎座タワー23F）</li>
<li><strong>参加費</strong>：無料（事前申込制・先着100名）</li>
<li><strong>主催</strong>：株式会社マイナビ出版 Web Designing</li>
<li><strong>申込</strong>：<a href="https://webdesigning.book.mynavi.jp/cms-summit-august2026/">イベント特設ページ</a></li>
</ul>
]]>
        
    </content>
</entry>

<entry>
    <title>CLIPを使ってローカル画像の自然言語検索を低コストで実現する</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202608/13_1705.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.3010</id>

    <published>2026-08-13T17:05:00+09:00</published>
    <updated>2026-08-13T19:01:32+09:00</updated>

    <summary>通常、ローカルにある画像素材を探す際は、ファイル名やフォルダ名、画像に付けられた...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p>通常、ローカルにある画像素材を探す際は、ファイル名やフォルダ名、画像に付けられたタグなどを手掛かりにすることが多いと思います。しかし、あらかじめ丁寧に情報を整理しておかないと、検索が難しくなってしまうことがあります。</p>

<p>以前の記事で、<a href="/knowledge/blog/x-tech/202605/28_0953.html">過去に作成したコードを自然言語で検索する方法</a>を紹介しましたが、このような自然言語検索は、テキストだけでなく画像にも応用できます。</p>

<p>今回は、画像と文章の意味的な近さを比較できる<a href="https://openai.com/ja-JP/index/clip/">CLIP</a>というモデルを使い、ローカルフォルダ内の画像を自然言語で検索するPythonツールを作成します。RAGやLLM、外部APIは使用せず、学習済みモデルをCPU環境でローカル実行するシンプルな構成で画像検索を試します。</p>
]]>
        <![CDATA[<h2>CLIPとは</h2>

<p>CLIPは、画像と文章をそれぞれベクトルに変換し、両者の意味的な近さを比較できるモデルです。画像と文章の内容が近い場合、それぞれから生成されたベクトルも近くなります。</p>

<p>機械学習を用いた画像検索というと、カテゴリごとに教師画像を用意したり、モデルを学習させたりといった事前準備が大変なイメージがありますが、CLIPでは学習済みモデルに任意の検索文を与えられます。検索対象となる画像をあらかじめベクトル化しておき、検索文のベクトルとの類似度を計算すれば、文章の意味に近い画像を順番に取得できます。</p>

<p>今回は、OpenAIが公開したCLIPの代表的なモデルの一つである<a href="https://huggingface.co/openai/clip-vit-base-patch32"><code>openai/clip-vit-base-patch32</code></a>を使用しました。実装例や情報が多く、<a href="https://huggingface.co/docs/transformers/ja/index">Hugging Face Transformers</a>から簡単に利用できます。また、比較的軽量で、CPU環境でも試しやすいというメリットがあります。</p>

<p>ただし、このモデルは日本語向けに学習されたものではありません。そのため、今回の検索文はすべて英語としました。</p>

<h2>今回作るもの</h2>

<p>今回は、<a href="https://www.pexels.com/ja-jp/">Pexels</a>からダウンロードした100枚の画像を検索対象としました。画像には、オフィス、会議、工場、倉庫、IT機器、自然、風力発電、都市、建築、人物、料理といった内容が含まれています。</p>

<p>これらの画像を検索できるようにするために、ローカルフォルダ内の画像をベクトル化してファイルに保存するプログラム<code>encode_images.py</code>と、入力された検索文をベクトル化して画像ベクトルとの類似度が高い画像名を表示するプログラム<code>search_images.py</code>を作成します。また、今回のコードはCPU実行を想定しています。</p>

<h2>実装</h2>

<p>フォルダ構成は次のようになっています。</p>

<pre><code>clip-image-search/
├─ images/
├─ src/
│  ├─ encode_images.py
│  └─ search_images.py
└─ data/
   ├─ image_features.pt
   └─ image_names.txt</code></pre>

<h3>encode_images.py</h3>

<p><code>encode_images.py</code>では、<code>images</code>フォルダにある画像（今回はJPEG）すべてを参照して意味ベクトルを作成して<code>image_features.pt</code>として保存します。</p>

<pre class="hljs-pre"><code class="python"># encode_images.py

from pathlib import Path

import torch
from PIL import Image
from transformers import CLIPModel, CLIPProcessor


MODEL_NAME = "openai/clip-vit-base-patch32"
PROJECT_DIR = Path(__file__).resolve().parents[1]
IMAGES_DIR = PROJECT_DIR / "images"
FEATURES_PATH = PROJECT_DIR / "data" / "image_features.pt"
NAMES_PATH = PROJECT_DIR / "data" / "image_names.txt"


image_paths = sorted(IMAGES_DIR.glob("*.jpg"))
images = [Image.open(path).convert("RGB") for path in image_paths]

model = CLIPModel.from_pretrained(MODEL_NAME)  # CLIPモデル読み込み
processor = CLIPProcessor.from_pretrained(MODEL_NAME)  # CLIP用の前処理読み込み
model.eval()  # モデルを推論用に切り替え

inputs = processor(images=images, return_tensors="pt")  # 画像をCLIPが読み取れる形式に変換
with torch.inference_mode():
    features = model.get_image_features(**inputs)  # 画像を意味ベクトルに変換
    features = features / features.norm(dim=1, keepdim=True)  # ベクトルを正規化

FEATURES_PATH.parent.mkdir(exist_ok=True)
torch.save(features, FEATURES_PATH)  # 画像の意味ベクトルを保存
NAMES_PATH.write_text(  # 画像ファイル名一覧を保存
    "\n".join(path.name for path in image_paths),
    encoding="utf-8",
)
print(f"{len(image_paths)}枚の画像ベクトルを保存しました。")</code></pre>

<h3>search_images.py</h3>

<p><code>search_images.py</code>では、ユーザーが入力した検索文から意味ベクトルを作成し、保存済みの各画像の意味ベクトルとの類似度を計算することで検索文にマッチする画像を見つけます。</p>

<pre class="hljs-pre"><code class="python"># search_images.py
# encode_images.pyと共通するimport文および定数は省略

image_features = torch.load(FEATURES_PATH, weights_only=True)  # 画像の意味ベクトルを読み込み
image_names = NAMES_PATH.read_text(encoding="utf-8").splitlines()  # 画像名一覧読み込み

model = CLIPModel.from_pretrained(MODEL_NAME)
processor = CLIPProcessor.from_pretrained(MODEL_NAME)
model.eval()

query = input("検索文（英語）: ")

inputs = processor(text=[query], return_tensors="pt")  # 検索文をCLIPが読み取れる形式に変換
with torch.inference_mode():
    text_features = model.get_text_features(**inputs)  # 検索文を意味ベクトルに変換
    text_features = text_features / text_features.norm(dim=1, keepdim=True)

similarities = image_features @ text_features.T  # 類似度（正規化済みベクトルの内積）を計算
top_indices = similarities[:, 0].argsort(descending=True)[:5] # 上位5件を取得

for rank, index in enumerate(top_indices, start=1):
    print(f"{rank}. {similarities[index, 0]:.3f}  {image_names[index]}")  # 上位の類似度と画像名を表示</code></pre>

<h2>実行</h2>

<p>実行に必要なライブラリをインストールします。</p>

<pre class="hljs-pre"><code class="bash">pip install torch transformers pillow</code></pre>

<p><code>encode_images.py</code>を実行して画像の意味ベクトルを作成します。初回実行ではモデルをダウンロードするため時間がかかります。また、一度実行した後は対象画像に変更がない限り再実行は不要です。</p>

<pre class="hljs-pre"><code class="bash">python src/encode_images.py</code></pre>

<p><code>search_images.py</code>を実行して検索を実行できます。検索文を入力すると、類似度の高い順に5件の画像名が出力されます。</p>

<pre class="hljs-pre"><code class="bash">python src/search_images.py
検索文（英語）: people having a business meeting</code></pre>

<h2>実行結果</h2>

<p>検索結果のうち、特徴的だったものをいくつか紹介します。</p>

<h3>人物とシチュエーション</h3>

<p>検索文：</p>

<pre><code>people having a business meeting（仕事の打ち合わせをする人たち）</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260812_01.jpg" width="614" height="345" alt=""></div>
</div>

<p>複数の人物がテーブルを囲み、打ち合わせをしている画像が最もマッチする画像として選ばれました。単に人物が複数写っている画像ではなく、オフィス内で話し合っている様子の画像が選ばれており、「people」「business」「meeting」という検索文の意味をおおむね捉えられているように見えます。</p>

<p>検索文：</p>

<pre><code>a warehouse worker using a tablet（タブレットを使う倉庫作業員）</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260812_02.jpg" width="520" height="347" alt=""></div>
</div>

<p>倉庫内でタブレット端末を持って歩いている人物の画像がマッチしました。「warehouse」「worker」「tablet」という検索対象には条件によく合う画像が含まれていたため、かなり意図に近い結果となりました。</p>

<h3>抽象的な検索</h3>

<p>検索文：</p>

<pre><code>a sustainable future（持続可能な未来）</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260812_03.jpg" width="455" height="612" alt=""></div>
</div>

<p>風力発電の風車の画像がマッチしました。「持続可能な未来」は、人物やタブレットのように明確な形を持つ物体ではありません。そのため「正解した」と断定することはできないのですが、「検索文から連想される画像（再生可能エネルギーである風力発電）が上位に表示された」と評価できそうです。</p>

<h3>複雑な視覚的判断</h3>

<p>しかし一方で、検索条件が複雑になると思った通りに検索できないこともありました。1つ目に紹介した<code>people having a business meeting</code>の画像では4人がミーティングをしていますが、他にも今回用意した画像の中には2～3人でミーティングをしている画像が複数あります。試しに<code>two people having a meeting</code>や<code>three people having a meeting</code>という文言で検索してみましたが、<code>people having a business meeting</code>と同じ画像が選ばれました。</p>

<p>また、この4人が会議している画像は次のように表現することもできそうです。</p>

<pre><code>a man standing apart from people seated at a table（テーブルに着席した人たちから離れて立つ男性）</code></pre>

<p>しかしこの文で検索したところ、次の画像がマッチしました。写っているのは1人だけで、検索にマッチする画像とは言えません。</p>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260812_04.jpg" width="369" height="492" alt=""></div>
</div>

<p>今回実行した結果では、人数を指定した検索では、会議というシチュエーションが強く反映され、人数の違いは結果に反映されませんでした。また、複数の人物・物体・行動を含む検索文について、すべての条件を満たす画像が上位に表示されるとは限りませんでした。<a href="https://openai.com/ja-JP/index/clip/">OpenAIの公式情報</a>にも、CLIPは物体数を数えるような体系的な課題や、距離を判断するような複雑な視覚課題を苦手とすることが示されています。</p>

<p>画像や環境によっても結果は異なるため、実務に用いる際はさらに多くの検証や工夫が必要ですが、これらの結果を踏まえると簡単な検索文なら役立つ場面は多いと言えます。</p>

<p>今回はOpenAI版CLIPを使用しましたが、使えるマシンスペックや必要とする性能に応じて<a href="https://github.com/mlfoundations/open_clip/">OpenCLIP</a>、<a href="https://huggingface.co/docs/transformers/v5.14.0/en/model_doc/siglip2">SigLIP2</a>、<a href="https://github.com/apple/ml-mobileclip/">MobileCLIP</a>などの後継・派生モデルも試してみると良いかもしれません。また、今回は総当たり検索を行いましたが、画像数が多く検索時間が問題になる場合は、<a href="https://faiss.ai/">Faiss</a>などのベクトル類似検索ライブラリや、ベクトルデータベースの導入を検討しても良いでしょう。</p>

<h2>まとめ</h2>

<p>今回は、CLIPを使い、ローカルフォルダに保存した画像を自然言語で検索するツールを作成しました。画像と検索文をベクトル化し、類似度を計算することで、検索文の意味に近い画像を取得できます。</p>

<p>また、CLIPによる検索はローカル環境で処理できるため、セキュリティ面（画像を外部APIへ送信しない）、コスト面（API利用等の従量課金がない）のメリットがあります。</p>

<p>もちろんCLIPには先述した限界があり、既存の画像管理をすべて置き換えるものではありませんが、ファイル名やタグだけでは探しにくい画像を見つけるための補助としては十分に有用だと考えられます。例えば社内画像素材をベクトル化しておけば、企業サイトなどで使用したい画像を<code>employees working in an office</code>といった文で検索できます。RAGやLLMを導入せず簡単にできる自然言語画像検索として、試してみてはいかがでしょうか。</p>
]]>
    </content>
</entry>

<entry>
    <title>CursorのAuto-reviewとは？承認を減らしながら安全にエージェントを動かす仕組み</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202608/04_1030.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.3002</id>

    <published>2026-08-04T10:30:00+09:00</published>
    <updated>2026-08-04T10:36:30+09:00</updated>

    <summary>Cursor 3.6では、新しい実行モード「Auto-review」が追加されま...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p><a href="https://cursor.com/ja/docs/agent/security/run-modes#auto-review">Cursor 3.6では、新しい実行モード「Auto-review」が追加されました</a>。</p>

<p>コーディングエージェントにシェルコマンドを実行させる場合、安全性を重視して操作のたびに承認を求めると、何度も確認ダイアログに対応しなければなりません。一方、すべての操作を自動承認すると、意図しないファイル変更や外部システムへのアクセスといったリスクが高まります。</p>

<p>Auto-reviewは、この「安全性」と「エージェントの自律性」のバランスを取るために追加されたモードです。</p>

<p>本記事では、Auto-reviewの判断フローと設定方法、サンドボックス環境を利用する時に判明した実務上の注意点を紹介します。</p>
]]>
        <![CDATA[<h2>Auto-reviewの仕組み</h2>

<p>大まかな流れは次のとおりです。</p>

<ol start='1'>
<li>Allowlistに一致する操作は、そのまま実行する</li>
<li>サンドボックスで実行可能な操作は、制限された環境内で実行する</li>
<li>それ以外の操作は、内蔵されているClassifierという分類器が内容を確認する</li>
</ol>

<p>Classifierは、実行しようとしている操作だけでなく、ユーザーから依頼された内容や現在の文脈も参照します。そのうえで、操作を許可するか、別の安全な方法を試すようエージェントへ返すか、ユーザーに承認を求めるかを判断する、という部分が特徴的になっています。</p>

<h2>Classifierの判断は設定で調整できる</h2>

<p>Auto-reviewのClassifierには、<code>permissions.json</code>を使って追加の指示を自然言語で与えられます。</p>

<pre class="hljs-pre"><code class="json">{
  "autoRun": {
    "allow_instructions": [
      "プロジェクト内を対象とした読み取り専用の操作は許可する"
    ],
    "block_instructions": [
      "ファイルを削除する操作では必ずユーザーの確認を求める",
      "プロジェクト外のファイルを変更する操作は許可しない"
    ]
  }
}</code></pre>

<p><code>allow_instructions</code>には許可寄りに判断してほしい操作を、<code>block_instructions</code>にはレビューのために<a href="https://cursor.com/ja/changelog/sdk-updates-jun-2026">保留してほしい操作を自然言語で記述します</a>。</p>

<p>ただし、ClassifierはLLMによる判断であり、厳密なセキュリティ境界ではないことに注意が必要です。重要な操作を確実に制限したい場合は、Allowlistを設定するなど、Classifierへの指示だけに依存するのは避けたほうがよいかもしれません。</p>

<h2>サンドボックスとビルドキャッシュ</h2>

<p>サンドボックスは、エージェントがアクセスできるファイルやネットワークが制限された実行環境です。意図しない変更の影響を抑えられる一方、通常のシェル環境とは挙動が異なることがあります。</p>

<p>その一例がビルドキャッシュです。</p>

<p>サンドボックスから通常のキャッシュディレクトリへアクセスできないと、<code>npm</code>、<code>Cargo</code>、<code>pip</code>などが毎回依存関係やビルド成果物を作り直し、処理が遅くなる可能性があります。</p>

<p>Cursorの<code>~/.cursor/sandbox.json</code>では、共有ビルドキャッシュを有効にできます。</p>

<pre class="hljs-pre"><code class="json">
{
  "enableSharedBuildCache": true
}</code></pre>

<p>Windows環境で<code>uv</code>とPlaywrightを組み合わせたスクリプトをエージェントに実行させる場合は、次のような設定が考えられます。</p>

<pre class="hljs-pre"><code class="json">{
  "enableSharedBuildCache": true,
  "additionalReadwritePaths": [
    "~/AppData/Local/uv",
    "~/AppData/Local/ms-playwright"
  ]
}</code></pre>

<p><code>additionalReadwritePaths</code>へ追加したディレクトリはサンドボックス内から読み書きできるようになります。便利な設定ですが、アクセス可能な範囲が広がるため、<a href="https://cursor.com/ja/docs/reference/sandbox">sandbox.json のリファレンス</a>に従って必要なディレクトリだけを指定した方がよいでしょう。</p>

<h2>Windowsにおけるサンドボックスの動向</h2>

<p><a href="https://cursor.com/ja/blog/agent-sandboxing">Cursorのサンドボックス実装解説</a>によると、Windowsに既存のサンドボックス機構はあるものの、シェル、Git、Python、パッケージマネージャーなどを幅広く利用するコーディングエージェント向けの制御を、ネイティブ機能だけで実現するのは難しいとのことです。</p>

<p>サンドボックスを利用してエージェントの自律実行範囲を制御する取り組みは、Cursor以外のコーディングエージェントにも広がっています。OpenAIもCodex向けに独自のWindowsサンドボックスを構築したことを先日発表しており、その詳細は<a href="https://openai.com/index/building-codex-windows-sandbox/">OpenAIによるWindowsサンドボックスの解説</a>で紹介されています。</p>

<p>さらにMicrosoft自身も2026年6月、AIエージェント向けの実行基盤「Microsoft Execution Containers（MXC）」を<a href="https://blogs.windows.com/windowsdeveloper/2026/06/02/windows-platform-security-for-ai-agents/">早期プレビューとして発表</a>しました。MXCは、エージェントがアクセスできるファイルやネットワークなどをポリシーで定義し、WindowsまたはWSL上でその制約を適用する仕組みです。</p>

<p>こうした動きから、サンドボックスによって安全な実行範囲を設ける考え方は、今後のコーディングエージェントに共通するトレンドになっていくのかもしれません。</p>

<h2>まとめ</h2>

<p>今回のAuto-reviewは、単なる便利機能ではなく、安全性と自律性のバランスを取るための試みとして興味深いものでした。今後も各社から同様の仕組みが登場すると考えられます。</p>

<p>一方で、私たちもその仕組みを利用するだけでなく、どのような前提で安全性が担保されているのかを理解していきたいと思います。</p>
]]>
    </content>
</entry>

<entry>
    <title>Cloudflare Workersの環境差をTypeScriptの型で守る</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202607/30_1014.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2998</id>

    <published>2026-07-30T10:14:00+09:00</published>
    <updated>2026-07-30T11:35:51+09:00</updated>

    <summary>はじめに Cloudflare Workersでは、単にコードを実行するだけでは...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<h2>はじめに</h2>

<p>Cloudflare Workersでは、単にコードを実行するだけではなく、データベース（Workers KVやD1）やオブジェクトストレージ（R2）などのサービスをバインドして（紐づけて）、複雑な機能を実現できます。また、環境変数を参照して処理を切り替えることもできます。</p>

<p>一般に、バインディングや環境変数は、本番やステージングなどの環境によって異なります。存在しないバインディングや環境変数を呼び出そうとするとエラーになるため、環境の混同が起きにくいコードを記述することが重要です。</p>

<p>この記事では、TypeScriptの型を使って、環境を混同しにくいコードを記述する方法を紹介します。</p>
]]>
        <![CDATA[<h2>環境ごとの設定管理</h2>

<p>型の話に入るまえに、まずは環境ごとの設定管理方法を確認します。</p>

<p>Cloudflare Workersでは、wranglerコマンドの設定ファイル（wrangler.jsoncなど）に<code>env</code>オブジェクトを記述することで、環境ごとの設定を管理できます（<a href="https://developers.cloudflare.com/workers/wrangler/environments/">Cloudflareの開発者向けドキュメント</a>）。</p>

<p>次の例ではstaging環境に対してWorkers KVがバインドされていますが、main環境にはバインドされていません。また、環境変数<code>WORKER_ENV</code>は環境ごとに値が異なります。</p>

<pre class="hljs-pre"><code class="json">{
  "$schema": "node_modules/wrangler/config-schema.json",
  "account_id": "...",
  "name": "main",
  "main": "_worker/index.ts",
  "vars": {
    "WORKER_ENV": "main"
  },
  "env": {
    "staging": {
      "name": "staging",
      "vars": {
        "WORKER_ENV": "staging"
      },
      "kv_namespaces": [
        {
          "binding": "KV_DEBUG",
          "id": "..."
        }
      ]
    }
  }
}</code></pre>

<p>wranglerコマンドに<code>--env</code>を指定することで、どの環境の設定を参照させるかを制御できます。</p>

<pre class="hljs-pre"><code class="bash"># main環境へのデプロイ
wrangler deploy

# staging環境へのデプロイ
wrangler deploy --env staging</code></pre>

<p>このように環境ごとに使える機能が異なると、条件分岐を間違えた時に実行時エラーになります。次は、この違いをTypeScriptの型として表現し、型チェック時に検知する方法を見ていきます。</p>

<h2>環境とTypeScriptの型</h2>

<p>環境ごとの違いをコード側から見ていきます。次のように<code>KV_DEBUG</code>を無条件に呼ぶと、main環境では実行時エラーになります。</p>

<pre class="hljs-pre"><code class="typscript">export default {
  async fetch(request, env, ctx): Promise&lt;Response&gt; {
    await env.KV_DEBUG.put('DEBUG_KEY', 'DEBUG_VALUE');
    return new Response('ok');
  }
} satisfies ExportedHandler&lt;Env&gt;;</code></pre>

<p>staging環境かどうかで分岐したいところですが、そのまえに<code>wrangler types</code>で生成される型を確認します。</p>

<pre class="hljs-pre"><code class="bash">npx wrangler types</code></pre>

<p>wranglerの設定ファイルで<code>env</code>オブジェクトに環境を記述している場合、<code>worker-configuration.d.ts</code>には次のような型が生成されます（wrangler 4.114.0の例）。</p>

<pre class="hljs-pre"><code class="typescript">interface __BaseEnv_Env {
    KV_DEBUG?: KVNamespace;
    WORKER_ENV: "staging" | "main";
}
declare namespace Cloudflare {
    interface GlobalProps {
        mainModule: typeof import("./_worker/index");
    }
    interface StagingEnv {
        KV_DEBUG: KVNamespace;
        WORKER_ENV: "staging";
    }
    interface Env extends __BaseEnv_Env {}
}
interface Env extends __BaseEnv_Env {}</code></pre>

<p>Cloudflare Workersのコードで通常参照する<code>Env</code>型は、<code>__BaseEnv_Env</code>型を継承しています。<code>__BaseEnv_Env</code>型は、全環境に存在するもの（<code>WORKER_ENV</code>）は非optionalな型として定義され、一部の環境にしか存在しないもの（<code>KV_DEBUG</code>）はoptionalな型として定義されます。また、環境変数<code>WORKER_ENV</code>は環境によって値が異なるため、ユニオン（<code>"staging" | "main"</code>）として定義されています。</p>

<p>一方で、<code>env</code>オブジェクトで定義した環境ごとに専用の型が生成されます。先ほどの例ではstaging環境を定義しましたので、<code>Cloudflare.StagingEnv</code>が定義されています。環境専用の型ですので、全てのプロパティは非optionalな型として定義されています。また、環境変数<code>WORKER_ENV</code>も純粋なリテラル（<code>"staging"</code>）として定義されています。</p>

<p><code>fetch</code>関数に渡されるのは集約された<code>Env</code>型ですので、<code>KV_DEBUG</code>はoptionalです。また、環境ごとの型がユニオンになる（例：<code>type Env = MainEnv | StagingEnv</code>）のではなく、<code>WORKER_ENV</code>などのプロパティ値がユニオンになっている（<code>WORKER_ENV: "staging" | "main"</code>）ため、条件分岐でプロパティ値をチェックしても型は絞り込めません。</p>

<pre class="hljs-pre"><code class="typescript">export default {
  async fetch(request, env, ctx): Promise&lt;Response&gt; {
    if (env.WORKER_ENV === 'staging') {
      await env.KV_DEBUG.put('DEBUG_KEY', 'DEBUG_VALUE');
    }
    return new Response('ok');
  }
} satisfies ExportedHandler&lt;Env&gt;;</code></pre>

<p>上記の例では<code>env.KV_DEBUG</code>はoptionalなままです（<code>undefined</code>の可能性があるため型チェックが通りません）。</p>

<p>そこで、環境専用の型へ絞り込む型ガードを用意します。</p>

<pre class="hljs-pre"><code class="typescript">const isStagingEnv = (env: Env): env is Cloudflare.StagingEnv => {
  return env.WORKER_ENV === 'staging';
};</code></pre>

<p>上記の型ガードで条件分岐すれば、<code>env</code>の型は<code>Cloudflare.StagingEnv</code>に絞り込まれ、<code>env.KV_DEBUG</code>は非optionalになります。</p>

<pre class="hljs-pre"><code class="typescript">export default {
  async fetch(request, env, ctx): Promise&lt;Response&gt; {
    if (isStagingEnv(env)) {
      await env.KV_DEBUG.put('DEBUG_KEY', 'DEBUG_VALUE');
    }
    return new Response('ok');
  }
} satisfies ExportedHandler&lt;Env&gt;;</code></pre>

<p>また、型ガードを通さず<code>env.KV_DEBUG</code>を直接呼ぶコードが残っていても、optionalのままなのでTypeScriptの型チェックで弾けます。</p>

<h2>制限事項</h2>

<p>この記事の例では型ガードに環境変数<code>WORKER_ENV</code>の値を利用しています。複数の環境で<code>WORKER_ENV</code>が同じ値を持つと、型の絞り込み結果に意味がなくなります。型ガードで利用する環境変数は環境ごとに異なる値を設定する必要があります。</p>

<p><code>wrangler types</code>が環境ごとの型を生成するようになったのは2026年1月リリースの4.60.0からですので、それ以前のwranglerを利用している場合はこの方法は利用できません。wranglerの更新が必要です。</p>

<ul>
<li><a href="https://github.com/cloudflare/workers-sdk/releases/tag/wrangler%404.60.0">wrangler 4.60.0のリリースノート</a></li>
</ul>

<p>また、wrangler 4.114.0時点では、<code>wrangler types</code>はトップレベル専用の型（この例ではmain）は生成しません。そのため、main環境の型に絞り込めるようにするには、自分で型を記述するか、<code>env</code>オブジェクトにmain環境用の設定を記述する必要があります。</p>

<p>例えば、トップレベルはstaging環境でもmain環境でもないdev環境とし、main環境とstaging環境をどちらも<code>env</code>配下に定義することが可能です。こうすると、<code>MainEnv</code>と<code>StagingEnv</code>の両方が生成されます。なお、次の例では説明のため、先ほどの例とは逆に<code>main</code>側へKVを置いています。</p>

<pre class="hljs-pre"><code class="json">{
  "$schema": "node_modules/wrangler/config-schema.json",
  "account_id": "...",
  "name": "dev",
  "main": "_worker/index.ts",
  "env": {
    "main": {
      "name": "main",
      "vars": {
        "WORKER_ENV": "main"
      },
      "kv_namespaces": [
        {
          "binding": "KV_DEBUG",
          "id": "..."
        }
      ]
    },
    "staging": {
      "name": "staging",
      "vars": {
        "WORKER_ENV": "staging"
      }
    }
  }
}</code></pre>

<h2>まとめ</h2>

<p>本記事でご紹介したように、<code>wrangler types</code>が生成する環境ごとの型と型ガードを組み合わせると、環境固有のバインディングを誤って参照しにくいコードを書けます。Cloudflare Workersを複数環境で運用する場合の参考になれば幸いです。</p>
]]>
    </content>
</entry>

<entry>
    <title>Copilot Studioのトピック開発で学んだTips 3選</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202607/27_1520.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2996</id>

    <published>2026-07-27T15:20:40+09:00</published>
    <updated>2026-07-27T15:20:28+09:00</updated>

    <summary>Microsoft Copilot Studioはローコードでエージェントを開発...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p><a href="https://www.microsoft.com/ja-jp/microsoft-365-copilot/microsoft-copilot-studio">Microsoft Copilot Studio</a>はローコードでエージェントを開発できる非常に便利なツールです。GUI中心で構築できるため、簡単なエージェントであれば短時間で動作するところまで到達できます。</p>

<p>一方で、トピック機能を使って少し複雑なエージェントを作ろうとすると「なんで動かないんだ...？」というハマりどころが現れます。この記事では、私が実際にCopilot Studioで開発していてハマったポイントを3つ紹介します。</p>
]]>
        <![CDATA[<h2>1. コードエディターを使う</h2>

<p>なぜ動かないのか分からない時は、Copilotなどの生成AIに質問することもあると思いますが、画面キャプチャやテキストで状況を説明するのには限界があります。そんな時に使えるのが<a href="https://learn.microsoft.com/ja-jp/microsoft-copilot-studio/guidance/topics-code-editor">コードエディター</a>です。</p>

<p>コードエディターはトピック設計画面の「詳細」から「コードエディターを開く」を選択すると表示できます。</p>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260731_01.png" width="1159" height="686" alt=""></div>
</div>

<p>コードエディターでは、ノードの接続関係、条件分岐、変数、各ノードの設定がYAML形式で表現されており、原因が特定しやすくなります。</p>

<h2>2. 変数は最初に初期化する</h2>

<p>変数を初期化しないままプロンプトで使用すると、エラーにならずノードがスキップされることがあります。</p>

<p>例えば以下のようなトピックを作ったとします。</p>

<ol start='1'>
<li>「生成応答を作成する」ノードで「今日の晩御飯は何がいい？」と聞く</li>
<li>生成された応答に対して質問ノードで「こちらでいかがでしょうか？」とユーザーに尋ね、ユーザーの応答を<code>user_request</code>変数に格納する</li>
<li>ユーザーが「OK」と回答した場合は終了、それ以外の場合はステップ1に戻り生成応答を再度作成する</li>
</ol>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260731_02.png" width="629" height="745" alt=""></div>
</div>

<p>生成応答ノードには、追加のプロンプトを設定できるので、ここに<code>user_request</code>を渡します。</p>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260731_03.png" width="1003" height="758" alt=""></div>
</div>

<p>「こちらでいかがでしょうか？」という質問に対して、ユーザーから受け取ったフィードバックをもとに生成応答を作り直すフローです。</p>

<p>しかし、このままでは初回の生成応答時点で<code>user_request</code>が未定義のため、生成応答ノードがスキップされてしまいます。内部的にはエラーになっているのだと思いますが、テスト実行時にも分かりやすいエラーメッセージは表示されません。</p>

<p>Copilot Studioでは、未初期化の変数を参照した場合でも必ずしもエラーが表示されるわけではありません。「ノードが実行されない」「生成応答が返ってこない」といった現象が発生したら、まず変数が初期化されているか確認することをおすすめします。今回のような再帰的なトピックやループ処理を設計する場合は、あらかじめ「変数値を設定する」ノードを配置し、変数を初期化しておきましょう。</p>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260731_04.png" width="350" height="546" alt=""></div>
</div>

<h2>3. 質問ノードはスキップされることがある</h2>

<p>Copilot Studioの質問ノードは、対象の変数に既に値が設定されていると質問自体がスキップされることがあります。</p>

<p>1つ前の例では<code>user_request</code>変数を空文字で初期化しましたが、実は空文字であっても値が設定済みとみなされるため、質問ノードが自動的にスキップされることがあります。これはCopilot Studioの質問ノードが、「値が定義されていない場合のみ質問する」という挙動を持っているためです。</p>

<p>この動作は質問ノードのプロパティから「毎回質問する」を有効にすることで防ぐことができます。</p>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260731_05.png" width="907" height="548" alt=""></div>
</div>

<p>質問のスキップは、入力済みの情報を再度尋ねないための便利な機能です。一方で、会話をループさせたり、ユーザーから何度もフィードバックを受け取ったりするシナリオでは意図しない挙動につながることがあります。会話の流れを設計したうえで活用することが重要です。</p>

<p>参考: <a href="https://learn.microsoft.com/ja-jp/microsoft-copilot-studio/authoring-ask-a-question#configure-question-behavior">質問する - Microsoft Copilot Studio | Microsoft Learn</a></p>

<h2>まとめ</h2>

<p>会話設計やトピック設計のベストプラクティスについては、Microsoft Learnの<a href="https://learn.microsoft.com/ja-jp/microsoft-copilot-studio/guidance/">Copilot Studio ガイダンス</a>にさまざまな情報がまとまっています。エージェント開発だけでなく、運用・改善も含めた考え方が整理されているため、あわせて参照することをおすすめします。</p>

<p>Copilot StudioはGUI中心で直感的に開発できる一方で、内部的には変数管理や会話状態の保持など、しっかりとした会話フレームワークの上で動いています。</p>

<p>今回ご紹介した点はどれも小さなポイントですが、知らないと数時間ハマってしまうこともあります。これからCopilot Studioでエージェントを作る方の参考になれば幸いです。</p>
]]>
    </content>
</entry>

<entry>
    <title>WebサイトのAI機能はどこに依存し、どこから壊れるのか―Fable 5、Mythos 5の停止に学ぶ</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202607/22_0941.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2987</id>

    <published>2026-07-22T09:41:00+09:00</published>
    <updated>2026-07-22T09:41:37+09:00</updated>

    <summary>何が起きたのか 2026年6月12日、AnthropicのAIモデル「Fable...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<h2>何が起きたのか</h2>

<p>2026年6月12日、AnthropicのAIモデル「Fable 5」と「Mythos 5」へのアクセスが一時的に停止されました。Fable 5は同月9日にリリースされたばかりのモデルでした。<a href="https://www.bloomberg.com/news/articles/2026-06-13/anthropic-says-us-limits-foreign-access-to-fable-5-mythos-5">Bloombergの報道</a>によれば、米商務省の命令はすべての外国籍者によるアクセスの即時停止を求めるものでした。しかし、APIリクエストから利用者の国籍を判別することは技術的に困難なため、Anthropicは全ユーザーへの提供停止を選択しました。大規模言語モデル（LLM）への輸出規制の発動は史上初のケースで、Fable 5を指定したAPIリクエストは利用できなくなりました。</p>

<p>命令の背景について、<a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/trump-adviser-david-sacks-says-anthropic-refused-to-fix-fable-5-jailbreak-before-us-export-controls">Tom's Hardwareの記事</a>では、政権関係者が、Fable 5のジェイルブレイク（安全機能を迂回する手法）の修正要求をAnthropicが拒否したためだと主張していることが紹介されています。一方、Anthropicは<a href="https://www.anthropic.com/news/fable-mythos-access">公式声明</a>で、報告されたジェイルブレイクは限定的なものであり、Fable 5だけを規制対象とすることに合理性はないと反論しています。</p>

<p>事実の整理はここまでとして、本稿ではWebサイトにAI機能を組み込む場面を想定し、この事件を「Webサイトはどこで外部AIに依存しうるのか」を確認する機会にします。</p>
]]>
        <![CDATA[<h2>WebサイトとAIの接点</h2>

<p>まず前提から確認します。情報掲載や問い合わせ導線を中心としたコーポレートサイトであれば、今回のようなモデル停止が起きても直接の影響を受けないケースが多いでしょう。</p>

<p>一方で、WebサイトにAI機能を組み込むには、多くの場合、外部のAIサービスをネットワーク越しに呼び出します。例えばAIチャットボット、AIサイト内検索などは、サイト側の処理だけで完結しません。呼び出し先のモデルやAPIが止まれば、サイト上の機能も止まります。今回の件で見えたのは、この依存関係です。</p>

<h2>Webサイトに関わるAI利用の3つのパターン</h2>

<p>WebサイトとAIの接点は、大きく3つのパターンに分けられます。</p>

<p>1つ目はSaaS型ウィジェットの埋め込みです。サイトにscriptタグを1行追加すると、チャットボットなどのUIが表示されるタイプです。実際の会話処理はベンダーのサーバーで行われ、そのベンダーはさらに裏側でAnthropicやOpenAIなどのモデルAPIを呼んでいます。つまりサイト→ベンダー→モデルプロバイダーという二段の依存が生まれます。サイト所有者はもちろん、導入に関わった側でも、最終的にどのモデルに依存しているかは把握しづらくなります。</p>

<p>2つ目はモデルAPIとの直接連携です。AIサイト内検索、説明文の動的生成など、WebサイトからモデルAPIを直接呼ぶ実装です。依存は一段ですが、そのぶんモデル停止の影響が機能に直撃します。</p>

<p>3つ目は制作プロセスでのAI利用です。コーディング支援や原稿作成にClaude CodeなどのAIツールを使う形です。ただしこれは制作の時に使うだけで、公開されるサイトには何も残りません。ツールが止まれば制作の効率は落ちますが、納品済みのサイトは壊れません。</p>

<p>この3つを区別すると、リスクの所在がはっきりします。パターン3は制作プロセス上の影響にとどまります。壊れるのはパターン1と2、つまりAI機能がサイトに組み込まれている場合です。しかもパターン1では、自分が何に依存しているかさえ見えにくくなります。</p>

<h2>導入前に確認したいこと</h2>

<p>この整理を踏まえると、AI機能を導入する時に確認したい観点は大きく3つあります。</p>

<p>1つ目は依存の可視化です。AI機能を提案・導入する際は、それが上記のどのパターンにあたり、依存の連鎖の先にどの事業者とどのモデルがいるのかを確認しておくと、停止した時の影響を説明しやすくなります。scriptタグを1行貼るだけの導入であっても、裏側のモデルまで確認できると影響範囲を判断しやすくなります。過去に導入したツールが、知らないうちにバージョンアップでAI機能を組み込んでいるケースもあるため、運用中のサイトについても、埋め込まれている外部サービスにAI由来のものがないかを確認しておくとよいです。</p>

<p>2つ目は主要動線の分離です。問い合わせ、資料請求、購入、予約といったサイトの主要動線は、AI機能なしでも完結できる設計にしておくと、停止した時の影響を抑えやすくなります。チャットボットはフォームへの近道であって、唯一の窓口にしないほうが安全です。AI機能が応答しない時は、エラーメッセージを表示するのではなくウィジェットごと非表示にし、FAQやフォームといった静的な導線に切り替える方法も考えられます。</p>

<p>3つ目は保守範囲の確認です。外部AIサービスの停止を保守の想定項目に含めるのか、誰が停止を検知し、誰の判断で機能を無効化し、誰がクライアントに連絡するのかを確認しておく必要があります。サーバー障害やCMSの不具合とは違い、外部AIの停止は保守の想定から抜け落ちやすい論点です。</p>

<h2>まとめ</h2>

<p>一般向けに提供されていたFable 5の停止が示したのは、外部AIサービスに依存する機能は、依存先の都合で止まることがあるという事実です。依存すること自体が悪いのではありません。問題は、その依存が見えないまま組み込まれ、止まった時に何が影響を受けるのか分からない状態になることです。どこに依存があり、止まったら何がどう壊れ、その時誰が何をするのか。AI導入の相談が増えていくなかで、こうした点を説明できる状態にしておくことは、Webサイトの設計・運用における重要な論点になるはずです。</p>
]]>
    </content>
</entry>

<entry>
    <title>なぜ最先端のAIは「300＋140＝460」を正解と言うのか─LLMに刻まれた4つの構造的限界</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202607/09_0951.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2967</id>

    <published>2026-07-09T09:51:50+09:00</published>
    <updated>2026-07-09T09:51:20+09:00</updated>

    <summary>AIに「わざと間違った答え」を含む質問をして、それを見抜けるか試したことはありま...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p>AIに「わざと間違った答え」を含む質問をして、それを見抜けるか試したことはありますか。</p>

<p>SNS上で、Gemini 3.5 Flashが「300 + 140 = 460は正しいですか？」という質問に「正しい」と答えた事例が話題になりました。正しい計算結果は440です。</p>

<p>この事例をいち早く分析したのが、香港を拠点に活動するソフトウェアエンジニア・テクノロジー実践者のKeith Liさんです。Keith Liさんは自身の<a href="https://www.linkedin.com/posts/keithlihk_%E6%B7%B1%E5%BA%A6%E6%8B%86%E8%A7%A3gemini-35-flash-%E9%83%BD%E4%B8%AD%E4%BC%8F%E8%A8%88%E9%8C%AF300-140-activity-7464541377889058816-3qVC/">LinkedInへの投稿</a>の中で、この誤りの背景として、以下の3つの技術的メカニズムを指摘しています。</p>

<ul>
<li>自己回帰生成：LLMは、次に来るトークンを1つずつ予測しながら出力を組み立てる、いわば話しながら考えている</li>
<li>Sycophancy（同調バイアス）：質問に前提が含まれていると、ユーザーの意図に同調しやすい場合がある</li>
<li>Chain of Thought（思考連鎖）：「推論過程を先に書いてから答えを出して」のように出力させると、中間手順が文脈に残り、正答しやすくなる場合がある</li>
</ul>

<p>一方で、私はもう1つ気になる点がありました。LLMが途中で誤りに気付いたとして、なぜ前の出力を取り消して書き直さないのでしょうか。</p>

<p>答えはシンプルです。自己回帰モデルには、バックスペースキーがなく、人間が文章を書く時のように、前の出力を消して書き直すことができません。</p>

<p>つまり一度出力されたトークンは即座に「確定した事実」となり、入力コンテキストに追加されます。モデルは「過去の自分の発言」を取り消す手段を持っていません。</p>

<p>矛盾に気づいたとしても、アーキテクチャの構造上、前に進み続けるしかない。結果として、最初の発言と計算結果が食い違ったまま出力が終わるという事態が起きます。</p>

<p>仮に最初のトークンを出力する前に十分な推論を行えるよう調整されたとしても、このアーキテクチャ上の制約そのものは解消されていません。</p>
]]>
        <![CDATA[<h2>同調しているのはAIだけか――同調バイアスの「鏡」</h2>

<p>同調バイアスは、AIがこちらに同調するクセとして語られます。けれど現場で本当に怖いのは、その裏側にあるもう一枚の鏡です。AIが自信たっぷりに返してきたとき、私たちもまた、その断定を無批判に受け入れてしまう。</p>

<p>考えてみれば不思議な構図です。こちらの前提に同調しやすいAIに対して、こちらもAIの結論に同調しやすい。お互いが相手に合わせにいくと、誤りを正す力がどこにも働かなくなります。「300 + 140 = 460は正しい？」と聞いた人が、「正しいです」という即答をそのまま信じてしまえば、間違いは二人がかりで通過してしまう。</p>

<p>だから私は、AIの出力を読むときに一度、「もし同僚がこれを断言してきたら、自分は鵜呑みにするだろうか」と置き換えてみるようにしています。人間相手なら当然する確認を、AI相手だと省いていないか。同調を一方向の問題にせず、自分の側の受け取り方まで含めて疑うことが、この鏡から抜け出す入口になります。</p>

<h2>個人の習慣から、チームのルールへ</h2>

<p>ここまでの話は、突き詰めると「一人ひとりが気をつける」で終わりがちです。けれど4つの限界はモデルの構造に由来するもので、気合や注意力で消えるものではありません。だとすれば、個人の心がけに頼るより、チームの仕組みに落とし込んだほうが確実です。</p>

<p>たとえば、こんな運用が考えられます。</p>

<ul>
<li>AIが生成した箇所には印を残し、レビュー時に「どこを重点的に見ればいいか」を伝える</li>
<li>数値計算やビジネスロジックを含む変更は、「実行して検算した結果」をレビュー依頼に添え、暗算のまま通さない</li>
<li>「AIがそう言ったから」を、設計判断の根拠とせず、人間が確認した事実に置き換えてからレビューに出す。</li>
</ul>

<p>どれも特別なツールは要りません。要は、AIの構造的なクセを「個人が覚えておくべき注意」から「チームが必ず通す手順」へ移すことです。</p>

<h2>まとめ</h2>

<p>Gemini 3.5 Flashの「300＋140」をめぐる今回の事例は、現在主流のLLMに広く見られる傾向を凝縮して示しています。Geminiの一例から、すべてのモデルについて断言はできませんが、TransformerベースのLLMに共通する設計上の特性として理解できます。</p>

<p>これらの構造的な限界を知ることは、AIを信頼しないことを意味しません。どの場面で信頼でき、どの場面で検証が必要か、どう問いかければ精度が上がるかを知ることです。</p>

<p>では、この事例を受けて、私たちのAI活用は具体的にどう変わるでしょうか。</p>

<ul>
<li>AIが生成したコードの計算・条件分岐は目視でレビューする習慣をつける：最初のトークンが誤ると後続もその誤りの上に積み上がります。特に数値処理やビジネスロジックを含む生成コードは、信頼する前にチェックが必要です。</li>
<li>仕様書・設計文書の生成時は「推論を先に、結論を後に」を指示する：「最終的な答えを出す前に、ステップごとの根拠を書いてください」という指示だけで、曖昧な仕様の見落としを減らせます。</li>
<li>数値計算や検証ロジックにはTool UseやFunction callingを使う：LLMに暗算させず、本物の計算機に処理させることが確実です。必要な場面では、AIに答えさせるより、AIにコードを実行させる方が信頼できます。</li>
</ul>

<p>ツールの設計上の特性を理解した上で使いこなすことが、生成AIをソフトウェア開発の現場で安全・効果的に活用するための出発点です。</p>
]]>
    </content>
</entry>

<entry>
    <title>cicd-sensorによるライフサイクルスクリプト監視を試す</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202606/25_1738.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2956</id>

    <published>2026-06-25T17:38:00+09:00</published>
    <updated>2026-06-25T17:38:28+09:00</updated>

    <summary>先日、齋藤によるサプライチェーン攻撃対策 - 最低限見直すべき5つのポイントとい...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p>先日、齋藤による<a href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202606/24_1330.html">サプライチェーン攻撃対策 - 最低限見直すべき5つのポイント</a>という記事を公開しました。齋藤の記事では今すぐ実施可能な対策も紹介していますので、まだお読みでないかたはぜひ、お読みください。</p>

<p>さて、サプライチェーン攻撃に限らず、対策を実施したあとは、対策が機能しているのか、すり抜けが生じているのかなどの確認も重要です。最近のサプライチェーン攻撃ではPCだけではなくCI/CD環境も狙われているため、この記事ではCI/CD環境向けの監視ツールである<a href="https://github.com/cicd-sensor/cicd-sensor/">cicd-sensor</a>を検証しました。</p>

<p>検証の条件は次の通りです。</p>

<ul>
<li>GitHub ActionsのGitHub-hosted runnerで利用</li>
<li>GitHub Actionsの再利用可能ワークフロー（cicd-sensor/cicd-sensor-action）経由で利用
<ul>
<li>cicd-sensor-actionのバージョンは<a href="https://github.com/cicd-sensor/cicd-sensor-action/releases/tag/v0.0.34">v0.0.34</a></li>
<li>cicd-sensorのバージョンは<a href="https://github.com/cicd-sensor/cicd-sensor/releases/tag/releases%2Fv0.0.39">v0.0.39</a></li>
</ul></li>
<li>cicd-sensorのレポートはGitHub Actionsのartifactとして保存</li>
</ul>
]]>
        <![CDATA[<p>長々と書きましたが、検証のために行ったことはワークフローにcicd-sensor/cicd-sensor-actionを追加したことのみです。</p>

<pre class="hljs-pre"><code class="yaml">jobs:
  build:
    permissions:
      contents: read
    timeout-minutes: 5
    runs-on: ubuntu-24.04
    steps:
      # ここから追加
      - name: cicd-sensorのセットアップ
        uses: cicd-sensor/cicd-sensor-action@777ddaafc9ec2e09c9779cdb860e75906adc19c2 # v0.0.34
      # ここまで追加
      - name: チェックアウト
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - name: Node.jsのセットアップ
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: '24.17.0'
      - name: 依存パッケージのインストール
        run: npm ci
      - name: ビルド
        run: npm run build</code></pre>

<h2>インストール処理が子プロセスを立ち上げないことの検証</h2>

<p>cicd-sensorはファイルアクセスやネットワーク通信なども監視できますが、今回はプロセスの監視を検証しました。</p>

<p>というのも、Node.jsエコシステムにおけるサプライチェーン攻撃ではpostinstallなどのライフサイクルスクリプトの悪用が続いているためです。ライフサイクルスクリプトはインストール処理（<code>npm ci</code>など）の子プロセスとして実行されます。そのため、cicd-sensorで<code>npm ci</code>の子プロセスを監視できるか検証しました。</p>

<p>cicd-sensorでは監視内容をYAMLファイルのルールとして記述できます。今回は親プロセスが<code>npm ci</code>であるプロセスを収集するルールを作成しました（.cicd-sensor/rules/install.yml）</p>

<pre class="hljs-pre"><code class="yaml">rule_sets:
  - ruleset_id: test/install
    rules:
      - rule_id: npm_ci_no_child_processes
        rule_name: "npm ciは子プロセスを実行しない"
        event_type: process_exec
        condition: |
          process.ancestors.exists(parent, 
            parent.exec_path.endsWith("/node") &&
            parent.argv.exists(arg, arg == "ci")
          )
        action: collect</code></pre>

<p>なお、検証した環境では<code>npm</code>コマンドは<code>node</code>コマンドを実行するスクリプトへのリンクになっていたため、作成したルールでは<code>npm</code>コマンドではなく<code>node</code>コマンドを監視しています（<code>parent.exec_path.endsWith("/npm")</code>ではなく<code>parent.exec_path.endsWith("/node")</code>と記述）。</p>

<p>検証用にライフサイクルスクリプトをブロックしていないリポジトリを用意し、GitHub Actionsを実行した結果、cicd-sensorのレポートに子プロセスが記録されました。</p>

<div class="c-media -sharp">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260623_01.png" width="765" height="236" alt=""></div>
</div>

<pre class="hljs-pre"><code>/home/runner/actions-runner/cached/2.335.1/bin/Runner.Worker
"/home/runner&lt;truncated, 60 bytes&gt;" "spawnclient" "142" "145"

/usr/bin/bash
"/usr/bin/bas&lt;truncated, 13 bytes&gt;" "-e" "/home/runner&lt;truncated, 63 bytes&gt;"

/opt/hostedtoolcache/node/24.17.0/x64/bin/node
"node" "/opt/hostedt&lt;truncated, 45 bytes&gt;" "ci"

/usr/bin/sh pid: 2214
"sh" "-c" "node install&lt;truncated, 38 bytes&gt;"</code></pre>

<p>調べてみると、このプロセスは画像処理ライブラリーとして有名な<a href="https://www.npmjs.com/package/sharp">sharp</a>のinstallスクリプトでした。検証した環境ではinstallスクリプトを実行しなくてもoptionalDependencies経由でバイナリーをインストールできますので、ライフサイクルスクリプトの実行をブロックするように.npmrcを調整しました。</p>

<pre class="hljs-pre"><code>ignore-scripts=true</code></pre>

<p>ライフサイクルスクリプトをブロックした状態で再度GitHub Actionsを実行すると子プロセスは記録されませんでした。</p>

<p>また、cicd-sensorでは記録だけではなく処理をブロックすることもできます。具体的にはルール内の<code>action</code>を<code>terminate</code>に変更します。</p>

<pre class="hljs-pre"><code class="yaml">rule_sets:
  - ruleset_id: test/install
    rules:
      - rule_id: npm_ci_no_child_processes
        rule_name: "npm ciは子プロセスを実行しない"
        event_type: process_exec
        condition: |
          process.ancestors.exists(parent, 
            parent.exec_path.endsWith("/node") &&
            parent.argv.exists(arg, arg == "ci")
          )
        # ここから変更
        action: terminate
        # ここまで変更</code></pre>

<p>この状態で、GitHub Actionsを実行すると<code>npm ci</code>のステップが失敗することを確認できました。</p>

<div class="c-media -sharp">
<div class="c-media__image">
<img src="/knowledge/blog/x-tech/img/20260623_02.png" width="410" height="109" alt=""></div>
</div>

<pre class="hljs-pre"><code>Run npm ci 
Error: Process completed with exit code 130.</code></pre>

<h2>まとめ</h2>

<p>この記事ではcicd-sensorを使ったCI/CD環境の監視を検証しました。</p>

<p>cicd-sensorは手軽に導入でき、現実的なルールも記述できることがわかりました。</p>

<p>組織的に監視するには、ルールの整備・配布、ログの保存・確認などが必要ですが、まずは現状把握として試してみてはいかがでしょうか。</p>
]]>
    </content>
</entry>

<entry>
    <title>デザインと開発をつなぐ共通言語 - デザイントークンのすすめ</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202606/24_1330.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2955</id>

    <published>2026-06-24T13:30:56+09:00</published>
    <updated>2026-06-24T13:59:34+09:00</updated>

    <summary>なぜデザインと開発はズレるのか デザイナーがFigmaで丁寧に作ったデータを、エ...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<h2>なぜデザインと開発はズレるのか</h2>

<p>デザイナーがFigmaで丁寧に作ったデータを、エンジニアが実装する。
ごく日常的なプロセスですが、その中には見えにくい摩擦が潜んでいます。</p>

<p>「この青、デザインと微妙に違う」
「この余白って何px？」</p>

<p>ひとつひとつは些細に見えますが、積み重なると無視できないコミュニケーションコストになります。
それだけではなく、デザインの一貫性が担保できず、最終的な成果物の品質に影響します。</p>
]]>
        <![CDATA[<h2>デザイントークンという共通言語</h2>

<p>こうしたすれ違いの根本は、デザインと開発の間に共通の参照先がないことです。
この問題に対するひとつの答えが、デザイントークンという考え方です。</p>

<p>色・タイポグラフィ・余白・角丸といった、デザイン上の意思決定を名前付きの変数として一元管理します。 <br />
<code>#2B7FFF</code> という値を直接使うのではなく、<code>color.brand.primary</code> という名前で参照する。 <br />
名前が「値」ではなく「役割」を表すことで、UIのどの文脈で使われる色なのかが伝わります。</p>

<p>それがデザインと開発の共通言語になります。 <br />
デザイナーはFigmaの変数として、エンジニアはCSS Custom Propertiesとして、この同じ名前を参照する。 <br />
デザイントークンを整備することで、認識のズレが生まれにくくなります。</p>

<p>この考え方の共通仕様化を進めているのが <a href="https://www.w3.org/community/design-tokens/">W3CのDesign Tokens Community Group（DTCG）</a> で、2025年10月には<a href="https://www.w3.org/community/reports/design-tokens/CG-FINAL-format-20251028/">トークンフォーマットの仕様</a>も公開されています。</p>

<p>主要ツールの中には、このフォーマットへの対応を進めていたり前向きな関心を示すものが出てきており、特定ツールに依存しない共通フォーマットとして徐々に浸透しつつあります。  </p>

<ul>
<li><a href="https://www.figma.com/blog/schema-2025-design-systems-recap/">FigmaがDTCGのimport/exportをビルトイン機能として追加</a></li>
<li><a href="https://www.sketch.com/blog/color-tokens/">SketchはDTCGの仕様策定に参加・関心を表明</a></li>
<li><a href="https://styledictionary.com/info/dtcg/">Style Dictionaryは最新のDTCGフォーマットへの対応を段階的に実装中</a></li>
<li><a href="https://tokens.studio/blog/tokens-studio-penpot-bringing-native-open-standard-design-tokens-to-everyone">Tokens StudioはDTCG準拠のトークン管理を実装</a></li>
</ul>

<h2>Tailwind CSSのthemeを参考にする</h2>

<p>とはいえ、仕様に沿ってゼロからトークンを設計しようとすると、定義すべき項目は膨大です。 <br />
色のスケール、タイポグラフィのバリエーション、余白の体系...。 <br />
気がつくとトークン設計自体がひとつのプロジェクトになってしまいます。</p>

<p>そこで役立つのが <a href="https://tailwindcss.com/">Tailwind CSS</a> の <a href="https://tailwindcss.com/docs/theme#default-theme-variable-reference">theme</a> を参照する方法です。</p>

<p>Tailwind CSS のデフォルト theme には、色のスケール、余白の単位、フォントサイズなど、実践的に整理されたデザイン判断が揃っています。 <br />
これをそのまま全部採用するのではなく「自分たちのプロジェクトに必要なものはどれか」を考えながらピックアップして使うのがポイントです。 <br />
例えばブランドカラーとして使う数色、スペーシングの基準となる単位、見出しと本文のフォントサイズ。 <br />
まずそれだけを選んで定義するところから始めれば十分です。</p>

<p><a href="https://github.com/style-dictionary/style-dictionary">Style Dictionary</a> などのツールを使えば、JSONで定義したトークンからCSS Custom Propertiesを生成し、FigmaのVariables（変数機能）と対応させることもできます。</p>

<pre class="hljs-pre"><code class="json">// color.json
{
  "color": {
    "blue": {
      "$type": "color",
      "300": { "$value": "#8ec5ff" },
      "500": { "$value": "#2b7fff" },
      "700": { "$value": "#1447e6" },
    },
    "rose": {
      "$type": "color",
      "300": { "$value": "#ffa1ad" },
      "500": { "$value": "#ff2056" },
      "700": { "$value": "#c70036" },
    },
  }
}</code></pre>

<pre class="hljs-pre"><code class="css">:root {
  --color-brand-primary: var(--color-blue-700);
}

...

button {
  background-color: var(--color-brand-primary);
}</code></pre>

<p>Figmaで <code>color/brand/primary</code> という変数を定義し、コードでは <code>--color-brand-primary</code> として参照します。 <br />
同じ構造、同じ命名。これがデザインとコードをつなぐ橋になります。</p>

<p>最初から完璧なトークン体系を用意する必要はありません。まずブランドカラーと基本的なスペーシングだけで十分です。 <br />
Tailwind CSSの既存themeを参考にしながら、プロジェクトに合わせて少しずつ育てていけます。</p>

<h2>Figma MCPでAIにも通じる</h2>

<p>トークンを整備することのもうひとつの効果があります。AIとの連携です。</p>

<p>Figma MCPは、AIアシスタントがFigmaのデザインデータを直接参照できるようにする仕組みです。 <br />
これを使うと、AIに「このコンポーネントをコードで実装して」と指示する時、Figmaのデータを文脈として渡せます。</p>

<p>ここでトークンが整備されているかどうかが、大きな差になります。</p>

<p>トークンがない状態では、AIはデザインの意図を読み取る語彙を持ちません。 <br />
生成されるコードに <code>color: #3B82F6</code> のようなベタ打ちの値が入りやすいのは、AIがその値の意味を判断する手がかりがないからです。</p>

<p>一方、トークンが整備されていれば、AIは <code>color.brand.primary</code> という名前からデザイン上の役割を理解し、システム全体と整合した判断を自律的に行えるようになります。 <br />
細かく指示しなくても、デザインの意図をAI自身が正しく解釈して動ける状態に近づきます。</p>

<p>ゴールはAIへの指示精度を上げることではなく、AIがデザインとコードの意図を正しく理解し、自律的に駆動できる環境を作ることです。 <br />
Figma MCPはその入り口であり、トークンはAIが判断するための語彙になります。</p>

<p>さらに今後の可能性として期待されているのが、コードからデザインへの逆方向です。 <br />
コードやトークンの変更を起点に、FigmaのデザインをAIが更新するワークフロー。 <br />
AIが両方向を橋渡しし、設計と実装が自律的なループで連携できる未来が、少しずつ近づいています。</p>

<h2>育てながら使う</h2>

<p>デザイントークンは完成させるものではなく、育てていくものです。</p>

<p>Tailwind CSSのthemeを参考にすることで、ゼロから設計するコストを大幅に下げられます。 <br />
そこにFigmaのVariablesを対応させ、チーム内で命名の規則を揃えていく。 <br />
それだけで、デザインと開発の会話はかなりスムーズになります。</p>

<p>そして整備されたトークンは、AIが自律的に正しく動くための語彙でもあります。 <br />
Figma MCPを通じてAIが設計の意図を読み取り、トークンに基づいてコードを判断し、必要とあればFigmaにも反映する。 <br />
そんなワークフローが現実に近づいた時、デザインと開発の連携はまったく新しいかたちになるでしょう。</p>

<p>デザイナーと開発者の共通言語を整備することは、同時にAIをそのループの一員として迎え入れるための準備でもあります。</p>
]]>
    </content>
</entry>

<entry>
    <title>RPAによるCMS投入前に、機械学習で入稿データの誤りを検出する</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202606/23_1002.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2954</id>

    <published>2026-06-23T10:02:31+09:00</published>
    <updated>2026-06-23T10:02:29+09:00</updated>

    <summary>Webサイトの更新作業では、CMSへのニュース記事投入をRPAで自動化することが...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p>Webサイトの更新作業では、CMSへのニュース記事投入をRPAで自動化することがあります。例えば、CSVにまとまった入稿データを読み込み、タイトル、本文、カテゴリなどをCMSへ登録していく処理です。手作業で1件ずつ登録するより速く、入力漏れや転記ミスも減らせるので、とても便利です。</p>

<p>RPA自体の実装ミスに関しても、投入後のページから取得できる情報が元の投入データと一致するか、といった方法で検証できます。RPAは基本的に「決まったルール通りに同じ操作を繰り返す」ものなので、そのルールが正しく動いているかは機械的に確認しやすいわけです。</p>

<p>しかし、ここで1つ問題があります。「そもそも入稿データ自体が間違っていたら、RPAはそのまま間違った内容を登録してしまう」ということです。例えば、記事のカテゴリが間違っているケースです。</p>

<pre class="hljs-pre"><code>タイトル：大学と共同で異常検知アルゴリズムの研究を開始
カテゴリ：商品・サービス</code></pre>

<p>このようなデータが渡された場合、RPAは「商品・サービス」カテゴリとしてCMSに投入します。人間からすれば「これは研究開発カテゴリでは？」と気づくかもしれませんが、RPAから見れば、指定されたカテゴリがそのまま「正しい」選択です。</p>

<p>そこで、CMS投入前の入稿データチェックとして、「記事のタイトルと本文からカテゴリを自動推定し、誤りが疑われる入力カテゴリを指摘する」簡単なプログラムを作ってみます。</p>
]]>
        <![CDATA[<h2>入稿データの誤りをどう見つけるか</h2>

<p>カテゴリ間違いを見つける方法として、まず思いつくのはルールベースのチェックです。例えば、次のようなルールを作る方法です。</p>

<ul>
<li>「研究」「実証」「検証」を含んでいたら研究開発</li>
<li>「採用」「役員」「制度」を含んでいたら人事</li>
<li>「環境」「地域」「削減」を含んでいたらサステナビリティ</li>
</ul>

<p>これはシンプルで分かりやすい一方、実際に運用しようとするとかなり大変です。</p>

<p>記事タイトルや本文の表現は毎回少しずつ違います。既存のパターンに合わない文言を見つけるたびにルールの修正が必要になり、最終的に複雑すぎてメンテナンス自体が困難なプログラムになってしまうかもしれません。</p>

<p>あるいは、LLMに判定させる方法も考えられます。LLMにタイトルと本文を渡して、「このカテゴリは妥当ですか？」と聞けば、それなりに自然な判断をしてくれます。ただし、外部APIを使う場合、コストやセキュリティ（未公開情報を渡して良いか）が気になる場合もあります。<a href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202603/26_1556.html">ローカルLLMを使うという方法</a>もありますが、実行時間がかかったり、ある程度のマシンスペックが必要だったりします。</p>

<p>そこで今回は、情報を外部に送信せず、かつローカルLLMよりも軽い方法として機械学習を使います。過去に投入済みのデータを学習材料にして、対象のサイト内で「どのような文章がどのようなカテゴリに分類されやすいか」という傾向をモデルに覚えさせ、新しい投入データの判定に使います。</p>

<h2>今回作るもの</h2>

<p>今回作るのは、次のような流れのプログラムです。Pythonの機械学習ライブラリ<a href="https://scikit-learn.org/stable/">scikit-learn</a>を使います。</p>

<ol start='1'>
<li>過去のCMS記事データを読み込む</li>
<li>タイトルと本文をもとにカテゴリ分類モデルを学習する</li>
<li>新規投入予定の記事データを読み込む</li>
<li>各記事のカテゴリを推定する</li>
<li>入力されているカテゴリと推定カテゴリを比較する</li>
<li>比較結果をCSVに出力する</li>
</ol>

<p>1、2の処理を<code>train.py</code>、3～6の処理を<code>check.py</code>として実装します。</p>

<p>なお、説明をシンプルにするため、モデルの性能評価は省略しています。実運用では、過去データの一部を評価用として分けるなど、未知の記事をどの程度正しく分類できるか確認すると良いでしょう。</p>

<h2>既存データを学習させる</h2>

<p>まず、過去に公開済みの記事データとして、次のような<code>cms_training_data.csv</code>があるとします（T006以降は省略）。</p>

<table>
<thead>
<tr>
<th>id</th>
<th>title</th>
<th>body</th>
<th>category</th>
</tr>
</thead>
<tbody>
<tr>
<td>T001</td>
<td>法人向けクラウドサービスの提供を開始</td>
<td>当社は中小企業の業務効率化を支援する...</td>
<td>商品・サービス</td>
</tr>
<tr>
<td>T002</td>
<td>画像認識技術を用いた検品支援の実証実験を開始</td>
<td>当社は製造ラインにおける外観検査の効率化を目指し...</td>
<td>研究開発</td>
</tr>
<tr>
<td>T003</td>
<td>執行役員の異動に関するお知らせ</td>
<td>当社は取締役会において執行役員の異動を決定しました...</td>
<td>人事</td>
</tr>
<tr>
<td>T004</td>
<td>再生可能エネルギー由来電力の導入を拡大</td>
<td>当社は事業所で使用する電力について...</td>
<td>サステナビリティ</td>
</tr>
<tr>
<td>T005</td>
<td>大学と共同で設備データ分析の共同研究を開始</td>
<td>当社は大学と共同で異常検知アルゴリズムの研究に取り組み...</td>
<td>研究開発</td>
</tr>
</tbody>
</table>

<p>このデータを以下の<code>train.py</code>で読み込み、カテゴリ分類モデルを<code>category_model.pkl</code>として出力します。</p>

<pre class="hljs-pre"><code class="python"># train.py
import pickle

import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline


TRAINING_CSV = "cms_training_data.csv"
MODEL_FILE = "category_model.pkl"


def make_text(df):
    return df["title"].fillna("") + " " + df["body"].fillna("")


training_df = pd.read_csv(TRAINING_CSV) # 1.過去のCMS記事データを読み込む

model = Pipeline(
    [
        ("tfidf", TfidfVectorizer(analyzer="char", ngram_range=(2, 4))),
        ("clf", LogisticRegression(max_iter=1000)),
    ]
)

model.fit(make_text(training_df), training_df["category"]) # 2. タイトルと本文をもとにカテゴリ分類モデルを学習する

with open(MODEL_FILE, "wb") as f:
    pickle.dump(model, f)

print(f"{MODEL_FILE} にモデルを保存しました。")</code></pre>

<p>既存データをモデルに学習させるにあたり、scikit-learnが提供する<code>TfidfVectorizer</code>クラスと<code>LogisticRegression</code>クラスを使います。</p>

<p><code>TfidfVectorizer</code>の役割は、TF-IDFと呼ばれる指標を用いて、記事データを機械学習で扱える数値ベクトル（数値の並び）に変換することです。実装例では、文字n-gramを使用し、文章を2～4文字の連続した文字列に分解して、それぞれの文字列の重要度を特徴量にしています。日本語の文章を機械学習で扱う場合、形態素解析ライブラリで単語に分ける方法もありますが、文字n-gramでもカテゴリごとの表現の傾向をある程度学習できます。実務では必要に応じて手法を選択すると良いでしょう。</p>

<p><code>LogisticRegression</code>の役割は、ロジスティック回帰と呼ばれる機械学習アルゴリズムを用いて、<code>TfidfVectorizer</code>が作成した特徴量とカテゴリとの関係を学習することです。これにより、あとで新しい記事を投入するときにも、カテゴリを予測することができるようになります。</p>

<h2>新規投入データをチェックする</h2>

<p>続いて、これからCMSに投入したい新規データとして次のような<code>cms_new_articles.csv</code>があるとします。<code>N002</code>で本来「研究開発」とするべきカテゴリを「商品・サービス」にしてしまっています。</p>

<table>
<thead>
<tr>
<th>id</th>
<th>title</th>
<th>body</th>
<th>category</th>
</tr>
</thead>
<tbody>
<tr>
<td>N001</td>
<td>法人向け勤怠管理サービスの新プランを開始</td>
<td>当社は勤怠管理サービスに小規模企業向けの新プランを...</td>
<td>商品・サービス</td>
</tr>
<tr>
<td>N002</td>
<td>大学と共同で異常検知アルゴリズムの研究を開始</td>
<td>当社は大学と共同で設備データを用いた異常検知アルゴリズム...</td>
<td>商品・サービス</td>
</tr>
<tr>
<td>N003</td>
<td>組織変更および新部門設置のお知らせ</td>
<td>事業拡大に伴い組織変更を実施し...</td>
<td>人事</td>
</tr>
</tbody>
</table>

<p>以下の<code>check.py</code>では、このCSVと上記<code>train.py</code>の実行で保存したモデル<code>category_model.pkl</code>を読み込み、新規投入データのタイトルと本文からカテゴリを推定し、結果（入力カテゴリと推定カテゴリが一致するか）を<code>output.csv</code>に出力します。</p>

<pre class="hljs-pre"><code class="python"># check.py
# 各importとmake_text()関数は省略しています

NEW_ARTICLES_CSV = "cms_new_articles.csv"
MODEL_FILE = "category_model.pkl"
OUTPUT_CSV = "output.csv"

with open(MODEL_FILE, "rb") as f:
    model = pickle.load(f)

new_df = pd.read_csv(NEW_ARTICLES_CSV) # 3. 新規投入予定の記事データを読み込む

texts = make_text(new_df)
predicted_categories = model.predict(texts) # 4. 各記事のカテゴリを推定する

results = new_df[["id", "title", "category"]].copy()
results["predicted_category"] = predicted_categories

def judge(row):
    if row["category"] != row["predicted_category"]:
        return "カテゴリ不一致の可能性あり"
    return "OK"

results["judge"] = results.apply(judge, axis=1) # 5. 入力されているカテゴリと推定カテゴリを比較する

results.to_csv(OUTPUT_CSV, index=False, encoding="utf-8-sig") # 6. 比較結果をCSVに出力する
print(f"{OUTPUT_CSV} を出力しました。")</code></pre>

<p>今回は実装をシンプルにするために単純に推定カテゴリの一致・不一致のみ出力しています。実運用では、各カテゴリに分類される予測確率を<code>model.predict_proba(texts)</code>で取得し、値に応じて（確率が低い、拮抗しているなど）「要確認」と判定する、などの処理を加えても良いでしょう。</p>

<p>なお、<a href="https://docs.python.org/3/library/pickle.html"><code>pickle</code></a>形式のモデルファイルを読み込む場合、信頼できないファイルを読み込むと任意のコードが実行されるセキュリティリスクがありますので、外部のモデルファイルを読み込む場合などは注意しましょう。</p>

<h2>出力結果</h2>

<p><code>check.py</code>の実行結果として出力されるCSV<code>output.csv</code>は、例えば次のような内容です。</p>

<table>
<thead>
<tr>
<th>id</th>
<th>title</th>
<th>category</th>
<th>predicted_category</th>
<th>judge</th>
</tr>
</thead>
<tbody>
<tr>
<td>N001</td>
<td>法人向け勤怠管理サービスの新プランを開始</td>
<td>商品・サービス</td>
<td>商品・サービス</td>
<td>OK</td>
</tr>
<tr>
<td>N002</td>
<td>大学と共同で異常検知アルゴリズムの研究を開始</td>
<td>商品・サービス</td>
<td>研究開発</td>
<td>カテゴリ不一致の可能性あり</td>
</tr>
<tr>
<td>N003</td>
<td>組織変更および新部門設置のお知らせ</td>
<td>人事</td>
<td>人事</td>
<td>OK</td>
</tr>
</tbody>
</table>

<p>このように、入力されているカテゴリと、タイトルおよび本文から推定したカテゴリが違うものを検出できます。</p>

<p>もちろん、必ずしも判定がうまくいくとは限りません。今回はかなり分かりやすい例を用いましたが、実際の判定の性能は過去データの量や質、カテゴリの複雑さ、採用する手法などに依存します。実務ではより多くの工夫が必要ですが、それでもチェック方法の1つとして、十分に試してみる価値があると思います。</p>

<h2>まとめ</h2>

<p>今回は、CMS投入データのカテゴリ間違いを、Pythonの機械学習ライブラリを用いてチェックする簡単な例を紹介しました。scikit-learnを使ったシンプルな分類であれば、外部APIも不要で軽量に動かせるので便利です。ルールベースより柔軟で、LLMより軽量な方法として、こうした小さな機械学習の活用も検討してみてはいかがでしょうか。</p>
]]>
    </content>
</entry>

<entry>
    <title>サプライチェーン攻撃対策 - 最低限見直すべき5つのポイント</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202606/19_0917.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2906</id>

    <published>2026-06-19T09:17:37+09:00</published>
    <updated>2026-06-19T09:18:04+09:00</updated>

    <summary>npm installを実行する時、何が起きているか意識したことはありますか？ ...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p><code>npm install</code>を実行する時、何が起きているか意識したことはありますか？ <br />
サプライチェーン攻撃「Shai-Hulud」は、その問いを考えさせられるきっかけになりました。</p>

<p>「Shai-Hulud」は公開済みnpmパッケージに悪意あるコードを混入させ、アクセストークンやAPIキーなど（以下「秘匿情報」）が大量に盗まれる攻撃で、これまで第一波（2025年9月 - Shai-Hulud）、第二波（2025年12月 - Shai-Hulud 2.0）、第三波（2026年5月 - Mini Shai-Hulud）と発生してきました。 </p>

<p>攻撃の仕組みはシンプルで<code>package.json</code>のライフサイクルスクリプト（<code>preinstall</code> / <code>postinstall</code>など）に不正なスクリプトを仕込み、インストール時にあなたの権限で自動実行させるというものです。 <br />
この影響範囲はローカルマシンに留まらず、CI/CD環境においても同様です。</p>

<p>これはバグや特殊な脆弱性を突いたわけではなく、npmが持つライフサイクルスクリプトという正規機能を悪用したものです。 <br />
これらのスクリプトはパッケージ開発者なら誰でも仕込める仕組みで、<code>npm install</code>を実行するだけで自動的に動きます。 <br />
つまり<code>npm install</code>は単なるダウンロードコマンドではなく、信頼していないコードが実行される入り口でもあるわけです。</p>

<p>自分たちの開発環境の設定を見直してみると、今日からでも対処できることがいくつかありました。 <br />
この記事では、今日からすぐ実施できるものから、中長期で取り組みたいものまで、特に効果的な5つの対策を紹介します。</p>
]]>
        <![CDATA[<h2>Shai-Hulud の仕組み</h2>

<p><a href="https://about.gitlab.com/blog/gitlab-discovers-widespread-npm-supply-chain-attack/">GitLab公式ブログ</a> や <a href="https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem">CISAによるサイバーセキュリティ勧告</a> によると、攻撃の流れは下記のような内容です。</p>

<h3>1. フィッシングでnpmアカウントを奪取</h3>

<p>npmやGitHubのアカウント更新を装ったフィッシングメールなど、何らかの方法で、メンテナーの秘匿情報を盗み取ります。</p>

<h3>2. 正規パッケージに悪意あるコードを混入・公開</h3>

<p>奪ったアカウントで、<code>preinstall</code>フックに悪意あるスクリプトを仕込んだ新バージョンを既存パッケージとして公開します。 
スクリプトは表向き正規ツールのインストーラーに見せかけた多段構成になっており、実際の悪意あるペイロードは難読化されたファイルの中に隠されています。</p>

<h3>3. インストール時に自動実行・秘匿情報を収集</h3>

<p>感染したパッケージをインストールしたユーザーが<code>npm install</code>を実行した瞬間、悪意あるスクリプトが起動し、以下の情報を収集します。</p>

<ul>
<li>GitHub/npmトークン: 環境変数・設定ファイル・CLIの設定から探索</li>
<li>クラウド認証情報: AWS・GCP・Azureの認証情報を環境変数や設定ファイルから収集</li>
<li>ファイルシステム全体: 正規のセキュリティツールを悪用してホームディレクトリ全体をスキャン</li>
</ul>

<p>収集した秘匿情報は、盗んだトークンを使ってインストールしたユーザー自身のGitHubアカウントに公開リポジトリを作成し、そこに送り出されます。 
さらにCI/CDのセルフホストランナーをインストールし、侵害後も持続的なアクセス手段を確保します。 
感染したシステム同士がトークンを共有するボットネット的な仕組みも持っており、攻撃インフラは非常に耐障害性が高くなっています。</p>

<h4>デッドマンズスイッチ</h4>

<p>GitHubとnpmの両方へのアクセスが同時に断たれるとユーザーデータの破壊が実行され、ファイルは上書き削除されるため復元はほぼ不可能とのことです。</p>

<p>大規模なテイクダウンが同時多発的なデータ破壊につながりうるという意味でも、対応が難しい設計です。</p>

<p>第三波ではさらに<code>gh-token-monitor</code>という永続化機構が追加されており、GitHubトークンの失効を60秒ごとに監視し、失効を検知した瞬間にホームディレクトリを削除します。 
そのため、感染が疑われる場合は秘匿情報のローテーションより先に永続化機構の無効化を行う必要があります。 
対応の順序を誤ると、インシデント対応中にデータが失われるリスクがあります。</p>

<h3>4. ワームとして自己増殖</h3>

<p>盗んだnpmトークンを使って、被害者が管理する全パッケージに同じ悪意あるスクリプトを注入し、バージョンをインクリメントして再公開します。 
既存ユーザーが次回<code>npm install</code>を実行した際に自動で感染が広がります。</p>

<p>この「感染 → 窃取 → 増殖」のサイクルがCI/CDのスピードで回り続ける上に、デッドマンズスイッチによる破壊リスクも抱えているのがShai-Huludのいちばん怖いところです。</p>

<h2>対策1: ignore-scriptsを有効にする（npmの場合）</h2>

<p>手軽さと効果のバランスでいえば、これがもっとも有効です。
<code>postinstall</code>や<code>preinstall</code>などのnpmライフサイクルスクリプトをまるごと無効にします。</p>

<p>（参考: <a href="https://docs.npmjs.com/cli/v10/using-npm/config#ignore-scripts">npm 公式ドキュメント</a>）</p>

<h3>.npmrcで設定する</h3>

<p>プロジェクトルートの <a href="https://docs.npmjs.com/cli/v11/configuring-npm/npmrc"><code>.npmrc</code></a> に書けばローカルとCI/CDの両方に適用されます。 </p>

<pre><code>ignore-scripts=true</code></pre>

<h3>postinstallを必要とするパッケージへの対応</h3>

<p>注意点として、セットアップに <code>postinstall</code> を必要とするパッケージは <a href="https://docs.npmjs.com/cli/v10/using-npm/config#ignore-scripts"><code>ignore-scripts=true</code></a> にすると動かなくなる場合があります。 </p>

<p>npmにはホワイトリスト機能がないため、代わりに「許可したパッケージだけを明示的にリビルドする」というアプローチが有効です。 <br />
<code>postinstall</code> を自前のスクリプトで上書きし、許可リストに含まれるパッケージのみ <a href="https://docs.npmjs.com/cli/v11/commands/npm-rebuild"><code>npm rebuild</code></a> で再ビルドします。</p>

<pre><code>{
  "scripts": {
    "postinstall": "npm run rebuild",
    "rebuild": "npm rebuild @swc/core sharp --ignore-scripts=false"
  }
}</code></pre>

<p><code>npm rebuild</code>はネイティブビルドのみを実行するため、依存パッケージの任意コードは動きません。 <br />
自動実行されるのはこのスクリプトだけで、何が動くかを自分たちで完全に管理できます。</p>

<p>なお、pnpmのようにホワイトリスト機能を持つパッケージマネージャーを使っている場合は <a href="https://pnpm.io/ja/blog/releases/10.19#versions-in-onlybuiltdependencies"><code>pnpm.onlyBuiltDependencies</code></a> で許可するパッケージを指定するだけで済みます。</p>

<h3>npm v12 からはホワイトリスト管理ができるように</h3>

<p>2026年7月リリース予定の npm v12 では <code>allowScripts</code> がデフォルト無効となり、スクリプト実行が必要なパッケージは <code>npm approve-scripts</code> コマンドでホワイトリスト管理できるようになります。</p>

<p>現時点では <code>ignore-scripts=true</code> と <code>npm rebuild</code> を組み合わせたアプローチが現実的ですが、v12からは npm のホワイトリスト機能で管理できる見込みです。</p>

<p>（参考: <a href="https://github.blog/changelog/2026-06-09-upcoming-breaking-changes-for-npm-v12/">Upcoming breaking changes for npm v12</a>） </p>

<h2>対策2: Safe Chainでインストール時のリアルタイム検証を導入する</h2>

<p><code>ignore-scripts</code>はスクリプトの実行を止めますが、悪意あるパッケージがインストールされること自体は防げません。</p>

<p>一例として、<a href="https://github.com/AikidoSec/safe-chain">Safe Chain</a> のような、インストール前にパッケージを既知の脅威と照合し、マルウェアをブロックするツールの導入を推奨します。 
Safe Chain セットアップ後はnpm・npx・yarn・pnpm・pipなど対応するパッケージマネージャーに自動で保護が掛かり、普段の作業フローをそのまま維持できます。</p>

<h3>仕組み</h3>

<p>既知のマルウェアはインストール時にブロックします。 
加えて、公開後48時間以内のパッケージはデフォルトでブロックされます。 
マルウェアが検出・削除されるまでには数時間～数日のタイムラグがあります。その隙をカバーするためです。</p>

<p>公開後一定期間経過していないパッケージをブロックするだけなら npmの <a href="https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age"><code>min-release-age</code></a> や pnpmの <a href="https://pnpm.io/ja/settings#minimumreleaseage"><code>minimumReleaseAge</code></a> でも可能です。 </p>

<h2>対策3: パッケージのバージョンを固定する</h2>

<p>悪意あるパッケージが検出・削除されるより前に引き込んでしまうリスクを減らすには、前述のとおり、公開直後のバージョンはインストールしないことも有効ですが、意図せずインストールされてしまうことがないようにバージョンを固定することも効果的です。</p>

<h3>lockfileをGit管理しnpm ciでのインストールを厳守する</h3>

<p>package.jsonに記載したパッケージのバージョンが範囲指定されている（<code>^</code> や <code>~</code> を使っている）場合、インストールのたびにバージョンが変動する可能性があるため、その時点で該当したバージョンが悪意あるバージョンであっても意図せず引き込んでしまう可能性があります。</p>

<p><a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json">package-lock.json</a>は、依存パッケージの正確なバージョンを記録したファイルです。 <br />
Gitにコミットして、インストールには<code>npm install</code>ではなく<a href="https://docs.npmjs.com/cli/v8/commands/npm-ci"><code>npm ci</code></a>を使うことで、記録されたバージョンだけをインストールするため、意図しないバージョンのインストールリスクを防げます。</p>

<h2>対策4: 秘匿情報の権限を最小限に絞る</h2>

<p>Shai-Huludで被害が拡大した一因は、広いスコープの秘匿情報がCI/CD環境に置かれていたことです。 
秘匿情報を盗まれると、それを使って別のパッケージに悪意あるコードを仕込み、感染を広げる踏み台にされてしまいます。</p>

<p>トークンの権限を用途ごとに必要最小限のスコープで分けて発行することが有効です。</p>

<p>そのためには、依存関係のインストール・テスト・成果物の作成・デプロイといった、必要な権限が異なる処理を同一ジョブにまとめず分離することを推奨します。 
特にデプロイ系のジョブは、ビルド・テスト系ジョブとは分けるのが基本です。</p>

<p>ジョブを分離することで、次の3つのメリットが得られます。</p>

<ul>
<li>影響範囲の限定: あるジョブが侵害されても横展開を抑えられる</li>
<li>最小権限の徹底: 各ジョブに必要な権限だけを付与できる</li>
<li>監査しやすさ: どこで何を実行したか追跡しやすくなる</li>
</ul>

<h2>対策5: pnpmへの移行を検討する</h2>

<p>pnpmはv10から、依存パッケージの<code>postinstall</code>スクリプトをデフォルトで無効にしています。 
対策1で紹介した<code>ignore-scripts</code>と同じ効果が、設定なしで最初から有効になっているわけです。</p>

<p>さらに、スクリプト実行を許可するパッケージの明示的なホワイトリスト管理や、公開直後のパッケージのブロック（「対策3」相当）、外部ソースからの依存禁止など、この記事で紹介した対策の多くがビルトインで備わっています。npmから移行するだけで、セキュリティの土台が一段上がります。</p>

<p>既存プロジェクトへの影響もあるため、すぐに全面移行する必要はありません。 
新規プロジェクトから試してみるのがおすすめです。</p>

<p>（参考: <a href="https://pnpm.io/supply-chain-security">pnpm 公式ドキュメント</a>）</p>

<h2>まとめ</h2>

<p>当社では次のような取り組みを進めています。</p>

<h3>開発環境の統一</h3>

<p>セキュリティ設定の一貫性を高めます。 
Node.jsやパッケージマネージャーのバージョン管理ツールとしてmiseを導入し社内標準としているほか、パッケージマネージャーをpnpmに統一することも検討しています。 
チーム全員が同じセキュリティ設定を自動的に適用できるようになります。</p>

<h3>パッケージ使用状況の可視化</h3>

<p>各プロジェクトで使用しているパッケージとバージョンを一覧化しているため、侵害が発生した際に影響範囲をすぐに特定でき、対応を迅速に進めることができます。</p>

<p>この攻撃で改めて認識したのは、普段何気なく実行している<code>npm install</code>が、知らず知らずのうちに悪意あるコードを動かしてしまう入り口になりうる、ということです。 
その意識を持つだけで、日々の開発習慣は大きく変わります。</p>

<p>最初にはじめるとしたら、対策1（<code>ignore-scripts</code>の有効化）と対策2（Safe Chain の導入）を検討してみてください。 
どちらも比較的導入しやすく、<code>postinstall</code>スクリプトを介した攻撃への直接的な盾になります。</p>
]]>
    </content>
</entry>

<entry>
    <title>vLLMのStructured OutputでJSONパース失敗率を改善してみた</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202606/10_1330.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2943</id>

    <published>2026-06-10T13:30:05+09:00</published>
    <updated>2026-06-10T13:35:25+09:00</updated>

    <summary>AI活用の一環で、過去のコードレビュー指摘をナレッジ化するプロジェクトに取り組ん...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<p>AI活用の一環で、過去のコードレビュー指摘をナレッジ化するプロジェクトに取り組んでいます。</p>

<p>このプロジェクトでは、蓄積したレビューコメントをLLMでJSON形式に正規化し、その出力結果をクラスタリングする処理を構築しました。</p>

<p>具体的には、システムプロンプトで出力フォーマットを厳密に定義し、以下のようなJSONを返す構成です。</p>

<pre class="hljs-pre"><code>{
  "tags": ["css"],
  "summary": "transitionに変数を使用していない",
  "canonical": "use css variable",
  "reason": "transitionは`var(--TRANSITION)`をご使用お願いします"
}</code></pre>

<p>後段のクラスタリング処理はJSONが前提のため、パース失敗が起きると該当コメントがそのまま欠損になります。本記事では、この失敗をvLLMの<a href="https://docs.vllm.ai/en/stable/features/structured_outputs/">Structured Outputs</a>によって解消した方法を紹介します。</p>

<p>※ 記事執筆時点ではvLLM v0.21.0で動作確認を行っています。</p>
]]>
        <![CDATA[<h2>問題：プロンプト指示だけでは5%程度が落ちる</h2>

<p>700件以上のコメントに対して、プロンプト指示だけでクラスタリングをしようとしたところ、以下の結果になりました。</p>

<table>
  <thead>
    <tr>
      <th>項目</th>
      <th>件数</th>
      <th>割合</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>総コメント数</td>
      <td>727</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>JSONパース成功</td>
      <td>689</td>
      <td>94.8%</td>
    </tr>
    <tr>
      <td>JSONパース失敗</td>
      <td>38</td>
      <td>5.2%</td>
    </tr>
  </tbody>
</table>

<p>38件が後段処理に届きません。リトライで拾う方法もありますが、そもそも失敗させたくないと考え、根本的な解決策を探しました。</p>

<h2>解決策：vLLMのStructured Output</h2>

<p>このプロジェクトでは推論サーバーにvLLMを使用しており、vLLMが提供するStructured Output機能で根本的に解決できるのでは、と試してみました。</p>

<h3>Structured Outputとは</h3>

<p>vLLMが提供する機能で、<code>response_format.json_schema</code> パラメータに<a href="https://json-schema.org/">JSON Schema</a>を渡すことで、Constrained Decoding（制約付き復号）が有効になります。</p>

<p>通常のサンプリングではLLMはあらゆるトークンを生成できますが、Constrained Decodingではスキーマに合致するトークンだけに候補を絞って生成します。これにより、JSONスキーマに沿った出力を生成しやすくなります。</p>

<h2>実装</h2>

<h3>1. JSON Schemaを定数として定義する</h3>

<p>import群の直後にPython dictとして定義しておきます（Pydantic modelを利用する方法もありますが、今回はこちらで）。</p>

<pre class="hljs-pre"><code class="python">REVIEW_THREAD_SCHEMA: dict = {
    "type": "object",
    "properties": {
        "tags": {
            "type": "array",
            "items": {
                "type": "string",
                "enum": ["a11y", "css", "html", "javascript", "performance",
                         "coding", "design", "security", "other"],
            },
        },
        "summary": {"type": "string"},
        "canonical": {"type": "string"},
        "reason": {"type": "string"},
    },
    "required": ["tags", "summary", "canonical", "reason"],
}</code></pre>

<p><code>tags</code>は<code>enum</code>で選択肢を制限しています。</p>

<h3>2. APIリクエストに<code>response_format</code>を追加する</h3>

<pre class="hljs-pre"><code class="python">def call_llm(messages: list) -> dict:
    body = {
        "model": MODEL_NAME,
        "messages": messages,
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "review_thread",
                "schema": REVIEW_THREAD_SCHEMA,
            },
        },
    }
    return body</code></pre>

<h2>結果</h2>

<p>プロンプト指示だけのケースと同じコメントに対して、改めて検証しました。</p>

<table>
  <thead>
    <tr>
      <th>指標</th>
      <th>導入前</th>
      <th>導入後</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>総コメント数</td>
      <td>727</td>
      <td>727</td>
    </tr>
    <tr>
      <td>成功率</td>
      <td>94.8%</td>
      <td>100.0%</td>
    </tr>
    <tr>
      <td>失敗件数</td>
      <td>38</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p>失敗が0件になりました。リトライ処理も不要になります。</p>

<h2>Tips：バックエンドの選択</h2>

<p>Structured Outputを使う際は、サーバー側のバックエンド設定も確認しておくとよいです。</p>

<h3><code>--structured-outputs-config.backend</code> フラグとは</h3>

<p>Constrained Decodingの処理エンジンは、<code>vllm serve</code>起動時の<code>--structured-outputs-config.backend</code>フラグで切り替えられます。</p>

<pre class="hljs-pre"><code class="bash">vllm serve &lt;model&gt; --structured-outputs-config.backend xgrammar</code></pre>

<p><a href="https://github.com/vllm-project/vllm/blob/469f3dcf/vllm/config/structured_outputs.py">ソースコード</a>を確認した限り指定できるバックエンドは以下のとおりです。</p>

<table>
  <thead>
    <tr>
      <th>バックエンド</th>
      <th>特徴</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://github.com/mlc-ai/xgrammar">xgrammar</a></td>
      <td>GPU上で文法処理を行う新しい実装。高速</td>
    </tr>
    <tr>
      <td><a href="https://github.com/guidance-ai/llguidance">guidance</a></td>
      <td>事前計算なしで安定した処理速度</td>
    </tr>
    <tr>
      <td><a href="https://github.com/dottxt-ai/outlines">outlines</a></td>
      <td>実績のある従来実装。幅広いスキーマに対応</td>
    </tr>
    <tr>
      <td><a href="https://github.com/noamgat/lm-format-enforcer">LMFE</a></td>
      <td>Python re互換のregexに対応。</td>
    </tr>
  </tbody>
</table>

<h3>デフォルト（<code>auto</code>）の挙動</h3>

<p>デフォルトは<code>auto</code>で、vLLMが利用可能なバックエンドの中から自動で選択します。執筆時では<code>xgrammar</code>→<code>guidance</code>→<code>outlines</code>の優先順位で選択されます。LMFEは明示的なフラグ指定のみ利用可能、公式のドキュメントにも言及がない状態でした。</p>

<p>基本的にはデフォルトのままで問題ありませんが、挙動を固定したい場合（再現性の確保、特定バックエンドのチューニング等）は明示的に指定するのがよいでしょう。</p>

<h2>まとめ</h2>

<p>「JSONで返して」とプロンプトに書くだけでは、どうしても数%の失敗が残ります。vLLMのStructured Outputを使えば、推論エンジン側でスキーマ準拠を強制できるため、この問題を根本から解消できます。</p>

<p>JSONを前提とした後段処理がある場合や、できるだけパース成功率を上げたい場合は、Structured Outputを使うことが有効だと感じました。</p>
]]>
    </content>
</entry>

<entry>
    <title>エディタ常駐型とCLIエージェント型AIコーディングツールの使い分け</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/x-tech/202606/02_1044.html" />
    <id>tag:www.mitsue.co.jp,2026:/knowledge/blog/x-tech//28.2914</id>

    <published>2026-06-02T10:44:59+09:00</published>
    <updated>2026-06-02T10:49:58+09:00</updated>

    <summary>はじめに AIコーディングツールは、大きく「エディタ常駐型の補完アシスタント」と...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/x-tech/">
        <![CDATA[<h2>はじめに</h2>

<p>AIコーディングツールは、大きく「エディタ常駐型の補完アシスタント」と「CLIベースのエージェント型ツール」という2つのアプローチに分かれます。本記事では、前者の代表例としてGitHub Copilot（エディタ補完機能）、後者の代表例としてClaude Code（CLIエージェント）を取り上げ、それぞれの特性と開発者としての使い分けを整理します。</p>

<p>※ GitHub CopilotにはエージェントモードやCLIモードも存在しますが、本記事では「エディタ常駐型」と「CLIエージェント型」の使い分けを主題にするため、主にエディタ補完・アシスタント機能としてのGitHub Copilotを扱います。</p>
]]>
        <![CDATA[<h2>2つのアプローチ</h2>

<h3>エディタ常駐型の代表例：GitHub Copilot</h3>

<p>GitHubがMicrosoft・OpenAIと共同開発したAIコーディング補助ツール。VS Codeや<a href="https://plugins.jetbrains.com/plugin/17718-github-copilot--your-ai-pair-programmer">JetBrains系IDEなどに統合</a>され、コーディング中にリアルタイムで補完・提案を行います。GitHubエコシステム（PR・Issue）との統合も強みです。</p>

<p>エディタから離れずに利用できるため、コーディングの流れを止めないのが特性です。</p>

<h3>CLIエージェント型の代表例：Claude Code</h3>

<p>AnthropicのClaude AIをベースにしたターミナル操作型のAIエージェント。コードの生成にとどまらず、ファイルの読み書き・コマンド実行・プロジェクト全体の把握・複雑なタスクの自律実行が可能です。</p>

<p>プロジェクト全体を「見て」「動かせる」、長い指示・複数ファイルの操作が得意なのが特性です。</p>

<h2>機能比較</h2>

<table>
  <thead>
    <tr>
      <th>比較項目</th>
      <th>エディタ常駐型の補完アシスタント</th>
      <th>CLIベースのエージェント型ツール</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>代表例</td>
      <td>GitHub Copilotのエディタ補完機能</td>
      <td>Claude Code</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>
    <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>

<h2>使い分けの判断基準</h2>

<h3>エディタ常駐型が向いている場面</h3>

<ul>
<li>コーディング中のリアルタイム補完（関数・クラス・テストコードなど）</li>
<li>定型コード（boilerplate、CRUD処理）の高速生成</li>
<li>コードコメント・JSDocの生成</li>
<li>「この関数は何をしているか」など短い質問への回答</li>
<li>PRレビューコメントの生成・Issueへの対応</li>
</ul>

<p>一言で言うと、エディタに常駐する補完エンジンとGitHubワークフローのアシスタントとして使うのに向いています。</p>

<h3>CLIエージェント型が向いている場面</h3>

<ul>
<li>機能の丸ごと実装（「ログイン機能を実装して」→ 複数ファイルを横断して変更）</li>
<li>プロジェクト全体のリファクタリング</li>
<li>バグ調査・根本原因の追跡と修正</li>
<li>仕様書・ドキュメントの生成（コードから逆引き、または要件から生成）</li>
<li>テストコードの一括生成</li>
<li>複数ファイルにまたがる変更（APIの追加に伴うルーティング・コントローラー・ドキュメントの一括更新）</li>
</ul>

<p>一言で言うと、複雑な指示をもとにプロジェクト全体を動かす作業に向いています。</p>

<h2>開発フローでの活用例</h2>

<table>
  <thead>
    <tr>
      <th>シーン</th>
      <th>向いているアプローチ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>日常のコーディング（補完重視）</td>
      <td>エディタ常駐型</td>
    </tr>
    <tr>
      <td>新機能の設計・実装（複数ファイル）</td>
      <td>CLIエージェント型</td>
    </tr>
    <tr>
      <td>PRレビューコメントの作成</td>
      <td>どちらも可</td>
    </tr>
    <tr>
      <td>テストコードの自動生成</td>
      <td>CLIエージェント型</td>
    </tr>
    <tr>
      <td>コード説明・ドキュメント追記</td>
      <td>どちらも可</td>
    </tr>
    <tr>
      <td>デバッグ・根本原因の調査</td>
      <td>CLIエージェント型</td>
    </tr>
  </tbody>
</table>

<h2>よくある質問</h2>

<p>Q. エディタ常駐型とCLIエージェント型の両方を同時に使う意味はあるか？</p>

<p>あります。エディタ常駐型はコーディング中の補完に、CLIエージェント型は複雑なタスクや複数ファイルにまたがる作業に使うという使い分けが効果的です。</p>

<p>Q. CLIエージェント型を使うにはプログラミングの知識が必要か？</p>

<p>基本的なターミナル操作（コマンドを入力して実行する）ができれば利用可能です。自然言語での指示で多くの作業を依頼できます。</p>

<p>Q. セキュリティ面での注意点は？</p>

<p>機密情報・個人情報・APIキーなどをAIに貼り付けることは避けてください。いずれのツールも入力内容がクラウドに送信されます。</p>

<h2>まとめ：選び方の基準</h2>

<ul>
<li>コードを書きながらリアルタイムで補完したい → エディタ常駐型</li>
<li>GitHubのPR・IssueフローでAIを使いたい → エディタ常駐型</li>
<li>プロジェクト全体を把握させて複雑な作業を任せたい → CLIエージェント型</li>
<li>複数ファイルにまたがる実装・リファクタを任せたい → CLIエージェント型</li>
<li>仕様書・ドキュメントを自動生成したい → CLIエージェント型</li>
</ul>
]]>
    </content>
</entry>

</feed>