karasu 週報:エージェントが録って見るデモ動画、月次リリーストレインの初便

  • #karasu
  • #ai
  • #claude-code
  • #architecture

karasu は、C4 モデルに着想を得た、システムアーキテクチャを記述するテキストモデリング言語です。 .krs ファイルにシステムを書くと、karasu がシステム構成・デプロイ構成・チーム構成のビューを描きます。 コードは一行も手で書いておらず、すべて Claude Code 経由で開発しています。

週報の 4 回目です。 対象は 10 月 25 日から 31 日までにマージした変更です。

今週の数字: マージした PR は 70 件(うち Dependabot が 12 件)、ADR として記録した決定は 18 件です。 ADR のうち 15 件は ADR だけの PR で、3 件は実装の PR の中で足しました。 10 月 25 日に月次のリリーストレインが初めて出て、karasu 0.10.0 と VS Code 拡張 0.5.0 を届けました。

ハイライト 1:デモ動画を録って、エージェントがコマで見る

先週の週報に載せた 2 本のデモ動画は、エージェントが録ったものです。 そのためのスキル(demo-video)と pnpm demo-video:record <scenario> --lang en|ja を足しました。

仕組みは次のとおりです。

  1. Xvfb の仮想ディスプレイの上で、本物の karasu app(karasu serve 経由)か、拡張を入れた VS Code を動かす。
  2. シナリオがポインタを動かし、クリックし、エディタに入力する。そのあいだ ffmpeg が画面全体を録る。
  3. 冒頭と末尾にタイトルカードをつなぎ、各場面にキャプションを重ねて、1920×1080 の MP4 にする。

キャプションとタイトルカードは言語ごとに持っていて、1 つのシナリオから英語版と日本語版の 2 本ができます。 VS Code は CDP(Chrome DevTools Protocol)で操作しています。 VS Code 拡張の UI テスト用のツールである ExTester は、arm64 の devcontainer では ChromeDriver が動かないため使っていません。

動画を作ったら、エージェント自身が中身を見ます。 エージェントは Read で画像を開けますが、動画は開けません。 そこで、録画を 3 秒ごとのコマに割って 1 枚の画像に並べ(contact sheet)、それを Read で開きます。 スキルの完了条件には「動画の中身を見ないまま『できた』と報告しない」と書きました。

下は、先週の serve-import の動画(71 秒)を 3 秒ごとに割ったものです。

先週のデモ動画 serve-import を 3 秒ごとに割ったコマ割り。24 コマが 6 列 4 段に並び、途中でカードの色が青から緑に変わっている

2 段目の 1 枚目まで青かったカードが、2 段目の 2 枚目では緑になっています。 同じコマの右上のコードパネルには、theme.krs.style の色を書き換えた差分が出ています。 緑のカードのコマを 1 枚だけ見ても、緑で描かれていることしか分かりません。 並べると、スタイルシートを保存したら描き直された、という変化が見えます。 キャプションの文言や、Share のダイアログから公開アプリが開くまでの流れも、この 1 枚から読めます。

スキルには、録っていて踏んだ落とし穴の表も入っています。 「動画の末尾に『変更を保存しますか?』が映る」「エッジを右クリックすると別のエッジのメニューが出る」「翻訳バーが出る」といった行が並びます。 どれも、コマンドの終了コードやログには出ず、画面にだけ出る症状です。 保存のダイアログは、VS Code を閉じるときに出ます。 閉じる直前にスクリーンショットを撮っても、そこには映りません。

fukurou の側でも、同じやり方を入れました。 この週報を書くリポジトリの devcontainer に ffmpeg を入れ、週報の手順を書き換えました。 これまでは、セッションでは動画の中身を見られないので、本文の説明が合っているかを私が確かめていました。 今は、セッションがコマ割りを作って開き、自分で確かめます。 上の図も、この手順で作ったものです。 振り返ると、それまで確かめられなかったのは、ffmpeg が入っていなかったからにすぎませんでした。

