karasu 週報:serve が import を解決、VS Code のプレビューを app と同じビューアに

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

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

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

今週の数字: マージした PR は 50 件(うち Dependabot が 11 件)、ADR として記録した決定は 6 件です。 今週はリリースを出していません。 ハイライトの 2 件を含む未リリースの変更は、今日の月次リリースで届ける予定です(「次回予告」を参照)。

ハイライト 1:karasu serve が import を解決するようになった

先週、npm 版の karasu serve が動いていなかった件を直しました。 ところが 0.9.0 の serve は、import を含むモデルを描けませんでした。 ブラウザ内のメモリ上のファイルシステムに、開いたファイルだけを /serve/index.krs という名前で入れていたためです。 import 先はどれも「File not found」になっていました。

設計では 3 つの案を比べました。

  • A:配信するディレクトリを丸ごと渡す。 サーバに GET /api/project を足し、配下の .krs と .krs.style をまとめて返す。
  • B:ファイルを 1 つずつ HTTP で読む。 パスを受け取るエンドポイントが 2 つ増え、コンパイルのたびにファイルの数だけ往復する。
  • C:サーバ側で import をたどり、要るファイルだけを返す。 数十 KB を節約するために、core に公開の関数を新しく足すことになる。

A のコストは、ディレクトリ全体を送ることです。 karasu のリポジトリ全体で測ると 108 ファイル・164 KB だったので、A を選びました。

アプリは受け取ったファイルを実際のパスに置き、ファイルの変更を検知するたびに取り直します。 これで import は karasu render と同じ規則で解決され、import 先のファイルやスタイルシートを保存してもプレビューが描き直されます。 import 先だけが変わったときは、ドリルダウンしていたビューもそのまま残ります。 GET /api/project はリクエストからパスを受け取らず、シンボリックリンクもたどりません。 配信するディレクトリの外のファイルは返らない作りです。 ついでに、サブディレクトリにあるファイル(/services/order)を開けなかった不具合も直りました。

serve の夜間の E2E テストも直しています。 このテストは、npm が実際に配る serve 用ビルドではなく、通常のアプリのビルドを配信していました。 先週の「パッケージになった状態で動かすテストだけが捕まえられる」と同じ話で、今は配布物と同じビルドで確かめています。

serve に Share が戻りました。 共有リンクは公開アプリ(karasu.kompiro.dev)を基準に作るので、受け取った人は karasu を動かさなくても図を開けます。

ハイライト 2:VS Code のプレビューを app と同じビューアに

VS Code 拡張のプレビューは、これまで拡張専用に手で書いた webview でした。 app のプレビューに機能を足すたびに、VS Code 側へ移植する必要がありました。 これを、app のビューアを webview の中でそのまま動かす形に置き換えました。 次の 3 つの PR に分けています。

  1. app 側に口を用意する。 ビューアを外から動かせるように、ファイルの読み先、ジャンプ先の通知、テーマや言語の切り替えを渡せるようにした。
  2. VS Code のプレビューを置き換える。 手書きの webview を削除し、app のビューアを動かす。パン・ズーム、CRUD ビュー、facet、エッジのホバーと詳細パネルが、移植しなくても VS Code で使えるようになった。
  3. プレビューからの編集をエディタに書き戻す。 右クリックメニューでエッジの向きを選ぶと、エディタのバッファに未保存の変更として入る。エディタの undo で取り消せる。

ビューアは、読むファイルをすべて拡張に問い合わせます。 拡張は、エディタで開いているバッファを先に読み、なければディスクを読みます。 そのため、保存前の編集が入力したそばからプレビューに出ます。 開いているファイルだけでなく、import 先のファイルの編集も同じです。 以前のプレビューはディスクから読んでいたので、保存前の編集は図に出ませんでした。 これは設計の調査中にコードを読んで見つけたもので、VS Code の上で再現は確かめていません。

