karasu 週報:言語 v2.0、フォーカスキャンバス、SVG 要素 97% 削減
karasu は、C4 モデルに着想を得た、システムアーキテクチャを記述するテキストモデリング言語です。
.krs ファイルにシステムを書くと、karasu がシステム構成・デプロイ構成・チーム構成のビューを描きます。
エージェントを使った開発を学ぶために半年ほど前に始めたもので、コードは一行も手で書いていません。
すべて Claude Code 経由です。
今回から週報を始めます。 毎回、その週にリリースしたものと、エージェントと一緒に作るなかで起きたことを書きます。
今週の数字: マージした PR は 41 件(うち Dependabot が 7 件)、ADR として記録した決定は 10 件です。
ハイライト 1:密な図のためのフォーカスキャンバス
アーキテクチャ図はすぐ混み合い、最初に読めなくなるのはエッジのラベルです。 ラベル同士が重なったり、途中で切れたりします。
karasu はこれを 2 層で扱うようになりました。 メインのキャンバスには、描ける余白があるラベルだけを描きます。 全文は、プレビューの上に開くフォーカスキャンバスで読めます。
- エッジをクリックすると、そのエッジがつなぐ 2 つのノードと、その間のすべてのエッジを表示する。ラベルごとに専用のレーンを割り当て、全文を描く。
- ノードの「Relations」ピルをクリックすると、そのノードと隣接ノードをすべて表示する。片側に依存元、反対側に依存先が並ぶ。

週の後半には、マウスがなくてもこのキャンバスを開けるようにしました。
- タッチデバイスでは、ノードの詳細パネルに「Relations」ボタンが出る。
- キーボードでは、コマンドパレットの項目から、ハイライト中のノードのキャンバスを開ける。
- キャンバス内は Tab で移動でき、閉じると元の場所にフォーカスが戻る。
ハイライト 2:.krs 言語 v2.0
言語そのものに破壊的変更を入れたリリースです。 中心にある考え方は、語彙を閉じたことです。
- タグとアノテーションはツールが定義するものになった。 これまでは利用者が自由に作れた。今は karasu が知らないタグを対象にしたスタイルルールは何にもマッチしないため、図の意味がプロジェクトごとにずれていくことはない。
facetとboundaryをコア記法にした。 これまでは実験的な機能だった。facet は「どのコンポーネントが PCI データに触れるか」のような横断的関心事を扱い、boundary は所属でノードをまとめる。- 一部の警告をエラーにした。 ノードを置いてはいけないコンテキストに置くとエラーになる。組み込みアノテーションの書き損じ(たとえば
@depracated)もエラーである。 - 「エラー」を定義した。 エラーとは karasu が受け付けない構文のことで、エラーが残っている間は新しい図を描かない。VS Code のプレビューは、中途半端に復旧した図を出す代わりに、最後に正しく描けた図を表示し続ける。
そのほかの変更
SVG 要素を 97% 削減。
実在の大きなモデル(Dify)では、エッジが交差する箇所に描く小さな弧(「ホップ」)が出力の 43% を占めていました。
同じストロークのホップが続く区間を、1 本の <path> へまとめる方式にしました。
これでホップの要素数は 38,572 から 894 に、出力サイズは 8.72 MB から 6.48 MB に減りました。
まとめるのは連続したホップだけです。
すべてのホップを色ごとにまとめればもう少し減りますが、重なり合う 4,757 組の描画順が入れ替わってしまいます。
Unicode の名前から文字が欠けなくなりました。
字句解析器は UTF-16 の単位を 1 つずつ読んでいました。そのため 𠮷野家 は黙って 野家 になり、分解形の café は cafe になっていました。
現在はコードポイント単位で読みます。
名前に紛れ込んだ絵文字やゼロ幅スペースのように扱えない文字は、捨てずに報告します。
コードポイント単位にしたことで、1 万行のモデルのトークン化が約 2 倍遅くなりました。そこで ASCII には高速経路を用意し、速度を元に戻しました。
ガター内のエッジの束ね。 同じガターを通って同じ端点に向かうエッジは、1 本のレーンを共有するようになりました。 当初は描画が約 25% 遅くなりましたが、空間インデックスを入れて元の速度に戻しました。出力はバイト単位で同一です。
ギャラリー向けの OGP 画像。 公開ギャラリーへの投稿に、SNS 用のプレビュー画像が付くようになりました。 最初の設計は「PNG を KV に保存し、cron ジョブで掃除する」でした。 しかし KV の読み取りは最大 60 秒ほど古い値を返すことがあり、Workers のリクエストには所要時間の上限がありません。そのため、時間で区切った掃除が正しいことを証明できませんでした。 最終的な設計では何も保存しません。キャッシュミスのときに画像を描画し、エッジキャッシュに 1 日置きます。 本番では、ミス時の CPU 時間が 523〜620 ms、ヒット時が 2〜5 ms です。
今週のエージェント開発
ファイルを実行してしまうフック。
Claude Code の PostToolUse フックで、編集のたびに TypeScript を整形するつもりでした。
ところがフックは /bin/sh(dash)で動いており、dash には [[ がないため、条件式が || のところで分断されていました。
その結果、フックはエージェントが編集した実行可能ファイルを実行し、.ts ファイルは一度も整形していませんでした。
目に見える失敗がなかったので、しばらく誰も気づきませんでした。
POSIX の case に書き換えて直しました。
エージェントのフックもコードであり、フックが守る対象のコードと同じだけ注意深く見る必要があります。
レビューを前倒ししました。 ワークフローを変え、draft PR を開く前に、ブランチの差分に対してエージェントのコードレビューを走らせるようにしました。 以前は PR を開いた後でした。 レビューでの修正が PR の最初の push に含まれるので、PR の履歴が読みやすくなります。 この変更は、以前の決定を置き換える ADR として記録しました。かつてこの案を却下した理由が、もう成り立たなくなっていたためです。
設計文書 → ADR のサイクル。 今週の機能の多くは、同じサイクルを通りました。PR で設計文書を出して議論し、実装したら ADR に昇格させ、設計文書は削除します。 この流れで 10 件の ADR を記録しました。 設計文書はエージェントと私が議論する場所で、ADR は後に残るものです。
次回予告
今週は 2 つの設計をマージし、実装待ちになっています。 エッジが曲がる方向に応じてチャネル内のレーンを並べる設計と、ノードとエッジの識別キーが衝突しないようにする設計です。
- サイト:karasu.kompiro.dev
- ソースコード:github.com/kompiro/karasu
感想や質問は、英語版を載せている dev.to のコメント欄でも受け付けています。