ここまで「エージェントが動画を見る」と書いてきましたが、実際に開いているのは静止画 1 枚です。 ここで言う「見る」は、時間を 1 枚の画像に畳んだものを読むことです。 それでも、スクリーンショット 1 枚では分からない、動画の中で起きた変化まで読めます。 不具合の画面録画も同じようにコマで読ませれば、再現や原因の見当づけに使えそうです。 ただ、karasu の不具合を動画から見つけて直した例は、まだありません。 次に画面まわりの不具合が来たら、録画をエージェントに渡すところから試します。

ハイライト 2:月次リリーストレインの初便

10 月 25 日に、月次のリリーストレインが初めて出ました。 月の最終日曜に、未リリースの変更をまとめて出す仕組みです。 その日の朝に、版を上げたブランチとトラッキング Issue が自動で立ちます。 私は Issue のリンクから PR を開き、CHANGELOG を読んでマージするだけです。

パッケージ 前の版 今回の版
karasu(CLI) 0.9.0 0.10.0
@karasu-tools/core 0.4.0 0.4.1
karasu-vscode(VS Code 拡張) 0.4.0 0.5.0
karasu-skills 0.2.0 0.2.1

溜まっていた changeset 16 件をまとめて出しています。 初便は途中で止まらず、npm と VS Code Marketplace まで届きました。 Marketplace への公開がトレインに乗ったのは、今回が初めてです。 先週の週報のハイライト 2 件(karasu serve の import 解決と、VS Code のプレビューを app と同じビューアにした件)も、この便で届きました。 .krs の言語仕様は変わっていません。 ただ、.krs.style の中で、どの名前の一部でもない - や ASCII 以外の文字は、これまで黙って捨てていました。 今はエラーにします。 書いたつもりで効いていなかった記述に、気づけるようになります。

VS Code 拡張には、毎週のプレリリースを足しました。 月 1 回の便だけだと、main に入った変更が拡張に届くまで最長で 1 か月かかります。 そこで、毎週水曜に main の先頭から拡張をビルドし、Marketplace のプレリリースチャネルへ出すことにしました。 版は <安定版の major>.<minor>.<YYYYMMDDNN> の形で、日付が入ります。 前回の版と中身が同じ週は出しません。

「中身が同じ」とは、.vsix に入るファイルが、package.json の版の番号を除いてすべて一致することです。 これを判定できるかは、先に確かめました。 Marketplace の 0.5.0 の .vsix と、同じコミットから手元で作った .vsix を比べたところ、THIRD_PARTY_NOTICES.md 以外のファイルはすべて一致しました。 この 1 ファイルも、同梱しているバイナリのプラットフォーム(手元が linux-arm64、CI が linux-x64)が違うだけです。 CI どうしで作れば一致するはずです。 プレリリース版は、拡張のページで「Switch to Pre-Release Version」を押すと使えます。 初回は 11 月 4 日(水)の予定です。

そのほかの変更

エッジのラベルが、フレームの枠線に乗らなくなりました。 ドリルダウンやグループの枠線をラベルの障害物に加えました。 4 つのモデルと examples/en の 5,061 ケースで測ると、枠線が横切るラベルは 132 から 0 に、枠の反対側に押し出されたラベルは 57 から 0 になりました。 代わりに、auto で省略されるラベルが 1,745 から 1,784 に増えています。

通れる経路があるのに、エッジがカードを突き抜けていた件を直しました。 同じ行にあるカード同士のエッジです。 原因は経路ではなく、カードからエッジが出る位置の候補(5 つ)がすべて埋まっていたことでした。 最後の手段として、辺全体から位置を探し直すようにしています。 カードを突き抜けるエッジは 4 本から 1 本に減りました。

スタイルの診断と描画が食い違わなくなりました。 .krs.style の値の範囲が 3 か所に書かれていて、互いに食い違っていました。 診断は「無視した」と言うのに、値がクランプされたり描かれたりしていたのです。 範囲はスキーマ 1 か所にまとめ、診断で拒否した宣言は描画でも使わないようにしました。 そのぶん、描画の結果も変わります。 範囲外の label-position はクランプせずに捨てます。 単位のない 0 以外の長さ、誤った単位、不正な hex、大文字の違う色名(White)も描きません。 color: ; のような空の値では karasu render が失敗します。 この変更は次のトレイン(11 月 29 日)で届きます。