書き戻しでは、ビューアが最後に読んだ内容とバッファの今の内容を比べます。 その間に入力があって内容が変わっていれば、何も書きません。 人の編集を上書きしないための確認です。 import 先のファイルにあるノードへのジャンプも、正しいファイルを開くようになりました。

そのほかの変更

karasu fmt がコメントを末尾に飛ばさなくなりました。 karasu fmt と保存時の整形は、プロパティ行の上や横に書いたコメントを、ファイルの末尾にまとめて出していました。 プロパティ行が AST に位置を持たないためで、エッジ・deploy・organization など、どのブロックでも同じでした。 今はコメントが元の行の隣に残ります。 追加したテスト 16 件は、すべて修正前の main で失敗します。

先週の e2e テストが見つけた 2 件を直しました。 先週の週報で「未修正」と書いた件です。 1 つは、エディタで入力中に Ctrl/Cmd+B などのショートカットが発火する件です。 Chromium 版の Monaco は、EditContext API を付けた div で入力を受けます。 この要素も入力中と判定するようにしました。 もう 1 つは、デプロイビューやマトリクスを開いたままリロードするとシステムビューに戻る件です。

ドリルダウンした枠の内側にレーンを収めました。 ドリルダウンしたビューで、カードの脇を回るエッジのレーンが、枠の辺の上や外に出ていました。 4 つのモデル・1,542 ビューで測ると、枠の辺の上や外を走る縦の区間は 3,100 から 31 に減りました。 代わりにキャンバスの面積が 1.5% 増えています。 1 つだけ交差が増えたビュー(Twenty の TwentyServer、+2,018)があり、これは別の Issue で追っています。

translate --from db がマイグレーション履歴に対応しました。 drizzle などのマイグレーションを順に再生し、DROP 済みや作り直し用の一時テーブルを残さなくなりました。 ところが、この修正自体が穴を開けていました。 文字列リテラル中の ) で CREATE TABLE の本体を切ってしまい、DEFAULT ') DROP TABLE a' のような値から DROP TABLE を再生していました。 リリースする前に別の PR で塞いでいます。

細かな修正。 [inferred] タグの付いたエッジが、タグなしのエッジと同じ色で描かれていました(仕様と食い違っていた)。3 つの色の案を測って、区別できる色に変えました。 .krs.style のレキサが ASCII 以外の文字を黙って捨てていて、#注文 のような日本語のセレクタが効きませんでした。 skill に同梱している参照ドキュメントの相対リンク 179 本が、インストール先で切れていました。今はコピーするときに書き換えます。

今週のエージェント開発

CodeRabbit のレビューは、人が追い切れないところに効いています。 karasu の PR は CodeRabbit がレビューしています。 以前の例では、同じ行にあるカード同士のエッジで、カードを避ける経路の 2 点が同じ高さに並ぶケースを指摘されました。 経路のチェックは高さが 0 の通路を無効と扱うので、通れる経路を捨てていました。 その結果、カードを突き抜ける直線が残っていたのです。 karasu のレイアウトの論理は十分に複雑で、私が細かくレビューするのは難しいと感じていました。 こうした、自分では思いもしなかった指摘に助けられています。

一方で、修正のたびの再レビューが枠を使い切ります。 エージェントが指摘を直して push するたびに、CodeRabbit が再レビューします。 これを繰り返すと RateLimit にかかり、PR が待ちになります。 ある PR では再レビューが 6 回回りました。 最初のレビューと修正 1 回分の 2 回で足りたはずで、追加のうち 3 回は同じ種類の問題を 1 か所ずつ見つけていました。 当時の枠は 1 時間に 2 回で、待ちは 38〜39 分でした。 4 回続けて push すると CodeRabbit の自動レビューが止まり、以降は手で頼む必要がありました。

再レビューをやめればよい、という話でもありません。 直近 60 件のマージのうち、CodeRabbit がレビューした 37 件で数えました。 レビューは 80 回で、うち 43 回が再レビューです。 再レビューで新しい指摘が出たのは 5 件の PR・10 件の指摘で、どれも修正のコミットが触ったファイルにありました。

