<?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/">
    <channel>
        <title>Darasa.jp Blog</title>
        <link>https://darasa.jp/dev</link>
        <description>Darasa.jp Blog</description>
        <lastBuildDate>Sun, 20 Sep 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>ja-JP</language>
        <item>
            <title><![CDATA[AIエージェントの規約と外部スキルを、リポジトリで管理する]]></title>
            <link>https://darasa.jp/dev/agent-rules-in-repo</link>
            <guid>https://darasa.jp/dev/agent-rules-in-repo</guid>
            <pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[CLAUDE.md に「絶対に守ること」を番号付きで書き、コードのコメントとテストから参照する。3種類のエージェントでスキルを共有し、他人のスキルは skills-lock.json でハッシュ固定する。その運用の記録です。]]></description>
            <content:encoded><![CDATA[<p>このサイトは、教材だけでなくコードもかなりの部分を AI と書いています。</p>
<p>やってみて分かったのは、<strong>AIの出力の質は、リポジトリ側の準備でかなり変わる</strong>ということでした。</p>
<p>同じモデルでも、リポジトリに何が置いてあるかで結果が違う。プロンプトを工夫するより、<strong>守ってほしいことをリポジトリに置くほうが効きます。</strong></p>
<p>いま置いているものを書きます。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="絶対に守ることに番号を振る">「絶対に守ること」に番号を振る<a href="https://darasa.jp/dev/agent-rules-in-repo#%E7%B5%B6%E5%AF%BE%E3%81%AB%E5%AE%88%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AB%E7%95%AA%E5%8F%B7%E3%82%92%E6%8C%AF%E3%82%8B" class="hash-link" aria-label="「絶対に守ること」に番号を振る への直接リンク" title="「絶対に守ること」に番号を振る への直接リンク" translate="no">​</a></h2>
<p><code>CLAUDE.md</code> が414行あります。その半分近くが禁止事項です。</p>
<p>大事なのは中身より、<strong>番号が振ってあること</strong>です。</p>
<div class="language-md codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-md codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> 絶対に守ること</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> A. 今の段階で新しく加わるもの</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">####</span><span class="token title important"> A-1. card_id を安定させる。これが唯一の取り返しのつかない失敗</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">####</span><span class="token title important"> A-2. レビューログを localStorage に入れない</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">####</span><span class="token title important"> A-3. レビューログは追記専用。編集も削除もしない</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">####</span><span class="token title important"> A-4. エクスポートは version 2。既存形式との相互互換を壊さない</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> B. 前の段階から引き継ぐ禁止事項（変更なし）</span><br></div></code></pre></div></div>
<p>番号があると、<strong>コードのコメントから参照できます。</strong></p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * ハラカ表示の設定を無視して、母音記号を必ず残す（CLAUDE.md A-6）。</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> *</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * ⚠️ 逆方向の例外は作らない。</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">forceHarakat</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>これがあると、数か月後の自分も、AIも、「なぜこうなっているか」を辿れます。<strong>規約の本文をコードに書き写す必要がなくなる</strong>のが大きい。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="禁止の理由を必ず一緒に書く">禁止の理由を、必ず一緒に書く<a href="https://darasa.jp/dev/agent-rules-in-repo#%E7%A6%81%E6%AD%A2%E3%81%AE%E7%90%86%E7%94%B1%E3%82%92%E5%BF%85%E3%81%9A%E4%B8%80%E7%B7%92%E3%81%AB%E6%9B%B8%E3%81%8F" class="hash-link" aria-label="禁止の理由を、必ず一緒に書く への直接リンク" title="禁止の理由を、必ず一緒に書く への直接リンク" translate="no">​</a></h2>
<p>やってみて一番効いたのがこれです。</p>
<p>「〜するな」だけ書くと、AIは別の方法で同じことをやります。禁止した行為の<strong>目的</strong>が分かっていないからです。</p>
<p>だから理由をセットで書きます。</p>
<blockquote>
<p><strong>A-2. レビューログを <code>localStorage</code> に入れない</strong></p>
<p><code>localStorage</code> の実効上限は多くのブラウザで 5 MB 前後。レビューログは5万件で約 5 MB に達する。<strong>数年使った利用者から順に壊れる。</strong> しかも同期 API なので、数 MB の読み書きのたびに UI が固まる。</p>
</blockquote>
<p>ここまで書いておくと、AIは「じゃあ IndexedDB ですね」と自分で言います。理由が分かっていれば、こちらが指定していないケースでも正しい側に倒れる。</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>備考</div><div class="admonitionContent_BuS1"><p>これは人間の同僚に説明するときと同じでした。</p><p><strong>「ダメ」より「壊れる理由」のほうが、圧倒的に伝わる。</strong> AIを相手にすると、そのことがはっきり可視化されます。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="規約はできる限りテストに落とす">規約は、できる限りテストに落とす<a href="https://darasa.jp/dev/agent-rules-in-repo#%E8%A6%8F%E7%B4%84%E3%81%AF%E3%81%A7%E3%81%8D%E3%82%8B%E9%99%90%E3%82%8A%E3%83%86%E3%82%B9%E3%83%88%E3%81%AB%E8%90%BD%E3%81%A8%E3%81%99" class="hash-link" aria-label="規約は、できる限りテストに落とす への直接リンク" title="規約は、できる限りテストに落とす への直接リンク" translate="no">​</a></h2>
<p>文章で書いただけの規約は守られません。</p>
<p>たとえば「トップレベルでブラウザの API を触らない」という規約があります。Docusaurus はビルド時にサーバー側で React を実行するので、<code>window</code> や <code>indexedDB</code> をモジュールのトップレベルで触ると落ちるからです。</p>
<p>これは「気をつける」では守れないので、テストにしました。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">src/vocab/test/ssr.test.ts   ← 規約 B-4 に対応</span><br></div></code></pre></div></div>
<p>規約の番号とテストのファイル名を対応させておくと、<strong>規約が生きているかどうかが CI で分かります。</strong></p>
<p><strong>テストに落とせない規約は、規約として弱い。</strong> 落とせないなら、せめて検査コマンドを作る。それも無理なら、その規約は守られない前提で設計を考えます。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="スコープ外をはっきり書く">スコープ外を、はっきり書く<a href="https://darasa.jp/dev/agent-rules-in-repo#%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E5%A4%96%E3%82%92%E3%81%AF%E3%81%A3%E3%81%8D%E3%82%8A%E6%9B%B8%E3%81%8F" class="hash-link" aria-label="スコープ外を、はっきり書く への直接リンク" title="スコープ外を、はっきり書く への直接リンク" translate="no">​</a></h2>
<p>これも効きました。</p>
<div class="language-md codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-md codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> スコープ外（勝手に着手しない）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> オンライン同期・アカウント・Firebase（次の段階。SYNC-PLAN.md は将来の資料）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> 日本語 → アラビア語の産出方向の出題</span><br></div></code></pre></div></div>
<p>リポジトリの中に将来の計画書があると、AIは親切心で先回りします。「ついでに同期の下準備をしておきました」みたいなことが起きる。</p>
<p><strong>将来の資料は「将来の資料である」と書いておく。</strong> これだけで先回りが止まりました。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="順序を指定する">順序を指定する<a href="https://darasa.jp/dev/agent-rules-in-repo#%E9%A0%86%E5%BA%8F%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B" class="hash-link" aria-label="順序を指定する への直接リンク" title="順序を指定する への直接リンク" translate="no">​</a></h2>
<p>マイルストーンには順序があります。そして、その順序に<strong>理由</strong>があることがあります。</p>
<blockquote>
<p><strong>Q3（保存層とエクスポート）は Q4（復習画面）より先に完成させる。</strong></p>
<p>サーバを持たない構成では、エクスポートが唯一の生命線である。画面を先に作ると、復習が習慣になった頃に履歴を失う利用者が出る。</p>
</blockquote>
<p>画面から作るほうが楽しいし、AIに投げても画面のほうが早く形になります。だからこそ、<strong>順序を先に固定しておく必要がありました。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3つのエージェントで同じスキルを共有する">3つのエージェントで、同じスキルを共有する<a href="https://darasa.jp/dev/agent-rules-in-repo#3%E3%81%A4%E3%81%AE%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88%E3%81%A7%E5%90%8C%E3%81%98%E3%82%B9%E3%82%AD%E3%83%AB%E3%82%92%E5%85%B1%E6%9C%89%E3%81%99%E3%82%8B" class="hash-link" aria-label="3つのエージェントで、同じスキルを共有する への直接リンク" title="3つのエージェントで、同じスキルを共有する への直接リンク" translate="no">​</a></h2>
<p>いま3系統のエージェントを使い分けています。それぞれ設定を置く場所が違います。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">.claude/skills/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">.agents/skills/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">.junie/skills/</span><br></div></code></pre></div></div>
<p>同じスキルを3か所にコピーすると、必ずズレます。なので<strong>実体を1か所に置いて、あとはシンボリックリンク</strong>にしました。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">.agents/skills/grill-me/          ← 実体</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">.claude/skills/grill-me  -&gt;  ../../.agents/skills/grill-me</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">.junie/skills/grill-me   -&gt;  ../../.agents/skills/grill-me</span><br></div></code></pre></div></div>
<p><code>.agents/</code> を実体側にしているのは、特定のツールに寄せたくないからです。ツールを乗り換えても、リンクを張り替えるだけで済みます。</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>注記</div><div class="admonitionContent_BuS1"><p>シンボリックリンクなので <code>git</code> にもそのまま入ります。clone した人の環境でも同じ構造になる。</p><p>素朴なやり方ですが、いまのところこれで困っていません。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="skills-lockjson--プロンプトも依存関係である"><code>skills-lock.json</code> ─ プロンプトも依存関係である<a href="https://darasa.jp/dev/agent-rules-in-repo#skills-lockjson--%E3%83%97%E3%83%AD%E3%83%B3%E3%83%97%E3%83%88%E3%82%82%E4%BE%9D%E5%AD%98%E9%96%A2%E4%BF%82%E3%81%A7%E3%81%82%E3%82%8B" class="hash-link" aria-label="skills-lockjson--プロンプトも依存関係である への直接リンク" title="skills-lockjson--プロンプトも依存関係である への直接リンク" translate="no">​</a></h2>
<p>これがいちばん書きたかった話です。</p>
<p>公開されているスキル（エージェント向けの手順書）を使うことがあります。他人のリポジトリにある Markdown です。</p>
<p><strong>他人のリポジトリの中身は、いつ変わるか分かりません。</strong></p>
<p>コードなら誰でもそう考えるのに、プロンプトだと「まあ Markdown だし」と流してしまう。でも、それはエージェントの振る舞いを決める入力です。<strong>変われば結果が変わります。</strong></p>
<p>なので、ロックファイルを置きました。</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"skills"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"grill-me"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"source"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mattpocock/skills"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"sourceType"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"github"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"skillPath"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"skills/productivity/grill-me/SKILL.md"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"computedHash"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"f361db4e15e6bf..."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>やっていることは <code>package-lock.json</code> と同じです。<strong>どこから来たか、どのパスか、中身のハッシュは何か。</strong></p>
<p>更新するときは、ハッシュが変わったことに気づいてから、差分を読んで、意図的に上げる。黙って変わるのを防ぐのが目的です。</p>
<p><strong>プロンプトは依存関係です。</strong> だからロックする。この発想はもっと広まっていい気がしています。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="やってみて分かった限界">やってみて分かった限界<a href="https://darasa.jp/dev/agent-rules-in-repo#%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E9%99%90%E7%95%8C" class="hash-link" aria-label="やってみて分かった限界 への直接リンク" title="やってみて分かった限界 への直接リンク" translate="no">​</a></h2>
<p>正直なところも書いておきます。</p>
<p><strong>規約が増えると、読まれなくなります。</strong> 414行はもう限界に近い。全部を毎回読ませるより、いま触っている領域の規約だけを見せる形にしたい、と思いつつできていません。</p>
<p><strong>規約どうしの矛盾に、AIは気づきません。</strong> 段階が進んで古い規約が実質死んでいても、そのまま従おうとします。定期的に自分で読み返して消す必要があります。</p>
<p><strong>「なぜ」を書けない規約は、たいてい間違っています。</strong> 理由が書けないルールは、自分がまだ理解していないだけのことが多かったです。これは AI 相手というより、自分の設計の甘さが可視化される仕組みとして機能しています。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="まとめ">まとめ<a href="https://darasa.jp/dev/agent-rules-in-repo#%E3%81%BE%E3%81%A8%E3%82%81" class="hash-link" aria-label="まとめ への直接リンク" title="まとめ への直接リンク" translate="no">​</a></h2>
<ul>
<li class=""><strong>禁止事項に番号を振る。</strong> コードのコメントから参照できる</li>
<li class=""><strong>禁止の理由を必ず書く。</strong> 理由が分かれば、指定していないケースでも正しい側に倒れる</li>
<li class=""><strong>規約はテストに落とす。</strong> 落とせない規約は弱い</li>
<li class=""><strong>スコープ外と順序を明示する。</strong> 先回りが止まる</li>
<li class=""><strong>スキルの実体は1か所、あとはシンボリックリンク</strong></li>
<li class=""><strong>外部のスキルはハッシュで固定する。</strong> プロンプトも依存関係</li>
</ul>
<p>このサイトはアラビア語の学習教材です。よかったら<a class="" href="https://darasa.jp/docs/intro">中身も</a>見てみてください🙇</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[GPL v2 only の派生物を、リポジトリごと隔離した]]></title>
            <link>https://darasa.jp/dev/gpl-v2-isolation</link>
            <guid>https://darasa.jp/dev/gpl-v2-isolation</guid>
            <pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[アラビア語の形態素解析に使いたかった辞書が GPL v2 only でした。どこからが派生物なのかを整理し、生成器と生成物を別リポジトリに分けて、境界を Makefile に置くまでの記録です。]]></description>
            <content:encoded><![CDATA[<p>このサイトを作っていて、いちばん時間を使ったのは実装ではありませんでした。</p>
<p><strong>ライセンスです。</strong></p>
<p>コードを書いている時間より、「これは派生物なのか」を考えている時間のほうが長かった週があります。しかも日本語で書かれた実例がほとんど見つからなくて、かなり心細かった。</p>
<p>同じところで止まる人がいると思うので、判断の過程ごと残しておきます。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="何をしたかったか">何をしたかったか<a href="https://darasa.jp/dev/gpl-v2-isolation#%E4%BD%95%E3%82%92%E3%81%97%E3%81%9F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%8B" class="hash-link" aria-label="何をしたかったか への直接リンク" title="何をしたかったか への直接リンク" translate="no">​</a></h2>
<p>単語カードを自動で作りたかったんです。</p>
<p>教材に出てくる単語を拾って、原形（レンマ）を求めて、活用を展開して、訳語を付けて、間隔反復で復習できる形にする。この「原形を求める」ところに<strong>形態素解析</strong>が要ります。</p>
<p>アラビア語の形態素解析器で、実用になるものは多くありません。そのなかで使えそうだった辞書データが、<strong>GPL v2</strong> でした。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="or-laterが無いという一行">「or later」が無い、という一行<a href="https://darasa.jp/dev/gpl-v2-isolation#or-later%E3%81%8C%E7%84%A1%E3%81%84%E3%81%A8%E3%81%84%E3%81%86%E4%B8%80%E8%A1%8C" class="hash-link" aria-label="「or later」が無い、という一行 への直接リンク" title="「or later」が無い、という一行 への直接リンク" translate="no">​</a></h2>
<p>ここが最初の分かれ道でした。</p>
<p>多くの GPL のプロジェクトは「version 2 <strong>or (at your option) any later version</strong>」と書いています。この場合、利用者は GPLv3 を選べます。</p>
<p>使いたかった辞書の LICENSE は、こうなっていました。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">... as published by the Free Software Foundation version 2.</span><br></div></code></pre></div></div>
<p><strong>「version 2.」で止まっています。</strong></p>
<p>つまり GPL v2 <strong>only</strong>。GPLv3 に上げる選択肢がありません。</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>注記</div><div class="admonitionContent_BuS1"><p>最初これを読み飛ばしていました。「GPL v2 ね」と思って先に進んで、あとから「or later が無い」ことに気づいて全部やり直しています。</p><p><strong>ライセンスファイルは、最後の一行まで読んでください。</strong> 一語で結論が変わります。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cc-by-sa-40-は助けにならなかった">CC BY-SA 4.0 は助けにならなかった<a href="https://darasa.jp/dev/gpl-v2-isolation#cc-by-sa-40-%E3%81%AF%E5%8A%A9%E3%81%91%E3%81%AB%E3%81%AA%E3%82%89%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F" class="hash-link" aria-label="CC BY-SA 4.0 は助けにならなかった への直接リンク" title="CC BY-SA 4.0 は助けにならなかった への直接リンク" translate="no">​</a></h3>
<p>「CC BY-SA 4.0 は GPL 互換」という話を見かけて、一瞬これで抜けられるかと思いました。</p>
<p>抜けられません。<strong>CC BY-SA 4.0 の GPL 互換は GPLv3 に対する一方向</strong>で、GPLv2 は対象にならないからです。</p>
<p>GPL v2 only という条件は、思っていたよりずっと狭いところに人を追い込みます。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="どこからが派生物なのか">どこからが派生物なのか<a href="https://darasa.jp/dev/gpl-v2-isolation#%E3%81%A9%E3%81%93%E3%81%8B%E3%82%89%E3%81%8C%E6%B4%BE%E7%94%9F%E7%89%A9%E3%81%AA%E3%81%AE%E3%81%8B" class="hash-link" aria-label="どこからが派生物なのか への直接リンク" title="どこからが派生物なのか への直接リンク" translate="no">​</a></h2>
<p>ここが本題です。3つ問いがありました。</p>
<ol>
<li class="">解析結果から作った <code>cards.json</code> は派生物か</li>
<li class=""><code>cards.json</code> を読み込むサイト本体は派生物か</li>
<li class="">生成器のソースは「対応するソース」として公開が要るか</li>
</ol>
<p>1 は、実質的に辞書の見出し語一覧の複製になり得ます。<strong>派生物として扱いました。</strong></p>
<p>3 も明らかに要ります。GPL は「配布するなら、それを作れるソースも渡せ」という要求なので、生成器を公開しないと <code>cards.json</code> を配れません。</p>
<p>問題は 2 でした。<strong>サイト本体まで GPL に引きずられると、教材ごと全部が GPL になります。</strong> それは避けたい。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="分割の方向は1つしか成立しなかった">分割の方向は、1つしか成立しなかった<a href="https://darasa.jp/dev/gpl-v2-isolation#%E5%88%86%E5%89%B2%E3%81%AE%E6%96%B9%E5%90%91%E3%81%AF1%E3%81%A4%E3%81%97%E3%81%8B%E6%88%90%E7%AB%8B%E3%81%97%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F" class="hash-link" aria-label="分割の方向は、1つしか成立しなかった への直接リンク" title="分割の方向は、1つしか成立しなかった への直接リンク" translate="no">​</a></h2>
<p>「生成器だけ公開して、あとは分ける」——最初はそう考えました。</p>
<p>ところが解析器は Go で書いてあって、コマンド側が <code>internal/</code> 以下のパッケージに依存していました。</p>
<p><strong>Go の仕様上、<code>internal/</code> は他のモジュールから import できません。</strong></p>
<p>つまり「生成器のコマンドだけを別モジュールに切り出す」ことが構造上できない。切り出せるのは、逆側（API サーバー）だけでした。</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>備考</div><div class="admonitionContent_BuS1"><p>ライセンスの都合で分割の方向を決めようとしたのに、<strong>言語仕様のほうが先に方向を1つに固定していた</strong>、という話です。</p><p>こういうのは実際に手を動かすまで分かりませんでした。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最終的な構成">最終的な構成<a href="https://darasa.jp/dev/gpl-v2-isolation#%E6%9C%80%E7%B5%82%E7%9A%84%E3%81%AA%E6%A7%8B%E6%88%90" class="hash-link" aria-label="最終的な構成 への直接リンク" title="最終的な構成 への直接リンク" translate="no">​</a></h2>
<p>こうなりました。</p>
<table><thead><tr><th>リポジトリ</th><th>中身</th><th>ライセンス</th></tr></thead><tbody><tr><td>サイト本体</td><td>教材と Docusaurus のコード</td><td>—</td></tr><tr><td><code>darasa-vocab-builder</code></td><td>カード生成器</td><td>MIT</td></tr><tr><td><code>darasa-vocab-cards</code></td><td>生成されたカードと、生成の入力</td><td><strong>GPL v2</strong></td></tr><tr><td><code>darasa-gloss-ja</code></td><td>訳語</td><td><strong>GPL v2</strong></td></tr><tr><td><code>darasa-morph</code></td><td>解析器と台帳の生成</td><td>MIT</td></tr></tbody></table>
<p>守っている規律は3つです。</p>
<ul>
<li class=""><strong>サイト本体に <code>cards.json</code> をコミットしない</strong></li>
<li class=""><strong><code>npm run build</code> は <code>cards.json</code> を読まない</strong></li>
<li class=""><strong>実行時に、GPL のリポジトリから fetch する</strong></li>
</ul>
<p>サイトには出典表示を置いて、「対応するソース」へ辿れるようにしています。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="分けた理由は秘匿ではない">分けた理由は「秘匿」ではない<a href="https://darasa.jp/dev/gpl-v2-isolation#%E5%88%86%E3%81%91%E3%81%9F%E7%90%86%E7%94%B1%E3%81%AF%E7%A7%98%E5%8C%BF%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%84" class="hash-link" aria-label="分けた理由は「秘匿」ではない への直接リンク" title="分けた理由は「秘匿」ではない への直接リンク" translate="no">​</a></h3>
<p>これは自分への戒めとして書いておきます。</p>
<p>リポジトリを分けると、コミットメッセージに「private にするため」と書きたくなります。でも実際の理由は<strong>配布物として分けること</strong>です。</p>
<p>GPL のリポジトリは公開されていて、そこに「対応するソース」への道筋がある。隠すために分けたのではなく、<strong>混ぜないために分けた。</strong> ここを書き間違えると、あとから読む人（と未来の自分）が判断を誤ります。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="境界を-makefile-に置いた">境界を Makefile に置いた<a href="https://darasa.jp/dev/gpl-v2-isolation#%E5%A2%83%E7%95%8C%E3%82%92-makefile-%E3%81%AB%E7%BD%AE%E3%81%84%E3%81%9F" class="hash-link" aria-label="境界を Makefile に置いた への直接リンク" title="境界を Makefile に置いた への直接リンク" translate="no">​</a></h2>
<p>人間が「ここから先は混ぜない」と覚えておく方式は、いつか必ず破られます。</p>
<p>なので、境界をコマンドに落としました。</p>
<div class="language-makefile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-makefile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">## vocab-source: 教材語彙を darasa-vocab-cards に書き出す</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">##</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">## これが教材と生成器の境界である。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">## 決定的なので、2回実行すればバイト単位で一致する。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">vocab-source:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">	$(NODE) scripts/vocab-source/build.mjs</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">## vocab: 書き出し → 生成 → 検証 → コミット</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">##        生成そのものは builder が行う。cards.json はここには来ない。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">vocab: vocab-source</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">	$(MAKE) -C $(BUILDER_REPO) build verify commit</span><br></div></code></pre></div></div>
<p>サイト本体に残っているのは、<strong>教材の MDX から見出しを抜き出して TSV に書き出すところまで</strong>です。そこから先は別リポジトリの <code>make</code> に投げます。</p>
<p>さらに、規律が守られていることを検査するコマンドも置きました。</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">make vocab-source-verify   # MDX を書き換えていない / build が cards.json を読まない</span><br></div></code></pre></div></div>
<p><strong>チェックできない規律は、規律として弱い</strong>と思っています。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="諦めたもの">諦めたもの<a href="https://darasa.jp/dev/gpl-v2-isolation#%E8%AB%A6%E3%82%81%E3%81%9F%E3%82%82%E3%81%AE" class="hash-link" aria-label="諦めたもの への直接リンク" title="諦めたもの への直接リンク" translate="no">​</a></h2>
<p>ライセンスの都合で、機能を1つ落としています。</p>
<p>生成の過程で単語の<strong>頻度</strong>データを使っています。「どの語を、どの順で教材に入れるか」を決めるためです。</p>
<p>このデータもライセンス上グレーだったので、<strong><code>cards.json</code> に頻度を持ち込まないことにしました。</strong> ビルド時に選定へ使うだけで、成果物には残さない。</p>
<p>その結果、<strong>「あなたはコア語彙の何％をカバーしています」という指標が出せなくなりました。</strong> 学習アプリとしてはかなり欲しい機能です。</p>
<p>でも、カバレッジを出すために頻度データを配布物に入れるのは、線を越えます。<strong>機能のために線を動かさない</strong>と決めました。</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>注記</div><div class="admonitionContent_BuS1"><p>ここは今でも少し惜しいと思っています。</p><p>ただ、一度「このくらいならいいか」で線を動かすと、次からその位置が基準になります。判断を人に説明できなくなるほうが怖い。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="実行時-fetch-の代償">実行時 fetch の代償<a href="https://darasa.jp/dev/gpl-v2-isolation#%E5%AE%9F%E8%A1%8C%E6%99%82-fetch-%E3%81%AE%E4%BB%A3%E5%84%9F" class="hash-link" aria-label="実行時 fetch の代償 への直接リンク" title="実行時 fetch の代償 への直接リンク" translate="no">​</a></h2>
<p><code>cards.json</code> を別リポジトリから実行時に取ってくるので、代償もあります。</p>
<ul>
<li class="">初回表示で外部リクエストが1回増える</li>
<li class="">そのリポジトリが落ちるとカードが出ない</li>
<li class="">オフラインでは使えない</li>
</ul>
<p>正直なところ、ビルド時に取り込めたら楽でした。でもそれをすると、成果物にGPLのデータが同梱されます。<strong>取り込まないことが、そのまま境界の実装になっている</strong>ので、ここは受け入れています。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="まとめ">まとめ<a href="https://darasa.jp/dev/gpl-v2-isolation#%E3%81%BE%E3%81%A8%E3%82%81" class="hash-link" aria-label="まとめ への直接リンク" title="まとめ への直接リンク" translate="no">​</a></h2>
<ul>
<li class=""><strong>ライセンスは最後の一行まで読む。</strong>「or later」の有無で結論が変わります</li>
<li class=""><strong>どこからが派生物かを、先に紙の上で決める。</strong> コードを書き始めてからだと戻れません</li>
<li class=""><strong>言語仕様が分割の方向を決めることがある。</strong> Go の <code>internal/</code> がそうでした</li>
<li class=""><strong>境界は人の記憶ではなく、コマンドとテストに置く</strong></li>
<li class=""><strong>機能のために線を動かさない</strong></li>
</ul>
<p>同じ場所で止まっている人の役に立てば嬉しいです。</p>
<p>このサイト自体はアラビア語の学習教材なので、よかったら<a class="" href="https://darasa.jp/docs/intro">そちらも</a>どうぞ🙇</p>]]></content:encoded>
            <category>ライセンス</category>
        </item>
        <item>
            <title><![CDATA[開発記を始めます ─ このサイトを何で作っているか]]></title>
            <link>https://darasa.jp/dev/dev-blog-start</link>
            <guid>https://darasa.jp/dev/dev-blog-start</guid>
            <pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[darasa.jp の開発記を始めました。アラビア語学習サイトを個人で作る過程で考えたことと、その実装を書いていきます。学習者向けのブログとは別の場所です。]]></description>
            <content:encoded><![CDATA[<p>このサイトの開発について書く場所を作りました。</p>
<p><a class="" href="https://darasa.jp/blog">/blog</a> のほうはアラビア語を学ぶ人向けなので、技術の話を混ぜると読む人がねじれてしまう。なので分けることにしました。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ここに書くこと">ここに書くこと<a href="https://darasa.jp/dev/dev-blog-start#%E3%81%93%E3%81%93%E3%81%AB%E6%9B%B8%E3%81%8F%E3%81%93%E3%81%A8" class="hash-link" aria-label="ここに書くこと への直接リンク" title="ここに書くこと への直接リンク" translate="no">​</a></h2>
<p>darasa.jp を作る過程で、判断に迷ったところを書いていきます。</p>
<ul>
<li class="">間隔反復をサーバーなしで回すために決めたこと</li>
<li class="">学習の記録をブラウザだけに持つ設計と、その代償</li>
<li class="">アラビア語をWebで表示することの難しさ</li>
<li class="">OSS のライセンスの都合でリポジトリを分けた話</li>
<li class="">AI と一緒に開発するための、リポジトリ側の準備</li>
</ul>
<p>「やってみた」ではなく、<strong>なぜそう決めたか</strong>のほうを残しておきたいと思っています。数か月後の自分が読み返して、判断の理由が分かる状態にしておきたいので。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="このサイトについて">このサイトについて<a href="https://darasa.jp/dev/dev-blog-start#%E3%81%93%E3%81%AE%E3%82%B5%E3%82%A4%E3%83%88%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6" class="hash-link" aria-label="このサイトについて への直接リンク" title="このサイトについて への直接リンク" translate="no">​</a></h2>
<p>アラビア語（フスハー）を日本語で学べる学習サイトです。文字の読み方から上級文法まで87課、練習問題が1,122問。完全無料で、アカウント登録もありません。</p>
<p>技術構成はだいたいこんなところです。</p>
<ul>
<li class="">Docusaurus 3（MDX に演習コンポーネントを埋めている）</li>
<li class="">TypeScript</li>
<li class="">学習の進捗はブラウザに保存（サーバーもDBも持っていない）</li>
<li class="">単語の間隔反復は FSRS-6 を IndexedDB の上で回している</li>
</ul>
<p>詳しくは<a class="" href="https://darasa.jp/docs/intro">コース紹介</a>を見てください。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="書く順番">書く順番<a href="https://darasa.jp/dev/dev-blog-start#%E6%9B%B8%E3%81%8F%E9%A0%86%E7%95%AA" class="hash-link" aria-label="書く順番 への直接リンク" title="書く順番 への直接リンク" translate="no">​</a></h2>
<p>しばらくは週1本のつもりです。最初はライセンスの話と、AI との開発体制の話から書きます。</p>
<p>この2つが、作っていていちばん「他の人はどうしてるんだろう」と思った部分だったので。</p>]]></content:encoded>
            <category>Docusaurus</category>
        </item>
    </channel>
</rss>