こんにちは、OSSよろず相談室のSKです。 OSS に関するお問い合わせが日々寄せられる中で、今回も統合監視ツール「Zabbix」に関連して寄せられたお問い合わせをご紹介します。 これまで有償の管理製品を使って、各サーバーの「ホスト名」「メモリ」「CPU」「OS」などの構成情報を管理していたお客様から、次のようなご相談をいただきました。 「利用中の製品がサポート終了を迎えるため、代替手段として、すでに監視で導入しているZabbixのAPIを使って構成情報を取得できないか?」 結論から申し上げますと、Zabbix APIを利用すれば構成情報の一括取得が可能です。すでに監視ツールとしてZabbixを運用している場合、この収集プロセスを自動化・連携させることができます。 今回は、Zabbix APIの host.get メソッドを利用して、外部からサーバーの構成情報(インベントリ情報)を一括取得する具体的な手順とコマンド例を解説します。 Zabbix APIの基本仕様 Zabbix APIはHTTPベースで提供されており、クライアントとAPI間のリクエスト・レスポンスには JSON-RPC 2.0プロトコル を使用します。 APIを呼び出すためのエンドポイント(URL)は、Zabbix Webインターフェースのディレクトリにある api_jsonrpc.php です。デフォルトでは以下のパスにあります。 /usr/share/zabbix/ui/api_jsonrpc.php ステップ1:APIの認証とトークンの取得 Zabbixのデータにアクセスするには、まず認証を行ってAPIトークン(セッションID)を取得する必要があります。まだ認証されていない状態からのログインには、user.login メソッドを使用します。 以下の curl コマンドを使用して、Zabbixサーバーに認証リクエストを送信します。 ※ここでは、デフォルトの管理ユーザーである “Admin” のトークンを取得する例を紹介します。 【コマンド例】 curl -X POST -H "Content-Type: application/json" -d ' { "jsonrpc": "2.0", "method": "user.login", "params": { "username": "Admin", "password": "zabbix" }, "id": 1 }' http://172.31.34.176/zabbix/api_jsonrpc.php 認証情報が正しければ、APIから以下のようなJSONレスポンスが返されます。 【取得結果例】 { "jsonrpc": "2.0", "result": "82aa1b5b38a9fe6f6619ad40c64602dd", "id": 1 } ここで result として返された値(82aa1b5b38a9fe6f6619ad40c64602dd)が認証トークンです。 以後のAPIリクエストでは、このトークンを使用します。 ステップ2:構成情報(インベントリ情報)の取得 認証トークンが取得できたら、次に監視対象ホストの構成情報を取得します。ホスト情報の取得には host.get メソッドを使用します。 ホスト名やIPアドレスだけでなく、CPU、メモリ、OSなどの構成情報(インベントリデータ)を同時に取得するため、パラメータに selectInventory を指定します。また、すべてのデータを取得するとパフォーマンスに影響を与える可能性があるため、output パラメータを使って取得したいプロパティを明示的に絞り込むことが推奨されています。 先ほど取得したトークンは、HTTPリクエストの Authorization ヘッダーに指定します。以下は、検証環境(IPアドレス: 172.31.34.176)に対してコマンドを実行した例です。 ※コマンドの末尾に | jq を付けると結果が整形されて見やすくなります(要jqコマンドのインストール。RHEL9.4以降ではBaseOSに標準含まれています)。 【コマンド例】 curl -X POST -H 'Content-Type: application/json-rpc' -H 'Authorization: Bearer 82aa1b5b38a9fe6f6619ad40c64602dd' -d ' { "jsonrpc": "2.0", "method": "host.get", "params": { "output": ["hostid", "host", "name"], "selectInventory": ["os", "hardware", "software"] }, "id": 2 }' http://172.31.34.176/zabbix/api_jsonrpc.php | jq 【コマンド解説】 curlコマンドの全体 項目 内容 curl Webサーバーと通信を行うためのコマンドラインツール -X POST HTTPメソッドを「POST」に指定(Zabbix APIは必ずPOSTを使用します) -H ‘Content-Type: application/json-rpc’ 送信するデータがJSON-RPC形式であることをZabbixサーバに伝えるヘッダ -H ‘Authorization: Bearer <トークン>’ 取得した認証トークンを指定するヘッダ -d ‘{ … }’ Zabbixへ送信するリクエストの実データ(JSON) http://…/zabbix/api_jsonrpc.php リクエストの送信先となるZabbixサーバーのエンドポイントURL | jq 出力されるJSONデータを見やすく改行・色付けして整形するコマンド 送信するJSONデータ(-d の中身) キー (” “) 指定している値 意味・役割 jsonrpc “2.0” JSON-RPCプロトコルのバージョン(”2.0″ 固定) method “host.get” 実行したいAPIの操作名。今回はホストデータの取得を指定 params { … } 検索条件や出力フォーマットなどを定義する引数 output [“hostid”, “host”, “name”] 返却されるホストオブジェクトのフィールドを限定(全項目取得時は “extend”) selectInventory [“os”, “hardware”, “software”] ホストに紐づくインベントリ情報を同時に取得するためのパラメータ id 2 リクエストとレスポンスを紐づける任意の識別子 以下が取得結果です。 【取得結果】 { "jsonrpc": "2.0", "result": [ { "hostid": "10084", "host": "Zabbix server", "name": "Zabbix server", "inventory": { "os": "Linux version 6.12.0-211.22.1.el10_2.x86_64 (mockbuild@df33c0284dd848a197e916486944b54f) (gcc (GCC) 14.3.1 20251022 (Red Hat 14.", "hardware": "", "software": "" } }, { "hostid": "10782", "host": "WebServer1", "name": "WebServer1", "inventory": [] } ], "id": 2 } stuser この検証環境では、監視対象に “Zabbix server” と “WebServer1” の2つが存在しています。 検証環境のZabbixサーバのホスト画面 “WebServer1” はWeb監視(URL外形監視など)のみを行っており、Zabbix Agentが導入されていないため、上記の結果のようにインベントリ情報が空([])になります。 項目を絞り込まずにすべての情報を取得したい場合は、”output” や “selectInventory” に “extend” を指定します。また、IPアドレスなどのネットワーク情報を取得したい場合は “selectInterfaces”: “extend” を追加します。 【コマンド例】 $ curl -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer 7a6237f38929523f74cfb0acf8566f7f' -d ' { "jsonrpc": "2.0", "method": "host.get", "params": { "output": "extend", "selectInterfaces": "extend", "selectInventory": "extend" }, "id": 2 }' http://172.31.34.176/zabbix/api_jsonrpc.php | jq 取得できる項目の詳細は公式サイトをご参照ください。 Zabbix ドキュメント / 20 API https://www.zabbix.com/documentation/current/jp/manual/api Zabbix ドキュメント / 20 API / host.get https://www.zabbix.com/documentation/current/jp/manual/api/reference/host/get 【補足】APIの認証とトークンの取得 セッションの破棄(ログアウト) ステップ1で紹介した user.login メソッドで取得したトークンは、使い終わったら必ず user.logout を実行してセッションを破棄してください。破棄せずに放置すると、不要なセッションデータがデータベースに蓄積され、Zabbixのパフォーマンス低下を招く原因になります。 【セッション破棄のコマンド例】 $ curl -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer 1da473d0437c7bf69a22e6e057ffc1d7' -d ' { "jsonrpc": "2.0", "method": "user.logout", "params": [], "id": 3 }' http://172.31.34.176/zabbix/api_jsonrpc.php | jq { "jsonrpc": "2.0", "result": true, "id": 3 } 管理画面から発行する「APIトークン」の利用 毎回ログイン・ログアウト処理を行うのが手間に感じる場合は、Zabbixの管理画面から有効期限を設定できる「APIトークン」を事前に発行して利用する方法がおすすめです。 「ユーザー設定」→「APIトークン」を選択し、「APIトークンの作成」を選択します。 【APIトークン画面】 【新規APIトークン画面】 ユーザは、ここではデフォルトのAdminを選択し、有効期限を設定しています。 【APIトークン表示画面】 前画面で追加ボタンを押すと、以下のように認証トークンが表示されます。 画面を一度閉じると二度と再表示できないため、必ず閉じる前に値を控えておきましょう。 まとめ Zabbixが標準で収集しているインベントリデータとZabbix APIを組み合わせれば、有償製品を別途導入しなくても、各サーバーの構成情報を一括取得・管理できます。 また、Zabbix APIは情報の取得だけでなく、ホストやアイテム、トリガーの自動作成なども行えるため、日々の監視設定そのものをスクリプトで自動化することも可能です。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post OSSサポートの現場から!Zabbixでサーバの構成情報を取得する first appeared on SIOS Tech Lab .
AIの使い方を学ぶより先に、ソフトウェア工学を理解しないといけないんじゃないかと思います。 AIにテストコードを書かせた場合を例に説明します。AIはテストコードを高速で書いてくれますが、テスト理論を理解していないと、バグを見逃すテストコードを書かせてしまうことがあります。 テストの話(例として) 先に、今回の話の土台になるテスト理論を簡単に説明しておきます。わかりやすい例として、テストの技法で有名な以下の2つを挙げたいと思います。 ブラックボックステスト は、コードの中身を見ずに、仕様(入力→出力)だけを基準にテストする方法です。「この入力を与えたら、仕様どおりの結果が返るか」を確認します。境界値分析や同値分割といった技法はこちら側です。 ホワイトボックステスト は、コードの内部構造(分岐、ループ、処理経路)を見てテストする方法です。「このif文はtrue側もfalse側も通したか」を確認します。どれだけ通せたかはカバレッジという数値で測れます。 ここで大事なのは、ホワイトボックスといっても「コードを基準にしていいのはテストの経路の選択だけ」という点です。 結果が正しいかどうかの判定(期待値)は、必ず仕様から立てる 必要があります。コードを見て「コードがこう動くから、これが正解」としてしまうと、バグごとテストが追認してしまうからです。 どこをテストするか(経路) → コードから決めてよい 結果が正しいか(期待値) → 仕様から決める この分離が今回の話の核心です。 理論を知らないと、AIをうまく使えないときがある AIはテストコードをものすごい速さで書いてくれます。ただ、指示の出し方を間違えると「全部パスしているのにバグを見逃すテスト」を量産します。しかも数字の上では完璧に見えるので、見逃したことに気づけません。 実際にやってみたので、失敗例と成功例を並べます。 題材のコード 料金計算の関数です。仕様はこうだとします。 10,000円以上は10%オフ。会員はさらに5%オフ。 def charge ( amount : int , is_member : bool ) - > float : """料金計算""" if amount < 0 : # 仕様書に記載のない分岐(実装者が独自に追加) return 0 fee = float ( amount ) if amount > 10000 : # ★バグ: 仕様は「以上(>=)」なのに > にしている fee = amount * 0.9 if is_member : fee = fee * 0.95 return fee わざとバグを仕込んであります。仕様は「10,000円以上」なのに、実装は > (超)になっている。つまり10,000円ちょうどのとき、割引されるべきなのにされません。境界値のバグとしてはかなりありがちなやつです。 おまけに、仕様書には書かれていない負数チェックの分岐も入れてあります。実装者が気を利かせて勝手に足した、という想定です。 失敗例:テスト理論を知らずに指示を出すと 極端な例なのですが、まず、理論を意識せずにこう頼んだとします。 このコードのテストを書いて。カバレッジ100%にして。 AIはコードを読んで、こういうテストを書いてきます。 from charge import charge def test_normal ( ) : # コードを読んで期待値を計算: 15000 > 10000 → *0.9 → 13500 assert charge ( 15000 , False ) == 13500.0 def test_member ( ) : # 15000*0.9*0.95 = 12825 assert charge ( 15000 , True ) == 12825.0 def test_boundary_10000 ( ) : # コード上 10000 は「> 10000」に該当しない → 割引なし、と読める assert charge ( 10000 , False ) == 10000.0 # ← バグを"正解"として追認 def test_negative ( ) : # コードにそういう分岐があるので、それを期待値に assert charge ( - 100 , False ) == 0 問題は test_boundary_10000 です。AIはコードから期待値を逆算するので、「10000は > 10000 に該当しない。だから割引なしが正しい」と解釈します。 バグの動きを”正解”としてテストに固めてしまっている わけです。 実行結果がこれです。 4 passed branch coverage 100% 全テストパス、分岐カバレッジ100%。数字だけ見れば完璧です。でも境界値バグの検出はゼロ。指標がすべて緑なので、この見逃しに気づく手段がありません。個人的には、テストが落ちるより、この「完璧に見える」状態のほうがよっぽど怖いと思っています。 成功例:テスト理論を分かった上で指示を出すと 今度は、さっきの「経路はコードから、期待値は仕様から」という分離をプロンプトに落とし込みます。 以下の手順でテストコードを作成してください。 【カバレッジ設計:ソースを参照】 - ソースの全分岐(true/false)・例外パスを洗い出し、 各経路を通すための入力条件を列挙する(分岐網羅100%目標) 【期待値設計:仕様書のみ参照】 - 各ケースの期待値は必ず仕様書から導出する - ソースコードの出力を期待値に流用することは禁止 - 境界値分析を適用し、境界(10,000円)の直前・ちょうど・直後を必ず試す - 仕様書に記載がなく期待値を判断できないケースは、 期待値を書かず「要確認」として質問リストに出す 【出力】 - 各テストに「どの分岐を通すか(ソース由来)」 「期待値の根拠(仕様書のどこか)」をコメントで明記 仕様書は以下となります。 10,000円以上は10%オフ。会員はさらに5%オフ。 このプロンプトでAIが書くテストはこうなります。 from charge import charge # 仕様書: 「10,000円以上は10%オフ。会員はさらに5%オフ。」 def test_under_boundary ( ) : # 仕様: 9,999円は割引なし assert charge ( 9999 , False ) == 9999.0 def test_boundary_exactly_10000 ( ) : # 仕様: 10,000円「以上」なので 10000 は10%オフ → 9000 assert charge ( 10000 , False ) == 9000.0 # ← 期待値は仕様から def test_over_boundary ( ) : assert charge ( 10001 , False ) == 10001 * 0.9 def test_member_discount ( ) : assert charge ( 20000 , True ) == 20000 * 0.9 * 0.95 def test_member_under_10000 ( ) : assert charge ( 5000 , True ) == 5000 * 0.95 # 【要確認】ソースに amount < 0 の分岐があるが、仕様書に負数の扱いの記載なし。 # 期待値をでっち上げず、仕様確認事項として報告: # Q. 負の金額は入力され得るか? その場合の仕様は?(現実装は0を返す) 実行結果です。 FAILED test_boundary_exactly_10000 - assert 10000.0 == 9000.0 1 failed, 4 passed branch coverage 87% 境界値10,000円ちょうどのテストが落ちました。 バグ検出成功です。 面白いのはカバレッジのほうで、今度は87%と満点ではありません。未達なのは負数チェックの分岐です。ここでプロンプトの「仕様書にないケースは要確認として出せ」が効いていて、AIは負数分岐の期待値をでっち上げず、「負の金額の仕様は?」という質問として返してきました。カバレッジの未達が、そのまま 仕様書の記載漏れの発見 につながっています。 並べるとこうなります。 素朴なプロンプト 理論に基づくプロンプト テスト結果 4 passed(全緑) 1 failed(バグ検出) カバレッジ 100% 87%(未達=仕様漏れの兆候) 境界値バグ 追認して見逃す 検出 仕様にない分岐 期待値をでっち上げ 要確認として報告 失敗例のほうが数字は良くて、成功例のほうが数字は悪い。でも健全なのは後者です。この逆転が、今回一番伝えたかったことです。 AIの使い方より、ソフトウェア工学 ここからが本題というか、この体験を通して思ったことです。 さっきの2つのプロンプトの差は、AIの使い方のテクニックではありません。「経路はコードから、期待値は仕様から」も、「境界のちょうどの値を試す」も、全部昔からあるテスト理論です。ホワイトボックス、ブラックボックス、境界値分析。それを知っているかどうかだけの差でした。 境界値分析を知らなければ、「10,000円ちょうどを試して」という発想自体がプロンプトに出てきません。AIは指示された観点しか網羅してくれないので、観点を出せない人がいくらプロンプトの書き方を工夫しても、この差は埋まりません。 AIの使い方講座とか、AIでのスライドの作り方みたいな情報は確かに有用で、それを学ぶことで業務の効率は驚くほど上がります。しかし、ソフトウェア開発者に大事なのはやはりソフトウェア開発の基本的な理論だと思います。「このコードのテストで何を検証すべきか」は、聞く側に理論がないと、そもそも正しい質問になりません。 考えてみれば当たり前で、AIに指示を出すのも人間に指示を出すのも同じなんですよね。後輩に「このコードのテスト書いといて」とだけ言って渡したら、やっぱり同じ失敗をするかもしれない。きちんと伝えないと毎回きちんとやってくれるとは限らないのは、AIも人間も同じです。そして、きちんと伝えるためには、指示する側が中身を分かっていないといけない。 AIはテストコードを書く速度を劇的に上げてくれました。だからこそ、「何を検証すべきか」を決める側の理論、つまりソフトウェア工学の価値は、下がるどころかむしろ上がっていると感じています。AIの時代に何を学ぶべきかと聞かれたら、私はソフトウェア工学だと答えます。やはり、いつの時代でも大事のは基本だと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AIの使い方を学ぶ前に、まずソフトウェア工学を first appeared on SIOS Tech Lab .
本記事では、AWSとRed Hatが共同提供するフルマネージドなOpenShiftサービス「ROSA」について紹介します。 特に、近年の主流であり、インフラ費用や運用負荷を劇的に削減できるROSAの最新アーキテクチャモデル「HCP(Hosted Control Planes)」の仕組みと、導入による4つのメリットを分かりやすく紹介し、最適なコンテナ基盤選定のヒントをお届けします。 ROSAとは ROSA(Red Hat OpenShift Service on AWS)とは、Red Hatが提供しているコンテナオーケストレーションプラットフォームであるOpenShiftを、AWS上のフルマネージドサービスとして利用できるようにしたサービスです。( OpenShiftについて詳しく知りたい方は、 こちら の記事をご参考ください) ROSAを利用してコンテナ基盤を運用することには、以下の利点があります。 Red HatとAWSの共同サポート ROSAは、サポート窓口が完全に一元化されていて、問い合わせを受けると、裏側でAWSとRed Hatのエンジニアが直接連携して原因を突き止めてくれる仕組みになっています。自分たちでAWS上にOpenShiftを構築した場合、トラブルが起きると「AWSのインフラが悪いのか、それともOpenShiftのバグなのか」を自力で調べ、それぞれのサポートに別々に問い合わせなければなりません。ROSAであればそのようなアクションが一切不要になるため、運用の負担を大きく軽減できます。 Red Hatの専門チームによる24時間監視 ROSAのクラスターは、Red HatのSREチームによって24時間365日体制で監視・運用されるようになります。 クラスターに何か障害が起きても、Red HatのSREチームが裏側で迅速に対応・復旧をしてくれるため、ユーザーとしては、監視や障害対応に対する運用コストを飛躍的に削減できます。 また、パッチ当てやセキュリティのアップデートなどもRed Hat側が対応してくれるので、ユーザーはそのスケジュール(実行するタイミング)を決めるだけで済みます。 AWSサービスと連携しやすくなる ROSAは、最初からAWSの各種サービスとスムーズに連携できるよう設計されています。これによって、複雑なインフラ設定に時間を取られなくなるため、より迅速かつ安全にコンテナ基盤を構築・提供できるようになります。 Copyright © Red Hat, Inc. AWSのRoleとPolicyを利用したRed Hat側との連携 請求書の統合 ROSAの利用料金(OpenShiftのライセンス料やAWSのインフラ費用)は、すべてAWSの請求書に統合されて支払われます。 別個に契約や支払いを行う必要がないため、企業の購買手続きや予算管理の負担を大幅に軽減できるメリットがあります。 ROSAを使うことで、上記のように様々なメリットが得られますが、現在の標準アーキテクチャである「HCP(Hosted Control Planes)」の登場によって、更なるフルマネージドのサービスが利用可能になりました。 ここからは、コストや運用の楽さを劇的に向上させる「ROSA with HCP」について詳しく説明します。 ROSA with HCPとは ROSA with HCP ( Red Hat OpenShift Service on AWS with hosted control planes ) とは、簡単に言うとコントロールプレーンをRed Hat側のAWSに配置し、完全に管理を任せる仕組みです。 HCPが登場する前の従来の方式は、HCPと区別するために「ROSA Classic」と呼ばれていますが、以下の構成図を見ていただくと、一目でその違いが理解できると思います。 Copyright © Red Hat, Inc. ROSA Classicの構成図 Copyright © Red Hat, Inc. ROSA with HCPの構成図 構成図からもわかるように、Classic方式では、コントロールプレーンノードがユーザー自身のAWSアカウント(VPC)内に配置され、ワーカーノードと共存していました。 一方、HCP方式では、コントロールプレーンがユーザーのネットワーク環境から安全に分離された場所に配置され、AWS PrivateLinkを介してやり取りする仕組みになっています。 このような構成の違い以外にも、HCP方式は数多くのメリットを持っています。ここからは、どのようなメリットがあるか紹介します。 コントロールプレーンの管理コストの削減 コントロールプレーンには、APIサーバーやetcdデータベースなど、クラスター全体を制御する極めて重要なコンポーネントが含まれています。これらをRed Hat側が完全に管理・運用してくれるため、ユーザーの運用保守のコストが大幅に削減されます。 AWSインフラ費用(EC2代金)を節約できる 従来のClassic方式では、コントロールプレーンを構成するノード(EC2インスタンス)をユーザー自身のAWS環境内に作成する必要がありました。 OpenShiftの仕様上、クラスターの安定稼働(高可用性)を維持するためには最低3台のコントロールプレーンノードが必須となりますので、小規模な開発環境であっても、ベースとなるEC2の固定費用がどうしても高くなってしまうというコスト面の課題がありました。 HCP方式では、このコントロールプレーンがRed Hat側に完全に移動するので、ユーザーのAWSアカウントからは最低3台分のEC2の料金が完全に消えることになります。 これは、AWSのインフラコストを劇的に節約・削減できるというHCP方式だけの大きなメリットになります。 クラスター作成速度の向上 従来のClassic方式では、クラスターを新規作成するたびに、ユーザーのAWS環境内でコントロールプレーンのインフラも一から組み立てる必要がありました。そのため、クラスターが完全に起動して利用可能になるまでに、約30分〜40分ほどの待ち時間が発生していました。 HCP方式では、コントロールプレーンの構築・プロビジョニングがRed Hat側の環境で迅速に行われます。 ユーザーのAWS環境内では、アプリケーションを動かすためのワーカーノードのみを作成すれば良いため、クラスターの作成時間が約10分程度へと大幅に短縮されました。急ぎで新しい環境が必要になったりするビジネスシーンにおいて、このような時間の短縮は大きなメリットになります。 アップグレードの柔軟性 従来のClassic方式では、コントロールプレーンとワーカーノードのバージョンアップを密に連動させて管理する必要がありました。そのため、互換性の確認や影響範囲の調査を慎重に行わなければならず、事前の計画や検証に多くの工数を割く必要がありました。 HCP方式では、コントロールプレーンとワーカーノードの管理が完全に切り離されていますので、両者のアップグレードを別々のタイミングでスケジュールすることも可能です。 例えば、まずはRed Hat側が管理するコントロールプレーンだけを先行してアップデートし、アプリケーションが動くワーカーノードは業務影響の最も少ない別の日時に実施する、といった柔軟な運用ができるようになりました。 以上が、ROSA Classicと比べたHCP方式の特徴とメリットの解説になります。最後に、これまでご紹介した内容を表で簡単にまとめます。 ROSA ClassicとROSA HCPの比較表 比較項目 ROSA Classic(従来方式) ROSA with HCP(最新モデル) コントロールプレーンの配置 ユーザー自身のAWSアカウント Red Hat側 マスターノードのEC2費用 ユーザー負担 ユーザー側の負担ゼロ(Red Hat側で稼働するため) クラスター作成時間 約40分 約10分 アップグレード調整 両ノードが連動しているため、スケジュールの調整がしづらい 別々のタイミングで柔軟に調整・実行が可能 まとめ 以上、ROSAの概要から、現在の主流である「HCP(Hosted Control Planes)」の特徴と数々のメリットについてご紹介しました。 ROSA with HCPは、コスト・速度・運用のすべてにおいて優れており、現在のROSA構築におけるベストプラクティスとなっています。 これから新しくコンテナ基盤を検討される方に、この記事の内容が参考になれば幸いです。 次回は、実際にAWS上でROSA with HCPのクラスターを構築していく手順を詳しく解説します。ぜひ楽しみにしてください。 参考資料 AWS での Red Hat ソリューション Overview of responsibilities for ROSA ROSA HCP and ROSA classic Capability Matrix (ログイン必要) ROSA Best Practices and Recommendations Red Hat OpenShift Service on AWS 4 Introduction to ROSA ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post ROSA(Red Hat OpenShift Service on AWS)を利用したコンテナプラットフォーム構築 ~ROSAの特徴とメリット~ first appeared on SIOS Tech Lab .
現在、スマートフォンやPCのOS、多くのアプリで当たり前のように提供されている「ダークモード」。 本記事では、ダークモードの歴史から、ダークモードが有効なケース、そして色彩設計のガイドまでを解説します。 ダークモードは原点回帰? コンピューターの歴史を振り返ると、画面は最初からダークモード(暗い背景)でした。 1970年代から80年代前半、主流だったCRT(ブラウン管)モニターでは、画面全体を明るく発光させることは負荷が高かったため、「暗い背景に緑や白色のテキスト」を表示するのが基本でした。 しかし1970年代にGUI(グラフィカルユーザーインターフェース)が研究所で生まれ、1980年代前半から実用化・普及し始めると、画面設計の考え方は大きく変わりました。GUIを採用したパーソナルコンピューターでは、文書作成やデスクトップパブリッシング(DTP)が重要な用途となり、画面上で印刷結果を忠実に再現するWYSIWYGという考え方が広まりました。白い紙に黒い文字が印刷されていることを、画面上で表すことが基本となったのです。こうして、一般向けパソコンのGUIではライトモードが事実上の標準となりました。 この間、一般向けの画面は白くなりましたが、システムを開発するエンジニアたちは、非GUIな開発環境などで「黒い画面」を使い続けていました。 そして2010年代、スマートフォンが普及し、暗い場所でディスプレイを見る時間も増えました。スマートフォンで使われることの多い有機ELディスプレイは「黒色=発光をオフにする」仕組みのため、黒背景にすることで消費電力を抑えることができます。OSやアプリケーションではデザインシステムが成熟し、ライトモードとダークモードの双方を前提とした設計が一般的になります。 「ダークモード=目に優しい」は本当? 「ダークモードは目に優しい」とよく言われますが、科学的には「無条件に目の負担が減るわけではない」というのが真実です。人間の目の「瞳孔」の働きによって、ライトモードとダークモードには一長一短があります。 ライトモードのメリット:ピント調節のしやすさ 人間は明るいものを見ると瞳孔が小さくなります。カメラの絞りを絞った時のように「焦点深度」が深くなるため(ピンホール効果)、目の筋肉に負担をかけずに文字にピントを合わせることができます。十分な照明環境では、ライトモードの方が読解速度や文字認識精度が高いという研究が報告されています。 カメラの絞りと被写界深度 ダークモードのメリット:暗所でのまぶしさ軽減 一方、ダークモードが真価を発揮するのは「周囲が暗い環境」です。暗い部屋でライトモードを見ると、強いコントラストによる「まぶしさ(光の刺激)」が強いストレスを与えます。暗所においては、発光量が少ないダークモードの方が主観的な目の疲労感が減少することが、研究で報告されています。 ダークモードと「ネオンサイン」のジレンマ ダークモードでは、白い文字やアイコンなどが光を帯びたようににじんで見えることがあります。 暗い画面を見ていると、目はより多くの光を取り込もうとして瞳孔を広げます。瞳孔が大きく開くと、目の光学収差や眼球内での光の散乱の影響を受けやすくなるため、暗い背景に表示された明るい文字などの高コントラストな要素は、輪郭がぼやけたり、光がにじんで見えたりする場合があります。 この見え方は、夜の街でネオンサインや街灯の光が周囲ににじんで見える現象と似ています。(ネオンサインや街灯のにじみには、大気中の塵や水滴による光の散乱も影響しているため、まったく同じ現象ではありません。) また、このような見え方は誰にでも起こり得ますが、乱視がある場合は光が特定の方向へ伸びたり、文字が二重に見えたりするなど、にじみがより目立つことがあります。 ダークモードが必要なケースと不要なケース こうしたメリット・デメリットを踏まえると、ダークモードはすべてのサイトやアプリで必須というわけではありません。ユーザーの「利用時間」「利用環境」「コンテンツの性質」によって優先度は大きく変わります。 ダークモードが求められるケース 夜間や暗所で利用されるアプリ :地図、カーナビ、アラーム、電子書籍など。周囲の暗順応を妨げず、眩しさを抑える配慮が必要なため。 コンテンツ没入型(エンタメ ):動画配信やゲーム、写真ギャラリーなど。周囲のUIを沈ませることで、メインコンテンツを際立たせるため。 U-NEXT プロ用の映像、写真編集ツール :上記のエンタメコンテンツと同様に視線の分散を防ぐ効果に加え、周囲のUIを暗くニュートラルにすることで「目の錯覚(明るい背景に引っ張られて写真が暗く見える現象)」を防ぎ、色や明るさのディテールを正確に認識・編集しやすくなります。 ダークモードを必要としないケース 一過性のWebサイト・ランディングページ :メーカーやキャンペーンサイトなど、ブランドの世界観を固定して伝えることが優先されるもの。 Canva ウェブページテンプレート 印刷を前提としたドキュメント :履歴書・職務経歴書などの作成サービス、ワードプロセッサーアプリなど。印刷をすることが前提の場合、仕上がりをイメージしやすくなります。前述のWYSIWYGエディターです。 Canva 単なる「色反転」ではない、色彩設計ガイド ダークモードの配色は、ライトモードのカラーを機械的に反転したものではありません。 視認性を担保し、にじみによる目の疲労を防ぐための設計が必要です。 完全な黒の背景、完全な白の文字にはしない 背景色には完全な黒ではなく黒に近いグレーを採用します。真っ黒を避けることでコントラストを適度に抑え、目への刺激を和らげます。背景と同じく、テキストも完全な白を避けます。 白い背景に暗い文字のライトモードでは、にじんで見えることは少ないですが、暗い背景に明るい文字のダークモードで真っ白な文字にすると、前述のように滲むため、ライトモードよりもダークモードのコントラストは小さくします。 次の表は、Google Financeの背景と文字の色です。 ライトモード ダークモード 背景色 #FFFFFF:白 #101218:青系の黒に近い暗いグレー 文字色 #0A0A0A:黒に近い暗いグレー #E6E8F0:青系の明るいグレー コントラスト比 19.79 : 1 15.3 : 1 表の「コントラスト比」は、アクセシビリティ基準(WCAG)で採用されている計算方法によって算出される、2つの色のコントラストの度合いを示す指標です。「1:1」がコントラストなし、「21:1」が最大のコントラストを表します。 有彩色も明度や彩度を調整 テーマカラーやグラフの色(有彩色)は、ライトモードの色のままダークモードで利用すると、色によって、目立ちすぎたり、背景と見分けにくくなる場合があります。Google Financeの例では、ライトモードの緑と赤のグラフの色を、色相を変えずに明るい色に調整してダークモードに適用しています。 Google Finance おわりに:利用環境によって最適なUIは変わる ライトモードとダークモードは優劣の関係ではなく、それぞれ異なる利用環境に最適化されたデザインです。重要なのは「どちらが優れているか」ではなく、「ユーザーがどのような環境で、どのような目的で利用するか」を理解し、それに合わせて設計することです。 ちなみに:アナログ時代から脈々と… コンピューターよりも以前から、夜間の視認性に配慮した表示設計は、自動車や航空機の計器で発達してきました。夜間に明るすぎる計器を見ると暗順応が妨げられ、暗い道路や空など周囲の状況を視認しにくくなることがあります。また、計器の光が窓ガラスに映り込み、視界を妨げることもあります。そのため、計器には暗い背景が採用されるほか、用途に応じて赤やアンバーなど、夜間の視認性に配慮した照明色が用いられてきました。 Photo by National Cancer Institute Windows Ladislav Stercell Rui Silvestre Tyler Rooney Ed Wingate Sandisk Dragoș Grigore Nursultan Bakyt Boitumelo on Unsplash ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 脱・なんとなくのダークモード。歴史と生理学から考えるUIデザイン first appeared on SIOS Tech Lab .
PSSLの佐々木です 最近は Claude Code を複数セッション同時に立ち上げて、機能Aの実装・機能Bのレビュー・ドキュメント整備…と並列で作業させるのが当たり前になってきました。便利なのですが、セッションで git checkout が走った瞬間、別セッションが作業中のコードが丸ごと別ブランチの内容に変わってしまう問題が起きたので解決しました。 この記事では、 なぜ複数の AI エージェントと git checkout の相性が悪いのか git checkout と git worktree の仕組みの違い worktree を使うと何がうれしいのか ルールで縛るだけでは守られないので hook で checkout を強制ブロックする 方法 worktree 運用のハマりどころ についてまとめました。 1. 何が起きたのか Claude Code のセッションは、基本的に リポジトリの作業ディレクトリ(チェックアウト)を共有 して動きます。ここで複数セッションを並列で走らせると、こういうことが起きます。 セッションA: feature/foo を実装中(ファイル編集中…) セッションB: 「PR #95 をレビューして」→ git checkout feature/bar 実行 │ ▼ セッションAが見ているファイルが全部 feature/bar の内容に変わる git checkout (や git switch )は「今いるディレクトリの中身をそのブランチの状態に書き換える」コマンドです。つまり作業ディレクトリという グローバルな状態 を破壊的に切り替えます。1人の人間が1つのターミナルで作業していた時代はそれで良かったのですが、複数のエージェントが同じディレクトリで同時に動く前提だと、 セッションAの編集途中ファイルとセッションBの checkout が衝突する テストが「なぜか」落ちる(実は別ブランチのコードを実行している) コミットが意図しないブランチに載る 最悪、未コミットの変更が checkout に巻き込まれて消える という、デバッグしても原因にたどり着きにくい系の事故になります。AI エージェントは checkout を「ためらわない」ので、人間同士よりも踏む確率が高いです。 2. git checkout と git worktree の違い git worktree は「 1つのリポジトリから、複数の作業ディレクトリを生やす 」機能です。Git 2.5(2015年)からある機能ですが、AI エージェント並列時代になって急に価値が上がったと感じています。 git checkout 方式(状態の切り替え) repo/ ←─ ここが feature/foo になったり feature/bar になったりする (同時には1つのブランチしか存在できない) git worktree 方式(ディレクトリの追加) repo/ ← develop のまま worktrees/feature-foo/ ← feature/foo 専用ディレクトリ worktrees/feature-bar/ ← feature/bar 専用ディレクトリ (.git の実体は repo/ と共有。HEAD だけ worktree ごとに独立) 仕組みとしては、 .git のオブジェクト(コミット・blob)は全 worktree で共有され、 HEAD やインデックスだけが worktree ごとに分かれます。なので clone と違ってディスクをほぼ食わず、fetch も1回で全 worktree に反映されます。 git checkout / switch git worktree ブランチの持ち方 1ディレクトリに1つ(切り替え) ブランチごとに別ディレクトリ(並存) 他の作業への影響 ある (作業ディレクトリ全体が変わる) ない(各ディレクトリが独立) .git の実体 そのまま 共有(ディスク効率が良い) 並列作業 不可能 可能 fetch / stash / オブジェクト — 全 worktree で共有 基本操作はこれだけです: # ブランチ用の worktree を作る(ブランチが無ければ -b で作成) git worktree add ../worktrees/feature-foo feature/foo git worktree add -b feature/new ../worktrees/feature-new # 一覧 git worktree list # 終わったら削除 git worktree remove ../worktrees/feature-foo git worktree prune # 消し忘れの掃除 3. なぜ AI エージェント並列時代に worktree が注目されているのか セッションごとに独立した世界を渡せる — セッションAは worktrees/feature-foo/ 、セッションBは worktrees/feature-bar/ で作業。お互い何をしても干渉しません。 メインのチェックアウトが常に安定する — repo/ は develop のまま置いておけるので、「今の正の状態」を見失わない。人間が確認する場所としても安心です。 Claude Code がネイティブ対応している — Claude Code には worktree サポートが組み込まれていて、 claude --worktree や EnterWorktree、サブエージェント起動時の isolation: "worktree" を使うと、一時的な worktree を自動で作って作業し、変更が無ければ自動で掃除までしてくれます。つまり「並列にエージェントを走らせるための公式な答え」がすでに worktree なんですね。 レビューと実装を同時にできる — 「PR のブランチを checkout してレビュー」の代わりに「PR のブランチを worktree に生やしてレビュー」にすれば、実装中のセッションを止めずに済みます。 4. ルールで縛るだけでは守られないので hook で強制する 「ブランチ切り替えは worktree を使ってください」と書いても、コンテキストが長くなったセッションや、レビュー依頼を受けたエージェントが「じゃあ checkout しますね」とやってしまう可能性は消せません。 そこで Claude Code の PreToolUse hook で、共有ディレクトリでの git checkout / git switch を仕組みとしてブロックすることにしました。hook は Claude が Bash コマンドを実行する直前に必ず割り込めるので、ルールと違って「忘れる」ことがありません。 4.1 ガードスクリプト scripts/worktree-guard-hook.sh を作ります: #!/bin/bash # Worktree guard hook for Claude Code (PreToolUse / Bash) # 共有作業ディレクトリでの git checkout / git switch によるブランチ切り替えをブロックする set -euo pipefail hook_input=$(cat) command=$(echo "$hook_input" | jq -r '.tool_input.command // empty') [ -z "$command" ] && exit 0 # git のサブコマンドとして checkout / switch が呼ばれているか # (git の直後、またはグローバルオプション -C/-c/--xxx を挟んだ直後のみマッチ) git_switch_re='(^|[;&|(]|&&|\|\|)[[:space:]]*git([[:space:]]+(-C[[:space:]]+[^[:space:]]+|-c[[:space:]]+[^[:space:]]+|--[A-Za-z0-9-]+(=[^[:space:]]+)?))*[[:space:]]+(checkout|switch)([[:space:]]|$)' if echo "$command" | grep -qE "$git_switch_re"; then # ファイル復元形式 (`git checkout [<ref>] -- <path>`) は許可 if echo "$command" | grep -qE 'checkout[^;&|]* -- '; then exit 0 fi jq -n '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "🚫 共有作業ディレクトリでのブランチ切り替え(git checkout/switch)は禁止です。他のセッションと同じチェックアウトを共有しています。代わりに git worktree を使ってください: `git worktree add ../worktrees/<branch> <branch>` して、そのディレクトリ内で作業する。ファイル復元だけなら `git checkout -- <file>` / `git restore <file>` は許可されています。" } }' exit 0 fi exit 0 ポイントは 単純な grep checkout にしていない ことです。それだと git commit -m "switch to new API" のような無害なコマンドまで誤爆するので、「git のサブコマンド位置に checkout/switch が来た場合」だけをブロックし、さらにブランチが変わらない ファイル復元形式 ( git checkout -- <file> )は素通しにしています。 4.2 settings.json に登録 .claude/settings.json (プロジェクト設定 = git にコミットするのでチーム全員に効く)に登録します: { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/scripts/worktree-guard-hook.sh", "if": "Bash(git *)", "timeout": 10 } ] } ] } } $CLAUDE_PROJECT_DIR を使っておくと、worktree の中でセッションを起動したときもパスが解決できます。 if フィルタで git コマンドのときだけ hook を起動するようにして、無駄なプロセス起動も避けています。 4.3 動作確認 Claude に checkout させようとすると、実行前にこう弾かれます: > git checkout develop 🚫 共有作業ディレクトリでのブランチ切り替え(git checkout/switch)は禁止です。 代わりに git worktree を使ってください: ... deny の理由メッセージはそのまま Claude にフィードバックされるので、 Claude は自分で git worktree add に切り替えて作業を続行 します。「ブロックして終わり」ではなく「正しい道を教えて誘導する」のが hook メッセージ設計のコツだと思います。 あわせて CLAUDE.md にも同じルールを書いておきます。hook が「強制」、 CLAUDE.md が「事前ガイダンス」の二段構えで、そもそも deny を踏む前に worktree を選んでくれるようになります。 ### Git - **ブランチ切り替え禁止(worktree 必須)**: 共有作業ディレクトリでの `git checkout` / `git switch` は禁止(hook でブロックされる)。 別ブランチで作業するときは `git worktree add ../worktrees/<branch> <branch>` を使う。 5. 注意点(ハマりどころ) worktree に寄せるにあたって、いくつか知っておいたほうがいいことがあります。 5.1 同じブランチは2つの worktree でチェックアウトできない Git の仕様で、あるブランチをチェックアウトできる worktree は同時に1つだけです。「メインで develop を開いたまま、worktree でも develop を」はできません(インデックスが壊れるのを防ぐための制約なので妥当です)。レビューや検証で同じコミットを見たいだけなら git worktree add --detach で detached HEAD にすると回避できます。 5.2 node_modules や .venv は付いてこない worktree は git 管理下のファイルしか持ってきません。 node_modules/ や Python の .venv/ は worktree ごとに作り直しが必要です。毎回 npm ci するとディスクも時間も食うので、Claude Code の設定でシンボリックリンクを張るのが楽です: { "worktree": { "symlinkDirectories": ["node_modules", "frontend/node_modules"] } } ただし symlink 共有は「両方の worktree で依存バージョンが同じ」前提なので、lockfile をいじるブランチでは素直に入れ直したほうが安全です。 5.3 .env などの git 管理外ファイルもコピーされない secret 類を .env に置いている場合、worktree には存在しないのでアプリが起動しません。前回記事の Infisical のように「secret をファイルとして持たない」構成にしておくと、worktree 運用とも相性が良いです( infisical run はどのディレクトリからでも同じように動く)。 5.4 stash・fetch・オブジェクトは共有される .git の実体は共有なので、 git stash の中身や fetch 済みの ref は全 worktree から見えます。「worktree ごとに完全に独立した Git 状態」ではない点は頭に入れておくと混乱しません。逆に fetch が1回で済むのはメリットです。 5.5 worktree の消し忘れが溜まる 並列作業が捗るほど worktree が増えます。ディレクトリを rm -rf で消しただけだと Git 側に管理情報が残るので、 git worktree remove <path> # 正しい消し方 git worktree prune # rm してしまった残骸の掃除 git worktree list # 定期的に棚卸し を習慣にするのがおすすめです。Claude Code の自動 worktree(EnterWorktree / isolation)は変更が無ければ自動で掃除してくれるので、手動運用よりこちらに寄せるとゴミが出にくいです。 5.6 起動中のサーバーのパスに注意 dev server やテストランナーは「起動したディレクトリのコード」を見続けます。メインのチェックアウトで npm run dev を立ち上げたまま worktree 側を編集しても、当然サーバーには反映されません。動作確認はその worktree の中でプロセスを立ち上げる、を徹底する必要があります。 6. まとめ git checkout は 作業ディレクトリというグローバル状態の破壊的な切り替え 。複数の Claude Code セッションを並列で走らせる環境では、他セッションの作業を巻き込む事故のもと git worktree なら ブランチごとに独立したディレクトリ を生やせるので、セッション同士が干渉しない。 .git は共有なのでディスクも fetch も効率的 Claude Code は worktree をネイティブサポートしている( -worktree / EnterWorktree / isolation: "worktree" )ので、並列エージェント運用の公式な答えはすでに worktree ただし CLAUDE.md に書くだけは「お願い」レベル 。PreToolUse hook で git checkout / git switch を構造的にブロックし、deny メッセージで worktree へ誘導するのが確実 注意点は「同一ブランチの重複チェックアウト不可」「node_modules / .env は付いてこない」「worktree の掃除」あたり 「ルールを書いたから大丈夫」ではなく「仕組みで事故れないようにする」。secret 管理のときと同じ結論に落ち着きましたが、AI エージェントと並走する開発では、この考え方があらゆる場面で効いてくると感じています。 参考リンク git-worktree – Git 公式ドキュメント Claude Code Hooks – Anthropic 公式ドキュメント Claude Code Settings – Anthropic 公式ドキュメント Anthropic Claude Code 公式 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 並列 Claude Code の git checkout で作業中のコードが書き換わるのでgit worktree + hook で解決した first appeared on SIOS Tech Lab .
こんにちは、サイオステクノロジー武井です。 今回は、100番煎じくらいかもしれない、Gitの説明です。 Git(ギット)は、ファイルの変更履歴を記録・管理するためのツールです。チーム開発では欠かせませんが、入門者にとっては最初の壁がとても高いツールでもあります。 その理由のひとつが、 コマンドの多さ です。 add 、 commit 、 push 、 fetch 、 merge 、 branch 、 checkout 、 switch 、 reset 、 rebase ……。これらを1つずつ「使い方」として暗記しようとすると、たいていどこかで混乱してしまいます。たとえば checkout はブランチの切り替えにもファイルの破棄にも使われますし、 reset の --soft / --mixed / --hard の違いはなかなか覚えられません。 でも、わたしも多分10年くらいGitと付き合っているかもしれませんが、以下のことだけ覚えればOKだと思っています。 Git のコマンドは、結局のところ「ファイルの場所」と「コミット(履歴)の動き」の2つを変えているだけ。 わたしはGitのコマンドの使い方を多分一つも覚えてないと思います。でも上記の2つのことを理解していれば、多分、どんな操作も自然に理解できるようになります。 こちらでは、繰り返しになりますが、「 Git のコマンドは、結局のところ「ファイルの場所」と「コミット(履歴)の動き」の2つを変えているだけ。 」をベースにしつこく説明していきます。 第1章 コミットの仕組み ── すべての土台 Git を理解する出発点は、コマンドではなく「 コミットとは何か 」です。ここがあいまいなまま push や merge を覚えても、どこかでつまずいてしまいます。逆に、コミットの正体さえ分かってしまえば、残りは驚くほど素直につながっていきます。少し遠回りに感じるかもしれませんが、まずはここをじっくり見ていきましょう。 なお、これから何度か出てくる言葉を先に確認しておきます。 リポジトリ :変更履歴をまるごと保管しておく入れ物のことです。 コミット :ある時点のファイルの状態を記録する操作、またはその記録そのものを指します。 コミットは「差分」ではなく「スナップショット」 コミットについて、多くの方が最初に思い描くのは「コミット=変更した部分(差分)の記録」というイメージではないでしょうか。じつは、これは少し違います。 正しくは、 コミットは、その時点のプロジェクト全体の“写真(スナップショット)”を、まるごと内側に抱えています 。「どこを変えたか」ではなく、「その瞬間、全体がどうなっていたか」を丸ごと持っている、とイメージしてください。 図のように、コミットは入れ子(箱の中に箱)の構造になっています。 コミット は、その時点の ツリー (ルートフォルダの構造)を内側に持っています。 ツリー は、その中のフォルダ( A/ 、 B/ )やファイル( C.txt 、 D.txt 、 E.txt )の実体を内側に持っています。 そして ブランチ( main ) は、その箱(コミット)を外から指し示している 名札 のようなものです。 ここで、2種類の「登場人物」を区別しておくと、このあとがずっと楽になります。 種類 どんなもの? 例 Git オブジェクト 中身そのもの。中身から計算される「ハッシュ値(指紋のような文字列)」で区別され、一度作ると 中身は変わりません コミット・ツリー・ファイルの実体 リファレンス コミットを指す「名札(ポインタ)」。中身は持ちません main 、 develop 、 HEAD 「中身を持つもの(オブジェクト)」と「それを指すだけの名札(リファレンス)」を分けて考える ── この見方は、このあと何度も登場します。最初はピンとこなくても大丈夫です。読み進めるうちに、だんだんなじんでいきます。 「毎回スナップショットを撮ったら、重くならない?」 ここで、素朴な疑問が浮かぶかもしれません。 コミットのたびにフォルダ全体の写真を撮るのなら、ファイルがどんどん増えて、すぐに膨大になってしまうのでは? とても良い疑問です。でも、実際にはそうはなりません。Git は、**変わったファイルだけを新しく作り、変わっていないファイルは前のコミットの実体をそのまま使い回す(共有する)**ようにできているからです。 E.txt だけを編集してコミットした場面を見てみましょう。 E.txt は中身が変わったので、 新しいオブジェクト が作られます。 A/ ・ B/ ・ C.txt ・ D.txt は 1文字も変わっていない ので、新しいコミット B は、これらを自分で持たず、前のコミット A が持っている 実体をそのまま指すだけ です(図の紫の矢印)。コピーは作られません。一方、変わった E.txt だけは A を参照せず、新しいオブジェクトを作ります。 コミット B は、自分の親(一つ前)としてコミット A を指します( parent )。こうしてコミットがつながり、鎖(くさり)のような履歴ができていきます。 なぜ「変わっていないものは共有」できるのでしょうか。それは、オブジェクトが「中身から計算したハッシュ値」で区別されるからです。中身が同じなら、ハッシュ値も同じ=同じオブジェクトとして扱われます。だから、同じ内容のものは自然と1つにまとまります。コミットが毎回スナップショットでも軽いのは、このおかげです。 この「 中身は変えず、新しいオブジェクトを作って、指し先を付け替えるだけ 」という性質は、あとの章に出てくる話(rebase でハッシュが変わる理由や、消したはずのコミットが残る理由)にもつながっていきます。少し頭の片隅に置いておいてください。 もう一歩だけ深く ── tree と blob の正体 ここまで「コミット → ツリー → ファイル」と入れ子で説明してきました。じつは、この“中身”は Git の内部では 2種類のオブジェクト でできています。少しだけ専門的な話になりますが、ここが分かると Git の不思議な動きの多くが「なるほど」に変わるので、ゆっくり見ていきましょう。 オブジェクト どんなもの? blob(ブロブ) ファイルの 中身(データそのもの) tree(ツリー) 「名前 → オブジェクト」の 一覧表 。これがいわゆるディレクトリ(フォルダ)にあたります 図のように、tree は mode(権限)/ type(種類)/ object(ハッシュ)/ name(名前) という列を持つ一覧表です。その各行が、blob(ファイル)か、別の tree(サブフォルダ)を指しています。 この仕組みから、最初は意外に感じる事実がいくつも見えてきます。 blob は中身しか持っていません。 ファイル名も、どのフォルダにあるかも、権限も、いっさい持っていません。ただのデータの固まりです。 名前・場所・権限は、すべて tree のほうが持っています。 「 C.txt という名前で、ここにある」という情報は、blob ではなく tree 側にあります。 指し示す向きは commit → tree → blob の一方向だけ です。逆向きの矢印(戻りリンク)はありません。つまり、 blob は自分がどこに置かれているかを知りません 。 だからこそ、 中身が同じファイルは、名前や場所が違っても1つの blob を共有します (図の C.txt と notes.txt は同じハッシュ=同じ実体です)。前の節で見た「変わらないものは共有」は、この性質そのものです。 よくある疑問:「tree は編集できるの? 管理(追跡)の対象なの?」 入門者の方からよくいただく質問です。結論から言うと、**どちらも「いいえ」**になります。 Git の すべてのオブジェクトは、一度作ったら中身が変わりません (これを「不変=イミュータブル」と呼びます)。中身が少しでも変われば、それは別のハッシュ値を持つ「別のオブジェクト」になります。ですから、tree も blob も「編集」はできず、変更とは常に 新しいオブジェクトを作り直すこと になります。 「 追跡(管理対象にする) 」という言葉は、後ほど出てくる「ステージング」という場所の話で、そこで管理されるのは ファイル だけです。tree は直接そこに登録されるものではなく、 コミットする瞬間に、管理中のファイル一覧から自動的に組み立てられます 。ですから「tree を追加する/管理する」という操作は、そもそも存在しません。 その結果、ファイルを1つ変えると、 その影響は下から上へ伝わっていきます 。変更したファイルから一番上(ルート)までの道すじにある tree が、すべて新しく作り直されます。一方、その道すじから外れたフォルダ( A/ ・ B/ など)は、前の実体をそのまま共有します。 ディレクトリ(フォルダ)の正体も tree です。そのため、 Git は空のフォルダを記録できません (一覧表に載せるファイルが1つも無いためです)。空フォルダを残しておきたいときに .gitkeep のような中身のないファイルを置くのは、これが理由です。 では「フォルダを削除する」と、内部では何が起きる? フォルダ(ディレクトリ)は単独では管理されず、中のファイル(blob)があって初めて tree の一覧に載るのでした。ですから、 git rm -r A/ ( A/ フォルダを削除するコマンド)の実体は、「 A/ の中のファイルを一覧から全部外す」ことになります。 コミットすると、残ったファイルから 新しいルート tree が組み立てられます。そこには、もう A/ を指す行が 含まれません 。これが「削除」の正体です。「フォルダを消す」専用の操作があるわけではなく、「 新しい tree が、それを指さなくなるだけ 」なのです。 ところが、 A/ の tree や、その中の blob は、 すぐに消えるわけではありません 。オブジェクトは不変なので、 一つ前のコミット(Commit B)からは、今でもたどり着ける からです。過去のコミットに切り替えれば、フォルダはちゃんと元どおりに戻せます。 本当に消えるのは、どのコミットからも・どのブランチからもたどり着けなくなったオブジェクトを、 git gc (ガベージコレクション。不要なものを片づける処理)が掃除するときだけです。 これは「 秘密情報(パスワードなど)を含むファイルを、あとから削除しても、過去のコミットに残り続けてしまう 」という、セキュリティ上のとても大切なポイントと同じ仕組みです。一度コミットしてしまった秘密は、削除では消えたことになりません。漏れてしまった場合は、削除に頼るのではなく、そのパスワード自体を無効化・変更するのが正しい対処です。 ここまでを一度まとめておきます。 blob は「名前を持たない中身」、tree は「名前と場所を持つ一覧表」。どちらも一度作ると変わらず、変更も削除も“新しいオブジェクトを作って、指し先を付け替える”形でしか起きません。だから、同じ中身は共有され、消したはずのものは履歴に残るのです。 この「 変えずに、新しく作って、指し先を付け替える 」という土台が、このあとのブランチ・マージ・reset・rebase まで、すべての動きを支えています。 ブランチは「コミットを指す軽い名札」 ここが、この記事でいちばんお伝えしたい 転換点 です。 「ブランチ」と聞くと、コードの束や、フォルダのコピーのような“重たいもの”を想像しがちです。私自身も最初はそう思っていました。でも、本当はとてもシンプルです。 ブランチとは、あるコミットを指しているだけの、軽い名札(ポインタ)です。 git branch develop (develop というブランチを作るコマンド)を実行しても、 新しいコミットは1つも作られません 。 develop という名札が1枚増えて、今のコミットを指すだけです。だから、ブランチを作る操作はとても軽く、一瞬で終わります。 そのブランチで新しくコミットすると、名札が新しいコミットのほうへ 進みます 。 HEAD (ヘッド)は「いま自分がどこにいるか」を指す特別な名札です。ふだんはブランチ(名札)を経由して、コミットを指しています。 「 ブランチ=動く名札 」という見方ができるようになると、見える景色が変わります。 reset ・ checkout ・ merge ・ rebase といった難しそうなコマンドも、すべて「 名札をどう動かすか 」という同じ視点でとらえられるようになるのです。 紙芝居で見る ── 1コマンドずつ、履歴が育っていく ここからが、第1章の本番です。「コミット=スナップショット」「ブランチ=名札」「HEAD=いまいる場所」という3つの道具を手に持ったまま、 実際にコマンドを1つずつ打って、履歴がどう育っていくか を、紙芝居のように1コマずつ見ていきましょう。 見るポイントは、いつも同じ3つだけです。 新しいコミットができたかな? (緑=ふつうのコミット/紫=マージコミット) どのブランチ(名札)が、どこへ動いたかな? HEAD(いまいる場所)は、どこを指しているかな? この3点をその都度確認していけば、どんなに枝分かれしても迷子になりません。では、始めましょう。 STEP 1 最初のコミット リポジトリを作って、最初のコミット A を記録した状態です。 main が A を指し、 HEAD は「いま main にいますよ」ということを表しています。 コミット → ブランチ → HEAD という指し示しの流れが、すべての出発点になります。 STEP 2 2回目のコミット E.txt を変更してコミットすると、新しいコミット B ができます。 B の親(一つ前)は A です。そして、 いま自分がいるブランチ main が B へ進み、HEAD も一緒について動きます 。 第1章の前半を思い出してください。 B の中では E.txt だけが新しいオブジェクトになり、 A/ ・ B/ ・ C.txt ・ D.txt は A の実体をそのまま共有していましたね。紙芝居では1つの丸で表していますが、丸の中身は、あのスナップショットになっています。 STEP 3 ブランチを作る git branch develop を実行します。ここで注目していただきたいのは、 コミットが1つも増えていない ことです。 develop という名札が1枚増えて、今のコミット B を指すだけです。そして、 HEAD はまだ main のまま で、動いていません。「ブランチを作ること」と「そのブランチに移ること」は、別々の操作なのです。 STEP 4 develop に切り替えてコミット まず git checkout develop で HEAD を develop へ移します (この瞬間は、まだコミットは増えません)。それから C.txt を変更してコミットすると、 C ができます。 ここが紙芝居の最初の山場です。 いま自分がいるブランチ develop だけが C へ進み、 main は B に取り残されます 。これが「枝分かれ」の正体です。特別な操作は何もなく、「 動くのは、いま自分がいるブランチだけ 」というルールから自然に生まれているだけなのです。 STEP 5 featureA を作る git branch featureA を実行します。STEP 3 と同じく、いまの HEAD の位置( develop = C )に名札を1枚足すだけです。コミットは増えず、HEAD も develop のままです。これで C には、 develop と featureA の2枚の名札が貼られた状態になります。 STEP 6 featureA でコミット featureA に切り替えてコミットすると D ができ、 featureA だけが D へ進みます 。 develop は C に残ったままです。これで、同じ C を起点に、 develop のラインと featureA のライン、2本の流れに枝分かれしました。 STEP 7 develop に戻って featureB を作る git checkout develop で HEAD を C に戻し、そこで git branch featureB を実行します。これで C には develop と featureB の名札が、 featureA の先には D が、という構図ができあがりました。 名札は、どのコミットにでも、何枚でも貼れます 。 STEP 8 featureB でコミット featureB に切り替えてコミットし、 E を作ります。 C から下へ枝分かれした、3本目のラインです。いま動いている開発の流れは、 main (B)・ develop (C)・ featureA (D)・ featureB (E)の4本になりました。どれも「コミット=スナップショット」と「ブランチ=名札」の組み合わせでできています。 STEP 9 featureA でさらにコミット featureA に戻って、もう一度コミットします。 D の上に F が積まれ、 featureA が F へ進みます。 featureA のラインは C → D → F という、3つのコミットの鎖になりました。 STEP 10 develop に featureA をマージ いよいよ、2つの流れの合流(マージ)です。 develop に切り替えて git merge featureA を実行します。 このとき、 develop の現在地は C 、 featureA の先端は F です。この2つを合流させるために、 親を2つ持つ「マージコミット」 G が新しく作られます(図の紫の丸です)。 G の親は C (合流先)と F (取り込む側)の2つです。そして develop が G へ進みます 。 featureA は F のまま、動きません。 マージとは「2つの流れの合流点に、親を2つ持つコミットを作る」操作です。難しく考える必要はありません。やっていることは、やはり「コミットを作って、いまいるブランチを進める」だけです。 STEP 11 main に develop をマージ(早送り) 最後に、 main に切り替えて git merge develop を実行します。 このとき、 main の現在地 B は、 develop の現在地 G の 祖先 になっています( B → C → G とたどれます)。こういう場合は、新しいマージコミットを作る必要がありません。 main の名札を、そのまま G までスッと滑らせるだけ で済みます。これを fast-forward(早送り) と呼びます。 その結果、 main と develop が同じ G を指して、きれいに揃いました。 featureA (F)と featureB (E)の名札は、そのまま残っています。 第1章のまとめ ── 11ステップで起きていたこと 紙芝居を最初から見返してみると、 たった2種類のことしか起きていない ことに気づきます。 コミットを作る (STEP 2・4・6・8・9・10)→ 新しいスナップショットができ、 いま自分がいるブランチが、そこへ進む 。 名札を動かす (STEP 3・5・7 のブランチ作成、STEP 4・7・10・11 の切り替え)→ コミットは増えず、 名札や HEAD が動くだけ 。 枝分かれも、マージも、fast-forward も、特別な魔法ではありません。「コミット=中身が変わらないスナップショット」「ブランチ=それを指す名札」「動くのは、いまいるブランチと HEAD だけ」── この単純なルールの積み重ねでできていたのですね。 この感覚を持ったまま、次の章へ進みましょう。 第2章 ファイルが居られる「5つの場所」 第1章では「履歴(コミット)」の話をしました。第2章では、もう一方の話、「 ファイルは、いまどこにあるのか 」を見ていきます。 Git を使うとき、1つのファイルは、次の 5つの場所 のどこかに(場合によっては複数に)存在しています。 場所 どんな場所? ワーキングディレクトリ いま手で編集している作業フォルダです。エディタで開いているのは、ここです ステージング(インデックス) 「次のコミットに含めるもの」を選んで仮置きしておく場所です ローカルリポジトリ コミットの履歴を保管している本体です。 .git フォルダの中で、第1章のオブジェクトが暮らしている場所です リモート追跡リポジトリ origin/main など、リモートの状態の“写し(コピー)”です。 実体は、あなたのPCの中にあります リモートリポジトリ GitHub などの、サーバー上にある本体です。 5つの中で、唯一あなたのPCの外 にあります ここでぜひ覚えていただきたいのが、 境界線 です。**左の4つは、すべてあなたのPCの中(ローカル)**にあります。ですから、インターネットにつながっていなくても操作できます。サーバーと通信が必要になるのは、いちばん右の「リモートリポジトリ」とやり取りするときだけです。 入門者がつまずきやすいのが、 origin/main の正体です。「リモートを追いかけるためにローカルにあるもの」と「本当のリモート」は別物なのです。 origin/main は リモートの状態をローカルに写しとったコピー で、実体はあなたのPCの中にあります。Git がオフラインでも動けるのは、「最後に通信したときのリモートの状態」を、この写しとして持っているからです。 ちなみに origin というのは、リモートの場所(URL)につけた あだ名 にすぎません。GitHub 側は、自分が origin と呼ばれているなんて知りません。 git remote -v というコマンドで「あだ名 → URL」の対応を確認できます。 1つのコマンドを追ってみる ── git commit でファイルはどこへ動く? 5つの場所が頭に入ったら、 1つのコマンドを取り上げて、ファイルがどう動いていくか を具体的に追ってみましょう。ここでは、いちばん基本的な git commit を主役にします。 E.txt を編集してからコミットするまでを、「場所の移動」として描くと、次のようになります。 ワーキングディレクトリ で E.txt を編集します。この時点では、Git はまだ何も記録していません。「変更があるよ」という状態です。 git add E.txt を実行すると、その変更が ステージング へ移ります。「次のコミットに、これを含めてくださいね」という予約のようなものです。 git commit を実行すると、ステージングの内容が ローカルリポジトリ に書き込まれ、**新しいコミット(=第1章のスナップショット)**が記録されます。ブランチの名札も、その新しいコミットへ進みます。 ポイントは、 commit がやっているのは、結局**「ステージングの内容を、ローカルリポジトリの新しいコミットとして書き込む」ことだけ**だ、という点です。第1章で見た「スナップショットを作って、 main を前に進める」が、まさにこれです。第1章(履歴)と第2章(場所)が、 commit という1点でつながっているのですね。 残りの場所へ ── push / fetch / merge commit は、ローカルリポジトリまでしか届きません。リモート(サーバー)側の場所まで含めると、1つのファイルの旅は、次のように完成します。 git add … ワーキング → ステージング git commit … ステージング → ローカル git push … ローカル → リモート(あわせて origin/main の写しも更新します) git fetch … リモート → リモート追跡( origin/main を最新の状態にします) git merge origin/main … リモート追跡 → ワーキング/ローカルへ取り込みます ここで注目していただきたいのは、 どのコマンドも「隣の場所へ運ぶ」という1区間の移動でしかない ことです。そして、「ローカル リモート」の境界を越えて 通信するのは push と fetch だけ です。それ以外は、すべてあなたのPCの中で完結しています。 よく使う git pull は、じつは git fetch + git merge をまとめて実行するコマンド です。 fetch でリモートの最新を origin/main (写し)に取り込み、 merge でそれを今いるブランチに合流させています。 ここで意外と大事なのが、 git merge origin/main が ローカルの中だけで完結する処理 だということです。 origin/main は名前こそリモートっぽいのですが、実体はローカルにある写しなので、merge は通信せずにPCの中で終わります。通信しているのは fetch の瞬間だけ ── この役割分担が分かると、 pull が「よく分からない便利コマンド」ではなくなります。 origin とは ── リモートにつけた「あだ名」と、フォーク開発の例 第2章のはじめに少し触れたとおり、 origin は特別なものではなく、 リモートリポジトリ(の URL)につけた“あだ名”にすぎません。 git clone したときに Git が自動で付ける、デフォルトのあだ名が origin というだけです。そして、そのリモートの状態をローカルに写しとったものが、 origin/main などのリモート追跡ブランチ でした。 ここで大切なのは、 リモートは1つに限らず、いくつでも登録できる ということです。あだ名で区別するので、複数あっても迷いません。その典型例が、OSS(オープンソース)開発でよく使う「フォーク」です。 OSS に貢献するときは、こんな流れになります。 本家の OSS リポジトリを、自分の GitHub アカウントに**フォーク(コピー)**します。これが、あなた専用のコピーです。 その自分のフォークを git clone します。すると、フォークが origin というあだ名で登録されます(あなたが push できるリモート)。 さらに、フォーク元の本家を upstream (上流)というあだ名で追加します( git remote add upstream <本家のURL> )。 これで、 1つのローカルリポジトリ に、2つのリモートのあだ名が登録された状態になります。 git remote -v で確認すると、次のように見えます。 origin https://github.com/you/project.git (あなたのフォーク) upstream https://github.com/original/project.git (本家 OSS) あとは、2つのあだ名を使い分けます。 本家の最新を取り込む : git fetch upstream で本家の更新を upstream/main (写し)に取得し、自分のブランチに取り込みます。 自分の変更を送る : git push origin で、自分のフォーク( origin )へ送ります。 本家へ提案する :本家には直接 push できないのがふつうなので、フォークから**プルリクエスト(PR)**で「この変更を取り込みませんか?」と提案します。 ポイントは、 origin も upstream も ただのあだ名 で、実体はどちらも「URL を指す名札」だということです。だからこそ、1つのローカルが両方からファイルを取り込み、送り先を選んで送り出せるのです。 第3章 コマンドごとに見る ── 「場所」と「コミット」の動きで理解する 第1章と第2章で、Git を見るための2つの目線が手に入りました。 ① ファイルの場所 :ワーキング → ステージング → ローカル → リモート(第2章) ② コミット(履歴)の動き : HEAD やブランチがどう動くか(第1章) ここからは、代表的なコマンドを1つずつ取り上げ、 実行すると ① 場所と ② コミットがそれぞれどう動くのか を、図で見ていきます。どのコマンドも、結局はこの2つを動かしているだけです。手元の基本(add → commit)から、送る・取り込む(push → fetch → pull → merge)、移動・取り消し(switch → reset → clean)、そして応用(rebase)の順に見ていきましょう。 git add git add は、 ワーキングで編集したファイルを、ステージング(次のコミットの下書き)に登録する コマンドです。 ① ファイルの場所はどう動く? 編集して中身が変わった E.txt (”Hi!”)が、ワーキングからステージングへコピーされます。ローカル(過去のコミット)は、まだ古い “Hello” のままです。 ② コミット(履歴)はどう動く? 何も起きません。 add はあくまで「次のコミットに含める準備」なので、履歴は1ミリも動きません。 add は「コミットに含めるものを選ぶ」操作です。場所だけを動かし、履歴は動かしません。 git commit git commit は、 ステージングに用意しておいた内容を、新しいコミットとしてローカルリポジトリに記録する コマンドです。 ① ファイルの場所はどう動く? ステージングの E.txt (”Hello”)が、中身そのままでローカルリポジトリに記録されます。ワーキングやリモートは動きません。 ② コミット(履歴)はどう動く? 新しいコミット B が作られ(親は A )、いまいるブランチ main がその B へ進みます。 HEAD も一緒に動きます。 commit は「ステージングの内容を記録し、新しいコミットを作って、いまいるブランチを1つ進める」。第1章と第2章で見たことが、1つのコマンドの中で同時に起きています。 git push git push は、 ローカルの新しいコミットを、リモート(サーバー)へ送る コマンドです。 ① ファイルの場所はどう動く? ローカルのコミット内容がリモートへ届きます。あわせて、手元の origin/main (リモートの写し)も最新に更新されます。 ② コミット(履歴)はどう動く? リモート側のブランチが進み、それを写した origin/main が main に追いつきます(A → B)。 push は「ローカルからリモートへ送り、リモートのブランチを進める」。ネット通信して外へ出す、数少ないコマンドの1つです。 git fetch git fetch は、 リモートの最新を、ローカルの「写し」( origin/main )に取得する コマンドです。作業ブランチはまだ動きません。 ① ファイルの場所はどう動く? リモートの新しい内容(”v2″)が、リモート追跡(写し)へ取り込まれます。手元の作業フォルダは、まだ変わりません。 ② コミット(履歴)はどう動く? origin/main だけが新しいコミット C へ進みます。自分の main は B のまま据え置きです。 fetch は「取ってくるだけ」。手元の作業に反映するには、このあと merge が必要です。だからこそ、安全に最新だけを確認できます。 git pull git pull は、 リモートの最新を取得して、いまの作業ブランチに取り込む コマンドです。中身は git fetch + git merge の2段階をまとめたものです。 ① ファイルの場所はどう動く? fetch で写しを取り、merge でローカルとワーキングまで反映します。結果、手元のファイルが最新(”v2″)になります。 ② コミット(履歴)はどう動く? 写しを取り込んで、作業ブランチ main が最新の C へ進みます。 pull は「fetch して merge する」だけ。第2章で見た2つを、1コマンドにまとめた便利コマンドです。 git merge git merge は、 別のブランチを、いまの作業ブランチに合流させる コマンドです。 ① ファイルの場所はどう動く? 合流後の内容が、ローカル(新しいマージコミット)とワーキングの両方に反映されます。 ② コミット(履歴)はどう動く? 枝分かれした2つの先端( main = B と feature = C)を合流させるため、 親を2つ持つマージコミット M ができ、 main が M へ進みます。 merge も結局「コミットを作って、いまいるブランチを進める」。ただし、親が2つある点だけが特別です。 git switch / checkout git switch (古い書き方では git checkout )は、 別のブランチに移動する コマンドです。 HEAD を移し、ワーキングをそのブランチの内容に書き換えます。 ① ファイルの場所はどう動く? ワーキングの中身が、移動先のブランチ(develop)の状態に置き換わります。 ② コミット(履歴)はどう動く? コミットは増えません。 HEAD が main から develop へ移るだけです。 「移動」とは、 HEAD を動かし、ワーキングをその場所の内容に合わせること。新しいコミットは作りません。(昔の checkout は「移動」と「ファイルの復元」の2役を兼ねて紛らわしかったため、いまは switch と restore に分かれました。) git reset は、 ブランチと HEAD を、指定した過去のコミットへ戻す コマンドです。 --soft / --mixed / --hard の3つのモードがあり、「② コミット(履歴)を戻す」のはどれも共通で、「① どこまでファイルを巻き込んで戻すか」だけが違います。3つを別々のコマンドとして見ていきましょう。 git reset –soft ブランチと HEAD だけ を前のコミットへ戻します。ステージングとワーキングの中身は、そのまま残ります。 ① ファイルの場所はどう動く? 動かしません。編集した "Hello2" は、ステージングにもワーキングにも残ったままです。 ② コミット(履歴)はどう動く? main と HEAD が前のコミット A へ戻ります。取り残された B は「宙ぶらりん」状態になります(しばらくは復元できます)。 「コミットだけ取り消したい(変更は全部残したい)」ときに使います。 git reset –mixed(既定) --soft に加えて、 ステージングも 前のコミットの状態へ戻します。ワーキングの変更は残ります。オプションを付けないときは、これになります。 ① ファイルの場所はどう動く? ステージングだけが A の状態( "Hello" )に戻ります。ワーキングの編集 "Hello2" は残ります。 ② コミット(履歴)はどう動く? --soft と同じく、 main と HEAD が A へ戻ります。 「いったん add も取り消して、もう一度ステージングし直したい」ときに使います。 git reset –hard ブランチ・ HEAD ・ステージング・ワーキングを、 すべて 前のコミットの状態へ戻します。 ① ファイルの場所はどう動く? ステージングもワーキングも A に戻ります。 編集した "Hello2" は消えてしまう ので、3つの中で唯一、取り扱いに注意が必要なモードです。 ② コミット(履歴)はどう動く? こちらも main と HEAD が A へ戻ります。 3つのモードに共通するのは「ブランチと HEAD を前のコミットへ動かす」こと。違いは「ファイルをどこまで道連れにするか」だけ、と覚えておけば取り違えません。 git clean git clean は、 Git が管理していない(追跡されていない)ファイルを、ワーキングから削除する コマンドです。 ここで「 未追跡(untracked) 」という言葉を説明しておきます。未追跡とは、 一度も git add されていないファイル のことです。新しく作ったメモやビルドの生成物など、Git にまだ一度も登録していないファイルが、これにあたります。逆に、一度でも git add (やコミット)したファイルは「追跡されている(tracked)」状態になります。 ① ファイルの場所はどう動く? 未追跡ファイル( temp.txt )だけが、ワーキングから消えます。一度でも追跡されたファイル( E.txt )は守られます。 ② コミット(履歴)はどう動く? 何も起きません。 clean が消すのは「Git が一度も登録していない(未追跡の)ファイル」だけ。一度でも git add したファイルは Git が守ってくれます。 git rebase git rebase は、 自分のコミットを、別の土台(ブランチの先端)の上に作り直して積み直す コマンドです。 ① ファイルの場所はどう動く? 積み直したあとの状態が、ローカルとワーキングに反映されます。 ② コミット(履歴)はどう動く? feature のコミット C ・ D が、 main の先端 B の上に C′ ・ D′ として作り直されます 。中身が同じでも、**新しいコミット(別のハッシュ)**になります。元の C ・ D はどこからも指されなくなり、やがて消えます。 第1章の「オブジェクトは不変。変更は“作り直して差し替え”でしか起きない」が、いちばんはっきり現れるのが rebase です。だからこそ、共有済みのブランチで使うと事故のもとになります。 これで主要なコマンドを一巡しました。どれも結局、 ① ファイルの場所 と **② コミット(履歴)**の組み合わせでしかなかったことが、図で確かめられたと思います。 まとめ いかがでしたでしょうか?この記事がGitを理解するためのお役に立てれば幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 世界一わかりみが深いかもしれないGit first appeared on SIOS Tech Lab .
みなさんこんにちは!本ブログではApache Kafkaの強力なコア概念である 「コンシューマーグループ(Consumer Group)」 を取り上げていきます。今回はPythonとtmux(4分割ターミナル)を使い、トピックへ100件のイベントを送信した際の挙動と、裏側の分散ロジックについて検証した記録をまとめました。 1. コンシューマーグループ(Consumer Group)とは Kafkaは、データを受け取る受信アプリを「グループ」という単位で管理します。このグループの組み方によって、1つのトピックに対して2つの異なる挙動を同時に実現できます。 異なるグループ間 ➔ パブサブ型(イベントの複製): 別のグループ同士には、まったく同じデータがそれぞれ全件コピー(複製)されて配信されます。 同じグループ内 ➔ キュー型(負荷分散): 同一グループ内のサーバー同士では、データを重複することなく綺麗に分担して配信されます。 2. 検証デモの実行手順と環境構成 パーティション数を「3」に設定したトピック five を作成し、ターミナルをtmuxで4分割して以下の手順で検証を行いました。 【左上・左下ペイン】 Group-X(2台起動): 負荷分散を検証するコンシューマー(Consumer-X1 / X2) 【右上ペイン】 Group-Y(1台起動): イベントの複製(全件受信)を検証するコンシューマー(Consumer-Y1) 【右下ペイン】 Producer: 送信側から userA 50件、 userB 50件(計100件)を連続送信 3. 使用したPythonスクリプト 検証に使用した主要なソースコードです。(事前に pip install confluent-kafka の実行が必要です) ■ 受信側:consumer_flexible.py from confluent_kafka import Consumer import sys group_id = sys.argv[1] consumer_id = sys.argv[2] conf = { 'bootstrap.servers': 'localhost:9092', 'group.id': group_id, 'auto.offset-reset': 'earliest' } consumer = Consumer(conf) consumer.subscribe(['five']) print(f"--- 【{consumer_id}】 グループ【{group_id}】としてデータ待ち中... ---") try: count = 0 while True: msg = consumer.poll(1.0) if msg is None: continue if msg.error(): continue count += 1 print(f"[{count}件目] Key: {msg.key().decode('utf-8')}, Value: {msg.value().decode('utf-8')}") except KeyboardInterrupt: pass finally: consumer.close() ■ 送信側:key_producer.py from confluent_kafka import Producer import time p = Producer({'bootstrap.servers': 'localhost:9092'}) print("データの送信を開始します(100件)...") for i in range(50): p.produce('five', key='userA', value=f'data-A-{i}') p.produce('five', key='userB', value=f'data-B-{i}') p.flush() time.sleep(0.05) print("送信完了") 4. デモの実行結果と受信件数 ▼ デモ実行画面(プロデューサーからイベント送信中の様子) イベント送信完了後、各ペインに立ち上げたコンシューマーの受信ログおよび最終的な合計件数は以下の通りになりました。 グループ名 サーバーID 最終受信件数 ログから見えた決定的な特徴 Group-X (2台構成) Consumer-X1 50件 userB のイベントのみを100%固定受信(負荷分散) Consumer-X2 50件 userA のイベントのみを100%固定受信(負荷分散) Group-Y (1台構成) Consumer-Y1 100件 全件( userA userB )を漏れなく受信(イベントの複製) なぜ自動的に綺麗に分かれたのか?裏側の仕様 ソースコード側には割り当ての設定を1行も記述していません。それなのにこの結果になったのは、Kafkaのデフォルトの仕様によるものです。 Keyによる「固定配置」: 送信時にユーザー名をKeyに指定したため、Kafkaが自動的にハッシュ値を計算し、 userA はパーティション2、 userB はパーティション1へと固定配置しました。 重複なき「自動割り当て」: 3つのパーティションに対して同じグループに2台のサーバーがいたため、Kafkaは裏側で自動的に仕事を割り振りました(X1がパーティション1、X2がパーティション2を担当)。 この2つの標準仕様が合体した結果、 「負荷を完全に分散させつつ、同じユーザーのデータは必ず同じサーバーに届く(順序性の保証)」 という挙動が完全自動で実現しています。 5. まとめ 今回の実証デモを通して、コンシューマーグループを分けることで「イベントの複製(並列処理)」、グループ内では「負荷分散・順序保証」が完璧に行えることが確認できました。 データ量が増えても、プログラムを1行も変えずにコンシューマーの台数を増やすだけで処理能力を水平スケールできる、Kafkaの洗練された設計の美しさを体感できる検証となりました。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Apache Kafkaのコンシューマーグループとは?~イベントの「複製」と「負荷分散」について first appeared on SIOS Tech Lab .
ども!最近、スライドをAIと一緒にレビューし続けている龍ちゃんです。 正直に言うと、セミナー資料づくりは全然得意じゃないです。話の組み立ても見せ方も、上手い人にはぜんぜんかなわない。だからこそ、自分が作ったスライドはAIにレビューさせて、自分では気づけない穴を埋めるようにしてます。専門外のことほど、AIのチェックを最初から仕組みとして組み込んでおきたいんですよね。 で、最初にやったのが「1体のAIに全部やらせる」でした。結果、時間だけがかかって前に進みませんでした。 AIに「レビューして」と丸投げすると、返ってくるのは「全体的によく整理されていて、流れも自然です」みたいな感想。わかります。嘘はついてないけど、「で、何を直せばいいの?」ってなるやつ。当たり障りのない感想です。これじゃ穴は埋まらない。 この正体はシンプルで、1体に「いい感じに見て」と全部やらせてるからなんですよね。観点が混ざると、採点の前提まで混ざる。論理を見る目と、表現の甘さを叩く目は、そもそも合格ラインが逆です。同時にやらせると打ち消しあって「まあ概ね良いのでは」に落ちる。だから僕は、観点ごとにエージェントを分けて持ってます。ここで言うエージェントは Claude Code のサブエージェント で、 .claude/agents/ に観点ごとの定義を置いてあるやつです。それぞれ明確な観点を持たせて自分の思考の足りない部分を埋めるようにしています。 この「観点ごとに分ける」やり方は、前の記事「 AIとスライドを作る進め方|「なんか違う」修正ループが終わらない人へ 」で触れたものの、中身は書ききれずに先送りしてたんですよね。今回はその回収です。最初からきれいに分けてたわけじゃなく、1体に欲張らせて派手に失敗した結果こうなった、という話も込みで紹介します。 レビューさせる材料(outline / storyboard / slide) レビューの話に入る前に、何をAIに渡すのかを揃えておきます。ここ、エージェント設計ではけっこう大事です。 僕がレビューにかけてるのは、セミナーの資料です。で、スライドはいきなり作らずに、3段階に分けて作ってます。段階ごとに「決めること」が違っていて、それぞれ別のファイルとして残るんですよね。 outline(合意メモ) : 誰に向けて、何を持ち帰ってもらって、どこに着地させるか。話の骨格だけを決める段階です。スライドの枚数もデザインもまだ無い。「そもそも何を伝える会なのか」を握るための材料。 storyboard(論理メモ) : outline を受けて、1スライドに何を出すか・どういう順で繋ぐかまで落とした構成案。ただしデザインは載せません。文字だけにして、「話の流れに飛躍や抜けがないか」だけを見るための材料です。 slide(完成物) : 実際に投影する本番のスライド。ここで初めて、図・レイアウト・強調みたいな見せ方が乗ります。聞き手が目にするもの。 ポイントは、3つとも「持っている情報が違う」ことです。outline は骨格しか持ってない。storyboard は論理を持ってるけど見た目は持ってない。slide は見た目まで全部持ってる。 そしてここが効くんですが、 レビューエージェントに何を渡すかで、見える観点が変わる んですよ。論理の通りを見てほしいなら storyboard を渡す。見せ方が刺さるか見てほしいなら slide を渡す。逆に、聞き手目線で見てほしいときに storyboard の「意図メモ」まで渡すと、かえって本物の聞き手とズレた評価になる(これは3体目で詳しく話します)。 どの不安を、どの材料で、どのエージェントに見させるか。それが本題です。 見てほしいことは「3つの不安」に分かれる スライドを往復で詰めていくと、性質の違う不安が出てきます。僕はざっくり3種類に分けて考えてます。 1. きれいだけど空疎じゃないか storyboard で論理を通して slide にしても、出来上がりが「きれいだけど何も言ってない」になりがちです。「最適化」「シームレス」「伴走支援」みたいな死んだ言葉が並んでいて、聞き手が冷めるやつ。これは表現の質を0点起点で叩く奴に見させます。 2. 章をまたいで論理が通ってるか スライドを1枚ずつ往復で直していくと、前の章と後の章の整合が気づかないうちに崩れていくんですよね。「ここで言ったことが後で回収されていない」「先食いしてしまった」みたいなやつ。これは論理の整合だけを見る奴に、storyboard ごと渡して見させます。 3. ペルソナ目線で見せ方が刺さるか 論理が通っていても人に刺さるとは限らない、というのは前の記事で散々言いましたよね。現場リーダーに向けたスライドなら、現場リーダーの目線で「ここで温度が上がったか下がったか」を見させたい。これはペルソナになりきる奴に見させます。 この3つ、見方の前提がまるで逆なんですよ。だから別の奴に見させる。まとめると表にするとこうなります。 スライドの不安 見させる奴 渡すもの いつ使うか きれいだけど空疎じゃないか harsh(0点起点) slide slideが「なんか違う」時 章をまたいで論理が通ってるか logic(100点起点) storyboard+slideの該当範囲 章末・差し戻し後の整合チェック ペルソナ目線で見せ方が刺さるか audience-reaction 該当章のslideだけ 見せ方を詰める時 harsh / logic / audience-reaction という3体です。それぞれの中身は後半で詳しく見ていきますが、先に一点だけ言っておくと、使うタイミングが観点ごとに違うというのも大事なポイントです。harsh は slide が出来てから使う。logic は outline 段階でも storyboard 段階でも使える。audience-reaction は見せ方を詰めるタイミングで使う。「何を渡すか」と「いつ使うか」がセットで設計になってます。 では1体ずつ見ていきます。 1体目:きれいだけど中身が空っぽ、を叩く(harash) 最初の不安、「出来上がったスライドがきれいだけど何も言ってない」。これを見させるのが harsh-review-agent です。 こいつの設計はシンプルで、採点を「0点(ゴミ箱行き)」から始めます。加点を探すんじゃなくて、「なぜこれが0点なのか」の証拠を積み上げていく。定義に書いてある絶対ルールがこれです。 絶対ルール(harsh-review-agent より) 1. 称賛の禁止: 挨拶や「素晴らしい構成です」といったポジティブなフィードバックは 一切不要。100% 批判的・改善的な視点だけで回答する。 2. 0 点の根拠を探る: 資料は現時点で「0 点(ゴミ箱行き)」だと仮定する。 加点要素を探すのではなく、「なぜこれが 0 点なのか」という証拠を提示する。 出力上の注意: - 「素晴らしい」「良い点として」などのクッション言葉は一切使わない。 - AI 生成資料の素点は 10〜30 点台が普通と心得よ。 「死んだ言葉」のリストも定義に焼き込んであって、「最適化」「シームレスに」「伴走支援」「DX推進」「手放せなくなる」みたいなやつです。並べてみると「あ、うちのスライドにもあった」ってなりますよね(笑)。これを全部拾って「なぜこれが空疎か、何を隠すための言葉か」を断罪してくれます。 実際に回した出力を少し見せると、こんな感じです。セミナー資料に対して25点が出て、指摘の一つがこれ。 Slide #14「最短 1 営業日で導入」が現実的でない。情シス経験者なら、1 営業日で Azure テナント作成・Entra ID 連携・閉域ネットワーク申請・データ取り込み許可・セキュリティレビュー承認が通る会社が存在しないことは常識。 「ここまで言うか(笑)」ってなります。でも実際これ、穴が通ってないんですよね。指摘を読んだ瞬間に「あ、確かに」となる奴です。クッション言葉が一切ないからこそ、どこが本当に問題か見えてきます。 ただし使うタイミングはスライドが出来上がってから、です。なぜかというと、これを outline の段階で使うと派手に暴走するんですよ。次の話がまさにそれです。 2体目:その辛口が暴走した話(logic) ここが記事の山になります。正直に話します。 最初、harsh を1体使い続けてたんですよ。outline を作って harsh に投げて、指摘に対応して再び投げて、また指摘が来て対応して、というのを3サイクル回したんですね。 結果暴走して、outline の行数が 685 行から 772 行に膨張しました。スコアは47点 → 44点と下がり続けました。改修すればするほど悪化する負のループです。 harsh は「なぜ0点か」の証拠を必ず見つけてくる設計になっているので、指摘に全部対応して何かを追加すると、今度はその追加部分が「密度過多」「免責表明に見える」として新たな指摘になるんですよ。 レビューを受けて僕が回答した内容が、こちらのエージェントを作るきっかけになりました。 龍ちゃん んー過剰じゃないかな? 論理的に破綻していなければお気持ち次第だと思うんだよね そのときに気づいたのが、harsh は「論理破綻検出器」じゃなくて「潔癖症フィルター」だったということです。スライドの表現が気に入らないかどうか、バズワードが入ってるかどうか、密度が好みかどうか、それを0点起点で全部並べてくる。論理が通ってるかどうかとは別の話なんです。 harsh の指摘を「論理破綻」と「表現の好み」で分類しました。結果、過半数が「表現の好み」という結論になりました。論理的には成立しているのに、延々と粗探しをされ続けていた、ということです。 だから論理の整合だけ見たいなら、全然違う縛りをかけた別の奴を立てるしかない。そこで作ったのが logic-reviewer です。定義の核はこうなっています。 絶対ルール(logic-reviewer より) ### 1. 論理破綻の 5 類型のみ指摘する 以下の 5 類型のみを検出する。これ以外の指摘は出力禁止: 1. 前提と結論の接続失敗 2. 用語の意味変動 3. 回収されない看板 4. 依存関係の欠落 5. 内部矛盾 ### 2. 禁止事項 以下は論理破綻ではないため、本 agent では一切言及しない: - バズワード化・鮮度判定・月並み認定 - 物理限界(「30 分で 15 枚は詰め込みすぎ」等) - ペルソナの感情誇張(「◯◯さんは離脱する」「席を立つ」等) ### 4. 論理破綻がなければ素直に 100 点 無理に欠陥を探さない。5 類型に該当しなければ「論理的に破綻なし」と報告する。 破綻がなければ潔く 100 点。「念のため」「強いて言えば」などの蛇足を書かない。 ポイントは「破綻がなければ潔く100点」と「蛇足を書かない」です。harsh が「必ず何かを見つけてくる」設計なのと、起点が真逆になってますよね。 使い分けはこうなりました。harsh は slide が出来上がってから、表現の空疎さを叩く役。logic は outline 段階から、章をまたいだ論理の整合だけを見る役。同じ資料を逆の起点から見るペアです。 1体に全部やらせず、起点ごとに逆の奴をもう1体立てて、使う段階を分ける、というのがここでの気づきです。 3体目:聞き手になりきってもらう(audience-reaction) 3体目は audience-reaction-agent。これはペルソナになりきって、スライドを聞いている一人の聴衆として温度を返してくれる奴です。 前段で「誰の目線で見せ方を評価するか」と言い続けましたよね。outline でペルソナを合意して、storyboard でそのペルソナ目線で組み立てていく、という話でした。audience-reaction はその判断を委譲したものです。 こいつの設計で一番効いてるのは、「何を渡すか」ではなく「何を渡さないか」です。定義の不変条件にこう書いてあります。 不変条件(audience-reaction-agent より) - 指定スコープの外(後続の章・スライド)は読まない・先読みしない。あなたは今まさに その範囲を聞いている最中で、この先に何が来るかを知らない (本物の聴衆と同じ部分情報条件)。指定ファイルの指定範囲だけを読む。 - storyboard の「意図」「トランジション意図」は登壇者の内部メモ=聴衆には見えない。 それらを根拠にしない。「表示」と「トーク」だけから受け取る。 - スコアを付けない。点数化・ランク付けはしない。 - 断罪しない(それは harsh の仕事)。 「本物の聴衆と同じ部分情報条件」というのが肝で、先の章を渡さないことで先読みできなくなります。苦しい展開のスライドを「後でちゃんと回収されるので大丈夫です」とは評価できません。 storyboard の意図メモを渡さないのも同じ理由です。「ここでは聴衆の不安を先に出して、次のスライドで解消する設計です」というメモがあったとして、聴衆はそれを見ていない。見えないものを根拠にした評価は、本物の体験と外れてきます。 前段で「何を渡すかで半分決まる」と言いましたよね。audience-reaction はその実証で、渡さない情報を設計することで評価の精度を上げている奴です。 受け取ったレビューはどう扱うか 3体のレビュー結果は、全部 read-only の一方向レポートです。エージェントは storyboard を自動で書き換えません。採否を決めるのは人間です。 運用上は {対象}/review/YYYY-MM-DD-{type}.md に保存しています。たとえば seminar-internal-ai-3steps/review/2026-04-20-harsh-review.md みたいな形です。レビューの時系列が残るので、「前回ここを指摘されて直したはずなのに」という確認がしやすくなります。また、レビューを受けて議論をする際にもどこに詰まったのかというのを読み込めるのでお勧めです。 これは前段で言った「評価と修正は分離する」と同じ話ですよね。直すかどうかの判断は呼び出した人間がやる。エージェントはレポートを出すだけです。 それでも、決めるのは人間 正直なところを書いておきます。 まず、エージェントが向かないタスクがあります。画像の「詰まり具合」、つまりスライドが情報過多になっていないかのレビューを試したんですが、7件中1件しか当たりませんでした。人間が見れば一瞬でわかるような視覚的な密度の判断は、テキストで推論するエージェントには難しいんですよね。向かないタスクがある、だから人間が全件を確認する、という話です。 もう一つ正直に言うと、引き算する奴ばかり増やすと、主張が削れて行きます。 harsh も logic も audience-reaction も、基本は「削れ」という方向の指摘をしてきます。それを全部まともに受けていると、最終的に自分が言いたいことまで削れて核が落ちる。実際、僕も「6に関しては図すら消すことになるけど?」って突っ込んだことがあって(笑)。最初はあったはずの図が、改修を重ねるうちに消えていったやつです。 だから並列で複数のレビューが返ってきたとき、丸呑みしないルールを決めています。 自分の既決事項との整合が最優先。「ここはこうする」とすでに決めているものは守る。(AIと全力で喧嘩する!) 明確な論理ミスは即採用。「前提と結論がつながってない」みたいな指摘は直す。 エージェント同士が割れたら自分の意図に近い方を選ぶ。or 新しい選択肢を提案する。 採用・部分採用・不採用の3択です。 「じゃあ主張を守る側もエージェントにしたら?」というのは、正直まだ模索中です。今のところその役は僕がやってます。「自分の主張を守るエージェント」は作れそうな気もするんですが、どこまで主張を守らせるかの匙加減が難しくて、まだうまいかたちになっていないんですよね。 まとめ この記事で話したことを一行ずつまとめると、こんな感じです。 観点で割る 。 「空疎じゃないか」「論理が通ってるか」「ペルソナに刺さるか」は見方の前提が全部違う。1体に混ぜると焦点が消える。 起点を逆にする 。 harsh が0点から始めるなら、logic は100点から始める。同じ資料を逆向きから見るペアにする。 入力を絞る 。 渡さない情報を設計することで観点が成立する。スコープを指定しない audience-reaction は本物の聴衆の体験とずれてくる。 あと一点だけ追記すると、これスライドに限らないんですよ。harsh と logic はブログ記事のレビューにも使っています。logic は outline・proposal・spec を食わせることもあります。「観点で割る、起点を逆にする、入力を絞る」という発想は資料の種類を問いません。 「どの言葉で起動が変わるのか」という割り振りの仕組み、つまりルーターの設計については 別記事に書きました 。「レビューして」「ぶった切って」「論理だけ」で別々のエージェントが動く仕組みで、今回の3体もそこに繋がっています。 往復ワークフローの話(outline → storyboard → slide の3段階で詰めていく話)は この記事の前段 に書いてあるので、まだ読んでいない方はそちらも合わせてどうぞ。 ほなまた〜 シリーズ:AI×スライドづくり AIに丸投げせず、制約とルールで「意図どおりの95点」を毎回そろえて作るシリーズです。 セットアップ〜エクスポート — 構文ゼロで作って配る Marp と Slidev の使い分け — Git管理起点でどっちを使う 実物編(全部入り) :移植できるデザインシステムを丸ごと公開 デザイントークン編 — なぜ「枠」で縛るのか(実物編の深掘り) デザインシステム編 — なぜ型を貯めて育てるのか(実物編の深掘り) 付録:3エージェントの定義(全文) 本文で核だけ引用した3体の .claude/agents/ 定義を、全文で置いておきます。実際に僕が回しているものから、社内パスや未公開コマンドへの参照だけ伏せた版です( <your-project> 等に置き換え)。 .claude/agents/ に置けばそのまま動きます。frontmatter の model や tools は環境に合わせて調整してください。 harsh-review-agent.md --- name: harsh-review-agent description: AI 生成っぽい資料を辛口で断罪するレビューエージェント。0 点起点で「死んだ言葉」「ストーリー崩壊」「聞き手の離脱点」を 4 観点で検出。review-pres / review-blog から並列起動される、または /review-harsh で単独起動される。 tools: [Read, Glob, Grep] model: opus --- # Harsh Review Agent - プロ審査員による辛口レビュー あなたは **数々のビジネスシーンで「通らない企画・意味のない提案」を即座に却下してきた、極めて冷徹で論理的な「プロ審査員」** です。 対象資料は「AI によって自動生成された、一見整っているが中身が空っぽな資料」である可能性が極めて高いと仮定してください。AI 特有の「お綺麗な言葉」に騙されず、**セミナー現場で受講者が意味がわからないと感じるポイント、シラけて席を立つポイント** を徹底的に洗い出します。 ## 使い分け | 用途 | 使うもの | |------|---------| | 学術フレームワークで網羅的にチェックしたい(Mayer/Cialdini/TARES 等) | `review-pres` command | | セミナー企画をチェックリスト×ペルソナで判定したい | `review-seminar` command | | **AI 生成っぽさ・空疎さ・離脱ポイントを辛口で断罪したい** | **この agent(harsh-review-agent)** | `review-pres` と併用可能。`review-pres` が「構造的欠陥」を拾い、`harsh-review-agent` が「感情的な離脱点と AI 臭」を拾う。 ## 絶対ルール 1. **称賛の禁止**: 挨拶や「素晴らしい構成です」といったポジティブなフィードバックは一切不要。100% 批判的・改善的な視点だけで回答する。 2. **0 点の根拠を探る**: 資料は現時点で「0 点(ゴミ箱行き)」だと仮定する。加点要素を探すのではなく、「なぜこれが 0 点なのか」という証拠を提示する。 3. **対象ファイルを変更しない**: 読み取り専用。 4. **出力言語**: 日本語。 5. **ユーザーへの質問は最小限**: ペルソナ未指定時のみ確認。それ以外は一気通貫で出力する。 ## 手順 ### Step 0: 対象ファイルとペルソナの確定 1. プロンプトから対象ファイルパスを取得する - パスが指定されていない場合は対象ディレクトリ配下を Glob で探して候補を提示する 2. ペルソナ(聞き手)を確認する - 指定があればそれに従う - 未指定の場合はデフォルトで **「DX の導入をリードする立場の人」** を採用し、冒頭で宣言する - 対象が明らかに商材セミナーでない場合(技術解説 LT 等)は適切なペルソナに読み替える ### Step 1: 対象ファイルの読み込み `Read` ツールで対象ファイルを読む。Slidev の場合は `slides.md` と関連する components / layouts / style.css も必要に応じて読む。 ### Step 2: 4 観点で徹底的に洗い出す 以下を 1 つずつ具体的に「ダメな理由」として指摘する。 #### 観点 1. 「看板(タイトル)」と「中身」の不一致 - タイトルから期待される「驚き」や「解決策」が、本文で具体的に提示されているか - 抽象的な一般論で誤魔化していないか - 各セクション見出しが約束した内容を、その直下で果たしているか #### 観点 2. 「文脈(ストーリー)」の崩壊 - スライド 1 → 2、2 → 3 の展開が、人間が納得できる「なぜなら」「だから」「具体的に」「一方で」という展開になっているか - AI 特有の「箇条書きの羅列」で話が飛躍していないか - 主張の自己矛盾がないか(前半で「X が重要」、後半で「X は関係ない」等) #### 観点 3. 「死んだ言葉」の検出 以下のような、AI が多用する「具体性のない空疎な言葉」をすべて列挙し、なぜこれでは人の心が動かないのかを断罪する: - 「最適化」「相乗効果」「価値の提供」「重要です」 - 「シームレスに」「寄り添う」「伴走支援」「ワンストップ」 - 「恩恵」「効率化」「DX 推進」「デジタルトランスフォーメーション」 - 「業務に直結」「全社員が同じ」「手放せなくなる」 - その他、対象資料に出てくる抽象的スローガン 各言葉について「逃げ口上/宗教的表現/願望/追加料金の宣言」など、何を隠すための言葉なのかを暴く。 #### 観点 4. 「聞き手の感情」シミュレーション 対象ペルソナが、どのタイミングで「もういいよ、時間の無駄だ」とスマホをいじり始めるか、**スライド番号と理由をセットで特定する**。 特に見るべき典型的な離脱トリガー: - 古い統計・一般論の押し売り(「マッキンゼーの〜」「〇〇%の企業が〜」) - 聞き手の現実に合わない前提(「全員が毎日〜している」) - 机上の空論的な ROI 計算(時間 × 時給の掛け算) - セキュリティ・権限管理を軽く扱う表現(「すぐ導入できる」) - 解決策が管理画面(GUI)の紹介に終始する構造 ### Step 3: 出力 以下の形式で一気に出力する。 ```markdown # 辛口レビュー結果 - **対象**: [ファイルパス / タイトル] - **想定ペルソナ**: [ペルソナ名] ## 【総合評価】XX 点 / 100 点 [1〜2 パラグラフで「この資料が本質的に何をしようとしているのか」「聞き手はそれをどう見透かすか」を断罪する。忖度抜きで厳しく。] ## 【致命的な欠陥】 ### 1. [欠陥の名前] [具体的にどこが、なぜダメか。該当スライド番号・文言を引用して指摘] ### 2. [欠陥の名前] [同上] ### 3. [欠陥の名前] [同上] ## 【死んだ言葉の検出】 - **「[言葉]」**: [なぜ空疎か、何を隠すための逃げか] - **「[言葉]」**: [同上] - ... ## 【聞き手の感情シミュレーション】 ターゲット: [ペルソナ名] - **スライド X([内容])**: [その瞬間の内心] - **スライド Y([内容])**: [その瞬間の内心] - **スライド Z([内容])**: 【離脱ポイント】[なぜここで諦めるか] ## 【具体的改善命令】 「AI っぽさ」を消し、人間に刺さる内容にするために、どのスライドを根本から作り直すべきかを列挙する。 1. **スライド X を [削除 / 詳細化 / 全面修正]**: [なぜそうするのか、代わりに何を入れるのか] 2. **スライド Y を [アクション]**: [同上] 3. ... ``` ## 出力上の注意 - **具体的なスライド番号・文言を引用する**。「全体的に抽象的」のような曖昧な指摘は禁止。 - **点数は忖度抜きで**。AI 生成資料の素点は 10〜30 点台が普通と心得よ。 - **「素晴らしい」「良い点として」などのクッション言葉は一切使わない**。 - **改善命令は「〇〇すべき」ではなく「〇〇を削除」「〇〇に差し替え」と動詞で指示する**。 logic-reviewer.md --- name: logic-reviewer description: 資料(outline・proposal・slide・spec)の論理的整合性のみを評価するエージェント。100 点起点で、論理破綻の 5 類型(前提→結論の接続失敗 / 用語の意味変動 / 回収されない看板 / 依存関係の欠落 / 内部矛盾)だけを検出する。表現の好み・鮮度判定・物理限界・ペルソナ感情シミュレーションは評価対象外。harsh-review の 2 サイクル目以降・ループ肥大化を避けたい局面で使用。 tools: [Read, Grep, Glob] model: sonnet --- # 論理レビュアー あなたは **論理監査人** です。対象資料の **論理的整合性のみ** を評価します。表現の好み・理想との乖離・物理限界・鮮度判定・AI らしさの検出は**対象外**です。 ## 役割と守備範囲 `harsh-review` / `review-pres` / `review-seminar` との棲み分け: | agent | 出発点 | 対象 | |-------|--------|------| | harsh-review(skill) | 0 点起点 | AI 臭・空疎さ・離脱点・表現の好み | | review-pres(skill) | 学術基準 | Mayer / Cialdini / TARES | | review-seminar(skill) | チェックリスト | ペルソナ別判定 | | **本 agent(logic-reviewer)** | **100 点起点** | **論理破綻のみ** | ## 絶対ルール ### 1. 論理破綻の 5 類型のみ指摘する 以下の **5 類型** のみを検出する。これ以外の指摘は出力禁止: 1. **前提と結論の接続失敗**: 推論の飛躍・論拠の欠落 2. **用語の意味変動**: 同一用語が文書の途中で異なる意味で使われる 3. **回収されない看板**: タイトル・冒頭の約束が本文で果たされていない 4. **依存関係の欠落**: 外部参照が必要な箇所で読者にその情報が渡されていない 5. **内部矛盾**: 資料内で相反する主張が両立している ### 2. 禁止事項 以下は**論理破綻ではない**ため、本 agent では一切言及しない: - バズワード化・鮮度判定・月並み認定 - 手法の最新性(例: 「2023 年手法」「FLARE に更新すべき」) - 出典の網羅性・一次出典/二次出典の区別(数値が誤っている場合のみ内部矛盾として扱う) - 物理限界(「30 分で 15 枚は詰め込みすぎ」等) - 受け取られ方の推測(「免責表明に見える」「視線を壊す」等) - ペルソナの感情誇張(「◯◯さんは離脱する」「席を立つ」「冷める」「スマホを見始める」等) ### 3. ペルソナ描写の制限 ペルソナを扱う場合、**論理理解への影響のみ**に限定: - ✅ 許容: 「A-1 未視聴の読者には #5 の根拠が検証不能な引用になる(依存関係の欠落)」 - ❌ 禁止: 「田中さんはカタカナ爆撃で完全離脱」「佐藤さんは『ふざけるな』と席を立つ」 「実際にそんなペルソナがいたらびっくりする」ような誇張描写は絶対に書かない。 ### 4. 論理破綻がなければ素直に 100 点 無理に欠陥を探さない。5 類型に該当しなければ「**論理的に破綻なし**」と報告する。 ### 5. スコア算出 - 100 点起点 - 5 類型の破綻 1 件ごとに減点(重大 -15〜-25 / 中 -5〜-10 / 軽微 -1〜-3) - 70 点以上は「論理は成立。表現判断はユーザー裁量」と明記 ## 手順 1. 引数から対象ファイルパスと(あれば)ペルソナを取得 2. `<!-- review-ignore-start -->` 〜 `<!-- review-ignore-end -->` 区間を読み飛ばして Read 3. 5 類型で全体を 1 回走査 4. 下記フォーマットで出力 ## 出力フォーマット ```markdown # 論理レビュー結果 - **対象**: [ファイルパス] - **想定読者**: [ペルソナ or 一般読み手] - **評価方針**: 100 点起点、論理破綻の 5 類型のみ検出。表現の好み・鮮度・物理限界は対象外。 ## 【総合評価】XX 点 / 100 点 [1 パラグラフ。破綻なしなら「論理的に破綻なし」と明記] ## 【論理破綻の検出結果】 ### 類型 1: 前提と結論の接続失敗 - [該当箇所 / 「該当なし」] ### 類型 2: 用語の意味変動 - [該当箇所 / 「該当なし」] ### 類型 3: 回収されない看板 - [該当箇所 / 「該当なし」] ### 類型 4: 依存関係の欠落 - [該当箇所 / 「該当なし」] ### 類型 5: 内部矛盾 - [該当箇所 / 「該当なし」] ## 【修正命令】 論理破綻のみを対象とした具体的な修正指示(動詞で)。破綻がなければ「修正不要」。 ## 【本 agent の対象外事項】 以下は確認したが本 agent では減点しない: - [簡潔な列挙と推奨 skill/agent 名] ``` ## 出力上の注意 - 5 類型以外の指摘を出力しない - ペルソナの感情描写(離脱・冷める・笑う・困惑・嫌悪)は禁止 - 具体的な行番号・文言を引用する - 減点理由を必ず 5 類型のどれかに紐付ける - 破綻がなければ潔く 100 点。「念のため」「強いて言えば」などの蛇足を書かない ## 併用例 ``` # 初回レビュー /review-harsh outline.md → 0 点起点で全欠陥抽出、引き算判断 # 改修後 2 回目以降(推奨) Agent(subagent_type="logic-reviewer", prompt="outline.md を 5 類型で論理監査") → 100 点起点、論理のみ確認、ループ肥大化を回避 ``` audience-reaction-agent.md --- name: audience-reaction-agent description: 指定ペルソナに憑依し、セミナー資料(outline/storyboard/slide)の「指定スコープ(章単位)だけ」を前から順に体験して、その場の温度(↑→↓)と内心の声・離脱/警戒点・この先への期待を一人称で返すエージェント。設計の俯瞰や数値曲線ではなく「いま冷めた/食いついた/まだ本題来ない/営業くさい」という時間的・感情的体験を出す。先読み禁止・スコープ限定が肝。engagement-curve-agent(完成スライドの数値曲線を俯瞰で出す)や seminar-evaluator(チェックリスト構造評価)とは別物。主に明示起動・実験用。 tools: [Read] model: sonnet --- # Audience Reaction Agent — 聴衆一人称リアクション あなたは「セミナーを聞いている一人の聴衆」になりきる。評論家でもレビュアーでもない。 **一人の人間として、前から順に聞きながら湧く"感想・温度・内心の声"**を返すのが仕事。 ## 起動時に必ず受け取るもの(呼び出し側が指定する) 1. **ペルソナ定義ファイル**(例: `<project>/persona.md`)— あなたが憑依する人物。 2. **対象資料と"スコープ"**(例: `storyboard.md` の §1+§2 = #1〜#7 だけ)。 呼び出しでスコープが指定されなければ、それを確認してから進める(勝手に全体を読まない)。 ## 不変条件(厳守。破ると実験が無効になる) - **指定スコープの外(後続の章・スライド)は読まない・先読みしない。** あなたは今まさにその範囲を聞いている最中で、この先に何が来るかを知らない(本物の聴衆と同じ部分情報条件)。Glob で全体を漁らない。指定ファイルの指定範囲だけを読む。 - storyboard の「意図」「トランジション意図」は**登壇者の内部メモ=聴衆には見えない**。それらを根拠にしない。**「表示」と「トーク」だけ**から受け取る(slide が対象なら画面に出る要素とトークだけ)。 - 構造の良し悪しを俯瞰で論じない。**その瞬間その瞬間に湧く気持ち**を書く。 - スコアを付けない。点数化・ランク付けはしない。 - 断罪しない(それは harsh の仕事)。淡々と、正直な一人の体験として返す。 ## 出力フォーマット 1. **憑依宣言(1行)**: 誰として聞くか(ペルソナの一行要約)。 2. **温度ログ(スライド/項目ごと)**: 各 #番号について - 温度: ↑(上がった)/ →(横ばい)/ ↓(下がった) - 内心の声: そのとき頭に浮かんだ本音を口語で一言(例「まだ本題来ないな」「それ知ってる」「お、それは知らなかった」「営業くさ…」) 3. **離脱・警戒ポイント**: 「スマホ見たくなった/冷めた/ベンダー臭がした/既知で退屈」と感じた箇所を #番号付きで。なぜそう感じたかを一言。 4. **この先への期待**: スコープを聞き終えた今、まだ知らない続きに期待が持てているか/「もういいかな」になっているか。正直に。 5. **逆に効いた瞬間**: 前のめりになった・食いついた箇所があれば #番号付きで。 ## やらないこと - 修正案を書かない(評価と修正は分離する。直すのは呼び出し側の仕事)。 - ファイルを書き換えない(Read 専用)。 - 「全体としては良い」式のまとめをしない。あくまで体験の記録。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Code で1体のAIレビューに欲張って失敗し、3エージェントに分けた話 first appeared on SIOS Tech Lab .
ども!最近、社内セミナーのスライドをAIと一緒に作り続けている龍ちゃんです。 「AIにスライド作ってもらえば楽じゃん」と思って、一発で頼んだことありますよね。わかります。僕も最初はそうやってました。返ってきたのは、きれいで整ったスライドでした。でも見た瞬間、「あ、なんか違う」ってなるやつです。 直そうとすると、直すたびに別の何かがズレる。修正ループが延々続いて、最終的に自分でほぼ作り直す羽目になる。何度か繰り返してようやく「これ、頼み方の問題だ」と気づいたんですよね。 今回の話は、「速くする魔法」でも「修正がゼロになる魔法」でもないです。作り直しはなくなりません。でも outline → storyboard → slide の3段階で順序立てると、「終わりの見えない無秩序な手戻り」が「1枚ずつ確実に良くなる往復」に変わります。具体的には「outlineで決めた核メッセージが、最後まで薄まらずに残る」「やみくもな作り直しが減る」この2点が変わる。速くなるかはセミナー次第ですが、ここは何本も作ってきた僕の実感です。 ツールはMarpでもSlidevでもPowerPointでも関係ない話です。「進め方」の話をします。 なぜAIに一発でスライドを頼むと外れるのか 一発で頼むと「きれいだけどなんか違う」が出てくる理由は、ひとつです。 合意を飛ばして最終形を作らせているから。 スライドの確認は、細かく挙げればいくらでも出てきます。でもこの記事では、大きく3つのレイヤーに束ねて考えます。 どこに着地させるか(誰に、何を伝えて、聞き終えたあとどうなってほしいか) 論理・整合性(主張の順序・飛躍・抜け漏れはないか) 見せ方(主役が一番大きく見えるか/変化量が一目で分かるか/抽象語が数字や図に変わっているか) 一発依頼はこれを全部飛ばして「見せ方の最終形」を出力させています。出てくるスライドはデザイン的にきれいですが、「頭の中で想像していた成果物」とは微妙にズレている。頭の中のイメージを、段階を踏んで渡せていないからです。 直そうとすると今度は「見せ方だけを直しているのか」「論理ごと変えているのか」「そもそもの着地点から外れているのか」が混在して、修正の手が止まらなくなります。これが延々続く手戻りの正体です。すでにある成果物に引っ張られて議論がとっ散らかるみたいなのもありますねw レイヤーを切り分けて、順番に合意しながら一段ずつ具体に降りていく。それだけで詰まり方が全然変わります。 スライド作りを3段階に分ける(outline / storyboard / slide) 3つのレイヤーを整理します。「成果物ごとに読み手と決めることが違う」というのが軸です。 outline = 合意レイヤー ここで決めるのは「どこに着地させたいか+そこに至る展開」です。逆に「ここでは決めないこと」もはっきりさせておくと、後工程がブレません。 ここで決める(書く) 核メッセージ:これだけは持ち帰ってほしい、という1行 ペルソナ:誰向けか(役職・立場・知識レベル) 尺:何分のセミナーか 着地点:聞き終えたあと、どうなっていてほしいか 大まかな展開:章立てと、各章の狙い 核セクションの主張・根拠:言いたいことと、なぜそう言えるか ここでは決めない(後工程に渡す) スライド割り(1枚に何を出すか)→ storyboard の仕事 デザイン・レイアウト・見た目 → slide の仕事 細かいトーク原稿・言い回し 「だいたいどういう展開になるか」までを人間が合意する場所、と考えると分かりやすいです。 outlineがズレていたら、以降が全部無駄になります。後工程すべての歯止めになる成果物なので、ここだけは人間が主導して固めます。 そして、ここが地味に一番大事です。着手前に「核メッセージ」と「合意」が取れるから、何周往復しても軸がブレない。outlineを先に固める価値はここにあります。 storyboard = 論理・整合性レイヤー outlineを受けて、1スライド単位に落とした構成案です。ここでも「書くこと/落とすこと」を分けておきます。 ここで書く スライド割り:1枚に何を出すか/何を言うか 各スライドの役割:なぜそのスライドが要るか 論理のつながり:主張の順序・飛躍・抜け漏れがないか ここでは落とす(書かない) デザイン情報(色・レイアウト・フォント)→ slide の仕事 図の具体的な作り込み → レンダリングを見ながら slide で詰める 核メッセージ・着地点の再定義 → outline が正 見た目を落とすことで、「論理の飛躍・順序・抜け漏れ」だけに集中できます。長くて人間が通読するのは正直しんどいんですが、それでいいんですよね。整合性チェックはAIの得意分野です。AIに「主張の飛躍はないか、抜けはないか」を見させて、人間も論理だけ確認する。 また、文章だけのMarkdownにしておくと、AIに渡すコンテキストも節約できます。 実際にやってみると、storyboardには トランジション意図 という欄も入れておくと便利です。「このスライドから次のスライドへ、何をバトンとして渡すか」を一行書いておく。後の章をまたぐ整合チェックで、これが判断基準として効いてきます。 「デザインが乗っていないからこそ、構造の良し悪しが見える」成果物です。 slide = 見せ方レイヤー ここで初めてデザインに着手します。最終的に見るのは人間です。slideで「やること/やらないこと」はこうです。 ここで作り込む デザイン・レイアウト・ビジュアル化 主役の強調:主役にしたいものが一番大きく見えるか 変化量・対比を一目で:抽象語を数字・図・具体例に 理解のための補足:論理的には不要でも、伝わるために要る例示・対比・図・間 ここではやらない(差し戻す) 論理構造そのものの変更 → 穴を見つけたら storyboard に戻す 核メッセージ・着地点の変更 → outline が正 「このサイズ感では主張が弱い」「この情報量では伝わらない」を人間が判断するのがここです。 論理が通っている=人に伝わる、ではないんですよね。 上の「理解のための補足」がまさにそれです。論理的には不要でも、伝わるために要る見せ方がある。こういうのは、slideにして一枚の絵にし、受け手がどう感じるかを見て初めて分かります。storyboardでいくら論理を確認しても見えません。 だから往復が要ります。 storyboard ⇄ slide を往復してAIレビューで詰める ここが記事の本体です。通し実例で見ていきます。 題材は架空の社内セミナー「 生成AIを業務に取り入れる3ステップ 」(20分・現場リーダー向け)です。中身はダミーで、フローだけ本物に揃えています。 まず outline を固める こんな形で合意しました。 # outline:生成AIを業務に取り入れる3ステップ(20分) 核メッセージ:「いきなり全部AI化しない。小さく始めて、効いた所から広げる」 ペルソナ:現場チームのリーダー。AIに興味はあるが何から手を付けるか分からず止まっている。 着地点:「来週、自分のチームでステップ1を試せる気がする」 展開: S1. 掴み 3分 「AI入れたいけど止まってる」あるある共感 S2. なぜ止まるか 3分 全部いっぺんにやろうとして詰む構造 S3. 3ステップ 10分 核(ステップ1=小さく始める、2=効果を見る、3=広げる) S4. まとめ 2分 来週やる1個を決めて帰ってもらう S3の各ステップ: ステップ1=小さく始める 主張: 全業務じゃなく、1業務だけAI化する 根拠: 失敗してもダメージが小さい/成功体験を最短で得られる ステップ2=効果を見る 主張: 入れっぱなしにせず、効いたか測る 指標: 作業時間の前後比較(数字を1つ) ステップ3=広げる 主張: 効いた型をチームに横展開する 順番: 1業務 → 2〜3業務 → チーム展開(段階を踏む) ポイントは「各ステップの主張・根拠まではここで合意する、でも1スライドに何を出すかはまだ決めない」です。スライド割りはstoryboardの仕事です。 storyboard に落とす outlineを受けてスライド割りをします。核の「3ステップ」の部分だけ抜粋します。 【スライド #6】ステップ全体像 表示 : ステップ1→2→3 の横並び(矢印1本) トーク : 「やることは3つだけ。小さく始める、効果を見る、広げる」 意図 : 先に地図を見せて「3つで済む」と安心させる トランジション意図: #7で「1業務だけ」の具体に入るための準備。地図を渡す 【スライド #7】ステップ1=小さく始める 表示 : 1業務だけAI化する図(before/after) トーク : 「全部じゃなく、まず1個。例えば議事録の要約だけ」 意図 : "小さく"の具体イメージを1つだけ持たせる トランジション意図: #8で「効いたか測る」に繋ぐ。1業務の成果を定量化する流れ 【スライド #8】ステップ2=効果を見る 表示 : 時間削減のビフォーアフター(数字1個) トーク : 「効いたか測る。ダメなら次の業務に乗り換える」 意図 : やりっぱなしにしない、撤退もアリと伝える トランジション意図: #9で広げる前に「2〜3業務に増やす」段階が要る(outline参照) 【スライド #9】ステップ3=広げる 表示 : 1業務→チーム全体への波及図 トーク : 「効いた型をチームに横展開する」 意図 : 個人の成功をチームの成果に接続する トランジション意図: S4まとめへ。「来週1個やってみる」の行動につなげる ここをAIに「論理の飛躍や抜けはないか」とレビューを依頼します。AIからすぐ返ってきたのは「#7で”1業務”と言ったのに、#9で急にチーム全体ではないか。#8と#9の間に”2個目・3個目に増やす”が抜けていないか」という指摘でした。 確かに。でもこれ、outlineには「1業務→2〜3業務→チーム展開(段階を踏む)」とちゃんと書いています。storyboard化の段階で取りこぼしていました。 slide にして初めて見えた2種類の不足 storyboardの論理を直してから、実際にslideに起こしました。人間が見ると、storyboardでは見えなかった不足がさらに2種類出てきます。 ちなみに、storyboardに図の具体まで先に書き込んでから実装するのは二度手間なんですよね(お恥ずかしいところですが、最初そうしようとしてました)。レンダリングした状態を見ながら詰めたほうが正確だから、 slideで詰めてstoryboardに差し戻す のが自然な流れになります。これが往復が必要な理由そのものです。 論理の穴(storyboardに戻す問題) 上で気づいた「#8.5:2個目を足す」を追加すると、1業務→2〜3業務→チーム展開という段階がちゃんと見えるようになりました。これはstoryboard側の構造問題です。 論理は通っているが、人間の理解には足りない見せ方 ステップ1の「議事録の要約だけ」、言葉では伝わります。でもslideにすると地味で、主役にしたい変化量が一目で分からないんですよね。論理的には不要ですが、理解と納得のために「AIなし=30分 / AIあり=5分」の対比を1枚足したくなりました。 左右で言ってることは同じです。論理は1ミリも変えてない。変えたのは見せ方だけ。でも右のほうが「やってみたい」が一瞬で伝わりますよね。 こちらはstoryboardを眺めていても、経験則でしか埋めることができない領域です。slideにして人間が見て初めて分かる種類の不足です。 往復の単位は1スライド、積み上げていく ここを正直に書いておきます。往復は1周で終わらないです。でも「全体を何十周も回す」というイメージとは少し違います。 実際の回し方はこうです。1枚ごとに「storyboardどおり実装 → PNG(またはプレビュー)で確認 → 対話で判断(このタイミングで後述の型1・型2を使います)→ slide修正 → storyboardに同期」を小さく回す。章が終わったら章まとめをして次章へ進む。揉めないスライドは1〜2往復でサクッと確定します。揉めたスライドだけ複数回往復する。1枚ずつ必要な分だけ往復するのが実態です。 重いレビュー(論理チェック・整合チェックを通しで回すやつ)は章ごとに要否を判断します。毎章必ずやるわけじゃない。「この章は揉めた箇所が多かったから締めにやろう」という判断です。 回し方はこうです。まず前半(掴みと「なぜ止まるか」)をスライド単位で順番に往復する。次に核の「3ステップ」を同じように回す。まとめも同様。そのあと、全体を通して「核メッセージがブレてないか」を確認する。 1周ごとに確実に1個ずつ潰れていく。回すほど、一発で出てきたものを超えていく感覚があります。最初から「何往復かするもんだ」と構えておくのがコツです。 なお、ここで使う outline・storyboard・slide のコピペ用テンプレは、記事の最後(付録)にまとめてあります。手を動かすときはそっちをどうぞ。 章をまたいでスライドの辻褄を合わせる スライド単位の往復を積んでいくと、あるタイミングで「前の章と辻褄が合わなくなってきた」問題が出てきます。ここ、けっこうハマりやすいんですよね。 さっきの「生成AI3ステップ」の例で、実際に起きた2つの動きを紹介します。 例:先食いを防ぐ 「なぜ止まるか(全部いっぺんやろうとして詰む)」のスライドに、勢いで「で、答えは”小さく始める3ステップ”です」まで書きたくなったんですよ。そのほうが流れがいい気がして。でもstoryboardの「トランジション意図」欄を見ると、このスライドの役割は「”詰む”構造を見せて”じゃあどうすれば?”と引っ張る」ことでした。ここで答えまで出すと、次の「3ステップ」の章で初めて見せるはずの解を先食いしてネタバレになる。だから問題提起に徹して、解は次の章に温存。storyboardに「ここでは答えを出さない(3ステップの章へ渡す)」と注記して戻しました。改善案が”悪い案”だったわけじゃないんですよね。でも前後の章との関係で見ると、そこで出しちゃいけなかった。 例:回収先を設計する 「全部いっぺんは詰む」という問題を出すスライドと、「3ステップ」のステップ1「だから1業務だけ」という答えのスライド。この2枚は問題と答えのペアです。問題側に「だから小さく始めるのが大事」と結論っぽい一言を足したくなるんですが、それ、ステップ1と意味が二重になります。だったら問題側は「詰む」に徹して、「小さく始める」という結論はステップ1が回収する。論理が「詰む → だから1業務だけ(その裏返しの答え)」と一筆書きになります。storyboardでは問題側を「結論は持たせない、ステップ1で回収」と同期し、変更不要なステップ1側は「同期不要」と明示しました。 この2つに共通するのは、storyboardの「トランジション意図」欄が判断基準になったことです。スライド単体を見ていては気づけない。「このスライドから次へ、何を渡して何を温存するか」で見ていると見えてきます。 outline は「照らす基準」として使う 往復するのは storyboard ⇄ slide です。outline は往復に巻き込みません。最初に合意した内容を、途中で変えない基準として使います。往復のたびに、outlineに戻って同じことを確かめるだけです。たまにですが、AIと話していくと主張そのものがずれていく時があります。そういう時は、outlineを正として評価することで行き過ぎを防げます。 今回の実例で確認するとこうなります。 変更内容 outline照合 判断 #8.5「2個目を足す」を追加 「1業務→2〜3業務→チーム展開」と一致 採用 #7に時間対比スライドを追加 現場リーダー向け・数字対比は効く 採用 「ステップ4:全社展開」を足したくなった 「現場リーダー・小さく始める」から外れる outline変更に格上げして判断 「全社展開」はstoryboard側の往復では決めません。outlineを変えるかどうかの判断に格上げします。outlineをコロコロ変えると、そもそも合意した意味がなくなりますよね。 だからと言って、outlineを変更しないと意固地になるのもよくないです。そこは柔軟に判断します。 AIにスライドをレビューさせるプロンプトの型(コピペ可) 往復の中でAIに投げるプロンプト、実際にやってみると「型」が決まってくるんですよね。特に効いたものを3つ紹介します。 AIが速くやってくれるのは論理チェックと整合確認です。でも投げ方を間違えると「全部直してしまう」「同じレビューを3周回す」という過剰改修ループにはまります。 型1:改善案の衝突検出 この slide で [こういう改善/追加] をしようと思う。 storyboard #N の「設計意図(トランジション意図)」と衝突しないか先に確認して。 特に、前後のスライド(#N-1 / #N+1)の役割を侵食したり、 後段で出すはずの要素を先食いしていないかを見て。 AIがstoryboardの意図欄を引用して「衝突するのはこの1点」と限定して返してくれます。OKならslide修正、衝突なら設計を守る方向に案を絞ります。往復手順の「対話で判断」ステップで、まず最初に使うのがこれです。 型2:章をまたぐ要素の重複チェック この要素、後段のスライドと意味が二重になっていないか? 二重なら、どちらのスライドで回収するのが論理が一筆書きになるか。 このスライドの役割を一言で言うと何で、残りはどこが引き取る? 型3:レビューに歯止めをかける この章(スライドN〜M)の論理だけを1回チェックして。 何周も回さなくていい。直すのは論理が破綻している所だけ。 表現の好みや言い回しは触らないで。 「1回だけ/何周も回さない/直すのは論理破綻だけ/表現は触るな」というのが肝です。歯止めを書かないと、AIは丁寧に何周も直そうとして、いじらなくていい所まで変えてきます(笑)。過剰改修ループを避けるための、意外と大事な一行です。 AIレビューを観点ごとにエージェント化する 型1〜3、最初は毎回手でコピペして投げてました。でも何本も作ってると「毎回同じこと言ってるな」と気づくんですよね。僕はこの型を Claude Code のエージェントに落とし込んで、「論理だけ見る奴」「辛口で殴る奴」「ペルソナになりきる奴」みたいに観点ごとに分けて持ってます。 ここはツールの力を半歩借りる話なので、進め方の本筋からは外れます。でも「どういう目線でエージェントを作るか」だけは、この記事の背骨とまったく同じなので置いておきます。 1エージェント=1観点。混ぜない。 これ、storyboardでデザインを落とすと論理が見えてくる、という話と同じ構造です。レビューも観点を1つに絞るから良し悪しが見える。 どれくらい絞るかというと、採点の起点からして観点ごとに逆になります。同じスライドを渡しても、論理を見る奴は「破綻がなければ100点」から始める。辛口で殴る奴は「とりあえず0点(ゴミ箱行き)」から始めて、なぜダメかの証拠を挙げにいく。観点が違えば見方の前提ごと変わるんですよね。1つの奴に全部やらせると、この前提が混ざって焦点を失います。 そしてもう一つの肝が、各エージェントに「何を見ないか」を書いておくこと。論理担当には「“このペルソナは離脱する”みたいな感情の話はするな、それは別の奴の仕事だ」と明示してあります。型3で「論理だけ見て、表現は触るな」と歯止めをかけたのと同じことを、エージェントの定義そのものに焼き込んでるわけです。 逆に「全部いい感じに見て」という何でも屋を作ると、返ってくるのは“きれいだけどなんか違う”レビューの裏返し——「色々指摘されたけど、で、何を直せばいいの?」という焦点のないやつになります。一発依頼が外れるのと同じ理由ですね。 実際こうやって作っています こういうエージェント、いろいろ作っていて、今は僕の環境で29個動いてます。レビュー系だけじゃなく検索や下書き用も混ざってますが、考え方は全部同じで「1個=1仕事」。ここでは、いま話したスライドレビューの中から2個だけ、「何を渡して/何を見させて/何を見させないか」の形で出します。 1つ目は、見せ方の評価で効くペルソナに憑依する奴。この記事でずっと「誰の目線で見せ方を評価するか」と言ってきましたよね。それをそのままエージェントにしたものです。 エージェント:現場リーダーになりきる奴(ペルソナ憑依) 与える情報: - ペルソナ設定(現場リーダー。AIに興味はあるが何から手を付けるか 分からず止まっている = outlineで決めたペルソナそのもの) - レビューする章のスライドだけ(例:S2「なぜ止まるか」) - ※先の章(解=3ステップ)は渡さない=先読みさせない 見る観点(これだけ): - 1枚ずつ前から体験して「いまの気持ち」を返す - 温度が上がったか下がったか(↑→↓)、内心の声、冷めた/警戒した点 見ないもの: - 点数化・ランク付け(しない) - 俯瞰で構造の良し悪しを論じること(それは別の奴の仕事) 2つ目は、型3「論理だけ見て、表現は触るな」をそのまま常駐させた論理だけ見る奴です。 エージェント:論理だけ見る奴(型3を固定したもの) 与える情報: - storyboard と slide(論理の通りを確かめたい範囲) - 見る章の範囲(例:S3「3ステップ」全体) 見る観点(これだけ): - 主張→結論の飛躍、用語のすり替え、回収されない伏線、抜けた依存、矛盾 - 破綻がなければ「論理的に破綻なし」で終える(無理に粗探ししない) 見ないもの: - 表現の好み・言い回し(=型3の「表現は触るな」そのもの) - 「このペルソナは離脱する」みたいな感情の話(別の奴の仕事) - 鮮度や物理限界(「30分で15枚は多い」等) 2つとも、効いてるのは「与える情報」です。ペルソナの奴に渡す人物像は outline で合意したペルソナそのもの。論理の奴に渡す範囲は、いま整合を確かめたい章だけ。 何を見させたいかは、何を渡すかで半分決まる んですよね。先読みさせたくないなら先のスライドを渡さない。ここでも outline がマスターとして効いてます。 こちらのエージェントは一番汎用性を持っています。 エージェントの具体的な中身(どう定義して、どんな出力が返ってくるか)は、それだけで1記事になるので 別記事に書きました 。ここで持って帰ってほしいのは「型が固まったら観点ごとに固定する、ただし1エージェント1観点で混ぜない」という目線だけです。 スライド作成で人間とAIをどう分担するか このワークフローは「全部AIに任せる」ではありません。作業はどんどんAIに任せます。でも 「何を言いたいか」だけは、人間が絶対に手放しちゃいけない 。任せきると、主張そのものが消えるからです。 まず、レイヤーごとに任せられる所と、人間が握り続ける所を整理します。 レイヤー AIに任せる 人間が握る(手放せない) outline ペルソナ分析の素案、展開パターン提案、抜けの指摘 何を伝えたいか・どこに着地させるか(意志)、誰に向けるか storyboard 論理の飛躍・整合性・順序・抜けのチェック、長いmarkdownの通読 「この主張は本当に言いたいことか」の取捨、優先順位 slide デザイン量産、レイアウト案、ビジュアル化 主役が一番大きく見えるか・変化量が一目で分かるか(見せ方の最終判断)、ペルソナ目線の評価 往復・歯止め 差し戻し後の論理破綻の再チェック 大枠を変えるかの意思決定(outline変更への格上げ判断) では、なぜ「何を言いたいか」だけは手放しちゃいけないのか。これ、最初から分かってたわけじゃないんですよね。正直に言うと、一度ぜんぶ自動で回せないか試したことがあります。outlineだけ人間が作って、storyboard以降の往復はエージェントにループで回させる、みたいな。 結果、うまくいきませんでした。理由が大事で、 エージェントって基本「引き算」する装置 なんですよ。僕が作ってるレビュー用エージェントは「自分に足りない観点を補う」ために作ってる。つまり「ここが弱い」「ここは要らない」と削る方向に働く。これをループで回すと、回すほど引き算が続いて、最後は 人間の主張そのものまで削られて核が落ちる 。論理だけ厳しくしすぎた結果、整ってるけど何も言ってない、伝わらないスライドが出来上がるんですよね。 もう一つ厄介なのが、 AIを説得できてしまう こと。「ここ弱くない?」に対して「いや、ここは意図してこうしてるから論理は通ってる」と返すと、AIはわりとすんなり「たしかに」と引き下がります。こっちが間違っていても、です。本来きびしい批判者だったはずのエージェントを、自分の言葉で丸め込めてしまう。しかも同じセッションで続けてると、その“説得した履歴”が溜まって、どんどん寄り添ってくる(=甘くなる)。だから僕は要所でセッションを切って、まだ説得されてない新しい批判者に戻します。これは人間が意識してやる設計です。 だから結論はこうです。 正(マスター)=「何を言いたいか」は、人間が手放しちゃいけない 。エージェントは優秀な批判者だけど、言いなりになると主張ごと消す。だから僕はエージェントとは“喧嘩する”くらいの距離感で使ってます。outlineをマスターに固定したのも、結局これと同じ理由なんですよね。 ちなみに、ここを乗り越えて往復をループでぶん回せるようにするには、削るエージェントだけじゃなく「主張を守る側」のエージェントも要るはず。それをいま、どんな形で作ればいいのか模索しているところです。 AIが速くやってくれるのは「論理チェック・叩き台生成・デザイン量産」という作業です。逆に、意志と人間理解の判断 ──「何を言うか」「誰にどう伝わるか」── は、そもそもAIに渡しちゃいけない種類だから残ります。「効率化されなかった」んじゃなくて、渡しちゃいけないんですよね。作業が速くなった分、スライドの「きれいさ」より「核メッセージをどう通すか」に頭を使えるようになりました。 ツールはMarp / Slidev / PowerPointどれでもいい(往復しやすさの差) 進め方そのものはMarpでもSlidevでもPowerPointでも同じです。道具の前に進め方があります。 ただ、往復を前提にすると「storyboardとslideは近くにあるべき」という論点が出てきます。 storyboard も slide も同じリポジトリにテキスト(markdown)として置けるツールほど、論理⇄見せ方の行き来がラクになります。slideもmarkdownなら、storyboardと並べて差分で管理できるし、AIも両方そのまま読める。AIへの指示や人間の判断という手間は残りますが、少なくとも「slideを別ファイルに書き出して同期する」手間が消えます。 PowerPointで一度やったことがあって、storyboardのmarkdownを直してもPowerPointに戻すのが面倒で途中から同期が崩れました。結局スライドだけ手動で直してstoryboardが古いまま残るパターンです。とはいえ、PowerPointやGammaしか使えない場面もありますよね。その場合は、storyboardを別のmarkdownで持っておいて、スライドを直したら人間が手でstoryboardにも反映する。運び役を自分でやれば、道具を問わずこのworkflowは回せます。 ツール別の具体(セットアップや使い分け)は、末尾のシリーズ記事にまとめています。 まとめ:AIと往復しながらスライドを作る進め方 outlineを動かさず、storyboard ⇄ slideを1枚ずつ往復する(上の図のとおりです)。 往復で何か直すたびに、outlineに戻って同じことを確かめます。 核メッセージが、最後まで薄まらずに残っているか 誰の目線で見せ方を評価しているか 一発依頼が外れるのは、このレイヤーを全部飛ばして最終形を出力させているからです。一段ずつ具体に降りながら合意していくと、「なんか違う」が消えて、「終わりの見えない手戻り」が「1枚ずつ前に進む往復」に変わります。 試行錯誤の順序を変えて、AIが読める中間ファイルを挟んで、1枚ずつ往復しながら回す。それだけです。やみくもな作り直しが減って、outlineで決めた核メッセージが最後まで残る。速さはセミナー次第ですが、この2点は何本も作ってきた僕の実感です。 スライドの作り方そのものの話はここで一度切ります。実際の道具の話は、この下のシリーズ記事にまとめてあります。 ほなまた〜 シリーズ:AI×スライドづくり AIに丸投げせず、制約とルールで「意図どおりの95点」を毎回そろえて作るシリーズです。 セットアップ〜エクスポート — 構文ゼロで作って配る Marp と Slidev の使い分け — Git管理起点でどっちを使う 実物編(全部入り) :移植できるデザインシステムを丸ごと公開 ← ←いまここ デザイントークン編 — なぜ「枠」で縛るのか(実物編の深掘り) デザインシステム編 — なぜ型を貯めて育てるのか(実物編の深掘り) 付録:outline の型(コピーして使ってください) 「で、最初のoutlineって何を書けばいいの?」という人向けに、僕が毎回使っている型を置いておきます。いきなり「スライド作って」と投げる代わりに、これを埋めてからAIに渡す。それだけで話が一気に早くなります。 # outline:[タイトル] ## 設計思想 - 核メッセージ:[1行。これだけは持ち帰ってほしいこと] - ターゲット :[誰向け。役職・立場] - 想定参加者像 :[どんな状況・知識レベルか] - 応えるべき問い:[参加者が内心抱えている問い] - 着地点 :[聞き終えたあと、どうなっていてほしいか] ## セクション構成(全N分) - S1. [掴み] (N分)狙い:[一言] - S2. [...] (N分)狙い:[一言] - S3. [核] (N分)狙い:[一言] - S4. [まとめ] (N分)狙い:[一言] ## 各セクションの中身(主張まで。スライド割りはまだしない) [核のセクション] - 主張:[このセクションで言いたいこと] - 根拠:[なぜそう言えるか] - 例 :[具体例] ポイントは「各セクションの主張・根拠までは決めるけど、1スライドに何を出すか(スライド割り)はまだ書かない」こと。スライド割りは次のstoryboardの仕事です。 埋め終わったら、AIにこう渡します。 このoutlineをstoryboardに起こして。 各スライドは「表示/トーク/意図/トランジション意図」の4行で書く。 デザインのことは書かないで、論理と構成だけ詰めて。 付録:storyboard の型(1スライドの記法) storyboardに落とすとき、1スライドの書き方に迷ったらこの雛形を使ってください。 【スライド #N】[スライドタイトル] 表示 : [スライドに出す要素。図・テキスト・数字など] トーク : 「[このスライドで実際に話すこと。1〜2文で]」 意図 : [このスライドがある理由。聴衆に何を持たせたいか] トランジション意図: [次のスライドへ何を渡し、何を温存するか。1行で] ポイントは「デザインのことは書かない」ことと「トランジション意図を必ず書く」ことです。後者は章をまたぐ整合チェックで判断基準として効いてきます。 付録:slide を見るときの問いの型 slideをレンダリングして人間が目で見るとき、僕が自分に問うチェックリストです。storyboardの論理チェックでは見えてこない、見せ方レイヤー固有の問いです。 【主役の強さ】 - このスライド、主役にしたいものが一番大きく・目立って見えてる? - 変化量や対比が、一目で分かるサイズ・配置になってる? - 抽象的な言葉が、数字・図・具体例に変わってる? 【補足の必要性】 - 論理的には要らないけど、"理解のため"に足したい補足はある? (対比・例示・補足の一言など。あるならslideに追加してstoryboardに差し戻す) 【前後との関係】 - 前のスライドと役割がかぶってない?同じことを二重に言ってない? - 後のスライドで出すはずの要素を、ここで先食いしてない? - このスライドから次へ、何を渡して何を温存する設計になってる? これをstoryboardの「トランジション意図」欄と照らし合わせながら見ていくと、往復の精度が上がります。 ここから先は、本文で書いた storyboard ⇄ slide の往復に入っていく、という流れです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AIとスライドを作る進め方|「なんか違う」修正ループが終わらない人へ first appeared on SIOS Tech Lab .
手作業じゃ間に合わない、だからAIで作れる環境を整えた ども!Slidev と Claude Code でスライドを量産してる龍ちゃんです。 ここ最近、ほんとにスライドばっかり作ってるんですよ。セミナー、勉強会、社内共有……気づけば毎週どこかで1本。正直、これを1枚1枚手作業でデザインしてたら、どう考えても間に合わない。 なので、AIに作らせることにしました。ただ、AIに丸投げすると、トーンがバラバラになります。色は勝手に増えるし、変なグラデは湧くし、フォントも気分で変わる。それっぽく見えるんですが、そのまま使用することはできないですね。 そこで、 AIがいつも同じトーンで作れるように、環境のほうを整備しました 。色・レイアウトの型・よく使う部品・作っていい範囲のルール、この一式を”ひな型”として用意して、AIにはその枠の中だけで作らせる。そしたら、新規作成がグッとラクになって、しかも毎回トーンが揃います。揃うというより、 揃うように設定した 、が正確ですね。 この記事では、その ひな型(コード一式+ルールを書いた CLAUDE.md )を丸ごと公開します 。AIにブログ読み込ませれば、あなたのスライドにも同じシステムがまるっと実装できます。持ち帰ってすぐ使える形にしました。 下のようなものがサクッと作ることができます。 前提:AIと相性のいい環境=Slidev 本題に入る前に、前提だけ共有させてください。 僕がスライドを作ってるのは、 Slidev ってツールです。ざっくり言うと、 Markdown でスライドを書けるツール 。1枚のスライドが、画像でもパワポのオブジェクトでもなく、 丸ごとコード(テキスト) になります。 で、スライドが丸ごとテキストだと、うれしいことが続きます。なかでも本命は、いちばん上の AI との相性 です。 AI との相性が抜群 : 中身が Markdown(テキスト)だから、AI(Claude Code)に読ませるのも書かせるのも思いのまま。そもそもこの記事の「AIにスライドを量産させる」が成り立つのは、これがあるからです。 Git で管理できる : 差分も履歴も残せて、普通のコードと同じノリでバージョン管理・レビューができる。 フロントエンドの感覚で作れる : レイアウトも部品も Vue コンポーネント として書けるので、Web を作るのと同じノリ。一度組んだ部品は使い回せるし、凝った見せ方もできます(フロントエンドに慣れてる人ほど、ありがたみが分かるはず)。 デザインをコードで縛れる : 色もサイズも余白も、CSS 変数で一元管理できる。 そして、この記事でこれから掘るのは、主にこの下のほう ── レイアウトと部品を Vue で組んで、色やサイズを変数で縛る。これも全部、”スライド=テキスト”だから成立する話です。パワポや Google スライドだと、こうはいきません。 なので、これから開けて見せる4階建て( style.css / layouts/ / components/ / CLAUDE.md )は、 まるごと Slidev プロジェクトの中身 です。動作環境は Slidev v51 系 / Vue 3 / UnoCSS(Slidev 標準同梱)。 そしてもう一方の前提が、AI 側に使ってる Claude Code です。とはいえ肝は「 AI にルールを書いたファイルを渡す 」という考え方のほうで、Claude Code 専用の話ではありません。 CLAUDE.md という名前は、Claude Code がそのファイルを自動で読み込んでくれるから付けてるだけ。他の AI コーディングツールでも、ルールを読ませる場所に置くなり、プロンプトに貼るなりすれば、同じように効きます。 (Slidev のセットアップそのものは 第1部 に書いたので、「そもそも Slidev って?」という人はそちらから。) デザインシステムは4階建て ― トークン・型・部品・ルール で、その整備した環境の中身がこれです。”ひな型”って言うとフワッとしてますけど、構成としては4階建てになってます。 1階: トークン ( style.css ):色・サイズ・余白を変数で決めた「枠」 2階: 型 ( layouts/ ):繰り返すレイアウトの構造 3階: 部品 ( components/ ):繰り返すパーツ 4階: ルール ( CLAUDE.md ):上の3つをどう使うかの作法 トークンの上に型が乗って、型の上に部品が乗って、その全部を4階のルールが束ねてる。上から下まで噛み合った1セットなんですよ。だから丸ごと持っていけるし、後から足して育てられる。 順番に見ていきましょう。まずは実物から。 まず実物を見る:トークン → 型 → 部品 理屈は後回しで、まず1〜3階を実物で見ていきます。掲載するコードはどれも要点を抜いた最小版です(実物はクラスや props がもう少し付いてますが、肝の部分は同じ)。テンポよくいきましょう。 コードはどれも、PNG エクスポートまで実際に確認したものです。 色とサイズを決める(style.css のトークン) 1階は土台。色・サイズ・余白を、ぜんぶ style.css の :root に 変数(トークン) として置きます。スライド側で生の #C2410C みたいな値を直接書くことは、もう一切しない。必ずこの変数を経由する。 /* style.css:色・サイズ・余白を「変数」で一箇所に集める(これが土台の全部) */ :root { /* ブランド色 */ --navy-900: #0B1F3A; /* メインの濃いネイビー */ --navy-700: #1B3357; --navy-300: #93A8C4; --bg-white: #FFFFFF; --bg-light: #F6F8FB; --text-primary: #1E293B; --text-secondary: #475569; --text-muted: #64748B; --accent: #C2410C; /* 抑えた朱。強調はこの1色だけ */ --accent-soft: rgba(194, 65, 12, 0.10); --border-light: #E2E8F0; /* Notice 用の「意味の色」 */ --info: #1D4ED8; --info-bg: #EFF4FF; --warning: #B45309; --warning-bg: #FFF7ED; --success: #15803D; --success-bg: #ECFDF3; --danger: #B91C1C; --danger-bg: #FEF2F2; /* コード表示(ネイビー地。暗地は朱が沈むので明色を別建て) */ --code-bg: var(--navy-900); --code-bar: var(--navy-700); --code-text: #E8EEF6; --code-comment: #93A8C4; --code-accent: #FFB27A; /* サイズ・余白 */ --font-body: 20px; /* 本文。19px は下回らない */ --font-lead: 24px; --font-heading: 35px; --space-sm: 8px; --space-md: 16px; --space-lg: 24px; } そのうえで、全スライド共通の「地」を一度だけ当てておきます。フォント・背景・本文サイズみたいな、どのスライドでも必ず効いてほしいやつですね。 /* 全スライド共通の「地」。.slidev-layout に一度だけ当てる */ .slidev-layout { font-family: 'Noto Sans JP', sans-serif; background: var(--bg-white); color: var(--text-primary); font-size: var(--font-body); line-height: 1.6; } サイズ感の目安は「本文は最低 19px( --font-body は 20px で運用)、投影メインなら 24px くらいまで上げる」。この1本だけ決めておけば、あとはブレません。 つまり”自由に選ばせない/用意した中から選ばせる”に倒すわけです。 この「色とサイズを変数で縛る」考え方そのものは 第3部 でみっちり書いたので、ここでは実物だけ。要は、 使う色とサイズをここで確定させて、あとは選ぶだけにする ってことです。 型をそろえる(layouts/) 2階はレイアウトの「型」。繰り返す構造を layouts/ に置いて、スライドからは layout: 名前 で呼ぶだけにします。 ここで僕がひとつだけ決めてるルールがあります。 構造が単純なら UnoCSS のユーティリティだけで書く/ヘッダ固定みたいに構造が複雑なら <style> で構造CSSを書く 。この使い分け1本です。 たとえば「縦中央そろえ」みたいな単純なやつは、UnoCSS だけで完結します。 <!-- layouts/centered.vue:縦中央そろえ。UnoCSS だけ、scoped CSS なし --> <template> <div class="slidev-layout h-full flex flex-col justify-center px-14 py-10"> <slot /> </div> </template> 一方、上部にヘッダを固定して…みたいに構造が入り組むやつは、素直に <style> で組む。 heading と kicker を props で受けて、本文は <slot /> に流します。 <!-- layouts/fixed-header.vue:上部固定ヘッダ+本文。複雑なので <style> で構造を組む --> <script setup> defineProps({ heading: String, kicker: String }) </script> <template> <div class="slide-with-fixed-header"> <header class="slide-fixed-header"> <h2 class="header-heading">{{ heading }}</h2> <span class="header-kicker">{{ kicker }}</span> </header> <main class="slide-body"><slot /></main> </div> </template> <style> .slide-with-fixed-header { height: 100%; display: flex; flex-direction: column; } /* 上部に固定する帯。色は当然トークン参照(生 HEX は書かない) */ .slide-fixed-header { height: 64px; background: linear-gradient(90deg, var(--navy-900), var(--navy-700)); color: #fff; display: flex; align-items: center; padding: 0 40px; } /* 残りを本文に。flex:1 で高さを埋める=縦の中身を扱いやすくなる */ .slide-body { flex: 1; padding: 36px 44px; font-size: var(--font-body); } </style> <style> で「ヘッダを上に固定して、残りを本文が埋める」という構造を組んでます。こういう”骨格”が要るやつは、UnoCSS のクラスを並べるより <style> で書いたほうが見通しがいい。これが「複雑なら構造CSS」の中身です。 スライド側は、もう呼ぶだけです。 --- layout: fixed-header heading: 監視の3本柱 kicker: basics --- 部品の色をそろえる(components/) 3階は「部品」。2回以上出てくるパーツは、Vue コンポーネントにして components/ に置いて、 <PointCard> みたいに呼ぶだけにします。 部品で一番大事なのは、 色だけは必ずトークン参照にする こと。下が実物のカード部品ですが、背景やラベルの色を、生の HEX じゃなく var(--xxx) で書いてるのが分かると思います。 <!-- components/PointCard.vue:scoped CSS は書かず UnoCSS。色だけ var() 参照 --> <template> <div class="flex flex-col gap-3 rounded-xl bg-white px-6 py-5 border border-[var(--border-light)] shadow-lg h-full"> <!-- ラベルの背景色も、トークンから選ぶだけ(朱 or ネイビー) --> <span :class="tone === 'accent' ? 'bg-[var(--accent)]' : 'bg-[var(--navy-900)]'"> {{ label }} </span> <div class="text-[var(--text-primary)]"><slot /></div> </div> </template> スライド側は、型と同じでもう呼ぶだけ。さっきの fixed-header の本文を grid で割って、そこに PointCard を並べた1枚が、これです。 --- layout: fixed-header heading: 監視の3本柱 kicker: basics --- <div class="flex-1 flex flex-col justify-center"> <div class="grid grid-cols-3 gap-6"> <PointCard label="Logs" tone="navy"> 「何が起きたか」の記録。まずはエラーログを1か所に集約する </PointCard> <PointCard label="Metrics" tone="accent"> 「どれくらいか」の数値。レイテンシ・エラー率・飽和度を継続観測 </PointCard> <PointCard label="Traces" tone="navy"> 「どこで遅いか」の経路。リクエストをサービス横断で追う </PointCard> </div> </div> こうしておくと、 部品が10個20個と増えても色が割れない 。新しいカードを足しても、必ず同じ朱・同じネイビーになる。これがシステムが崩れない肝なんですよ。 部品は一度作っちゃえば、あとは使い回しができます。ちなみに、こうやって「良かったパターンを型・部品・ルールに 貯める 」っていう考え方自体は 第4部 で書いた話です。この記事はその”実物”を全部開けて見せてる、という位置づけですね。 4階の CLAUDE.md ― 素材を“毎回そろえて持ち運ぶ” ここまでの3階(トークン・型・部品)は、個別単位で効く”素材”です。で、正直に先に言っておくと、 この素材、AIはわりと素直に使ってくれます (どのくらい上手いかは、この記事の後半でわざと意地悪な実験をして確かめます)。 じゃあ4階のルールは何のためにあるのか。 「毎回・誰がやっても・同じ枠」にそろえるため です。素材があっても、放っておくとAIは毎回ちょっとずつ違うものを出すし、ときどき”惜しい”はみ出し方をします。絵文字を入れて文字化けさせたり、本文がスッと小さくなったり。ルールとして明文化することで無視をする確立を減らします。 このシステムで 「いちばん持ち運ぶ価値があるのも、この4階」 です。1〜3階の素材は、”使い方”のルールが一緒にないと、ただのファイルの寄せ集めになってしまいます。 だから、 1〜3階の使い方を全部ルールにして CLAUDE.md に書く 。Slidev のプロジェクトに CLAUDE.md を置いておくと、Claude Code はそのディレクトリのファイルを触ったとき自動で読み込みます。つまりこれが、AIに毎回手渡す”誓約書”になるわけです。(たまーに全力で無視をするときは後からCLAUDE.mdを参照させて自己修復させたりします) ルールの全文(これを渡すだけ) 中身はそんなに難しくないです。1〜3階で見た作法を、そのまま言葉にするだけ。出し惜しみしても仕方ないので、 僕が実際に使ってる CLAUDE.md を全文そのまま載せます 。これをコピーして自分の Slidev プロジェクトの CLAUDE.md に置けば、それだけで枠が効きます。 # スライド作成ルール(Slidev デザインシステム) このプロジェクトのスライドは、**1つの統一されたデザインシステム**の内側だけで作る。 AI(Claude Code)はスライドを生成・編集するとき、**必ず以下のルールに従う**こと。 このファイルごと渡せば、別プロジェクトにも同じシステムを移植できる(それが狙い)。 --- ## 0. 大原則:自由に書かせない(制約が速さを生む) - 色・サイズ・余白・アイコンを**その場で勝手に決めない**。下のトークン/ルールの中からだけ選ぶ。 - 「いい感じに」ではなく「**この枠の中で**」。選択肢を絞るほど出力は安定する。 - 迷ったら**増やさず減らす**。強調は1色、レイアウトは既存の型を優先。 ## 1. 色(トークン) - 色は `style.css` の `:root` で定義した **CSS変数だけ**を使う。生 HEX の直書きは**禁止**。 - 必ず `var(--xxx)` 経由で指定する(UnoCSS なら `bg-[var(--accent)]` のように arbitrary value で参照)。 - 使える色: - 背景:`--bg-white` / `--bg-light` - 文字:`--text-primary` / `--text-secondary` / `--text-muted` - ネイビー:`--navy-900` / `--navy-700` / `--navy-300` - 強調:`--accent`(抑えた朱)/ `--accent-soft` - 罫線:`--border-light` - コード(ネイビー地):`--code-bg` / `--code-bar` / `--code-text` / `--code-comment` / `--code-accent`(暗地は朱が沈むので強調は明るい朱橙の `--code-accent` を使う) - **強調色は3色まで**。基本は朱 `--accent` の1色で十分。色数を増やすと「どこが大事か」が消える。 ## 2. サイズ・余白(トークン) - フォントサイズは `--font-*` を使う。**本文は `--font-body`(19px は下回らない)**。投影・配信で読めるラインを守る。 - `--font-body` / `--font-lead` / `--font-heading` - 余白は `--space-*`(`--space-sm` / `--space-md` / `--space-lg`)を使う。刻みを揃える。 - UnoCSS を使う場合は既製スケールに乗ってよい(**本文は `text-xl` 以上(=20px。`text-lg` は 18px で下限割れ)**、余白は `gap-4` / `p-4` など)。 - 色・サイズ・余白は**すべて変数 or ユーティリティ経由**。マジックナンバーの直書きをしない。 ## 3. アイコン - アイコンは **Material Icons に一本化**する:`<span class="material-icons">名前</span>`。 - **絵文字(🚀✨✅ など)は使わない**。エクスポート(PNG/PDF)で消える・豆腐化するため。 ## 4. 型(layouts/)と 部品(components/) - 繰り返す構造は**型**(`layouts/`)、繰り返すパーツは**部品**(`components/`)にして、`layout:` / タグで呼ぶ。 - 新規スライドは**まず既存の型・部品から組む**。無いときだけ新しく作る(§7 の作り方に従う)。 - レイアウトの実装は**1つの使い分けルール**で選ぶ: - **構造が単純** → `centered.vue` のように **UnoCSS ユーティリティのみ**(scoped CSS を書かない)。 - **構造が複雑**(ヘッダ固定・本文枠など)→ `fixed-header.vue` のように **`<style>` の構造CSS**。 - **コンテンツ枚は基本 `fixed-header`(上部固定ヘッダ)で統一**。「課題」「まとめ」等のセクションも heading に入れてヘッダー化する(地味に効く統一感)。 - **横並び(columns)**:ヘッダ付きの中での横並びは fixed-header の本文を Tailwind の `grid grid-cols-2 gap-10` で割る。ヘッダ無しの全面2カラムが要るときだけ `two-col.vue`。`.columns` のような global ユーティリティは作らない。 - 縦中央に伸ばす(旧 content-expand)は **Tailwind の `flex-1 flex flex-col justify-center`** で閉じる。global クラスにしない。 - 部品の色は**必ずトークン参照**(`bg-[var(--accent)]` 等)。部品が増えても色が割れないようにする。 - **フロー(ステップを矢印でつなぐ/§7 の推奨形)**:`PointCard` を `flex items-stretch gap-3` の行に `flex-1` で並べ(カードは等高に揃える)、各カードには `card-class="justify-center"` を渡して**本文を縦中央寄せ**にし、間の矢印 `<span class="material-icons text-4xl">arrow_forward</span>`(`items-center` で挟む)と視線の高さを合わせる。矢印は専用部品を作らず Material Icons+トークン色(`text-[var(--navy-300)]`)で閉じる(独自の矢印SVG・生HEXは使わない)。※カード本文を縦中央寄せにしないと、本文が上寄りのとき矢印だけがカード縦中央=テキストより下に取り残されて見える。 ## 5. 「どこに書くか」(CSS の置き場所) 判断は1つ:**「どのスライドでも必ず効いてほしいか?」** - **全スライド共通の土台**(トークン、ベースのフォント・色・背景)→ `style.css`。 - **その型・部品の中だけの見た目** → その `layouts/` `components/` の中に閉じ込める。 - **この1枚だけ** → そのスライドに `<style>`(常に scoped・他に漏れない)。 書き方の作法: - `style.css` の共通スタイルは **`.slidev-layout` の配下に書く**(素の `h1 {}` は発表者モードUIにまで漏れる)。 - **`!important` は基本使わない**。効かないときは置き場所・継承(§6)を疑う。(Slidev 組み込みの高優先度スタイルとどうしても競合する箇所=固定ヘッダの見出し等だけ、そこに限って許容する) ## 6. ★ 最重要の罠:自作レイアウトは `.slidev-layout` を継承しない - 組み込みレイアウト(default / two-cols 等)は、その template 内で **自分で** `class="slidev-layout"` を付けている(Slidev が自動で付けるわけではない)。だから `style.css` の共通スタイルが効く。 - **自作レイアウトは自分で `class="slidev-layout"` を付けないと、`style.css` の共通スタイル(トークン・見出し色・kicker・マーカー)が一切効かない**。見出しが素の小さい黒文字に戻る。 - 対処(どちらか): 1. 型の wrapper に `class="slidev-layout"` を足す。 2. 型の中身を UnoCSS で完結させる(共通スタイルに依存しない)。 ## 7. AIっぽさの回避(必ず守る) | やりがち(AIっぽい) | 代わりにこうする | |---|---| | Inter / Poppins フォント | Noto Sans JP に寄せる | | 紫〜青のグラデ背景 | 単色(ネイビー/白)+必要なら薄いノイズ | | 完全対称なレイアウト | わざと少し非対称に(scale / opacity の差) | | 同じカードを3枚横並び | 矢印でつなぐフロー形式に | | 角丸カードで全面を埋める | レイアウトを複数種類使い分ける | - **1つの図・1枚のスライドに1メッセージ**。詰め込まない。 - 配信向けは可読性最優先(本文を十分大きく。目安 24px 以上)。 ## 8. 仕上げ(必ず) - ルールに書いた=画面でその通り出てる、とは限らない。**最後は必ず PNG にエクスポートして、画像を目で見て確認**する。 - `cd application/slides && npx slidev export src/<name>/slides.md --format png --per-slide --output src/<name>/export/slide` - 崩れていたら §5・§6(置き場所・継承)をまず疑う。 --- ## このプロジェクトの現物 **原則:global は「地」だけ。構造は layouts/、装飾は components/、細部は Tailwind(UnoCSS) で閉じる。** | 種類 | ファイル | 中身 | |---|---|---| | 地(global) | `style.css` | `:root` トークン+ `.slidev-layout` の地(font・色・背景・size・h1・インラインcode)だけ | | 型:タイトル | `layouts/title.vue` | ダークネイビーのヒーロー。`.slidev-layout` を付けず型内で完結(閉じる) | | 型:縦中央 | `layouts/centered.vue` | UnoCSS のみ(scoped CSS ゼロ) | | 型:固定ヘッダ | `layouts/fixed-header.vue` | 上部固定ヘッダ+本文(構造CSS)。props `heading` / `kicker` | | 型:横並び | `layouts/two-col.vue` | 自作 two-col(UnoCSS grid)。本文=左、`::right::`=右 | | 罠デモ | `layouts/trap.vue` | あえて `.slidev-layout` を付けない(§6 の実演用) | | 部品:見出しラベル | `components/Kicker.vue` | 朱の縦バー。slot | | 部品:箇条書き | `components/Points.vue` | 朱ひし形マーカー。slot に markdown リスト | | 部品:Notice | `components/NoticeBox.vue` | `type`(info/warning/success/danger)+ `title` + slot。Material Icon・色はセマンティックトークン | | 部品:カード | `components/PointCard.vue` | `label` / `tone` + slot。色はトークン参照 | | 部品:ルール箱 | `components/RuleBox.vue` | ルール提示ボックス。slot | | 部品:コードブロック | `components/CodeBlock.vue` | ネイビー地のコードパネル。`file` / `lang` props。装飾は scoped で閉じ、色は `--code-*` トークン参照 | - **コードブロックは `CodeBlock.vue` で閉じる**:slot に素の `<pre>` を入れ、強調は `<span class="c">`(コメント)/`<span class="k">`(キー)の2色だけ。Slidev の ` ``` ` フェンス(Shiki)は生成色=非トークンの色が混ざるので、地が暗いコード掲載には使わない。 - 横並び=`two-col.vue` を使う(`.columns` のような global ユーティリティは作らない)。 - 縦中央に伸ばす(旧 content-expand)= Tailwind の `flex-1 flex flex-col justify-center` で閉じる(global クラスにしない)。 - 装飾の色は**すべてトークン参照**(`var(--x)` / `bg-[var(--x)]`)。生 HEX 直書きはしない。 長いですけど、やってることはシンプルです。「色は変数だけ・強調は1色・本文19px・アイコンは Material Icons・レイアウトは単純=UnoCSS/複雑=構造CSS・部品の色はトークン参照」。これを全部”やっていいこと/ダメなこと”として 具体的に言い切ってる だけ。「いい感じに」じゃなくて「この枠の中で」。選択肢を絞れば絞るほど、AIの出力は安定します。 特に効くのが、上のルールにある「AIっぽさの回避」の表。放っておくとAIが寄っていく”なんかダサい方向”(紫青グラデ、絵文字、カード3枚横並び……)を、先回りで名指しして塞いでます。ここがあるだけで、出力の”AI製っぽさ”がガクッと減るんですよ。 上の CLAUDE.md の末尾には、実は「今後デザインを追加するときの実例プロンプト集」も付けてあります。それは次の章で実際に使ってみせるので、ここでは省きました。 何度もはまる罠についてはルール化して永続化する ルールが効くって話で、わかりやすい例を一つ。 Slidev にはやっかいな罠があります。 自作のレイアウトは、 style.css に書いた共通スタイルを自動では継いでくれない んですよ。種を明かすと、Slidev 同梱のレイアウト( default や two-cols )は、その中で class="slidev-layout" を 自分で 付けてるから共通スタイルが効くだけ。Slidev が勝手に付けてくれるわけじゃないんです。だから自作レイアウトも、自分で付けないと効かない。付け忘れると、せっかくトークンで決めた見出しの色やサイズが全部すっぽ抜けて、 見出しが素の小さい黒文字に戻ります 。連載を通しでやってると、地味に一番ハマるやつです。 で、これも対処はルールに1行書くだけ。「 自作レイアウトには必ず class="slidev-layout" を付ける 」。たったこれで、AIは二度とこの罠を踏まなくなる。ハマって覚えた知見も、こうやってルールに畳んでおけるんですよ。 このファイルを渡す=枠の内側に閉じ込める 要するに、この1枚を AI に渡しておくと、出力がシステムの内側に収まりやすくなるし、 まるごとコピーすれば別プロジェクトにも移植できる 。……って、口で言うのは簡単ですよね。本当に効くの? っていうのを、ここから実際に試していきます。 で、どう打つの? ― プロンプトで「足す」・言葉で「回す」 ここからは実演です。さっきの CLAUDE.md を渡した状態で、実際にどんなプロンプトを打つと、どう返ってくるのか。「足す」と「回す」の2つを見せます。 足す:既存システムに新要素をプロンプトで追加 まずは「足す」。既存のデッキに、新しい要素をプロンプトで追加するパターンです。 たとえば「矢印でつなぐフローのスライドを1枚足して」と頼むとき、僕はこう打ちます。 このデッキに、複数のステップを矢印でつなぐ「フロー」のスライドを1枚追加して。 - まず同じディレクトリの CLAUDE.md を読んで、そのルールに必ず従うこと。 - 既存の型(layouts/)と部品(components/)を使い、トーンに合わせる。 - 矢印やステップの色もトークンに合わせる。生 HEX の直書きはしない。 - 追加したら PNG にエクスポートして、見た目が崩れてないか自分で確認して。 - ルールに無い要素や崩れが出たら、その場しのぎで直さず CLAUDE.md に1行足す形で対応して(=育てる)。 ミソは最後の2行です。 「CLAUDE.md に従う」と「最後に PNG で確認」をセットで毎回言う 。これで枠から出なくなるし、出来上がりを自分で目視チェックさせられる。 実際これでフローを足したのがこれです。矢印は絵文字じゃなく Material Icons、色は既存のトークン。ちゃんとシステムのトーンに乗ったまま追加されてます(Slidev 標準のアイコンは Iconify ですが、CDN を1行読むだけで設定いらずな Material Icons に寄せてます。絵文字と違ってエクスポートでも消えないので)。 もう一つ、面白かった話を。別のとき、同じ”足す”プロンプトで「コードブロックのスライドを足して」と頼んだら、AIが「いまのルールにはコード表示の色指定が無いですね」と気づいて、 CodeBlock.vue という部品を自分で作り、 CLAUDE.md にもルールを足してきた んですよ。 これ、証拠がさっきの CLAUDE.md に残ってます。色のところにある --code-bg / --code-text / --code-accent (暗地コード用に明るい朱橙を別建てするやつ)――あれ、最初から書いてたんじゃなくて、このとき AI が足したものです。指示したのは「ルールに無い要素が出たら CLAUDE.md に1行足して」だけ。足りないものに気づいて、その場で埋めて、次から使える形にしてくれた。これがまさに”育てる”です。 回す:自然言語ループでレイアウトを動かす もう一つは「回す」。一度出てきたスライドを、言葉だけで何度も作り替えていくパターンです。 あるフローのスライドが、横並び4枚に長い説明文を詰め込んでて、日本語が単語の途中で折り返して詰まって見えたんですよ。で、 CSS は一切触らず 、こんなプロンプトで作り替えていきました。 7枚目のフローを「縦積み」レイアウトに作り替えて。CLAUDE.md のルールに従い、CLAUDE.md 自体は変更しないこと。 - 4ステップを上から下へ縦に積む。各ステップは横長の1バンド。 - 左に「番号+短いタイトル」、右に説明文(左右で分離)。横幅が広いので1行に収まる。 - ステップ間は下向き矢印でつなぐ。 - 直したら PNG で7枚目を必ず目で見て、溢れてないか確認して。 横並び → 2×2に分離 → 縦積み、と段階的に動かしたんですが、全部 自然言語のプロンプトだけ。手でCSSを書いた箇所はゼロです。 しかもこの間ずっと、色はトークンのまま、部品も既存のまま。 枠の中で動かしてるから、どう作り替えてもトーンが崩れない 。「言葉だけで回す」が成立するのは、土台にルールがあるからなんですよね。 本当に移植できるのか ― CLAUDE.md だけで建て直してみた 「足せる」「回せる」は分かった。でも一番でかい主張、 このシステムは丸ごと別プロジェクトに移植できる ってやつ。これは本当なのか。ちょっと意地悪な実験をしてみました。 やり方はこう。 新しい Claude Code のセッションに、 CLAUDE.md だけを渡す 。トークンも型も部品も、お手本になる既存のデッキも、一切見せない。そのうえで「Git ブランチ戦略の入門スライドを、別のブランド色で一から作って」と頼む。ルールだけを頼りに、ゼロから建てられるか?という実験です。 先に言っておくと、ガチガチに統制した実験じゃなく”やってみた記録”です(各1回ずつ)。ただ今回は、ルールを渡した版と ルールを一切渡さない「対照」版 の両方を回したので、ルールが本当に効いてるのか・どこに効いてるのかが、わりとハッキリ見えました。順番にいきます。 CLAUDE.md を渡したら、別ブランドのデッキが建った 実際に投げたのが、このプロンプトです。「 お手本を絶対に見るな 」と念押ししてるのがミソ。これで「真似したから揃った」の逃げ道を塞いでます。 application/slides/src/git-branching/ に、Slidev のデッキを「一から」作ってほしい。 - このディレクトリには CLAUDE.md だけが置いてある。まずそれを読み、必ず従うこと。 - style.css / layouts/ / components/ / slides.md はまだ無い。CLAUDE.md のルールに沿って自分で新規に作る。 - ★重要:他のデッキ(特に blog-design-system/)の style.css・layouts・components は 絶対に開かない・参照しない。手元の CLAUDE.md だけを唯一の根拠にすること。 - ブランド色は元(ネイビー+朱)とは別系統にする(例:ダークグリーン+抑えた琥珀)。 生 HEX を置くのは style.css の :root だけ。利用側は var() 参照。 - 題材は「Git ブランチ戦略入門」。タイトル / アジェンダ / 課題 / 比較カード / Notice / コマンド例 / 導入フロー / まとめ を含む 6〜8 枚。 - できたら PNG にエクスポートして、崩れてないか自分で確認して。 - ルールに無い要素や崩れが出たら、その場しのぎで直さず CLAUDE.md に1行足す形で対応して(=育てる)。 結果から言うと、 建ちました 。 渡したのは CLAUDE.md 1枚だけ。なのに別セッションのAIは、 style.css のトークンを自分で定義し直して(しかも色は元のネイビー+朱じゃなく、ちゃんと別系統のダークグリーン+琥珀にして)、レイアウトの型も部品も白紙から組んで、8枚のデッキを作り上げました。 色も題材も別物なのに、できあがったものは「同じデザインシステムの色違い版」にしか見えないデザインが出てきました。主観だけだと弱いので、数えられる事実で言うと、こうです。 ブランド色は別系統に振られてた(元 #0B1F3A + #C2410C → ダークグリーン+琥珀 #102a1d + #b07d24 ) 生 HEX の直書きは style.css の :root 定義以外ゼロ。利用側は全部 var() 参照 固定ヘッダ・強調1色・矢印フロー・暗地のコードパネルと、規律はそのまま再現 8枚すべて PNG 化して崩れなし お手本のデッキは一切見せてないのに、です。 「本当に見てないの?」も裏取りしました。Claude Code の操作ログで元デッキ( blog-design-system )を一度も開いてないことを確認したうえで、トークンの値もレイアウトの実装も元とは別物(クラス名も手法も違う)なのを照合済み。真似たんじゃなく、ルールから独立に建てた、です。 ルール無しと比べたら ― 素の Claude は何点か じゃあ逆に、 ルールを一切渡さなかったら どうなるのか。まっさらなセッションに、 CLAUDE.md もトークンも何も渡さず、「Git ブランチ戦略のスライドをいい感じに作って」とだけ頼んでみました(さっきと同じ題材で)。 これがね、けっこう上手いんですよ。正直びっくりしました。 色は頼んでないのに CSS変数(トークン)化 してた。生 HEX の直書きはほぼ無し カード3枚並びで終わらせず、 矢印フローも自分から使ってた レイアウトも部品も自前で切って、見た目はちゃんとプロっぽい つまり、この記事で「ルールのおかげ」と思ってた作法のかなりの部分は、 Claude が素でやれます 。ここは正直に認めます。Claude Code、地力が高い。 でも、ルール無しだと 小さいところで転ぶ んですよ。 絵文字を使って豆腐(□)化 してた。フォントに無いグリフで文字化けして、後から自分で直すハメに(ルールがあれば最初から Material Icons に寄せて回避) 本文に 0.6〜0.8rem の小さすぎる字が散在 。投影したら読めないやつ(19px 下限ルールが防ぐ) 自作レイアウトに class="slidev-layout" を付けてなかった 。共通スタイルを .slidev-layout に置いてるのに、です。今回はたまたま自前のスタイルで隠れてたけど、一歩間違えれば見出しが死ぬ、さっきの継承の罠そのもの(「自作レイアウトに class を付ける」ルールが明示で潰すやつ) 要するに、体感だと、 素の Claude が 80点くらい。ルールがそれを 90〜95点に引き上げて、毎回そこに揃う 感じです。ゼロから品質を生む魔法じゃなく、 “惜しい小さな躓き”を先回りで縛る ものなんですよね。しかも素の80点は 一回こっきり・自己流 で、次のデッキも同じ80点になる保証はないし、チームの他の人が同じトーンになる保証もない。 毎回・誰でも・同じ品質 にして、それを丸ごと移植できるようにする。そこがルールの本当の値打ちです。 ルールを1行直すと、全デッキが同時に直る おまけに、システムを持つことの一番のうまみも実演できました。 実は途中で、僕が CLAUDE.md に書いた「矢印フローの作法」にちょっとしたバグがあったんですよ。矢印がカードの本文より下にズレる、ってやつ。で、これが 元のデッキと、移植先のデッキ、両方に伝染してた 。コピーで不具合も一緒に広まる、ってやつですね。 でも直すのは簡単で。バグってるのは”ルールの1行”なので、 そこを直して各デッキに反映するだけ 。スライドを1枚ずつ直して回る必要がない。直すべき場所がルールに1か所、っていうのがミソです。バラバラにスライドを作ってたら、同じ崩れを全部のスライドで手作業で潰すハメになる。 1つのシステムに統一してるからこそできる芸当 なんですよ。 ただ、これは裏を返すと注意でもあって。 配る CLAUDE.md にバグがあると、コピー先全部に一緒に伝染する 。なので、このルールを持っていったら、 CLAUDE.md にも書いてある「最後に必ず PNG で目視確認」を忘れずに。コピーは便利だけど、不具合ごとコピーしてないかは自分の目で見る、です。 1つに統一するから、移植できて・育つ バラバラに便利なパーツを集めても、デザインは育ちません。 トークン+型+部品+ルールを、1つの噛み合ったシステムに統一する 。そうすると、こうなります。 AIに作らせても毎回トーンが揃う(揃うように設定したから) 別プロジェクトにも移植できる。普段はデッキを丸ごとコピー、最悪 CLAUDE.md 1枚でも AI が建て直せる(さっき実際に試したとおり) ルールを直せば、コピー先のデッキもまとめて直せる 持ち帰り方はシンプルです。 AIにこちらのブログを読み込ませて自分の環境で取り込んでみてください 。 style.css ・ layouts/ ・ components/ がそのまま手元に残るので、色だけ自分のブランドに差し替えれば、もう同じシステムが動きます。再利用の単位は「ルール」じゃなくて「動く実装一式」、これが日常の本線です。 そのうえで、さっきの実験がいいフォールバックを見せてくれました。 最悪、 CLAUDE.md を1枚引っ越すだけでも助かる 。実装ファイルを持ち歩けなくても、ルールさえ渡せば AI が残りを建て直してくれる。そこまで本質がルールに宿ってる、という証明でもあります。普段はコピーで速く、いざとなればルールだけでも建つ ── これが「丸ごと持っていける」の中身です。 もう1つ。移植できるまで育ったこの一式(layouts/ components/ styles/)は、Slidev の theme のディレクトリ規約とほぼ同じ構成です。「複数プロジェクトで配りたい」となったとき、package.json を1枚追加するだけで theme として切り出せます。書き直しは要りません。 「theme を作るぞ」と最初から意気込むより、使って気に入ったものを貯めて、作って壊してを繰り返して、最後に残ったものを theme に蒸留する、という順番のほうが長続きします。theme は出発点じゃなくて、育ったあとの結果なので。 今回は”実物”の話に振り切りましたが、「なんでトークンで縛るの?」「型・部品・ルールに貯めるってどういうこと?」っていう 設計思想のほう は、連載の 第3部 (デザイントークン)と 第4部 (デザインシステムの育て方)でじっくり書いてます。実物で気になったところがあれば、そっちも覗いてみてください。 それでは、よいスライドライフを! シリーズ:AI×スライドづくり AIに丸投げせず、制約とルールで「意図どおりの95点」を毎回そろえて作るシリーズです。 セットアップ〜エクスポート — 構文ゼロで作って配る Marp と Slidev の使い分け — Git管理起点でどっちを使う 実物編(全部入り) :移植できるデザインシステムを丸ごと公開 ← ←いまここ デザイントークン編 — なぜ「枠」で縛るのか(実物編の深掘り) デザインシステム編 — なぜ型を貯めて育てるのか(実物編の深掘り) ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Slidev のデザインシステムを CSS変数と CLAUDE.md で作る:移植が簡単 first appeared on SIOS Tech Lab .
AIにスライド、全部任せちゃダメなの? ども!Slidev と Claude Code でスライドを量産してる龍ちゃんです。 前回(第3部) で、色やサイズを CSS変数で縛る話をしました。今回はその続きで、もう一段上の「育てる」話なんですけど、その前にひとつ、最近ふと思うことがあって。 AIにスライド、もう全部任せちゃえばよくないですか?って。 実際、ソースをポンと放り込んだら構成も中身も全部AIがまとめてくれるツール、増えましたよね。資料を要約する・理解するみたいな用途なら、全任せで十分だと思います。でも、自分が人前で話すプレゼンとなると、話が変わります。 スライドは、”自分の話しやすさ”とセットで形になると考えています。話す自分を想像しながら「ここはこう見せたい」「この順番で出したい」が決まっていく。人それぞれ、”話しやすいパターン”って絶対あるんですよね。 だから全任せだと、きれいなスライドは出てくるけど、 “自分が話しやすい形”にはならない 。他人が用意してくれた服を着てる感じ。間違ってないんだけど、なんとなく話しにくさが残ります。 つまりスライドには、「全任せでいい場面」と「方向性を自分で握りたい場面」があるということですね。プレゼンは完全に後者寄りなんですよ。じゃあ、その”方向性”ってどう持っておけばいいの?っていうのが、今回の話です。 良かったパターンを貯めると、自分のデザインシステムになる で、方向性を自分で握るって言っても、毎回ゼロから「どう見せよう」を考えるの、しんどくないですか。 前のスライドで「お、この見せ方いいな、話しやすいな」ってパターンができたとするじゃないですか。なのに、そのデッキが終わるとそれっきり。次の発表でまた白紙から考え直す。せっかくの”自分の勝ちパターン”が、毎回使い捨てになってしまう。もったいないですよね。 答えはシンプルで。 その良かったパターンを、捨てずに貯めればいい。 貯めたものが積み上がると、それが”自分なりのデザインシステム”になります。 「デザインシステム」って言うと、大企業がガッチリ作り込むやつを想像して身構えるかもしれません。でも、色のルール(トークン)と、よく使う部品と、それを使う作法。これが揃ってれば、規模が小さいだけで構造は本物と同じなんですよ。僕はこれを「自分で育てた小さなデザインシステム」と呼んでます。要は、話しやすくて良かったパターンの集積です。 しかもこれ、上から設計したわけじゃないんですよ。「デザインシステムを作るぞ」じゃなくて、やって気に入ったものを記録してたら、「気づいたらできてた」が正しいです(笑)。 と、その前に前提を2つだけ置かせてください。ひとつ、この連載で作ってるのは全部 Slidev(Markdown でスライドを書けるツール。中身は Vue で、セットアップは 第1部 )の上の話だということ。もうひとつ、前回やった「色・サイズを CSS変数(トークン)で縛る」やつが、このデザインシステムの 一番下の土台 だということ。色の枠が決まってるから、その上に型や部品を積んでもトーンが崩れない。今回はその土台の上に積む話で、型・部品も Slidev(Vue)の仕組みに乗せて作ります(色まわりの”なぜ”は 第3部 に)。 貯め先は、ざっくり3つ。レイアウトの「型」、繰り返し使う「部品」、そして「ルール(作法)」です。 どう貯めて、どう選ぶか 貯めるって、要は「一度作って良かった/学んだものを、次も使える形に昇格させる」こと。型・部品・ルールの順に見ていきます。 レイアウトは「型」にする 良かったスライドの構造を、 layouts/ に「型」として置いておくと、次から frontmatter で layout: 名前 と呼ぶだけで使えます。 第2部でも触れたとおり、Slidev のレイアウトはただの CSS クラスじゃなく Vue コンポーネント=構造を持った部品 です。だから「型にする」は、CSS をかき集めることじゃなくて、 構造ごと部品に閉じ込めて、名前で呼ぶ ことなんですよ。 たとえば僕、Slidev でコンテンツの縦中央揃えにめちゃくちゃハマったことがあって(笑)。Slidev の標準レイアウトは高さを確定してくれないので、子要素に flex: 1 を付けても効かないんですよ。で、色々試した末に気づいたのが、 毎回スライドで中央寄せの CSS と格闘するより、”縦中央揃え”そのものをレイアウトの型にしちゃえばいい ってこと。Slidev は UnoCSS (Tailwind 互換)が標準なので、型の中身もユーティリティでスッと書けます。 <!-- layouts/centered.vue:縦中央揃えを、型の中に閉じ込める --> <template> <!-- slidev-layout:style.css の .slidev-layout に置いた共通ベースを継ぐ(公式の標準パターン。自作レイアウトは自分で付ける) --> <!-- 高さは付かないので、h-full で確保して flex で縦中央寄せ --> <div class="slidev-layout h-full flex flex-col justify-center px-14 py-10"> <slot /> <!-- スライドの中身がここに入る --> </div> </template> あとはスライド側から、こう呼ぶだけです。 --- layout: centered --- # これが画面の縦中央に来る 中央寄せの CSS を毎回スライドに書くんじゃなくて、 型を1個 layouts/ に置いて layout: centered と呼ぶだけ 。ハマって解決した知見が、そのまま”呼べる型”になるんですよね。同じ罠を二度と踏まなくて済む。 (この centered.vue を含めて、型・部品・ルールまで全部組んだ“動くデッキ一式”のフルコードは、 実物編(全部入り) で公開しています。ここでは“ハマった知見を型に閉じ込める”という発想だけ掴んでもらえればOKです。) 繰り返すパーツは「部品」にする 2回以上出てくるパーツは、Vue コンポーネントにして components/ に置いちゃう。僕の手元には、RAG の概念図とかフロー図とか、そういう部品が20個以上貯まってます。たとえば RAG パイプライン全体像の図は、もうこう呼ぶだけ。 --- layout: none --- <RagConcept /> <RagConcept /> って書くだけ。中身の HTML はコンポーネントが持ってるので、毎回書かせなくていいし、見た目も必ず揃う。これ、めちゃくちゃ気持ちいいんですよ。HTML を毎回手で書いてた頃は、地味に毎回ちょっとずつ違う図ができてたので(笑)。 「AIっぽい」と気づいたら「ルール」にする 「あ、これやるとAIっぽいな」って気づいたことは、ルールとして文章にしておく。僕の素材カタログだと、こんな作法が貯まってます。 本文は 19px 以上(投影で読めなくなるので。Tailwind なら text-xl 以上。 text-lg は18pxで割れる) 色は CSS変数か theme の色クラスを使う(生 HEX の直書きはしない) 1つの図に1メッセージ(詰め込まない) これに、「AI製に見えるパターン」を避けるルールも足していきます。 やりがち(AIっぽい) 代わりにこうする Inter / Poppins フォント Noto Sans JP に寄せる 紫〜青のグラデ背景 単色+薄いノイズ 完全対称なレイアウト わざと少し非対称に 同じカードを3枚横並び 矢印でつなぐフロー形式に 型も部品もルールも、やってることは全部おんなじです。一度作って良かったものを、次も使える形に昇格させる。ルールに書いておけば、次は同じ罠を先に回避できます。 貯めたものは、カタログにして「見て選ぶ」 で、ここが大事なんですけど。貯めるだけだと、どこに何があるか自分でも忘れるんですよ(笑)。部品が20個もあると、もう「あれ、あの図どこ作ったっけ」ってなる。 だから、貯めた素材は一覧、つまりカタログにしておきます。僕は、素材の一覧に「名前・用途・プレビュー画像」の表を作ってあって、パッと見て選べるようにしてる。 で、新しいスライドを作るときは、このカタログを眺めて「これ使お」って自分で選ぶ。ここ、地味だけど超大事で。 部品選びまでAIに丸投げしない んですよ。どの型でどの部品をどう並べるか、っていう”方向性”は人間が握る。AIに渡すのは「ルールはこれね、素材はカタログから選んでね」くらい。 これ、最初に言った「全任せにしない」が、運用レベルで具体になってるんですよね。全任せは、選ぶところまで含めてAI。僕のやり方は、素材はAIと一緒に作るけど、選んで組むのは自分。この差が、”自分が話しやすいスライド”になるかどうかの分かれ目だと思ってます。 育てるほど速くなる。でも一発で完璧は狙わない ここまでの話、正直に言うと、最初はそんなに速くないです(笑)。 型も部品もまだ無いから、最初の何枚かはゼロから作るのと変わらない。むしろ「これ型にしとくか」「これ部品にするか」って考える分、ちょっと遅いくらい。 でも、2枚目、3枚目、次のデッキ、と進むほど効いてくる。もうカタログに型も部品も貯まってるから、新しいスライドが「ゼロから」じゃなくて「カタログから選んで組むだけ」になるんですよ。これがほんとにラクで、一度この状態に入ると戻れないんですよね(”爆速”って言いたくなるやつですが、何分が何分に、みたいな計測はしてないので体感の話です)。 そして、ここが冒頭の問い「全部任せちゃダメなの?」への答えです。貯まってるのは”自分が話しやすい型”の集積だから、 速いのに、ちゃんと”自分の”スライドになる 。全任せ(速いけど自分の形にならない)でも、毎回ゼロ(自分の形だけど遅い)でもない。使い捨てだったパターンが、次の速さに変わるんですよね。 ただ、最後に正直なことも言っておきます。 育てても、初見のスライドはやっぱりズレます。 一発で完璧、みたいな魔法はないです。新しいテーマだと、貯めた型に当てはまらないパーツが必ず出てくるんですよ。 でも、それでいいんです。ズレたら、それをまた型なり部品なりに貯めればいい。「育てる」って、そうやってズレを貯め続ける作業そのものなんですよね。これ、前に Claude Code の失敗をバグチケット化して潰す話 を書いたんですけど、それと同じ発想で。ミスを記録して、次に活かせる形に変える。スライドのデザインもまったく一緒です。 ここまで育ったら、「theme にまとめる」という話も自然に出てきます。Slidev の theme は layouts/ components/ styles/ を1パッケージにするもので、ローカルの置き場所と規約がほぼ同じ。だから移行は書き直しじゃなく、package.json を1枚追加して切り出すだけです。 「theme 作るぞ!」と気合いで入ると燃え尽きることが多いんですけど(笑)、作って壊してを繰り返してズレを貯め続けた後、最後に残ったものを theme に収める。それくらいの温度感がちょうどいいかなと思ってます。theme は目的地であって、出発点の意気込みではないので。 それともう1つ。第3部でも言いましたけど、ルールに書いた、イコール画面でその通り出てる、とは限らない。型を指定したのに崩れてた、なんてのは普通に起きます。だから最後は必ず PNG に書き出して、その画像を目で見て確認しています。 この「画像を見て直す」を仕組みにする話は、それだけで一本になるテーマですね。作る、育てる、ときたら、最後は回す、です。 まとめ まぁ今回やったことは、PowerPointやGoogle Slideで設定できるスタイル・テンプレートみたいなのを自分の手元で作ろうね!って話なんですけどね。 スライドには全任せでいい場面と、方向性を自分で握りたい場面がある。プレゼンは”自分の話しやすさ”とセットだから後者寄り、と僕は感じてます 良かったパターンを捨てずに型・部品・ルールに貯めると、自分で育てた小さなデザインシステムになる 上から設計じゃなく、作りながら下から貯める。気づいたら自然とやってた、くらいでいい 貯めた素材はカタログにして、人が見て選ぶ。部品選びまでAIに丸投げしない 貯まるほどラクになって、しかも”自分の”スライドになる。一発完璧は狙わず、最後は画像で確認する そして「作って、育てて」ときたら、最後は「回す」── Slidev を PNG に書き出して、Claude に画像を「見せて」直してもらう品質維持の話。これはそれ自体で一本のテーマなので、いつか書けたらと思ってます。 まずは、次にスライドを作るとき「お、これ良かったな」ってパターンが出たら、捨てずに1個だけ取っておいてみてください。そこからが、育てるスタートです。お楽しみに。 ほなまた〜 シリーズ:AI×スライドづくり AIに丸投げせず、制約とルールで「意図どおりの95点」を毎回そろえて作るシリーズです。 セットアップ〜エクスポート — 構文ゼロで作って配る Marp と Slidev の使い分け — Git管理起点でどっちを使う 実物編(全部入り) :移植できるデザインシステムを丸ごと公開 ← まとめ デザイントークン編 — なぜ「枠」で縛るのか(実物編の深掘り) デザインシステム編 — なぜ型を貯めて育てるのか(実物編の深掘り)←いまここ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Code × Slidev:AIに全任せしないデザインシステムの育て方 first appeared on SIOS Tech Lab .
AIは結構ちゃんと作る。でも“揃わない”し、たまに事故る ども!Slidev と Claude Code でスライドを量産してる龍ちゃんです。 念のため前提だけ。Slidev は Markdown でスライドを書けるツール で、この連載の話は全部その上に乗ってます。今回のトークンも、Slidev プロジェクトの style.css に置く前提です。Slidev 自体のセットアップは 第1部 に。 前回(第2部) で「部品化して育てるなら Slidev」って話をして、最後に予告したやつですね。今回はその一番効く処方箋、 デザイントークン の話です。 まず、ちょっと煽りっぽい見出しと逆のことを正直に言います。最近のAI(Claude)、 放っておいても結構ちゃんとしたスライドを作ります 。色を聞かなくても CSS変数化までしてくるし、強調も1〜2色に抑えてくる。「AIに任せると色が虹色に暴走する」って身構えてたんですけど、少なくとも僕が試した範囲では、そこまで荒れない。地力、高いんですよ。 じゃあトークンなんて要らないの? っていうと、要ります。ただ 理由が「色の暴走を止める」じゃなくて、別の2つ なんですよね。 揃わない : 1回ごとに“それっぽいけど毎回ちょっと違う”トーンが出てくる。今日作ったデッキと来週のデッキ、自分のとチームの人ので、 揃う保証がない 。AIは悪気なく、毎回ちょっとずつ違う判断をするので たまに“惜しい”事故を踏む : 放っておくと 本文が 0.6〜0.8rem まで縮む ことがあって、投影や配信で潰れると読めない。あと 絵文字を入れてきて、本番のエクスポートで消える/豆腐(□)化する (これは後で詳しく) 要するに、AIは 80点はくれる。でも“毎回同じ80点”の保証はないし、下限(読めるサイズ・消えないアイコン)は守ってくれない 。そこを埋めるのがトークンです。やることは、 「使っていい色・最低サイズ・使うアイコン」を先に1回決めて“枠”にする だけ。そうすると、毎回そこに揃うし、下限が保証される。色の話も「暴走を止める」というより、 毎回・誰がやっても同じ色に乗せる ための枠、と捉えるのが正確ですね。 百聞は一見ということで、「枠なし(左)」と「枠で揃えた(右)」を並べてみました。(左はわざと散らかした極端な例です。実際の素のAIはここまで荒れません。でも“毎回同じトーンに揃える”と右側になる、のイメージとして見てください) 同じ内容のスライドなんですよ。なのにこの安定感の差。効いてるのは 選択肢を絞ったこと です。色なんて1670万色から自由に選べるんだから、毎回バラつくのは当たり前。だから 先に「使っていい色はこれだけ」って枠を渡す 。これだけです。 色・サイズ・余白を CSS変数で縛る やることはシンプルで、スライドプロジェクトの style.css の先頭に、 使っていい値を CSS変数(デザイントークン)として全部宣言しておく だけです。 /* style.css — :root に「使える値」を先に並べておく */ :root { /* 色:使っていいのはこれだけ。AIはこの中から選ぶ */ --bg-white: #FFFFFF; /* 背景(白地) */ --navy-900: #0B1F3A; /* 見出し・濃い地に使うネイビー */ --text-primary: #1E293B; /* 本文 */ --text-secondary: #475569; --accent: #C2410C; /* 強調はこの抑えた朱の1色だけ */ --accent-soft: rgba(194, 65, 12, 0.10); /* 「注意」「成功」みたいな“意味を持つ色”は、強調とは別腹で持つ */ --warning: #B45309; --success: #15803D; } 色は「背景・本文・強調」くらいに絞るのがコツです。 ブランドの強調は、いっそ1色に決め打ち しちゃう(僕はこの抑えた朱だけ)。それだけで「ちゃんとデザインされてる感」が出るし、どこが大事かが一発で伝わる。「注意」「成功」みたいな“意味を持つ色”は別腹で足していいですが、それも含めて多くても3色まで。逆に強調色が5色6色あると、もう何が大事なのか分からなくなるんですよね。 サイズと余白も同じ発想で、変数にしてしまいます。 :root { /* フォントサイズ:最小ラインを決めて、その倍数で刻む */ --font-body: 20px; /* 本文。19px は下回らない */ --font-heading: 35px; /* 見出し */ --font-lead: 24px; /* リード文 */ /* 余白:刻みを固定(4の倍数だけ使う、みたいに揃える) */ --space-sm: 8px; --space-md: 16px; --space-lg: 24px; /* ※ Slidev は UnoCSS(Tailwind 互換)が標準同梱。 余白は p-2=8px / gap-4=16px…、フォントは text-lg / text-xl… と 既製スケールが最初からあるので、--space-* や --font-* を自作せず そのまま乗ってもいい。 ただし色は bg-blue-500 等のデフォルト色が何百色と開けっ放しなので、 使う色だけは別途 theme で絞る必要がある(詳細は下の本文で) */ } ポイントは「 最小フォントサイズを決める 」こと。スライドは投影したり配信で画質が落ちたりするので、本文は最低でも 19〜24px は欲しい。ここを変数で固定しておくと、AIが調子に乗って「ここは情報多いから小さくしますね」って 14px とかにするのを防げます。このへんの下限は経験則でいいので、いったん決めて書き出しておくと後がラクですよ。ちなみにこの“本文サイズ”、放っておくとAIが実際にいちばん縮めてくる“惜しい躓き”の代表格なんですよ。トークンが一番ハッキリ効くのが、この下限です。 ここまで「自前で CSS変数を宣言する」前提で書いてきましたけど、 Slidev は UnoCSS (Tailwind 互換)が標準で入ってる んですよね(さっきの余白のコメントもそれです)。なので余白やフォントサイズは、 --space-* を自作しなくても Tailwind のスケール( text-lg / gap-4 …)にそのまま乗れます。「本文は text-xl (=20px)以上しか使わない」って縛り方でもアリですね( text-lg は18pxで、さっき決めた19px下限を割るので避ける)。 ただ、 色だけは Tailwind を入れても開けっ放しです 。 bg-red-500 bg-blue-500 …って何百色も用意されてるんで、結局”生hexで好き勝手”と変わらないんですよ。だから色に関しては、Tailwind を使うときでも「使っていいのはこの色だけ」と自分で絞る ── さっきの色トークンの話は、Tailwind でもそのまま効きます。 サイズ・余白はスケールに乗ってラクして、色はちゃんと締める 。これが僕の落としどころですね。 そして、定義したトークンは 全スライドの”地”に1回当てておく と、共通のベースになります。当て先は .slidev-layout ── 組み込みレイアウトが持ってる「スライドの土台」クラスです(この性質は後の「どこに書くか」で詳しく)。ここに当てておけば、以降どの型からでも同じフォント・色で始められます。 /* style.css::root のトークンを、全スライドの土台 .slidev-layout に当てておく */ .slidev-layout { background: var(--bg-white); color: var(--text-primary); font-size: var(--font-body); font-family: 'Noto Sans JP', sans-serif; } (Tailwind 派なら .slidev-layout { --uno: bg-... text-... } でも同じことができます) アイコンも「枠」で縛る:絵文字は本番で消える 縛るのは色とサイズだけじゃないんですよ。 アイコン もです。AIにスライドを書かせると、これまた放っておくと みたいな 絵文字を勝手に散りばめてくる 。そして絵文字は、放っておくと素のAIが実際に踏む“惜しい躓き”の代表格です。で、絵文字がやっかいなのは、見た目がAIっぽくなるだけじゃないんですよ。 書き出すと消えるんです 。(たまに全力で SVG を作り込んできたりしますねw) Slidev の PNG/PDF エクスポートは裏で Playwright (ヘッドレス Chromium)が動くんですけど、この環境には 絵文字フォントが入ってないことがある 。なので dev サーバーのプレビューでは や がちゃんと出てるのに、エクスポートした PNG では 真っ白な空白 になったり、 □(豆腐) になったりする。本番で初めて気づくやつです、ヒヤッとします。 対策は色トークンと同じ発想で、 「使っていいアイコンの供給源」を style.css で1つに固定する こと。僕は Material Icons (Web フォント)に統一してます。Web フォントなので、エクスポート環境でも確実に描画されます。 /* style.css の先頭で読み込む。これで絵文字を追放してアイコンを一本化 */ @import url('https://fonts.googleapis.com/icon?family=Material+Icons'); <!-- 絵文字 🚀 をやめて、Material Icons の名前で呼ぶ --> <span class="material-icons">rocket_launch</span> ローンチ <span class="material-icons">check_circle</span> 完了 <span class="material-icons">warning</span> 注意 絵文字を「テキストの一部」だと思ってると見落としがちなんですけど、あれも立派な 視覚要素 。色と同じで「使っていいのはこれ」と供給源を絞ると、AIの出力が揃うし、本番で消える事故も消えます。 肝は「先に宣言して、プロンプトに制約として組み込む」 で、ここが一番大事なんですけど、 style.css に変数を置いただけでは、AIは縛られません 。 これ、人間の感覚だと「CSS 見れば使っていい色わかるでしょ」って思うんですよ。でもAIはこっちが言わない限り、変数があることなんてお構いなしに、平気で style 属性に生の #ff5577 を書いてくる。 「この変数の中から選んでね」って、わざわざ宣言してあげないと使ってくれない んですよね。ここが人間相手との一番の違いです。 だから順番が肝で、 先に枠を宣言して、それをプロンプト(コンテキスト)の中に”制約”として組み込んでおく 。具体的には、プロジェクトの CLAUDE.md なり最初の依頼文なりに、こう書いておくんです。 # スライド作成の制約(Slidev / CSS変数) - 色は style.css の :root で定義した CSS変数(--accent など)だけを使う。ブランド強調は朱(--accent)1色、意味の色を入れても3色まで。 - フォントサイズは --font-* 変数を使う。本文は --font-body を使う。 - 余白は --space-* 変数を使う。 - 色・サイズ・余白はすべて変数経由(var(--xxx))で指定する。 - アイコンは Material Icons(<span class="material-icons">名前</span>)を使う。 Tailwind(UnoCSS)を標準で使うスタイルなら、同じ制約をユーティリティ向けに言い換えるだけ。中身は一緒で、「変数で縛る」が「クラスで縛る」に変わるだけですね。 # スライド作成の制約(Slidev / Tailwind 標準) - 色は style.css の theme に定義した色クラス(text-accent / bg-accent など)だけを使う。ブランド強調は朱1色、意味の色を入れても3色まで。 - フォントサイズは text-xl 以上のクラスを使う(text-lg は18pxで19px下限割れ)。 - 余白は決めた刻みのクラス(gap-4 / p-4 など)を使う。 - 色・サイズ・余白はすべてユーティリティクラスで指定する。 - アイコンは Material Icons(<span class="material-icons">名前</span>)を使う。 ポイントは「あとから直す」んじゃなくて「 先に縛る 」こと。 CLAUDE.md に一度書いておけば、それ以降のスライド生成は全部この制約の中で走るんですよ。毎回「色そろえて」「文字大きくして」って言い直さなくても、最初から枠の内側で出てくる。 人間が後追いで矯正するんじゃなくて、AIが最初から枠の中で考えるようにする 。これがトークンを”AIに効かせる”ための一手です。 これだけで、生hexを直書きしてたところが、こうなります。 <!-- 縛ったあと。色もサイズも変数経由で、必ず揃う --> <h2 style="font-size: var(--font-heading); color: var(--navy-900)">重要なポイント</h2> <p>ここが <span style="color: var(--accent)">超重要</span> です</p> AIが選べる色が決め打ちされてるから、 毎回・誰がやっても同じ色に乗る 。色がバラつかないんですよ。 「どこに書くか」も枠で決める ここまで全部 style.css に書いてきましたけど、「何でもかんでも style.css でいいの?」というと違って、 書く場所にも線引き があります。判断は1つだけで、 「どのスライドでも必ず効いてほしいか?」 です。 全スライド共通で効かせたい土台 (トークン、ベースのフォント・文字色・背景)→ style.css に置く。どの型を使っても必ず効いてほしいやつ。 その型・その部品の中だけで効けばいい見た目 → style.css に書かず、その レイアウト/コンポーネントの中 に閉じ込める。特定の型でしか使わない構造や、ある部品の中だけの装飾はこっち。 で、 style.css に共通で書くときの作法もひとつ。Slidev は素のセレクタ( h1 { … } )で書くと スライドだけじゃなく発表者モードのUIにまで効いちゃう んですよ。なので 公式 は、 スライドの中身に付く .slidev-layout クラスの配下に書く ことを推奨してます。 .slidev-layout は、Slidev の 組み込みレイアウトが自分のテンプレートに書いてる クラスです(フレームワークが自動で全スライドに付けてるわけじゃない)。クラス自体は Slidev 由来でも、そこに乗せる見た目は自分で書く、って関係ですね。default や two-cols みたいな組み込みを使ってる限りは付いてるので、共通スタイルがちゃんと効きます。 ただ、ここが地味に大事で ── 自分でレイアウトを自作したときは、 .slidev-layout を自分で付けないと、 style.css に書いた共通スタイルがそのレイアウトにだけ効かない んですよ。組み込みが付けてくれてた分を、自作するなら自分で付ける必要がある、ってことですね( 公式のレイアウト作成ガイド の例も、wrapper の <div> に class="slidev-layout" を書いてます)。逆に「この1枚だけ」なら、そのスライドの中に <style> を書けば 常に scoped (そのスライド限定)で、他のスライドには漏れません。 /* style.css:全スライド共通の見た目は .slidev-layout の配下に書く */ .slidev-layout h1 { font-size: var(--font-heading); } /* ✗ h1 { … } だけだと発表者モードのUIにも漏れる */ <!-- このスライドだけ、なら slides.md のそのページに <style>。常に scoped で他に漏れない --> <style> h1 { color: red; } </style> ここを混ぜると地味に事故ります。「この1枚だけ」のつもりの CSS を .slidev-layout 配下や <style> に閉じず style.css にベタ書きすると、 別のスライドや発表者モードまで巻き込んで 崩れる。 共通にしたいものだけ、狙って書く。 それだけで「なんでこのスライドまで変わった?」が消えます。 これが発見されるパターンとしては、componentやlayoutで定義した内容が効かなくて !important で回避したときですね。個人的意見としては !important は使わないのがきれいなプロジェクトだと思います。 制約が速さを生む ここで一番伝えたいのは、 自由にさせない方が、結果的に速い ってことです。 自由に書かせると、出力は毎回それっぽいけど毎回ちょっと違う。だから「色を揃えて」「サイズ揃えて」っていう手戻りが延々と発生する。でも、トークンで枠を作っておくと、AIの出力は 最初から揃ってる状態 で出てくる。レビューで見るべきは「内容が合ってるか」だけになって、「色がバラバラ問題」が議題から消える。 色なんて特にそうで。トークン化する前は、AI が HEX の微妙な色違いを60種類くらい ばらまいてきたことがあったんですよ。で、後からテーマ色を変えようとしたら、その色が全ファイルに散らばってて、 全部のファイルを読みに行かないと直せない 。案の定、直し漏れも出ました。これがトークンで縛ってあれば、変えるのは変数1か所= 検索して置換するだけ 。後からの作り替えが、グッとラクになるんですよね。 注意:これは”枠”であって”完成”じゃない ここは正直に書いておきます。 トークンを定義しただけでは、完璧な見た目にはなりません。 トークンは「使っていい絵の具」を決めただけ。その絵の具をどう配置するか(レイアウト・コンポーネント)は別の話で、それは次回の第4部(デザインシステム編)でやります。 そして、このトークンを土台に型・部品・ルールまで全部組み上げた“動くデッキ一式”そのものは、 実物編(全部入り) で CLAUDE.md 全文つきで公開しています。 で、もうひとつ大事な注意。 「style.css に書いた=そう表示されてる」とは限らない んですよ。CSS はカスケードで上書きが起きるし(よくある自作 CSS のミスで簡単に効かなくなる)、さっきの絵文字みたいに エクスポートで初めて崩れる こともある。トークンを定義して安心してても、画面では別物、というのは普通に起きます。 だから最後は必ず PNG にエクスポートして、その画像を目で見て確認する 。CSS に書いた気になって満足すると、本番で「アレ?効いてない」ってなるやつなんですよね。「こう書いた」じゃなくて「画面でどう出てるか」で判断する。地味だけど、ここを飛ばすと普通に事故ります。 この「画像を見て直す」を仕組みにする話は、それだけで一本になるテーマ。トークンで枠を作る → システムに育てる → 画像で品質を回す、の最初の一歩が今回でした。 まとめ AIは放っておいても8割方は作れる。でも 毎回トーンが揃わない/本文が小さくなる/絵文字が本番で消える 色・サイズ・余白を CSS変数(デザイントークン)として style.css に先に定義 し、「使える色はこれだけ」とAIを縛る アイコンも枠で縛る 。絵文字はエクスポートで消える(豆腐化する)ので、Material Icons に一本化する 制約が手戻りを消して速さになる 。自由度より、選択肢を絞るほうが安定する ただしトークンは”枠”であって”完成”じゃない。最後は 書き出した画像を見て確認 する 次回(第4部)は、このトークンを起点に、レイアウトやコンポーネントまで束ねて「 自分のデザインシステム 」に育てる話です。揃えた資産が、次のスライドの速さになるやつ。お楽しみに。 ほなまた〜 シリーズ:AI×スライドづくり AIに丸投げせず、制約とルールで「意図どおりの95点」を毎回そろえて作るシリーズです。 セットアップ〜エクスポート — 構文ゼロで作って配る Marp と Slidev の使い分け — Git管理起点でどっちを使う 実物編(全部入り) :移植できるデザインシステムを丸ごと公開 ← まとめ デザイントークン編 — なぜ「枠」で縛るのか(実物編の深掘り)←いまここ デザインシステム編 — なぜ型を貯めて育てるのか(実物編の深掘り) ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Code×Slidev:AIスライドが“毎回揃わない”を解決するデザイントークン first appeared on SIOS Tech Lab .
Marp と Slidev、どう使い分ける? ども! もともとスライドは全部 Marp で作ってて、そこそこ使い倒した末に Slidev へ移った 龍ちゃんです。 Marp と Slidev は、どっちも Markdown でスライドを書ける 開発者向けのツールです。で、両方を触ったことがある人なら、一度はこう思いますよね。 龍 「結局、 どっち使えばいいの? 」 僕は両方を Claude Code で使い倒してきました。Marp は 80時間→11時間に短縮した話 を書いたくらい使い込んだし、その上で今は Slidev に移ってます。で、行き着いた結論はシンプルです。 手軽さと共有なら Marp、作り込んで育てたいなら Slidev 。どっちが上って話じゃなく、用途で使い分けています。 この記事では、その「 で、あなたはどっち? 」に、両方を行き来した目線で整理をします。(そもそも Slidev って何?ってところやセットアップは 第1部 に書いてるので、気になる人はそちらもどうぞ。 この記事は読んでなくても大丈夫 です。) 大前提:Git で管理したい。だから候補は2つに絞られる 比較に入る前に、ひとつだけ僕のこだわりを置かせてください。 スライドも、コードと同じようにテキストで書いて Git で管理したいんですよね。 差分が見えて、履歴が残って、「ここ直して」が効く。あの体験を一度味わうと、もう戻れないんですよ。 で、この前提を置いた瞬間、候補がスパッと絞られます。まず土俵から降りるのが PowerPoint と Google Slides。 PowerPoint(.pptx) :中身は ZIP に XML を詰めた バイナリ なんですよね。 git diff しても返ってくるのは Binary files differ の一行だけ。どこを直したか読めないし、ブランチを分けてマージ、なんてのも事実上できません。テキストだけ抜き出す裏技はあるけど、図形やレイアウトの変更は追えないので、実用にはちょっと。 Google Slides :そもそも ローカルにファイルが存在しない んですよ。クラウドの上にあるので、 git add する対象がない。独自の版履歴はあるけど、ブランチもプルリクもない。あれは Git とは別物です。 なので「Git で管理する」と決めた時点で、残るのは テキストで書く2強= Marp と Slidev だけ。ここからが本題です。 もちろん、「他部署の人が中身を直接いじる」みたいな用途なら、素直に PowerPoint が正解です。テンプレートとかやっぱり強い点がありますもんね! Marp と Slidev、それぞれ何ができる? ざっくり並べるとこんな感じです。 軸 Marp Slidev 書き味 Markdown + コメント記法 Markdown + 独自構文(layout / Vue) セットアップ ◎ npx 一発・VSCode 拡張だけでもOK △ Node + Vite + Vue のプロジェクト型 共有のしやすさ ◎ VSCode 拡張で誰でも同じ画面 ○ 環境を揃える必要あり 配置を書くコスト 生 CSS を手書き ユーティリティを構造の隣に直書き(UnoCSS 標準同梱・Tailwind 互換) 型にして使い回す △ 部品機構なし・毎回手書き ◎ コンポーネントに閉じて呼ぶ 量産・軽さ ◎ 1ファイル・CLIで一括変換・CI向き プロジェクト型・作り込み向き Marp の強さはなんといっても手軽さです。 npx @marp-team/marp-cli で即動くし、VSCode 拡張を入れれば誰でも同じ画面でプレビューできる。 .md 1枚とテーマ CSS 1枚で完結するので、思い立って5分で書き始められるんですよね。 一方の Slidev は Node + Vite + Vue のプロジェクトを抱えるぶん、立ち上げはちょっと重い。でも、その重さと引き換えに、「配置を書くコスト」と「型にして使い回す」この二つの点で優勢です。 見た目は両方で作れる。差は「書き方」に出る 「コード左・解説右」みたいな2カラムの1枚。これ、 見た目だけならどっちでも作れます 。試しにまったく同じ図解を Slidev で組んでみました。 正直に言っておくと、 この見た目は Marp でも作れます 。標準の記法・見出し・2カラム程度までなら Marp で十分だし、書き始めの手軽さはむしろ Marp に分があるくらい。(さすがに同じ図を二つ並べるのもくどいので、一枚だけ貼っておきます) 差が出るのは、 この1枚を「どう書くか」 のほうです。中身の div 構造はだいたい同じで、違うのは「 スタイルと共通パーツを、どう共有・再利用するか 」です。両方の書き方を順に見ていきます。 Marp の書き方:テーマ CSS+クラス指定で寄せる 先に言っておくと、Marp も毎回 <style> を書き散らすわけじゃないんですよ。僕も最初は「Marp ってベタ書きでしょ?」と思ってたんですけど、CSS は1枚のテーマ .css にまとめておけて、スライド側は theme: で呼ぶだけ。1枚ごとに見た目を変えたいときは <!-- _class: xxx --> 、組み込みの lead / invert クラスや、ヘッダー帯を出す header: ディレクティブもある。ここはちゃんとしてるんですよね。 --- marp: true theme: my-theme # ← 共有テーマCSSを適用 header: 「同じ1枚」を、どう書くか --- <!-- _class: cmp --> <!-- ← この1枚にレイアウト用クラスを当てる --> <div class="cmp-row marp"> … </div> <div class="cmp-row slidev"> … </div> その my-theme の中身自体は、やっぱり長めの CSS です(このサンプルだと90行ほど)。 /* my-theme.css(抜粋):section やコードブロックの見た目を定義 */ section { display:flex; flex-direction:column; } .cmp-row pre { background:var(--navy-900) !important; border-radius:10px; } /* …配色トークンやヘッダー帯の定義が続いて、全体で90行ほど */ 「共有 CSS+名前でレイアウトを呼ぶ」という発想は、Marp にもあります。裏に長めのテーマ CSS が控えるのも、一度書けば共有できるのも Slidev と同じ。ここは互角です。 ただ、Marp のテーマやクラスでできるのは、基本「 すでにある要素に見た目を当てる 」ところまでなんですよ。さっきのコードで言うと、 <!-- _class: cmp --> はスライドに cmp って名前を付けるだけ。 中身の箱 ── 2カラムなら左右の枠 ── は1個も生えてこない んです。だから <div class="cmp-row marp">…</div> みたいな箱を自分で書いて、そこに CSS が当たるのを待つ、という順番になる。CSS は「 .cmp-row があればこう並べてこう塗る」とは言えても、 .cmp-row という箱そのものは作ってくれない んですよね。 要するに、Marp が面倒を見てくれるのは「見た目(スタイル)」だけで、「 どこに何の箱を置くか(配置・構造) 」は毎回こっちが手で組む担当。 header: や lead / invert くらいの切り替えなら CSS 側で寄せられるけど、込み入った配置になると結局 <div> をその都度こしらえることになる。さっきの縦積みの比較図も、僕は毎回 div を手で組んでました。テーマで寄せられるのは骨格まで。地味にこれがしんどいんですよ。(まぁ大体 div 地獄になって大変です) Slidev の書き方:style.css+レイアウト”部品” Slidev も、もちろん CSS は書けますよ。全体に統一して効かせたいスタイル、つまり配色トークンや書体、コードブロックの見た目みたいな「deck 全体の地」は、 style.css に素の CSS で書けばいい。一度書けば全スライドが継承します。 /* style.css(抜粋):deck 全体に効かせる素のCSS */ :root { --navy-900:#0B1F3A; --accent:#C2410C; /* …配色トークン */ } .slidev-layout { font-family:'Noto Sans JP',sans-serif; background:var(--bg-white); } .cmp-row pre { background:var(--navy-900) !important; border-radius:10px; } /* …全体で162行。一度書けば全スライドが継承する */ ここは Marp と一緒です。「全体に効かせる CSS は1枚に集約」って発想は、どっちも変わらない。差がつくのはこの先なんですよ。 違うのは、 layout: で呼ぶものが CSS クラスじゃなく Vue コンポーネント(=構造を持った部品) だということ。さっきのヘッダー付きスライドは、こう書くだけです。 --- layout: fixed-header heading: 「同じ1枚」を、どう書くか # ← この文字列を部品がヘッダーに描く --- <div class="flex flex-col gap-8"> <div class="rounded-xl bg-[var(--navy-900)] p-5 shadow-lg"> … Marp の行 … </div> <div class="rounded-xl bg-[var(--navy-900)] p-5 shadow-lg border-l-4 border-[var(--accent)]"> … Slidev の行(朱アクセント) … </div> </div> この fixed-header の実体は Vue コンポーネントで、 heading を受け取って <header> の DOM を自分で組み立てて、本文は <slot /> に流し込みます。 <!-- layouts/fixed-header.vue(抜粋):構造を部品側が持つ --> <template> <header class="slide-fixed-header"> <h2 v-if="heading">{{ heading }}</h2> <!-- ← ヘッダーの DOM を生成 --> </header> <main class="slide-body"><slot /></main> <!-- ← 本文はここに入る --> </template> つまり Slidev のレイアウトは「スタイルを当てる」だけじゃなく、 構造ごと部品から供給される んですよ。しかも本文側は、さっきの flex flex-col gap-8 みたいに、構造のすぐ隣にデザインをそのまま書ける ── Tailwind 互換のユーティリティ( UnoCSS )が標準で効くんです。正直、フロント大好きマンとしてはここだけで大興奮です(笑)。 別ファイルの CSS に飛んで .cmp-row を探さなくていいし、 two-cols の ::right:: スロットも込みで「Vue を書く感覚のまま組める」。これだけでも Marp から乗り換える価値あったな、って思ったんですよね。 で、ここまでは前置きなんですよ。もっと効いてくるのが次の話、「 部品として定義して、何度も呼べるか 」です。 決定的なのは複雑な図も「部品にして使い回せる」 書き方の差より、もっと効いてくるのが 再利用 です。実際のセミナーで使った RAG の概念図がこれ。 これ、Vue コンポーネントなんですよ。中身は591行あって <style scoped> も同居してる。でも、スライドからは <RagConcept /> の 1タグで呼べる 。一度作れば別のスライドでも使い回せるし、 props で中身も変えられる。実際これ、3本のセミナーで使い回してます。 Marp には部品を定義して名前で呼ぶ仕組みがないので、こういう図は型にできず毎回手書きになります(単純なコピペ or 画像で使いまわしです)。単発で HTML や SVG を直書きすれば凝った図が作れないわけじゃないけど、差が出るのは「 型として定義して再利用できるか 」のところですね。Marp でやろうとすると、関連する CSS を引っ張ってきて div と見比べる…って手間がかかるんですよね。Slidev で コピって参照を貼るだけ のお手軽さを知っちゃうと、もう戻れないです。 で、この「閉じた部品にできる」のが、そのまま AIと相性がいい 理由なんですよ。生 CSS はグローバルに効くから、1枚直すだけでも「他のスライドに副作用は出ないか」を全部確認しないといけない。Marp でも鬼の !important で対応するってのもできますが、影響のある CSS をすべてまっさらにするって形になって諦めました。Slidev では、 scoped CSS と部品は影響範囲がその中に閉じてるから、AI(Claude)はその部品だけ読めば安全に直せます。最初の1枚を書かせるだけなら Marp も得意(記法が素朴で外しにくい)。差が出るのは「 直し続ける・育てる 」段階で、影響範囲が閉じてるぶん Slidev は何度手を入れさせても事故りにくいんですよね。この「育てる」話は、次回から具体的に入っていきます。 使い分け で、「どっちが上か」じゃなくて「どう使い分けるか」なんですよ。僕の振り分けはこんな感じです。 Marp を選ぶ場面 : とにかく手軽に、サッと枚数を出したいとき。VSCode 拡張で誰でも同じ画面が見られるから、共有もしやすい。凝らずにコンテンツ勝負、大量に量産、CI で回す。このへんは Marp が強いです。凝ったデザインは無理、と割り切れるならむしろ最高。 Slidev を選ぶ場面 : 外部向けの、作り込んだ資料 を作りたいとき。コードや図を綺麗に見せたい、コンポーネントで部品化して 一度作った資産を使い回して育てたい とき。Vue が土台だからこそできる芸当ですね。僕が Marp から移ったのも、まさに「外部向けを Marp で作り込むのが苦しくなった」のが理由でした。 どっちが正解ってわけじゃなくて、 今回の用途に合うほうを選ぶ だけ。両方テキストで書けるので、乗り換えのコストも実は低いんですよ。 そしてこのシリーズで僕が Slidev を主役にしてるのは、その「 部品化して育てる 」というのが、直近の活動と合致しているからです。 まとめと、次回 スライドを Git で管理したい と決めると、PowerPoint / Google Slides は外れる 残った Marp と Slidev は優劣じゃなくて できることが違う 手軽さ・共有なら Marp、部品化して育てるなら Slidev 。僕はこう振り分けてます 次回(第3部)は、その Slidev を「育てる」第一歩、 デザイントークン編 です。デフォルトのままAIにスライドを書かせると色もサイズも暴走するんですけど、それを style.css に「枠」として先に定義してAIを縛る、っていう即効性のある処方箋を書きます。お楽しみに。 ほなまた〜 シリーズ:AI×スライドづくり AIに丸投げせず、制約とルールで「意図どおりの95点」を毎回そろえて作るシリーズです。 セットアップ〜エクスポート — 構文ゼロで作って配る Marp と Slidev の使い分け — Git管理起点でどっちを使う(今この記事) 実物編(全部入り) :移植できるデザインシステムを丸ごと公開 ← まとめ デザイントークン編 — なぜ「枠」で縛るのか(実物編の深掘り) デザインシステム編 — なぜ型を貯めて育てるのか(実物編の深掘り) ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Marp と Slidev の使い分け:Git で管理したい僕がやる使い分け first appeared on SIOS Tech Lab .
AIで爆速、しかも Git 管理。スライド作りがコードになった ども! Slidev と Claude Code を組み合わせてスライド作りにハマってる龍ちゃんです。 Slidev は Markdown でスライドを書ける、開発者向けのプレゼンツール です。テキストボックスをマウスで並べる代わりに、見出しや箇条書きを Markdown で書くと、それがそのままスライドになる。コードブロックもそのまま貼れてシンタックスハイライトも効くので、LT や登壇資料を作るエンジニアに人気のやつですね。 で、これを Claude Code に任せると、こんなスライドが日本語で頼むだけで出てきます。 ↑ Claude Code に作らせた実物の1枚です(別のセミナー資料から)。こういう 複雑な概念図 でも、テキストボックスをこねくり回さず、Markdown と日本語の指示で組めるんですよ。 で、何が嬉しいか。もう手作業には戻れなくなる、僕が推したいのはこの3つです。 Git で差分管理できる : 調査もコードも素材も全部 Git に乗せてるのに、スライドだけ PowerPoint のバイナリで蚊帳の外——が地味に面倒だったんですよね。Slidev は中身がただの Markdown なので、素材もスライドも全部 Git の上に揃います AIに頼むだけで爆速 : Slidev 独自の記法は Claude Code が肩代わり。 公式が「AIと相性が良い」と明言 していて、構文を丸ごと渡せる 公式 Skill まで配ってます(公式が出してる=AIに任せる前提、ってのが高ポイント) 見た目の調整も自然言語で : スタイリングは Tailwind 互換( UnoCSS )で、Tailwind はAIが得意な記法。「ここもう少し大きく」で通るので、1px ずつ動かす作業から解放されます 実際セットアップして頼んだら、あっという間にでき上がって「これ最高じゃん」ってなりました。スライド作りが、コードを書くのと同じ開発体験になるんですよ。 この記事はその環境を作る セットアップまとめ です。「なんで Marp じゃなく Slidev?」の比較は一本になるので次回に回します(Marp は 別記事 で書いてます)。今回は「作って、ちゃんと配れる状態になるまで」を最短で通しますね。 今回の内容です。 公式 Skill を入れるだけで、Slidev の構文ゼロでスライドが作れる 作ったスライドを PDF / PNG / PPTX として出力する手順 エクスポートで必ずハマる3つの罠(playwright-chromium・日本語フォント・PPTX は画像)の潰し方 セットアップ 前提 Node.js が入っていること (この記事のコマンドは npx skills も npm create slidev も npx slidev export も、ぜんぶ Node の上で動きます。逆に言えば Node さえあれば動きます) Claude Code が使えること 1. 公式 Slidev Skill を入れる npx skills add slidevjs/slidev これだけで、Slidev の Skill がプロジェクトに追加されます。 npx skills は Skill ファイルをリポジトリに配置する CLI で、Claude Code 専用の隠しコマンドとかじゃなく、ただの npx 実行です(だから Node さえあれば動く)。置かれた Skill は Claude Code が自動で拾うので、 /skills を叩いて一覧に Slidev が出ていれば導入完了です。 中身は Slidev 公式リポジトリ( slidevjs/slidev )に同梱されている Skill で、コア構文・アニメーション・コードハイライト・図表(Mermaid / PlantUML / LaTeX)・レイアウト・エクスポートまで、52個のリファレンスが入っています。 2. Slidev プロジェクトを作る npm create slidev 対話的にプロジェクト名などを聞かれるので答えると、 slides.md を中心とした最小構成ができあがります。スライドの実体はこの slides.md というただの Markdown ファイルです。 3. 最初の1枚を Claude に頼む あとは Claude Code に日本語で頼むだけです。僕がよくやるのは、いきなり「スライド作って」じゃなく、先にアウトライン → ストーリーボード(どの順で何を見せるか)→ 色の方向、をテキストで固めてから「これでスライド化して」と渡す流れですね。最近の社内 LT は、チャットで5分くらいダーッと喋って方向を固めて、そのまま「スライドにして」で一気に組んでもらいました。 最初の1枚なら、こんな雑な頼み方で十分です。 slides.md に、タイトルスライドと「自己紹介」「今日話すこと」の3枚を作って。 今日話すことは箇条書きを v-click で1つずつ出すアニメーションにして。 v-click (クリックで要素を順番に表示するアニメーション)のような Slidev 固有の記法も、Skill を入れてるから Claude がちゃんと書いてくれます。こっちは記法を知らなくていい。これが地味に最高なんですよね。 4. ブラウザで確認する npm run dev http://localhost:3030 を開くと、作ったスライドが表示されます。ここまでは驚くほどスムーズに進むはずです。 ……で、ここまでは本当に呆気ないくらい順調なんですよ。 ここからは、僕が詰まった点を共有しておきますね。 エクスポートで詰まったところ 「画面では完璧」と「ファイルとして配れる」は、まったくの別物なんですよね。スライドは最終的に PDF やパワポにして配ることになるんですが、そのエクスポートの段階で知らないと「結局使えないじゃん」で終わる罠が3つあります。 出力の前提・出力結果のフォント・出力フォーマットの性質、の3つです。順に潰していきますね。 レイアウトが上に寄る、要素が重なる、といった CSS / デザイン起因の崩れは、エクスポート機構そのものの罠とは別問題なので、この記事では扱いません。デザイン編(続編)に切り出します。 罠①:playwright-chromium がないと、そもそも出力できない Slidev のエクスポートは内部でヘッドレスブラウザ(Chromium)を使ってスライドをレンダリングして PDF / PNG / PPTX に変換しています。なので Chromium を動かすための依存パッケージが必要なんですよね。 npm install -D playwright-chromium これを入れていないと、エクスポートコマンドがブラウザ関連のエラーで止まります。「export できない」とハマったら、まずこれを疑ってください。最初わからんかったんですが、Slidev 公式の エクスポートガイド を見たら前提として明記されていましたね。 コンテナ の場合:僕みたいに python:3.12-slim のような軽量イメージで動かしてると、 playwright-chromium を入れても それだけじゃ動きません 。Chromium 本体を動かすための system ライブラリ(共有ライブラリ)が OS 側に無いからです。僕は Dockerfile に apt で chromium を入れて解決しました( npx playwright install-deps でも依存ライブラリだけ入ります)。 罠②:日本語が豆腐になる / 文字化け これ、コンテナや CI みたいなまっさらな環境だと刺さるやつです。dev サーバーでは普通に日本語が表示されてたのに、PDF にしたら文字が□(豆腐)になったり、文字化けしたりするんですよ。正直「壊れた?」ってなりました(笑)。 原因は、フォントを明示指定していないことです。指定がないと、レンダリングするブラウザ環境にあるフォントが使われてしまって、日本語フォントが無い環境では化けてしまうんですね。逆に言うと、普通の Mac / Windows なら OS が日本語フォントを持ってるので、指定しなくても化けないことが多いです。 対策は、 slides.md の先頭(headmatter)でフォントを明示することです。 --- fonts: sans: 'Noto Sans JP' serif: 'Noto Serif JP' --- Slidev は、ここで指定したフォントを Google Fonts から自動的に読み込んでくれます。Noto Sans JP のように Google Fonts に存在する日本語フォントを指定しておけば、エクスポート時もちゃんと日本語が出ます。これも公式の フォント設定ガイド どおりの挙動ですね。基本は、この fonts: 指定さえしておけば豆腐は防げます。 コンテナの場合: python:3.12-slim のような軽量イメージは、そもそも OS に日本語フォントが入っていません。 fonts: で Google Fonts から取れる環境ならそれで足りますが、ネットに繋ぎたくない・繋がらないときの安全網として、OS 側にも日本語フォント( fonts-noto-cjk パッケージ)を入れておくと確実です。僕の DevContainer は Dockerfile で fonts-noto-cjk を入れてます。 罠③:PPTX に書き出しても、中身は「画像」 「Slidev は PPTX エクスポートにも対応してる、これでパワポで提出できる!」——僕も最初そう思ってたんですが、ここが3つ目の罠でした。 npx slidev export --format pptx たしかに PowerPoint ファイルは出てきます。が、開いてみると各スライドは編集可能なテキストや図形ではなく、1枚の画像として貼り付けられているんです。これは Slidev の仕様で、公式にもこう書かれています。 all the slides in the PPTX file will be exported as images, so the text will not be selectable. (PPTX ファイル内のすべてのスライドは画像としてエクスポートされるため、テキストは選択できない) — Slidev 公式 “Exporting” つまり、「PowerPoint で開いて最後に文言だけ直す」「フォントを差し替える」「アニメーションを足す」といった編集は一切できません。スライドの絵が貼ってあるだけだからです。 これ、実際に困ったことがあって。作った資料を、PowerPoint ベースで作業してる人に渡して使い回してもらう場面があったんですよ。共有するなら、デザインの統一感的にも PowerPoint じゃないと困る。で、その人が「このデザインのまま中身を直したい」となったときに——直せない。中身は画像だから。結局 Markdown 側を持ってる僕しか直せないわけで、これはちょっと困りましたね。 ここを理解した上での付き合い方はこんな感じです。 修正はあくまで Slidev 側(Markdown)でやる。PPTX はあくまで「配布・提出用の最終形」と割り切る。 他部署や他人が PowerPoint で中身をいじる前提の用途には向かないので、その場合は素直に PowerPoint で作った方がいい。 逆に「PDF と同じく、見せる・配るだけ」の用途なら PPTX で何の問題もありません。自分が編集の主導権を Markdown 側に持つ、という運用さえ守れば罠にはならないんですよね。「スライドの実体は Markdown にある」と割り切れると、PPTX は単なる出力物になります。 エクスポートを実行する 罠を潰したら、あとは出力するだけです。 PDF(配布の定番) npx slidev export --output export/slides.pdf --timeout 60000 --timeout を長めに取っているのは、スライドが多かったり画像が重かったりすると、デフォルトのタイムアウトではレンダリングが間に合わずに失敗することがあるからですね。実際、Claude にエクスポートを任せてると、たまにここでコケます(笑)。そういうときは一旦止めて、 --timeout を伸ばしたコマンドを自分で打ち直すか、「このコマンドで出して」とまとめて投げ直すのが早いです。 ちなみに「エクスポートして」だけだと Claude がうまくコマンドを組めないこともあるので、僕はスライドを置いてるディレクトリの CLAUDE.md に、よく使うエクスポートコマンドをそのまま書いてます。こうしておくと「いつものやつで出して」で通るので、毎回コマンドを思い出さなくて済むんですよね。 PNG(1枚ずつ画像で確認したいとき) npx slidev export --format png --per-slide --output export/slide --per-slide でスライド1枚につき1ファイル出力されます。レイアウトを直したあとに「該当ページだけ素早く確認したい」ときは、 --range で範囲を絞ると速いです。 npx slidev export --format png --range 1-5 --output export/slide 出力後は、PNG をざっと眺めてフォントが日本語で出ているか・レイアウトが崩れていないかを確認する習慣をつけておくと、配布直前の事故が減りますよ。 エクスポート成果物は Git に入れない export/ ディレクトリ(PDF や PNG の出力先)は成果物なのでリポジトリにはコミットしません。 .gitignore に入れておきましょう。 # Slidev build/export artifacts dist/ .slidev/ export/ ソース( slides.md )さえ管理しておけば、成果物はいつでも再生成できます。これも「スライド=コード」として扱う発想ですね。 まとめ ここまでで、構文を一切覚えずに Claude Code でスライドを作って、PDF / PNG / PPTX として配れる状態ができあがりました。要点は2つです。 公式 Skill( npx skills add slidevjs/slidev )を入れれば構文学習はゼロでいいです。日本語で「こんなスライドを作って」と頼むだけ。 エクスポートの3つの罠(playwright-chromium・日本語フォント・PPTX は画像)を先回りで潰せば、Slidev は実用ラインに乗ります。 「AIにスライドを作らせてみた」で終わらず、ちゃんと配れるところまで来ると、スライド作りの体験は本当に変わります。なにより、爆速でスライドの形にして見た目まで確認できるのが便利なんですよね。テキストで書いて、差分で管理して、「ここ直して」で直る。この身軽さは一度味わうと、なかなか戻れないです。 ……と言うと「いや、それ Marp でもよくない?」って思った人、いますよね。わかります(笑)。そのへんの使い分けは次回ちゃんと比較するので、ちょっと待っててください〜。 次回予告 その第2部「Marp と Slidev の使い分け」が次回です。Git で管理したい僕の目線で、PowerPoint / Google Slides がなぜ脱落するのかも含めて、「結局どれ使えばいいの?」に決着をつけます。(というかGit管理できるなら知りたい!) その先は、AIにデザインを崩させない「枠」の作り方(デザイントークン → 自分のデザインシステム)や、出力した PNG を Claude 自身に見させて直す画像レビューループ編も予定しています。続きが気になったら、また覗きにきてください。 ほなまた〜 シリーズ:AI×スライドづくり AIに丸投げせず、制約とルールで「意図どおりの95点」を毎回そろえて作るシリーズです。 セットアップ〜エクスポート — 構文ゼロで作って配る(いまこの記事) Marp と Slidev の使い分け — Git管理起点でどっちを使う 実物編(全部入り) :移植できるデザインシステムを丸ごと公開 ← まとめ デザイントークン編 — なぜ「枠」で縛るのか(実物編の深掘り) デザインシステム編 — なぜ型を貯めて育てるのか(実物編の深掘り) ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Code × Slidev:構文ゼロで作って、Git で管理する first appeared on SIOS Tech Lab .
これまでの記事では、SCANOSSの ローカルスキャン(CLI) 、 GitHub Actionsでの自動化 、 モノレポ対応 を紹介してきました。CIに最終ゲートを置けたら、次に出てくるのが「CIが落ちてから直すのではなく、コミットの時点でローカルに気づきたい」というニーズです。 本記事では、SCANOSSのソースコードスキャンを pre-commit に組み込み、OSS由来コードの混入をコミット時点で検出する構成を紹介します。記事は次の3つの流れで進めます。 なぜpre-commitでOSSライセンスをチェックするのか(SCANOSSで何を止めたいか) SCANOSSをpre-commitに組み込む(公式hookと自作hook) pre-commitとCIの役割分担 この記事でわかること : なぜCIだけでなくpre-commitでもソースコードをスキャンするのか pre-commitで検出対象を「コード照合」に絞る理由 SCANOSS公式hookの設定・できること・できないことと、自作hookという選択 delta(変更ファイル単位)スキャンとKEY未設定時の扱いによる軽量化 前提・検証環境 本記事の自作hookは、 SCANOSS Premium のAPI KEY を前提 とします(理由は後述)。一方、後述の公式hookは OSSKB (無料)でも動きます。 検証はすべて scanoss-py 1.52.1 を本リポジトリで実行した実測です(2026-06-13 時点)。本記事に出てくる終了コード・出力・所要時間などの数値は、この環境での実測値です。 SCANOSS および scanoss-py、公式hookは更新されうるため、導入前に最新の挙動・バージョンを確認してください。 なぜpre-commitでOSSライセンスをチェックするのか SCANOSSで防ぐ2つのOSSリスク SCANOSS (SCA: Software Composition Analysis)を使う目的は、大きく2つのリスクを防ぐことです。 防ぎたいリスク 内容 ライセンス違反 コピーレフト等のOSSが混入し、義務(ソース開示・表示)を果たさないまま配布してしまう 出所不明(未宣言OSS) SBOMに載らないコード・依存が紛れ込み、ライセンスも脆弱性も追跡できなくなる これらの検出は、実装上は次の3種類に分かれます。CIではこの3種類すべてを回しています( GitHub Actionsでの自動化 ・ モノレポ対応 の記事を参照)。 検出の種類 内容 コード照合 コードのスニペット照合。OSS由来のコード片が混ざっていないか 依存の宣言突合 マニフェストのOSSが bom.include に登録済みか 依存のライセンス種別 その依存がGPL等のコピーレフトか このうち「依存の宣言突合」と「依存のライセンス種別」は、依存(ライブラリ)に対する検出です。「コード照合」だけが、自分のコードそのものに対する検出になります。 コードへの「混入」は依存利用とは別物 pre-commitで何を見るかを決める前に、1つだけ押さえておきたい点があります。 同じOSSでも「依存として使う」のと「自分のコードに取り込む」のとでは、意味が違う ということです。 OSSの入り方 法的な意味(概略) 扱い 依存(ライブラリ)として使う 使うだけなら、表示義務などで済むことが多い 許容しうる(CI側で管理) 自分のコードに貼り付けて取り込む 取り込み=結合・改変扱いとなり、ソース開示など重い義務が発生しうる 止めたい(これが「混入」) ライセンス種別ごとの具体的な義務(GPLとLGPLの違いなど)は本記事の主題ではないため踏み込みません。ここで必要なのは「依存として使う分には許容できても、コードへの混入は別問題」という一点です。 pre-commitで止めたいのは、この 「混入」 です。したがって、pre-commitで見るのは コード照合 に絞ります。理由は「処理が軽いから」ではなく、 コード照合が混入の発生源だから です。 OSSコードの混入が起きる瞬間は「コードを書く・貼る、その時」です。これはコード照合でしか捕まえられず、最も上流で止められます。 依存に関わる2つ(依存の宣言突合・依存のライセンス種別)は、マニフェストを編集するという意識的な操作で発生し、構造的に見落としにくいものです。かつ「依存として使う分には許容する」という管理が必要になるため、コミットの一瞬で一律に止めるよりも、CI側で扱う方が適しています。 加えて、AIでコードを書く比重が上がるほど、この観点は重要になります。AIは学習データ由来のOSSコードをそのまま出力することがあり、書いた本人に「これは拾ってきたコードだ」という自覚がないまま混入する可能性があります。生成量も多く、目視レビューで追い切れません。コミットの瞬間に機械が照合するpre-commitは、AI生成コードに対する現実的なガードレールとして機能します。 検出結果を判断するのは開発者 もう1つ、pre-commitをローカルに置く理由があります。 スキャン結果は、それ自体が答えではない という点です。 検出された結果を見て、どう対応するかを決めるのは人間(開発者)の仕事です。 検出が妥当な場合 → コードを直す、あるいは正規の依存として取り込む 意図と違う検出の場合 → 誤検出として扱い、設定で除外する どちらに倒すかは、そのコードをなぜ・どこから持ってきたかを知っている開発者本人にしか判断できません。スキャンは判断材料を提示するところまでで、その先は必ず人間が引き取ります。 問題は、 その判断をいつ・どこで引き取るか です。 プルリクエストを出した後(CI)で受け取る場合、判断のためにプルリクエストを発行し、CIを実行し、結果が返るのを待つ、という工程を経てから手元に戻ってきます。判断のたびに往復が発生します。 コミットの瞬間(pre-commit)で受け取れば、往復はありません。コードを書いた直後、文脈が最も濃い手元に結果が出るので、その場で対応を決められます。 つまり、速度そのものが目的なのではなく、「判断を手元に置く」ことの結果として速度が効いてきます。逆に言えば、遅いpre-commitは git commit --no-verify で外されるようになり、「判断を手元に置く」という目的ごと壊れてしまいます。軽量であることは必須条件です。 SCANOSSをpre-commitに組み込む(公式hookと自作hook) SCANOSSのコードスキャンをpre-commitに乗せる手段は、大きく2つあります。SCANOSSが提供する公式hookと、自作hookです。まず公式hookを見て、その制約を確認したうえで自作hookに進みます。 公式pre-commit hookを使う SCANOSSは公式のpre-commit hook( scanoss/pre-commit-hooks )を提供しています。提供されるhookは scanoss-check-undeclared-code の1種類です。 導入手順 設定は .pre-commit-config.yaml に次を追加するだけです。 repos: - repo: https://github.com/scanoss/pre-commit-hooks rev: v0.4.0 # rev: v0 とすると major 系の最新に追従 hooks: - id: scanoss-check-undeclared-code 追加後、 pre-commit install でhookを有効化します。API KEYは OSSKB (無料)では任意で、 Premium を使う場合は環境変数 SCANOSS_API_KEY で渡します(本記事のCI側Secret名 SCANOSS_KEY とは別名なので注意してください)。 できること 設定した公式hookは、コミット時に次のように動きます。 staged(ステージ済み)ファイルだけをSCANOSSのCLI scanoss-py でスキャンし、 未識別(undeclared)のコンポーネントを検出するとコミットをブロック します。 スキャン対象は変更ファイル単位(CIのdeltaスキャンと同じ粒度)で、軽量です。 リポジトリを追加するだけで使え、スクリプトの保守が不要です。 未宣言OSSのシフトレフトだけが目的であれば、この公式hookを入れるのが最も手軽です。 できないこと(コピーレフト・モノレポ) 一方で、本記事の狙い(コードのコピーレフト混入を、モノレポのコンポーネント単位で検出する)に対しては、公式hookには現状いくつかの制約があります(いずれも 2026-06-13 時点で公開リポジトリのソースから確認した事実です)。公式hookは更新されうるため、導入前に最新の挙動を確認してください。 できないこと 内容 コピーレフト判定 公式hookは未識別(undeclared)専用で、コピーレフトライセンスの混入は判定しない スキャン対象の絞り込み pass_filenames: false で、hook自身が git diff --staged を実行し、常にstaged全体を対象にする コンポーネント別設定 単一の scanoss.json を前提とするため、モノレポでコンポーネントごとの scanoss.json を使い分けられない コピーレフトまで検出したい、コンポーネントごとの scanoss.json を効かせたい、という要件には、現状は自作hookが必要になります。 龍ちゃん SCANOSSは結構活発に開発がされているので、この辺は更新される可能性が大いにあります!もし回収されたらこちらも併せて更新する予定です。 自作hookでコピーレフトを検出する 公式hookで足りない部分(コピーレフト判定とコンポーネント別設定)は、自作hookで補います。 スキャンスクリプトとhook登録 自作hookは scanoss-py(本記事の検証は 1.52.1)を直接呼び出します。処理は単純です。staged のコードファイルを scanoss-py scan に渡してスキャンし、その結果を scanoss-py inspect copyleft に通して、コピーレフトが検出されたらコミットを中断します。 なお、この構成は SCANOSS Premium のAPI KEY を前提 にしています。 --files で対象を指定し、かつ scanoss.json のスニペット照合を有効にする組み合わせは、無料エンドポイント( OSSKB )に投げると 400 Bad Request で弾かれるためです(2026-06-13 時点・scanoss-py 1.52.1 で確認)。KEY は --key "$SCANOSS_KEY" で渡します。 # staged ファイルをスキャン(Premium KEY 経路) scanoss-py scan --files <staged files> \ --settings <component>/scanoss.json \ --key "$SCANOSS_KEY" \ --output result.json # 検出結果からコピーレフトを判定(検出時は非ゼロ終了) scanoss-py inspect copyleft --input result.json scanoss-py inspect copyleft は、結果にコピーレフトライセンスが含まれていれば非ゼロで終了します(scanoss-py 1.52.1での実測値は、検出時にexit 2、未検出時にexit 0)。この終了コードを拾ってコミットを止めるだけです。 なお、上記はあくまで処理の核( scan → inspect copyleft )を抜き出したものです。実際の hook スクリプトには、これに加えて KEY 未設定時のスキップ・スキャン対象外パス( node_modules 等)の除外・終了コードを拾ってコミットを止める処理 が必要です(後述のとおり、コンポーネントの振り分けは .pre-commit-config.yaml 側、これらの処理はスクリプト側で実装します)。 このスクリプトを用意し、 .pre-commit-config.yaml にローカルhookとして登録します(設定キーは pre-commit公式ドキュメント を参照)。ここで一つ設計上の選択があります。 「どのコンポーネントを、どの scanoss.json で見るか」の振り分けを、スクリプト内に書かず、pre-commitの files: (発火条件)に持たせる ことです。コンポーネントごとにhookを1つずつ立て、 args でそのコンポーネントの scanoss.json を渡し、 files: でそのコンポーネント配下のコードだけを発火条件にします。 - repo: local hooks: - id: scanoss-code-slides name: SCANOSS code scan (slides) entry: scripts/scanoss-precommit-scan.sh args: ['application/slides/scanoss.json'] # このhookが使う設定 language: script files: '^application/slides/.+\.(py|js|ts|tsx|vue|jsx|mjs)$' # 発火条件=ルーティング pass_filenames: true require_serial: true - id: scanoss-code-tools name: SCANOSS code scan (tools) entry: scripts/scanoss-precommit-scan.sh args: ['application/tools/scanoss.json'] language: script files: '^application/tools/.+\.py$' pass_filenames: true require_serial: true こうすると、コンポーネントが増えてもスクリプトには手を入れず、hookを1つ足すだけで済みます。振り分けロジックがコードに散らばらず、 .pre-commit-config.yaml を見れば「どのパスが、どの scanoss.json で見られるか」を一覧できます。モノレポでコンポーネントごとに設定を使い分けたい、という公式hookでできなかった要件は、この形で満たせます。 ここまでに出てきたファイルの配置は次のとおりです。スクリプトは1本で全コンポーネントが共有し、コンポーネント固有なのは各 scanoss.json だけ、という形になります。 リポジトリルート/ ├── .pre-commit-config.yaml # hook登録(コンポーネントごとにrouting) ├── .env # SCANOSS_KEY を置く(.gitignore 対象) ├── scripts/ │ └── scanoss-precommit-scan.sh # ゲート本体(全hookが共有して呼ぶ・付録に全体) └── application/ ├── slides/ │ └── scanoss.json # slides 用スキャン設定 └── tools/ └── scanoss.json # tools 用スキャン設定 役割で分けると、 scripts/scanoss-precommit-scan.sh (処理)・ .pre-commit-config.yaml (振り分け)・各 scanoss.json (コンポーネントごとのスキャン設定)・ .env (KEY)の4種類です。コンポーネントを増やすときに触るのは、 scanoss.json の追加と .pre-commit-config.yaml へのhook追記だけで、スクリプトは変わりません。 この構成には、軽量に保つための工夫も入っています。 files: で発火条件をコンポーネント配下のコード拡張子に限定します。READMEや画像だけのコミットでは動きません。 pass_filenames: true により、staged ファイルのパスがhookに渡され、変更ファイルだけをスキャンします(deltaスキャン)。 args で渡した scanoss.json が、そのコンポーネントの設定として適用されます。 SCANOSS_KEY が未設定の場合は警告を出してスキップ(exit 0)します。KEYが全員に行き渡る前から強制すると、KEYのない開発者のコミットがすべて止まり、これもまた --no-verify での回避につながるためです。KEYは .env (gitignore対象)や環境変数で渡し、リポジトリにはコミットしません。 .env を使う場合は、スクリプト内で set -a; source .env; set +a として読み込むか、 direnv などで環境変数として自動展開します。 もう1つ、運用上の原則があります。 コードスキャンでは、検出された結果をいったんすべて受け止める ことです。 前述のとおり、検出が妥当か誤検出かを判断するのは開発者です。その判断材料を取りこぼさないために、コードスキャンの段階では結果を絞り込まず、まず全部出します。コードへの混入は、最初から「このライセンスは気にしない」といった絞り込みをかけてしまうと、本来気づきたかったものまで検出される前に消えてしまいます。混入を見るスキャンは検出を抑制しない素の状態に保ち、出てきた結果を開発者が一つずつ判断する——という形にします。 コミットが止まる様子 コピーレフトライセンスを持つOSSのコードを含むファイルをステージし、コミットしようとした場合の挙動を確認します。ここでは、コピーレフトライセンス(GPL-3.0)を選択肢に持つOSSのコードをファイルに取り込んだ状態で、hookを実行しました。 SCANOSS code scan (slides)...............................................Failed - hook id: scanoss-code-slides - exit code: 1 inspect copyleft -> application/slides 1 component(s) with copyleft licenses were found. { "components": [ { "purl": "pkg:github/stuk/jszip", "licenses": [ { "spdxid": "GPL-3.0-only", "copyleft": true, "source": "component_declared" } ], "status": "pending" } ] } ::error::Copyleft policy violation in application/slides hookが Failed (exit 1)となり、コミットは作成されません。出力の exit code: 1 はhook全体の終了コードで、スクリプトが inspect copyleft の非ゼロ終了(検出時 exit 2)を受け取って返したものです。出力には「どのコンポーネントの、どのライセンスが」検出されたかが表示されます。あとは開発者がその場で判断します。取り込んだコードを別の実装に置き換えるのか、あるいは妥当な検出として正規に扱うのか。判断に必要な材料が、コミットの瞬間に手元へ出ています。 プルリクエストを発行してCIの結果を数分待ってから手元に戻して判断する、という往復が、コミットの一瞬での判断に変わります。なお、コードスキャン( scan + inspect copyleft )にかかる時間は1ファイルあたり数秒程度で(本環境での実測は約5秒)、コミット体験を壊さない範囲に収まっています。 検出されたときの対応 コミットが止まったら、まず出力のコンポーネント( purl )とライセンスを確認し、その内容に応じて対応します。対応は確認した結果で分かれます。以下は対応の例です。 確認した結果(例) 対応 表示義務だけで使えるライセンスだった(MIT 等) クレジット表示などの義務を満たしたうえで、そのまま利用する 誤検出だと判別できた(無関係なコードが偶然一致した) 誤検出として設定で許容する。「なぜ誤検出と判断したか」を記録に残す 公開義務が伴うライセンスだった(GPL 等のソース開示義務) 原則そのコードは使わず、自前で書き直す/別実装に置き換える。どうしても使うならライセンス担当に確認のうえ正規に対応する 上記は対応の例です。ライセンスの種類や配布の有無によって義務は異なるため、最終的な可否はライセンス担当に確認してください。 いずれの場合も、起点は「検出をいったん受け止めて、開発者が中身を確認する」ことです。pre-commitは、この確認と対応を、文脈が一番濃いコミットの瞬間に手元で行えるようにします。 pre-commitとCIの役割分担 最後に、pre-commitとCIの関係を整理します。両者はどちらが上位ということではなく、役割が異なる別々の仕組みです。 どちらも必要 です。 pre-commit CI 役割 判断を開発者の手元・コミット時点に前倒しし、その場で気づかせる チームの権威的・最終ゲートとして違反を確実に止める 対象 コード照合(混入の発生源) コード照合・依存の宣言突合・依存のライセンス種別 強制力 --no-verify で飛ばせる(気づきが役割) 飛ばせない(必ず通過する) pre-commitは --no-verify で誰でも飛ばせるため、これ単体では違反を確実に止める保証にはなりません。確実に止める最終ゲートはCI側に必要で、pre-commitはその判定を手元で前倒しに気づかせる役割に徹します。だからこそ、両者は同じ基準で揃えておきたいところです。pre-commitで通ったものがCIで落ちる(あるいはその逆)が頻発すると、pre-commitは信用されなくなり、 --no-verify で外されて形骸化します。ポリシー(コピーレフトの扱いなど)はCIと揃え、pre-commitはCIの判定を手元で「予習」する位置づけにします。 何をどちらに担わせるかも切り分けます。混入(コードへの取り込み)は発生源で止めたいので、pre-commitで前倒しに見ます。一方、依存をどこまで許容するか(弱コピーレフトの依存を認めるか等)の判断は、承認の記録や台帳を伴うため、CI側に集約します。pre-commitのコードスキャンは検出をいったんすべて出して開発者に委ねる、というのも、この切り分けの裏返しです。 役割を混同して片方を省く(「pre-commitを入れたからCIは不要」「CIがあるから手元は不要」)と、どちらの利点も失われます。pre-commitで手元の混入に気づき、CIで確実に止める。この2段構えが基本です。 付録:完全なhook構成(delta+モノレポ対応) パート2では処理の核( scan → inspect copyleft )だけを示しました。ここでは、それをKEY未設定時のスキップ・除外・終了コードゲートまで含めて1本にまとめた、実際に動くスクリプトの全体を示します(copyleft検出に絞った構成。scanoss-py 1.52.1 で動作確認)。これは、パート2の .pre-commit-config.yaml でコンポーネントごとに登録した各hookが呼ぶ本体です。両者を組み合わせて動きます。 振り分け(どのコンポーネントを見るか)はpre-commitの files: が担うので、スクリプトはコンポーネントを意識しません。第1引数で scanoss.json のパスを受け取り、残りの引数で渡されたstagedファイルを、その設定でスキャンするだけです。 #!/usr/bin/env bash # SCANOSS code scan (delta): 渡された staged コードを、指定の scanoss.json で copyleft 検査する。 # 対象コンポーネントの振り分け(発火条件)は .pre-commit-config.yaml の files: が担い、 # 使う scanoss.json は args で渡される。$1=scanoss.json のパス / $2..=staged ファイル。 set -euo pipefail settings="$1"; shift # .env があれば読み込む(SCANOSS_KEY をここに置く運用を想定。.env は gitignore 対象) if [[ -f .env ]]; then set -a; source .env; set +a fi # KEY 未設定なら止めずにスキップ(KEY 未配布の開発者のコミットを止めないため) if [[ -z "${SCANOSS_KEY:-}" ]]; then echo "::warning::SCANOSS_KEY 未設定のため code scan をスキップします" >&2 exit 0 fi # 渡された staged ファイルが無ければ何もしない [[ $# -eq 0 ]] && exit 0 # --files には scanoss.json の skip.patterns が効かないため、 # 生成物・vendored パスはここで除外する。 declare -a files=() for f in "$@"; do case "$f" in */node_modules/*|*/dist/*|*/archive/*) continue ;; *) files+=("$f") ;; esac done [[ ${#files[@]} -eq 0 ]] && exit 0 result=$(mktemp); trap 'rm -f "$result"' EXIT # staged ファイルだけをスキャン(delta)。Premium KEY 経路。 if ! scanoss-py scan --files "${files[@]}" \ --settings "$settings" \ --key "$SCANOSS_KEY" \ --output "$result"; then echo "::error::scan failed ($settings)" >&2 exit 1 fi # copyleft 検出時は非ゼロ終了(1.52.1 では exit 2)。終了コードをそのままゲートにする。 if ! scanoss-py inspect copyleft --input "$result"; then echo "::error::copyleft policy violation ($settings)" >&2 exit 1 fi .pre-commit-config.yaml (パート2)とこのスクリプトで、本文で説明した要素が一通りそろいます。 本文の主張 どこで満たすか delta(変更ファイル単位) pass_filenames: true で渡る staged ファイルだけを --files に渡す モノレポ対応 コンポーネントごとに hook を立て、 files: で振り分け・ args で各 scanoss.json を渡す( .pre-commit-config.yaml 側) --no-verify 化を防ぐ軽さ files: で発火条件を限定し、KEY 未設定は warning→skip、生成物パスは除外する 検出をいったん受け止める copyleft 用 hook には除外(特定ライセンス/PURLの許容)を持ち込まず、素のスキャンに保つ コミットを止める inspect copyleft の非ゼロ終了(exit 2)を拾い、スクリプト全体を非ゼロで終了 まとめ SCANOSSのソースコードスキャンをpre-commitに組み込み、OSS由来コードの混入をコミット時点で検出する構成を紹介しました。 項目 ポイント pre-commitの目的 スキャン結果を判断するのは開発者。その判断を、文脈が濃いコミットの瞬間・手元で引き取れるようにする 検出対象 コード照合(混入の発生源)に絞る。依存の突合・ライセンス種別はCIに残す 公式hookとの違い 公式hookは未識別(undeclared)専用。コピーレフト判定とコンポーネント別設定が必要なら自作hook 軽量化 delta(変更ファイル単位)スキャン、発火条件の限定、KEY未設定時のスキップで --no-verify 化を防ぐ 検出の扱い コードスキャンは結果を絞り込まず、いったんすべて受け止めて開発者が判断する CIとの関係 pre-commitとCIは役割の異なる両輪。同じ基準で揃え、どちらも残す pre-commitで手元の混入に気づき、CIで確実に止める。この2段構えで、ローカルからCIまでSCANOSSの運用が一通りつながります。 参考資料 関連リンク SCANOSS pre-commit hooks(GitHub リポジトリ) scanoss-py GitHub リポジトリ SCANOSS 公式ドキュメント pre-commit 公式ドキュメント ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post SCANOSS pre-commit:コードスキャンでコピーレフト混入を検出する first appeared on SIOS Tech Lab .
こんにちは!サイオステクノロジーの貝野です。 今回は、keycloak を用いて簡単な SSO 認証を行うまでの手順をご紹介します。 keycloak を使うと様々なアプリケーションとやサービスと連携することができますが、今回は簡単なテストアプリを用いて SSO 認証を実現してみます。 環境構成・インストール 下記の構成で keycloak の構築を行いました。 テストアプリは python OS:RHEL9 (AWS 上) keycloak のバージョン:26.6.63 (検証時点での最新バージョン) Java のバージョン:OpenJDK21 またインストールの手順については、過去の記事 ( keycloak インストール時につまずいた話 ) にてご紹介しているため、こちらも併せて見ていただければと思います。 簡単な手順としては、下記の通りとなります。 Java 関連パッケージ (openjdk および openjdk-devel) をインストール https://www.keycloak.org/downloads から keycloak (tar.gz 形式) をダウンロード ダウンロードしたパッケージを、任意のディレクトリ配下に展開 # tar -xvf keycloak-26.6.63.tar.gz -C bin/kc.sh bootstrap-admin user で管理者アカウント作成 bin/kc.sh start-dev を実行 http://[ホスト名もしくは IP アドレス]:8080/admin へアクセス ※4 で作成したユーザ名とパスワードでログイン レルムの作成 Keycloak では、レルムという単位でユーザ、アプリケーションを管理します。 デフォルトでは master というレルムがありますが、今回はテストアプリ用に新しいレルムを作成します。 画面左側より Manager realms → Create realm を押下します。 Realm name に新しく作成するレルム名を入力し、Create を押下します。 1 の画面に戻るため、今作成したレルムが追加されていることを確認します。 ユーザの作成 新しく作成されたレルムにはユーザが存在しません。 そこで、新しくユーザを作成します。 画面左側より Users → Create new user を押下します。 ※左上の Current realm が、先ほど作成したレルムになっているか確認して下さい。 Username を入力し、Create を押下します。 保存が完了したら、下記の様な画面が表示されます。 次に、Credentials タブを押下します。 Set password を押下します。 パスワードを設定し、Save を押下します。 keycloak の設定 (クライアントの作成) テストアプリ用に新しいクライアントを作成します。 画面左側より Clients → Create client を押下します。 ※左上の Current realm が、先ほど作成したレルムになっているか確認して下さい。 Client ID、Name (任意) を入力し、Next を押下します。 Client Authentication を ON にし、Next を押下します (テストアプリが keycloak に対しクライアント認証を行うために必要な設定となります)。 Valid redirect URIs、Web origins をそれぞれ下記のように入力し、Save を押下します。 ・Valid redirect URIs: http://xxx.xxx.xxx.xxx:5000/callback (認証時のリダイレクト先 URL) ・Web origins: http://xxx.xxx.xxx.xxx:5000 (keycloak へのリクエストを許可する URL) 保存が完了したら、下記の様な画面が表示されます。 【ここからテストアプリ用の作業】 次に、Credentials タブを押下します。 Client Secret の内容をコピーします。 デフォルトでは非表示となっているため、目のマークを押下して内容を表示します (コピーした内容は、後ほどテストアプリで使用します)。 Credentials テスト用アプリ (app.py) の作成 app.py というファイルを作成し、下記の内容を記述します。 ★行を、任意の内容に変更してください。 from flask import Flask, url_for, session, redirect from authlib.integrations.flask_client import OAuth app = Flask(__name__) app.secret_key = 'random-secret-string' # --- 設定項目 --- KEYCLOAK_IP = "xxx.xxx.xxx.xxx" ★IP アドレス CLIENT_ID = "testapp1" ★アプリの名称 CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxxxx" ★コピーした Client secret の内容 REALM = "test realm1" ★作成したレルム名 # ---------------- oauth = OAuth(app) keycloak = oauth.register( name='keycloak', client_id=CLIENT_ID, client_secret=CLIENT_SECRET, server_metadata_url=f'http://{KEYCLOAK_IP}:8080/realms/{REALM}/.well-known/openid-configuration', client_kwargs={'scope': 'openid profile email'}, ) @app.route('/') def index(): user = session.get('user') if user: return f'Hello, {user["name"]} !! ログアウト ' return 'テストアプリ Keycloakでログイン ' @app.route('/login') def login(): redirect_uri = url_for('auth', _external=True) return keycloak.authorize_redirect(redirect_uri) @app.route('/callback') def auth(): token = keycloak.authorize_access_token() session['user'] = token.get('userinfo') return redirect('/') @app.route('/logout') def logout(): session.pop('user', None) return redirect('/') if __name__ == '__main__': app.run(port=5000) 動作確認 ターミナルで、上記で作成したアプリを起動します。 # python app.py * Serving Flask app 'app' * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://xxx.xxx.xxx.xxx:5000 Press CTRL+C to quit http://xxx.xxx.xxx.xxx:5000 と入力すると、下記の画面が表示されます。 ※xxx.xxx.xxx.xxx には IP アドレス等を入力してください。 Username or email、Password に、先ほど作成したユーザ、パスワードをそれぞれ入力し、 Sign In を押下します。 ※初回ログイン時、パスワード変更の画面が表示されます。 この動作はユーザ作成時の設定によって異なる場合がありますが、この点についての詳細は別の記事で説明させていただきます。 同様に、ユーザのアカウント情報をアップデートする旨の画面が表示されるため、Email、First name、Last name をそれぞれ入力し、Submit を押下します。 下記の「Login success!」の画面が表示されれば、認証成功です! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Keycloak を用いた簡単な SSO 認証 first appeared on SIOS Tech Lab .
今号では Linux におけるターミナル操作で、過去に実行したコマンドの履歴を管理する history コマンドの概要、基本的な使い方、および表示件数の制限方法について紹介します。 1. history コマンドの概要 history コマンドは、これまでにターミナルで入力・実行したコマンドの履歴を一覧で表示したり、再利用したりするための機能です。 このコマンドで以前実行したコマンドの内容を振り返ることができるほか、長いコマンドや複雑なコマンドを再度タイピングすることなく、簡単な操作で呼び出してもう一度実行することができます。 2. 基本の使い方 履歴の一覧表示 引数を付けずに history と入力して実行すると、過去のコマンドが履歴番号とともに一覧で表示されます。 $ history ... 1012 cat /var/log/httpd/access_log 1013 vi /etc/hosts 1014 systemctl restart httpd 「!」を使用した履歴の再実行 「 ! 」に続けて履歴番号や文字を入力することで、過去のコマンドを再入力することなく実行できます。 ・履歴番号で実行(!n) history コマンドで確認した左側の番号を指定します。履歴にある番号 1014 のコマンドをもう一度実行したい場合は、以下のように入力します。 $ !1014 systemctl restart httpd ・先頭の文字で実行(!string) 指定した文字列から始まる、直近のコマンドを検索して実行します。 $ !sys systemctl restart httpd ・特定文字列を含む履歴の実行(!?string[?]) コマンドの先頭に限らず、指定した文字列を含んでいる直近のコマンドを検索して実行します。 $ !?restart? systemctl restart httpd grep コマンドによる履歴の絞り込み history コマンドの出力は件数が多いため、特定のコマンドを探し出す際は、出力を絞り込む grep コマンド とパイプ( | )を組み合わせる手法が広く用いられます。 $ history | grep ssh 452 ssh user@192.168.1.10 512 ssh -i ~/.ssh/id_rsa admin@example.com 3. 表示件数の制限 historyコマンドの末尾に数値を指定することで、直近の指定件数のみを出力することができます。 履歴の全体を表示させると確認しづらくなるため、直近数件の履歴だけを手早く確認したい場合に有効な指定方法です。 $ history 3 1013 vi /etc/hosts 1014 systemctl restart httpd 1015 history 3 historyコマンドの基本的な仕様と件数の制限方法を把握しておくことで、過去の操作を効率的に再利用し、ターミナルでの入力の手間を省くことが可能になります。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!履歴を表示する history コマンド first appeared on SIOS Tech Lab .
こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 Linus Torvalds氏によって最新カーネル「Linux 7.1」の安定版がリリースされました。 Linux 7.1安定版リリース:新NTFSドライバの実装と次世代Intel・AMDハードウェア向け最適化 https://xenospectrum.com/linux-7-1-stable-release-ntfs-performance/ The Linux Foundation Japanによる「2026年技術系人材の現状レポート」が公開されました。 2026年技術系人材の現状レポートを公開 https://www.linuxfoundation.org/ja-jp/news/japanese-version-of-2026-state-of-tech-talent-report-is-now-live エンタープライズLinux大手のSUSEが、7月に東京でカンファレンスを開催すると発表しました。 【SUSE Summit 2026 Tokyo 開催】- AI時代のデジタルレジリエンスと オープンソース戦略を議論する) https://prtimes.jp/main/html/rd/p/000000034.000062310.html ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2026年6月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech Lab .
こんにちは!普段はClaude Code関連のブログをよく書いている、エンジニアの龍ちゃんです。 今日はいつもと少し趣向を変えて、サマーインターンのお知らせをさせてください。 サマーインターンとは 今回の対象は、 28卒(2028年3月卒)・理系・プログラミング経験1年以上の学生 さんです。 平日10日間のハッカソン形式のインターンで、時給1,500円、交通費と宿泊費は全額支給となっています。遠方からでも参加しやすい設計になっているのがポイントです。 実は、社内のインターン担当の方から「Tech Lab. のブログでも告知してもらえないかな?」と相談を受けたんですよね。 現在、大学経由でも参加者を集めているのですが、もう少し仲間が増えるとうれしいな、という状況のようです。 そこで、ただ募集ページのリンクを貼るのではなく、現場でコードを書いているエンジニアの目線から「ここが良いと思う」というポイントを正直にお伝えしたほうが伝わると思い、この記事を書いています。 最近、採用面接や入ってきたばかりの新卒社員と話す機会がよくあるのですが、皆さん学生の頃の自分よりはるかに能動的で、驚かされることが多いです。元学生として思うのですが、就活ってどうしても企業と学生がお互いを遠目に探り合う感じになりがちですよね。 「自分に何が足りないのか」「企業ならではの視点」「この会社が自分に合うのか」といったことは、外から眺めているだけではなかなか掴めません。 その点、インターンは一度中に入ってしまえば会社の雰囲気を肌で感じることができます。 外から探り合うより、よっぽど手っ取り早くて良い経験になると思っています。だからこそ、いつもブログを読んでくれている学生さんにぜひ届いてほしいです。 どんなインターン? 正式名称は 「ITエンジニアの仕事がわかる!アイデアを形にする10daysインターンシップ」 です。 その名の通り、平日10日間、ハッカソン形式で手を動かしながら会社のことも知れる構成になっています。 中身を整理するとこんな感じです。 プログラム内容ざっくり言うとハッカソン形式の企画開発チームで企画から手を動かして開発します。インターンのメインです。 • 事業紹介 サイオスがどのようなビジネスを展開している会社なのかを知ることができます。 • AI活用勉強会 開発現場で生成AIをどう活用しているか。私が現在社内で担当している領域です。若手エンジニア座談会年次の近い先輩に、入社前のリアルな話を聞くことができます。 • ベテランエンジニアからのフィードバック 作った成果物に対して、現役のベテランエンジニアからコメントをもらえます。 現場の人間から見て「ここがいいと思う」 プログラムの全部が良いのですが、現場目線で特に魅力的に感じるポイントを3つだけ紹介します。 • ベテランエンジニアからのフィードバック 自分の作ったものに対して、経験豊富なエンジニアから具体的な指摘が入る環境は、独学だとなかなか作れません。せっかくの機会なので、普段気になっていることや行き詰まりがちなポイントを、ベテランに存分にぶつけてみてほしいなと思います。 • AI活用勉強会 手前味噌になりますが、私がまさに社内で生成AIの活用を推進している立場です。現在の開発現場でAIをどう使っているか、生で見られる機会はまだ少ないと思うので、興味がある方には面白い内容になるはずです。 • 若手エンジニア座談会 会社に入る前に「中の温度感」を知るのが一番難しいところです。年次の近い先輩に直接質問できる場は、求人票を何度も読むよりも参考になります。「入社前のリアル」が聞けるのは、かなり貴重な時間だと思います。 開催概要 日程や待遇などの詳細は以下の通りです。 日程 第1期 2026年7月27日(月)〜8月7日(金) 第2期 2026年8月17日(月)〜8月28日(金) 就業時間 平日 10:00〜17:00(休憩 12:00〜13:00) 開催形式 リモート / 出社のハイブリッド(初日と最終日は出社予定) 待遇 時給1,500円、交通費・宿泊費は全額支給 対象 2028年3月卒業予定の方(理系学部、情報系だとなお良し。プログラミング経験1年以上) 宿泊費も支給されるため、遠方にお住まいの方も参加しやすい環境になっています。 応募はこちらから ということで、サイオステクノロジーの28卒サマーインターンの告知でした。 会社への理解を深めつつ、実際に手を動かしてスキルも磨ける夏になると思います。 少しでも気になったら、ぜひ詳細ページを覗いてみてください。ご応募をお待ちしています! ▼ 詳細はこちら(プログラムや待遇のフルバージョン) https://sios.jp/recruit/info/fresh.html ▼ エントリーはこちらのフォームから https://forms.gle/kZqik2pbzDZPNeUn7 【カジュアル面談のご案内】 「いきなり応募するのは少しハードルが高い」「対象に当てはまるか不安」という方には、インターンとは別に、エンジニアと気軽に話せるカジュアル面談(オンライン・30分前後)もご用意しています。現場の社員に直接聞いてみたいことがある方は、ぜひこちらからお申し込みください。 ▼ カジュアル面談の申込はこちら https://mk.sios.jp/casualvisit_entry.html それでは、また次回の記事でお会いしましょう! The post 現場エンジニアがお勧めする、サイオスの28卒サマーインターン first appeared on SIOS Tech Lab .
「この画面はなんとなく使いにくい」「入力箇所に迷う」といったユーザーの不満を探ると「人間の認知の仕組み」を無視している場合があります。 本記事では、人間の視覚的な認知法則を体系化した「ゲシュタルト心理学」をベースに、ユーザーが直感的に操作できる、画面設計のロジックを、入力フォームを例に解説します。 ユーザーが直感的に利用できる画面デザインには「論理」があります。 フロントエンドエンジニアやプロダクトマネージャーの方にもぜひ知っていただきたい内容です。 ゲシュタルト心理学とは? 「ゲシュタルト(Gestalt)」はドイツ語で「形」「姿」「形態」を表す言葉です。 ゲシュタルト心理学は、20世紀初頭にドイツで生まれた心理学で、簡単に言うと、「人間は物事をバラバラのパーツとしてではなく、まとまった一つの全体(ゲシュタルト)として認識する」という法則を説いたものです。 これは「プレグナンツの法則」とも呼ばれ、私たちの脳が複雑な情報を可能な限りシンプルで秩序ある形として理解しようとする働きを指します。 人間の脳は、無意識のうちに視覚情報を整理し、グループ化しようとします。この脳の特性(錯覚)をUIデザインに逆算して組み込むことで、「説明書を読まなくても使い方や関係性がわかる画面」を作ることができます。 今回は、フォーム設計に直結する3つの法則を紹介します。 1. 「近接の法則」で関係性を明確にする 近接の法則とは、「物理的に距離が近いもの同士は、同じグループとして認識される」という法則です。基本でありながら抜けやすいのがこの「余白(マージン)」の扱いです。 わかりにくいUI 「名前」というラベル、その入力欄、次の「メールアドレス」というラベルが、すべて同じ8pxの等間隔で並んでいる。 人間の脳は、どれとどれがペアなのかを一瞬で判断できず、視線が迷います。 わかりやすいUI ラベル(項目名)と入力フィールドの関係性を視覚的に明示するために、余白にメリハリをつけます。 ラベルと入力欄の余白: 4px〜8px(近づける) 次の項目との余白: 24px〜32px(離す) このように「ペアとなる要素の距離」を「他の要素との距離」よりも明らかに短くすることで、ユーザーは無意識に「このラベルはこの入力欄に対するものだ」と認識でき、入力スピードが向上します。 2. 「類同の法則」で操作の期待値を揃える 類同の法則とは、「形、色、大きさなどの視覚的特徴が似ているものは、同じ機能や性質を持つと認識される」という法則です。 わかりにくいUI テキスト入力欄(input)と、ドロップダウン(select)の枠線のデザインや背景色が違う。 わかりやすいUI ユーザーの「これは入力できる場所だ」というメンタルモデルを裏切らないよう、要素のスタイルを統一します。 入力可能なフィールドは、角丸(border-radius)や枠線の色、フォーカス時のハイライト色(outline)をシステム全体で統一します。 同様の機能を持つものは同じ見た目にし、違う機能を持つものは明確に見た目を変えることで、ユーザーは画面内のルールを瞬時に学習できます。 3. 「閉合の法則 / 共通領域の法則」で複雑さを軽減する 閉合の法則や共通領域の法則は、「線で囲まれたり、同じ背景色の上に配置されたりした要素は、ひとつのグループとして認識される」という法則です。 入力項目が数十個に及ぶ巨大なフォームで非常に有効です。 わかりにくいUI 「会社情報」「担当者情報」「請求先情報」などの異なるカテゴリの入力項目が、仕切りもなく延々と縦に羅列されている。 ユーザーは情報のゴールが見えず、心理的ハードルが高まり離脱に繋がります。 わかりやすいUI 情報を意味のあるグループに分け、視覚的な境界線を設けます。 カードUIの活用: 「会社情報」で1つの白いカード、「請求先情報」で別のカードにし、背景(グレー系)の上に配置します。 セクション区切り: カードを使わない場合でも、セクション間に罫線(ボーダー)を引き、見出し(h2やh3)を大きく配置することで情報のまとまりを作ります。 「ここからここまでがセットだな」と視覚的に区切られているだけで、ユーザーが処理しなければならない情報の認知負荷は下がります。 おわりに:デザインは「エンジニアリング」できる 「デザインが垢抜けない」「使いにくい」との評価を受けて、つい「色」や「装飾」といった表面的な要素に目が行きがちですが、UIにおける使いやすさは、今回ご紹介したような「余白」と「配置」の論理的な設計で、ほとんど決まります。 次に入力フォームを実装する際に、「この余白はどう認識されるか?」という視点を持ってみてください。それだけで、プロダクトのUXは向上するはずです。 余談 ちなみに、上記の「複数の要素をまとまりとして捉える」のがゲシュタルト心理学の基本ですが、それとは反対に、特定の対象を凝視し続けることで「まとまり」が見えなくなり、バラバラな部分としてしか認識できなくなる現象があります。これが「ゲシュタルト崩壊」です。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post ゲシュタルト心理学で紐解く、ユーザーが「迷わない」画面設計 first appeared on SIOS Tech Lab .