そこで、Web のレビューより前で問題を減らしています。 今は次の順で進めています。

  1. 変更が触るパスからリスクの段階を決め、高いところほどレビューを深くする(設計はマージ済み、実装はこれから)。ADR やテストだけの変更は承認後に自動でマージし、ワークフローやリリース、依存の変更は人が差分を読む。
  2. PR を draft から外す前に、ローカルで /engineering:code-review スキルを走らせる。
  3. 続けて CodeRabbit CLI を 1 回だけ走らせる。CLI の枠(1 時間に 10 回)は PR のレビューとは別である。
  4. ここで問題がなければ ready にし、Web のレビューに回す。

書いた手順は守られませんでした。 CLI を走らせる手順は、もとはエージェントのローカルのメモにしかありませんでした。 10 月 1 日以降に PR を作るか ready にした約 290 セッションのうち、CLI を走らせたのは 1 回だけです。 手順書に書き足しても、翌日にはローカルのレビューを飛ばして ready にしたセッションがありました。 ready の直後に CodeRabbit の指摘への対応を引き継ぐ手順も、約 40 セッション中で呼ばれたのは約 7 回でした。 多くは私が手で打っていました。 そこで、手順を Claude Code の hook にしています。 gh pr ready の直後に対応へ引き継ぐ hook はマージ済みで、ローカルのレビュー前の gh pr ready を拒否する hook は PR を出したところです。

試行錯誤の途中で、プロセスの穴も見つかりました。 main のチェックアウトには、セッションの終わりに未コミットの変更を知らせる Stop hook を置いていました。 main へ直接コミットしてしまう事故を防ぐためのものです。 ところが、この hook はコミットそのものを止めておらず、終わり際に知らせるだけでした。 10 月 24 日には、私が手作業で残した変更を理由に、セッションが抜けられなくなりました。 Stop hook をやめ、main へのコミットを lefthook の pre-commit で拒否するようにしました。 git の hook なので、誰がコミットしても効きます。 cherry-pick や --no-verify は通ってしまうことも、ADR に書き残しています。

別のプロジェクトでは、影響度で決める自動マージに挑んでいます。 きっかけは How the Claude Code team does code review という記事です。 Claude Code チームが自分たちのレビューの進め方を書いたもので、エージェントを信頼して main へ入れる流れに強く影響を受けました。 非公開のプロジェクトで試しています。 考え方は karasu のリスク段階と同じで、もう一歩先まで進めています。

  • 段階は作者ではなく、変更が触ったパスで決まる。docs だけなら AI レビューを省き、普通の変更は AI レビューと検査と E2E を通す。マイグレーション・認証・外への送信・ゲート自身に触れたら、人の承認を足す。
  • 人の承認は端末からしかできず、エージェントは承認できない。
  • AI レビューの未解決の指摘が残っていればマージを止める。レビューは 3 回までで、2 回目からは前回からの差分だけを見る。
  • 過去の不具合の修正からレビュアーの試験を作り、19 件中いくつ見つけられるかを測っている。レビューの観点を直して 5 件から 7 件になった(同じ 19 件で直して測っているので、甘めに出ている)。
  • AI レビューは、ゲートそのものの穴も見つけた。変更が自分の判定の規則を書き換えて、自分を低い段階と判定させられた。今は main に入った版の規則で判定している。

まだ仕組みを作っている途中で、この流れを通ってマージした変更はありません。 動かせたら、結果をレポートします。

次回予告

  • 今日 10 月 25 日は月の最終日曜で、月次のリリーストレインが出る日である。ハイライトの 2 件を含む未リリースの changeset 16 件が、npm と VS Code Marketplace に届く予定。
  • リスクの段階でレビューの深さを変える仕組みの実装が残っている(Issue を open にしてある)。
  • 修正を push する前に、同じ種類の問題をまとめて直す手順を /coderabbit-converge に入れる Issue を立てた。

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