karasu 週報:npm 版の serve 修正と 0.9.0、カードを突き抜けるエッジをゼロに

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

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

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

今週の数字: マージした PR は 88 件(うち Dependabot が 16 件)、ADR として記録した決定は 12 件です。 10 月 15 日にはリリースを出しました。 karasu 0.9.0、@karasu-tools/core 0.4.0、karasu-vscode 0.4.0、karasu-skills 0.2.0 の 4 パッケージです。 前回紹介した .krs 言語 v2.0 は、このリリースで npm と VS Code Marketplace に届いています。

ハイライト 1:npm 版の karasu serve が動いていなかった

0.8.0 の npm パッケージでは、karasu serve がすべてのリクエストに「App not built」を返していました。 tarball に入っていたのは 5 ファイルだけで、プレビュー用のアプリが含まれていなかったためです。 serve はモノレポ内の相対パスでアプリを探していたので、リポジトリの中では動き、npm から入れると動きませんでした。 README に書いてある npx --yes karasu@latest serve . は、モノレポの外では一度も動いていなかったことになります。 外部のリポジトリで reverse-architecture スキルを試したときに気づきました。

修正では、serve 専用にビルドしたアプリを dist/app/ に同梱しました。 あわせて、pnpm pack で作った tarball を展開し、その中の serve を起動して確かめる CI ステップを足しました。 dist/app/ を同梱対象から外すとこのテストが落ちることも確認しています。 この不具合を捕まえられるのは、パッケージになった状態で動かすテストだけです。

同梱したアプリは 16MB(97 ファイル)あり、大半はエディタの Monaco でした。 serve はプレビューを表示するだけで、エディタを使いません。 そこで serve 用に別のエントリを用意し、Monaco のモジュールがバンドルに入るとビルドを失敗させるガードを付けました。 ガードの効き目は、Monaco の import をわざと戻して確かめています。このときビルドは 1,233 モジュールを検出して失敗しました。 serve 用アプリは 1.7MB(4 ファイル)になりました。 リリースした 0.9.0 の npm 上の展開後サイズは 3.3MB(12 ファイル)です。

0.9.0 は 3 回目のリリース実行で出ました。 npm の Trusted Publisher で 2 つの落とし穴を踏み、karasu-skills の公開が止まったためです。 Trusted Publisher は、GitHub Actions からトークンなしで npm へ公開するための設定です。 karasu 0.9.0 は karasu-skills 0.2.0 に依存しているので、一緒に止まりました。

  • E404: 新しく登録した設定は「未検証」で、2 日以内にその設定を通した公開が成功しないと失効する。karasu-skills の設定は、0.1.0 を手で公開した直後に登録した。リリースに使う changeset publish は npm にある版を飛ばすので、0.1.0 では設定を検証できない。そのまま 0.2.0 を出す前に失効していた。
  • E403: 2026 年 9 月 3 日以降に作った設定は、既定では npm stage publish しか許可しない。リリースのワークフローは npm publish を実行するので、「Allow npm publish」にチェックを入れる必要があった。

リリース手順書には、パッケージを上げるリリース PR をマージする直前に設定を登録する、と書き足しました。

ハイライト 2:カードを突き抜けるエッジをなくす

先週に続いて、エッジの経路を直しました。 直した対象は、本来は避けるべきカードの上をエッジが突き抜けてしまうケースです。

  • 同じ行にあるカード同士のエッジ。 これまでは、間に立つカードの上を直線で突き抜けていた。今は行の上か下の通路(チャネル)を回る。上下どちらを通るかは、既存のエッジとの交差が少ないほうで決める。
  • 横の列に置かれたカードへのエッジ。 Dify のモデルでは、Gateway に出入りする 3 本のエッジが、2 つのビューでそれぞれ 7 枚のカードを横切っていた。今は列の脇の隙間を通る。

ほかにも 3 件あります。 チャネル内のレーンを、エッジが上に曲がるか下に曲がるかで並べるようにしました。 同じチャネルを折り返して重なっていた経路は、1 本の区間にまとめました。 ドリルダウンしたビューでは、フォーカスしている枠をレーンやラベルがはみ出さないようにしました。

どの修正も、リバースエンジニアリングで起こした実在のモデルで測っています。 主に使ったのは Umami、Dify、Twenty、WordPress の 4 つで、計 1,542 ビューです。 測るのは、直したい箇所の数と、修正の巻き添えで変わるビューの数です。

修正 直したい箇所 変わったもの
同じ行のエッジ カードの突き抜け 19 → 0 5 ビュー、交差 +131(すべて交差マーク付き)
横の列へのエッジ Dify でカードの突き抜け 14 → 0 交差 38,147 → 38,176(+29)
枠内に収めるチャネル 5 枚のカードを並べたテストで枠外の区間 14 → 0 1,542 ビュー・7,659 本中、3 ビューの 5 本

横の列への修正では、通路を広く取る案もありました。 しかしその案は 13 ビューを変えてしまうため、変更が小さく済む案を選んでいます。

そのほかの変更

薄くしたエッジの交差マークも薄くします。 カードやエッジにホバーすると、関係のないエッジは薄くなります。 ところが、エッジの交差点に描く小さな弧(ホップ)や本数のチップは濃いまま残り、薄くなったエッジの形をなぞってしまっていました。 今はマークも一緒に薄くなります。 このため、先週 1 本の <path> にまとめたホップを、エッジごとの <path> に分け直しました。 Dify のモデルでは出力が 4.29% 増え、ホップの <path> は 757 個から 2,585 個になりました。 最大のビューの表示時間は 33.3 ms から 33.5 ms で、ほぼ変わっていません。 デプロイビューのエッジなど、これまでホバーに反応しなかったエッジも反応するようになりました。

