karasu 週報:言語 v2.0、フォーカスキャンバス、SVG 要素 97% 削減

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

karasu は、C4 モデルに着想を得た、システムアーキテクチャを記述するテキストモデリング言語です。 .krs ファイルにシステムを書くと、karasu がシステム構成・デプロイ構成・チーム構成のビューを描きます。 エージェントを使った開発を学ぶために半年ほど前に始めたもので、コードは一行も手で書いていません。 すべて Claude Code 経由です。

今回から週報を始めます。 毎回、その週にリリースしたものと、エージェントと一緒に作るなかで起きたことを書きます。

今週の数字: マージした PR は 41 件(うち Dependabot が 7 件)、ADR として記録した決定は 10 件です。

ハイライト 1:密な図のためのフォーカスキャンバス

アーキテクチャ図はすぐ混み合い、最初に読めなくなるのはエッジのラベルです。 ラベル同士が重なったり、途中で切れたりします。

karasu はこれを 2 層で扱うようになりました。 メインのキャンバスには、描ける余白があるラベルだけを描きます。 全文は、プレビューの上に開くフォーカスキャンバスで読めます。

  • エッジをクリックすると、そのエッジがつなぐ 2 つのノードと、その間のすべてのエッジを表示する。ラベルごとに専用のレーンを割り当て、全文を描く。
  • ノードの「Relations」ピルをクリックすると、そのノードと隣接ノードをすべて表示する。片側に依存元、反対側に依存先が並ぶ。

ノードの 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 つの設計をマージし、実装待ちになっています。 エッジが曲がる方向に応じてチャネル内のレーンを並べる設計と、ノードとエッジの識別キーが衝突しないようにする設計です。


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