karasu serve がシンボリックリンクの先を返していました。 配信するディレクトリに、外を指すシンボリックリンク(notes.krs -> ~/.ssh/id_rsa など)を置いたとします。 すると、/api/file/:name がリンクの指す先の中身を返していました。 リリース済みの 0.10.0 にも含まれています。 今はパスを実体まで解決し、配信するディレクトリの外なら返しません。 /api/watch が付けていた Access-Control-Allow-Origin: * も外しました。 修正は次のトレインで届きます。 それまでは、外を指すシンボリックリンクがあるディレクトリで karasu serve を動かさないのが安全です。

細かな変更。 analyze() が受け取ったモデルに循環の印を書き込んでいて、同じモデルが analyze() の前後で違って描かれていました。今は循環をモデルを組み立てるときに求め、analyze() は読むだけです。 パッケージのソースに 1 ファイル 600 行の上限を設けました。超えている 33 ファイルは今の行数で止め、縮んだら上限も下げます。svg-renderer.ts(2,965 行)、layout.ts(1,719 行)、view-extract.ts(2,042 行)の分割を始めています。 GitHub Actions のバージョンの固定を、actions.lock(10 月 28 日に GA)へ移しました。ワークフローが間接的に使うアクションも固定できます。

今週のエージェント開発

リスクの段階でレビューの深さを変える仕組みが動き始めました。 先週「設計はマージ済み、実装はこれから」と書いたもので、10 月 27 日に入りました。 PR は、変更が触ったパスから 4 つの段階に分かれます。

  • High: レビューの仕組みそのもの、CI、ロックファイルなどに触れる変更である。CodeRabbit が Critical と Major の指摘がなくなるまでレビューし、私が差分を読む。
  • Design: 設計文書である。draft から外したときに CodeRabbit が 1 回だけレビューし、私も読む。
  • Medium: CodeRabbit は回さない。ローカルのレビューの記録を見て、私が判断する。
  • Low: ADR の追加や、テストを足すだけの変更である。ローカルのレビューの記録と CI が通れば、私は読まずにマージする。

High 以外では、PR の本文にローカルのレビューの記録を書きます。 誰がレビューしたか、指摘ごとに直したか見送ったかを表にしたものです。 必須のチェックが、この記録が PR の最新のコミットに対するものかを確かめます。

今週閉じた PR は 76 件で、うち 70 件をマージしました(マージ率 92%)。 段階のラベルが付いてマージした 50 件の内訳は、High が 18 件、Design が 10 件、Medium が 12 件、Low が 10 件です。 Low の 10 件は私が差分を読まずにマージしており、自動マージ率は 20% です。 10 件のうち 9 件が ADR の PR で、残る 1 件はテスト観点の月次の見直しです。 9 件は、draft から外してから 3 分以内にマージされました。 Medium と Low の 22 件は、CodeRabbit を通さずにマージしています。

High が多いのは、今週の変更の多くがレビューやプロセスの仕組みそのものに触れたからです。 仕組みに触れる変更は High に入るように決めてあります。 ふだんの開発で段階がどう分かれるかは、もう何週か数えないと分かりません。

数えていると、High なのに CodeRabbit が一度もレビューしていない PR が 4 件ありました。 取りこぼしなのか、理由があって外したのかは、まだ確かめていません。 次の週報までに調べます。

「コードを読まない」をめぐる話を、この 1 週間でいくつも見かけました。

私はもともと、業務で開発するシステムのコードを全部読み続けてはいませんでした。 レビューでも、周辺のコードまで合わせて読むことはそれほどしていません。 AI を使うようになってからは、周辺の状況を AI に確かめさせながら、気になる箇所を深く読んでいます。 気にならない箇所は、さらっと読むようになりました。 そうできるのは、AI を優秀な業務委託と同じくらい信頼しているからだと考えています。