同じ名前のノードを取り違えなくなりました。 Shop の中の Api(Shop.Api)と、トップレベルに置いた service "Shop.Api" は、内部で同じキーを使っていました。 ノードとエッジの識別キーをすべて、衝突しない 1 つの関数で作るようにしました。 2 つのシステムが同じ ID のノードを持つ場合も、処理ごとに違うほうを採っていたので、1 つの規則にそろえました。

karasu diff が複数システムを正しく比べるようになりました。 比較モードでは、最初のシステムしか差分を取っていませんでした。 先頭のシステムを追加・削除すると別のシステム同士を比べてしまい、変わっていないカードが削除・追加として表示されていました。 今はシステムを位置ではなく ID で対応づけます。

細かな修正。 スタイルシートで label-offset: -20px と書くと符号が落ち、20px として扱われていました。 karasu render <ディレクトリ> は「File not found」で失敗していましたが、今はディレクトリ内の index.krs を読みます。

e2e テストが実バグを 2 件見つけました。 今週は Playwright の e2e テストを大きく足し、test.skip で飛ばしていたテストがなくなりました。 その過程で、jsdom の単体テストでは見えなかったバグが 2 件見つかっています(どちらも未修正)。 1 つは、エディタで入力している最中に Ctrl/Cmd+B などのショートカットが発火してしまう件です。 Monaco の入力先が contenteditable でも textarea でもない要素に変わっていて、入力中かどうかの判定をすり抜けていました。 もう 1 つは、デプロイビューやマトリクスを開いた状態でリロードすると、システムビューに戻ってしまう件です。

今週のエージェント開発

Dependabot のトリアージは hane のスキルで回しています。 karasu では Dependabot の PR を、そのままマージしません。 トリアージには、私が別に開発しているスキル集 hane の dependabot スキルを使っています。 hane は Claude Code のプラグインで、karasu の開発から生まれたスキルをまとめたものです。

/hane:dependabot を実行すると、エージェントは次の順に進めます。

  1. open の Dependabot PR をすべて集める。
  2. PR ごとに上流までさかのぼる。リリースノート、タグ間のコード差分、公開者やリポジトリ所有者の変化、インストール時に走るスクリプト、lock ファイルの依存関係、既知の advisory、公開からの経過時間を確かめる。
  3. 結果を設計文書にまとめ、PR ごとに「マージ推奨 / 保留 / 却下」を付けて返す。
  4. 私が採否を決めたら、マージとクローズを済ませ、設計文書を ADR に昇格させる。

このスキルは、patch の更新でも調査を省きません。 patch を装った改ざん版は、サプライチェーン攻撃が最も通りやすい経路だからです。 自動マージもせず、採否は必ず人間が決めます。 設計文書と ADR が残るので、ある版を「いつ・なぜ入れたか」を後から辿れます。

スキルが持つのは、どのリポジトリでも通じる手順だけです。 karasu 固有の運用は、karasu 側のルールファイル(.claude/rules/dependabot.md)に書いています。 たとえば、待機期間(クールダウン)を 7 日にそろえる設定や、bot の差分では正しい変更にならない更新を別の PR に差し替えて入れる条件です。 今週はこの組み合わせで 3 回のトリアージを回しました。

回してみて分かったことが 2 つあります。

1 つ目は、open にできる PR の数の上限です。 今週は待機期間を明けた npm の更新が 24 件あり、上限 8 件では 3 回に分かれて届きました。 回ごとにトリアージが要るので、上限を 25 件に上げました。 PR の総数は変わらず、同時に開く数が変わるだけです。

2 つ目は、lock ファイルを作り直すと、レビューした版から依存がずれることです。 差し替え PR で lock を作り直したところ、knip がレビュー済みの 6.40.0 ではなく、ちょうど待機期間を明けた 6.41.0 に上がっていました。 lock を 6.40.0 に戻し、この教訓を ADR に残しています。

CodeRabbit の指摘 261 件を分類しました。 karasu の PR は CodeRabbit がレビューしています。 Markdown ファイルへの指摘を、8 月 20 日から 10 月 10 日までの 261 スレッドすべて分類しました。 結果、約半分は本物の欠陥でした。 そのため、ドキュメントもレビュー対象から外さないと決めました。

残り半分のうち、繰り返し出る誤検知が 2 パターンありました。 どちらも、CodeRabbit に読ませているルール文の書き方が原因です。 たとえば「設計文書に到達点の一覧を置かない」というルールを、CodeRabbit は設計文書の実装手順にまで当てはめていました。 このルールが本来対象にしているのは、スライス計画の表だけです。 そこで、CodeRabbit の設定ではなく、ルール文の側に適用範囲を書き足しました。 この一文だけを文脈なしに読む人間にとっても、そのほうが誤解しにくくなります。

次回予告

  • VS Code 拡張を毎週プレリリースとして配る設計をマージした。実装はこれから。
  • npm の公開を Trusted Publisher の staged publishing に移す Issue を立てた。
  • 公開ギャラリーを私以外の人にも開くためのエピックを立てた。

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