ここで言う信頼は、ミノ駆動さんの「中身を気にせず使える」に近いものです。 ただ、私が整えたいのはコードの中身より、そのコードが書かれる土台です。 優秀な業務委託の人でも、チームの決めごとを知らず、手順を守らなければ、書いたコードは気になる箇所ばかりになります。 エージェントも同じです。 書き始める時点で迷わない材料が届き、書く過程で手順が守られていれば、読む量を減らせるはずです。 karasu では、この 2 つを次のように整えています。

書く前:エージェントが迷う余地を減らす。 karasu では、大きな変更は設計文書を先にマージしてから実装します。 今週のリスク段階(#3358 → #3365)も、このあと書く docs/process.md の分割(#3394 → #3402)も、この順です。 過去に下した決定は ADR に残っていて、設計の前に確かめる手順がスキルに入っています。

そのうえで、読む量そのものを絞っています。 docs/process.md は、すべてのセッションが最初に全文を読む開発手順書です。 これが 46 KB まで膨らんでいました。 全部を毎回読ませると、そのときに要る手順がほかの手順に埋もれます。 そこで、常に要る流れだけを残して 14,876 バイトにし、残りを docs/process/ の下のトピック別のファイルに移しました。 手順書には索引を置き、各ファイルを「いつ読むか」を書いています。 たとえばレビューの段階と記録の書き方は、PR を draft から外す手前の手順で読みます。 必要になった時点で読ませるこのやり方(Progressive Disclosure)が今のモデルにどこまで届くかは、9 月の記事で測りました。 そのときは、ファイルを開くと読み込まれるパス指定のルールはほとんど発火せず、スキルに書いた手順は届きました。 今回の分割も、ファイルを開いたときの読み込みには頼っていません。 常に読む手順書の中で、どの段階でどのファイルを読むかを名指ししています。

過程:「お願い」を仕組みに移す。 docs/process.md は、以前にも 12.7 KB まで減らしたことがあります。 それが 46 KB に戻っていました。 手で整えても、手順を足していくうちに元へ戻りました。 今回は pnpm lint:process-index を足し、索引とファイルの対応、リンク、手順書のサイズを lefthook と CI で確かめるようにしました。

先週「PR を出したところ」と書いた、ローカルのレビュー前の gh pr ready を拒否する仕組みもマージしました。 最初は Claude Code の hook で、実行しようとしたコマンドの文字列を調べていました。 ところが、CodeRabbit のレビューのたびに抜け道が見つかりました。 heredoc のあとに書いた gh、env -u や timeout -s を前に付けた gh、eval や xargs を通した gh です。 コマンドの文字列からは、最終的に何が実行されるかを読み切れません。 CodeRabbit のレビューが 20 回回ったところで、セッションの PATH の先頭に gh のラッパーを置き、gh が受け取った引数そのもので判定する形に作り直しました。

手順書に書いても守られなかったものを、lint や gh のラッパー、必須のチェックに移しています。 渡邉さんの言う、「お願い」から機械的な判定への移し替えです。

Low の自動マージは、この 2 つの土台の上に乗っています。 来週からも、マージ率と自動マージ率を数えていきます。 読まずにマージした PR から、あとで不具合が見つかったかも追うつもりです。 「読まなくて済む土台」については、週報とは別に記事を書きます。

次回予告

  • 11 月 4 日(水)に、VS Code 拡張の毎週のプレリリースが初めて出る予定である。
  • 双方向のエッジ(A -> B と B -> A)を両矢印の 1 本で描く設計がマージ済みである。試作では、Umami のモデルのあるビューでエッジが 41 本から 27 本に、交差が 157 から 61 に減った。実装の PR はレビュー中。
  • ADR から生成するファイルを PR ごとにコミットするのをやめ、毎日の定期実行で作り直す実装を進めている。並行する ADR の PR がほぼ必ず衝突していたためである。
  • karasu serve を自分のマシンからの接続だけに限り、Host ヘッダーを確かめる設計を進めている。
  • 600 行を超える 3 ファイルの分割は、まだ続きがある。

感想や質問は、英語版を載せている dev.to のコメント欄でも受け付けています。