Strata 仕様書
多言語・マイクロサービス対応 依存関係可視化・コールグラフ探索ツール
- 作成日: 2026-07-24(関数レベル対応で改訂)
- ステータス: ドラフト(レビュー待ち)
1. 背景と目的
依存関係の可視化ツールの多くは、単一言語・単一プロジェクト・特定 IDE に閉じており、 マイクロサービスで構成された実際のシステム(複数言語 + gRPC 境界)を横断して 「構造の劣化」や「処理の流れ」を見ることができない。
本ツール Strata は、仕事で使う技術スタック (Go / TypeScript / JavaScript / Protocol Buffers / マイクロサービス構成)を対象に、 依存構造の可視化(メトロ配線 + レベル化 + 地層バンド)とコールグラフ探索を行う IDE 非依存の CLI + Web ビューアである。すべてゼロから実装する。
主ユースケース(本ツールの存在理由)
マイクロサービスで作られたシステムを理解する。 「フロントのこのボタンを押すと、どのサービスのどの RPC が呼ばれ、 そのハンドラの先でどの関数・どのサービスが呼ばれるのか」を、 関数レベルのコールグラフとして、サービス境界(gRPC)を越えて辿れること。
- アーキテクチャ俯瞰(サービス間依存図)← 折りたたみ表示
- 構造劣化の発見(上向き依存・循環依存)← メトロ配線 + レベルバンド表示
- 処理の流れの追跡(エントリーポイント → RPC → ハンドラ → 下流)← トレース機能(独自)
第一級の対象とするリポジトリ形態(仕事のシステム構成):
リポジトリ(モノレポ)
├── proto/ # 共有の proto 定義(サービス間 API の「正」)
├── gateway/ # API ゲートウェイ
├── federation/ # GraphQL federation 層(スーパーグラフ/サブグラフ)
└── services/ # バックエンドのマイクロサービス群(複数)
├── user/
├── order/
└── ...
このディレクトリを strata serve . で読み込むだけで、 「gateway のこの関数 → RPC → user サービスのハンドラ → その先」というフローを関数レベルで辿れること。
法的な位置づけ
- 完全な独自実装(クリーンルーム)。公開情報のみを情報源とする
- ライセンスは AGPL-3.0-only(LICENSE / CONTRIBUTING.md 参照)
2. 設計原則
本ツールの可視化は、次の一般的な技法・原則の組み合わせとして設計する:
- メトロ配線(orthogonal routing): 要素を縦一列に並べ、依存を左レーンを通る 直交配線(角丸エルボー)で描く。向きは色(正常 = グレー/ティール、上向き = ローズ)と 依存先に刺さる矢印で表現する
- レベル化(layering): 「依存される側が下」になるよう並べ替えると、 上向きの依存 = レイヤー違反だけが視覚的に浮かび上がる
- 地層メタファー(Strata): レベルを地層のバンドとして塗り分け、 アーキテクチャの層構造そのものを画面の主役にする(本ツール独自の表現)
- proto を正とする接続: サービス間の呼び出しは proto(API 定義)を起点に静的に接続する
3. Strata の概要
3.1 形態
- CLI(Node.js ≥ 22.18、TypeScript ソースを直接実行、外部依存ゼロ・ビルド不要)
- Web ビューア(CLI 内蔵のローカル HTTP サーバー、または自己完結 HTML 1 ファイルに書き出し)
- IDE 非依存。リポジトリに置いて誰でも
strata serve .で閲覧できる
3.2 コマンド体系
strata scan [dir] [-o model.json] # 解析して依存モデル(JSON)を出力(既定: stdout)
strata serve [dir|model.json] [--port] # ローカルサーバーでビューアを起動(リロードで再解析)
strata export [dir|model.json] [-o report.html] # 自己完結HTMLを出力(チーム共有用)
strata check [dir|model.json] [--json] # 循環依存を検出し、あれば exit 1(CI用)
[--baseline FILE] [--update-baseline] # 既知の循環を許容し新規のみ fail
strata report [dir|model.json] [-o report.md] # アーキテクチャレポート(mermaid + Markdown)
strata trace [dir|model.json] <関数ID> [--up] # 端末上でコールツリーを表示(CI/CLI派向け)
3.3 特長(既存の依存可視化ツール群に対する立ち位置)
| 観点 | Strata |
|---|---|
| 解析対象 | Go / TS / JS / proto(+ Elixir の gRPC クライアント検出)。多言語モノレポが第一級 |
| 要素の粒度 | モジュール / パッケージ / ファイル / 関数 + RPC |
| マイクロサービス横断 | gRPC(proto)を「正」として関数レベルで接続。サービス間依存図は折りたたみで導出 |
| コールグラフ | RPC 境界を越える推移的トレース(上流・下流) |
| 構造の健全性 | レベル化 + 地層バンド表示。上向き依存・循環依存が視覚的に浮かぶ |
| API カタログ | 全 proto / service / RPC を一覧。未使用・実装なし・テストのみを自動判定 |
| チーム共有 | 自己完結 HTML エクスポート / ローカル Web サーバー(IDE 非依存) |
| CI 連携 | strata check で循環依存を検出して fail |
| 導入コスト | Node.js のみ・依存ゼロ・ビルド不要。AGPL-3.0 |
4. スコープ
4.1 MVP(今回実装)
- ワークスペース走査とプロジェクト自動検出(go.mod / package.json / .proto)
- モジュール・パッケージ・ファイルレベルの依存解析(import 解決)
- 関数レベルの解析(§6): 関数・メソッドの宣言抽出とコールグラフ構築
- proto: service / rpc 定義をノード化。
go_package・生成スタブ・RPC 名の突き合わせで クライアント呼び出し → RPC → サーバ実装 を接続(§6.4) - Web ビューア: ツリー + メトロ配線、レベル化ソート、循環赤表示、フォーカス、検索、 表示深度切替(サービス概観 ⇄ 関数詳細)、トレースパネル(コールツリー)
- API タブ(§8.5): 全 proto / service / RPC のカタログ、RPC 起点のフロー表示、 ソースコードビューア、ディープリンク
- HTML エクスポート / ローカルサーバー / CI 用 check / 端末用 trace
4.2 スコープ外(将来拡張候補)
- 型推論を伴う完全なコールグラフ(インターフェース実装の解決、高階関数の追跡)
- HTTP/REST ルーティングの突き合わせ(
fetch('/api/users')→ ルータ定義)※ gRPC 優先 - GraphQL スキーマと resolver の突き合わせ(federation のスーパーグラフ → サブグラフのホップ接続)
- エディタ連携でのコードジャンプの強化(MVP は
vscode://リンクとパス:行表示まで) - アーキテクチャルール定義と違反検出 / Git 履歴での時系列比較 / MCP サーバー化
- Kubernetes / docker-compose 定義からのサービス検出
- クラス・型・フィールドレベルの解析
5. 解析仕様(構造レベル)
5.1 ワークスペース走査
- 入力: ルートディレクトリ 1 つ(モノレポ想定。マルチレポは親ディレクトリを指定して一括解析)
- プロジェクトルート検出:
go.modのあるディレクトリ = Go モジュール、package.jsonのあるディレクトリ = JS/TS パッケージ(両方あれば両方として扱う) - 各ソースファイルは最も近い(最長プレフィックスの)プロジェクトルートに帰属
- 共有
proto/ディレクトリ(go.mod / package.json を持たない)はワークスペース直下の 独立ツリーとして扱い、全サービス共通の API 定義置き場として認識する(§1 のレイアウトを想定) - 除外(既定):
.gitnode_modulesvendordistbuildout.nextcoverage.cache.idea.vscodetargettestdatagengenerated__generated__など - 生成コードは除外:
*.pb.go*_grpc.pb.go*_pb.{ts,js,d.ts}*_pb2.py*.min.js*.d.ts→ 生成スタブへの参照は §6.4 により「proto の RPC への依存」として付け替える - テストコード(
*_test.go*.test.ts*.spec.ts)は既定で除外(設定で含められる) - 設定ファイル
strata.config.json(任意、ワークスペースルート):
{
"name": "my-platform",
"services": [ // サービス定義(グルーピング)。省略時はプロジェクト単位
{ "name": "user-service", "path": "services/user" },
{ "name": "web", "path": "frontend" }
],
"exclude": ["experimental"], // パス前方一致 or ディレクトリ名(完全に無視)
"includeTests": false,
"testPaths": ["tools/test-client"] // テスト扱いにする追加パス(§6.7 の集計のみに使う)
}
- テスト判定: ファイル名規約(
*_test.go*.test.ts等)に加え、tests/test/__tests__/e2e/ディレクトリ配下は自動でテスト扱い。testPathsで任意のパスを追加できる。テスト扱いのコードは依存グラフに出ないが、 §6.7 のテスト呼び出し検出には使われる(excludeは検出にも使われない完全な除外)
5.2 Go(構造)
- 階層: サービス > モジュール > ディレクトリ > パッケージ > 関数
go.modのmodule行からモジュールパスを取得し、モジュールパス + "/" + 相対ディレクトリを import パスとして全モジュール横断の索引を作る- import 文の抽出はコメント除去後のテキスト解析(Go ツールチェーン不要)。 単一形式・ブロック形式、エイリアス・
_・.に対応 - 索引に一致した import → パッケージ間エッジ(kind:
import)。標準ライブラリ・外部は MVP では無視
5.3 TS / JS(構造)
- 階層: サービス > パッケージ > ディレクトリ > ファイル > 関数
- 対象拡張子:
.ts.tsx.js.jsx.mjs.cjs(.d.ts除外) - import 抽出:
import ... from "x"/import "x"/export ... from "x"/require("x")/import("x") - モジュール解決の順序:
- 相対パス → 拡張子補完(
.ts.tsx.js…)、/index.*、.js→.ts読み替え(NodeNext 流儀) tsconfig.json/jsconfig.jsonのbaseUrl+paths(単一*パターン、JSONC 可)- ベア指定子 → ワークスペース内の他
package.jsonのnameと照合(モノレポ内パッケージ間) - 未解決かつ
_pb/_grpc/_connect/.pbを含む → proto 突き合わせ(§6.4) - それ以外の未解決(npm 外部依存など)は MVP では無視
- 相対パス → 拡張子補完(
5.4 proto(構造)
- 階層: … > ディレクトリ > proto ファイル > rpc
serviceブロックとrpc宣言を抽出し、RPC ノード(label:UserService.GetUser)を作る。 シグネチャからリクエスト/レスポンス型(meta.req/meta.res)、stream種別 (meta.streaming: client / server / bidi)、option deprecated = true(meta.deprecated)も記録 (※ 生成コード復元(*_grpc.pb.go)の場合は名前のみで、型・deprecated は取得しない)import "a/b.proto"→ ワークスペース内 proto をパス末尾一致で解決し proto→proto エッジ (google/protobuf/*は無視)option go_package = "path;alias"の path 部を索引化(§6.4 で使用)
6. 解析仕様(関数レベル)★本ツールの核
型推論なしのテキスト・構文レベル解析で「どこまで正確に関数呼び出しを解決するか」を明確に定義する。 誤検出(ないはずの依存が出る)より取りこぼし(あるのに出ない)を許容する方針。 確度の低い名前一致だけの推定はデフォルトで行わない。例外は RPC 名の突き合わせ(§6.4)で、 これは proto という「正」の情報源があるため中確度で採用する。
6.1 関数の抽出
| 言語 | 抽出対象 | ノード label 例 |
|---|---|---|
| Go | func Name(...) / func (r T) Name(...)(メソッド) | Run / (*Server).GetUser |
| TS/JS | function name(...) / const name = (...) => / const name = function / クラスメソッド / export default function | render / UserClient.getUser |
| proto | rpc Name(Req) returns (Res) | UserService.GetUser |
- Go の関数ノードはパッケージ直下(Go はパッケージが名前空間のため)。
meta.fileとmeta.lineで位置を保持 - TS/JS の関数ノードはファイル直下
- コメント・文字列リテラルは除去してから解析。ブレース対応で関数本体の範囲を特定する
6.2 呼び出しの解決(Go)
ファイルごとに import 別名表(alias → import パス)を作った上で、関数本体内の呼び出し式を解決する。
| 呼び出し形 | 解決方法 | 確度 |
|---|---|---|
alias.Func(...) | 別名表 → パッケージ索引 → そのパッケージの関数 Func | 高 |
Func(...)(修飾なし) | 同一パッケージ内の関数索引 | 高 |
alias.Func(...) で alias が proto の go_package | New<Svc>Client 等はスタブ生成として認識(エッジにしない) | - |
x.Method(...) で Method が RPC 名索引に一致し、かつ同ファイルがその proto のスタブを import | RPC 呼び出し(kind: rpc)→ RPC ノードへ | 中 |
x.Method(...) で Method が同一パッケージ + import 先パッケージ全体で一意に定義されたレシーバメソッド | そのメソッドへの call(s.helper() や DI 越しの h.usecase.Execute() を繋ぐ。複数候補があれば張らない) | 中 |
x.Method(...) その他 | 解決しない(型推論なしでは誤検出が多いため) | - |
※ 呼び出し式はチェーン(s.users.GetUser(...))全体を修飾子とし、最後の識別子をメンバとして解決する。
サーバ実装の検出: レシーバ付きメソッドの名前が RPC 名索引に一致し、 かつ同ファイルがその proto のスタブを import している場合、 RPC ノード → そのメソッド の実装エッジ(kind: impl)を張る。 これにより「クライアント → RPC → サーバハンドラ」が繋がり、トレースが境界を越えられる。
6.3 呼び出しの解決(TS / JS)
ファイルごとに import バインディング表を作る: import { getUser as gu } from './api' → gu → (./api, getUser)、import * as api from './api' → 名前空間。
| 呼び出し形 | 解決方法 | 確度 |
|---|---|---|
f(...) で f がローカル定義 | 同一ファイルの関数索引 | 高 |
f(...) で f が named import | バインディング表 → 解決先モジュールの export 関数 | 高 |
ns.f(...) で ns が namespace import | 解決先モジュールの export 関数 f | 高 |
this.method(...) | 同一ファイル内のクラスメソッド(呼び出し元と同一クラスを優先) | 中 |
obj.method(...) で method が RPC 名索引に一致(lowerCamel 化も許容: getUser ≒ GetUser)し、かつ同プロジェクトがその proto のスタブを参照 | RPC 呼び出し(kind: rpc) | 中 |
| その他のメソッド呼び出し | 解決しない | - |
※ ありふれたメソッド名(get map then 等)は RPC 突き合わせの対象外(誤検出防止)。
6.4 RPC 突き合わせ(サービス境界の接続)
proto を「正」として、生成コードを介した参照を RPC ノードに付け替える:
| 検出元 | 突き合わせキー | 生成エッジ |
|---|---|---|
Go: proto の go_package への import | go_package 完全一致 | ファイル/パッケージ → proto(kind: proto、構造レベルの補助) |
| Go: スタブ経由のメソッド呼び出し | RPC 名 + スタブ import | 呼び出し元関数 → RPC(kind: rpc) |
| Go: ハンドラ実装メソッド | RPC 名 + スタブ import | RPC → 実装関数(kind: impl) |
TS/JS: user_pb 等の生成スタブ import(未解決) | _pb/_grpc/_connect 接尾辞を除いた基底名 = proto ファイル名 | ファイル → proto(kind: proto) |
| TS/JS: クライアントメソッド呼び出し | RPC 名(lowerCamel 許容) | 呼び出し元関数 → RPC(kind: rpc) |
proto: import "a/b.proto" | パス末尾一致 | proto → proto(kind: import) |
- RPC 名が複数サービスで衝突する場合: スタブ import の対応関係で絞り込み、 それでも曖昧ならエッジを張らず警告として scan 結果に記録する(
warnings配列) - ビューアで表示をサービスレベルに折りたたむと、これらのエッジが集約されて サービス間依存図になる(専用画面は不要、同一モデルの集約で実現)
6.5 gateway / federation 層の扱い
前提: 各層の実装言語は決め打ちしない。 gateway / federation / 各サービスは Go の場合も TS/JS の場合も混在の場合もある。解析器はディレクトリの役割ではなく中身(go.mod / package.json / ファイル拡張子)だけで判断し、Go と TS の RPC 突き合わせを同格でサポートする。
| 経路 | MVP での扱い |
|---|---|
| gateway / federation のコード内部(Go/TS の関数呼び出し) | 通常の関数解析(§6.2 / §6.3)で追跡 |
| gateway / federation → バックエンドの gRPC 呼び出し | §6.4 の RPC 突き合わせで関数レベルで接続(MVP の中核) |
| grpc-gateway(proto アノテーションによる HTTP→gRPC 変換) | proto を読むため RPC ノード経由で自然に繋がる |
| federation 層の GraphQL ホップ(スーパーグラフ → サブグラフの HTTP 通信) | MVP では静的に接続しない。サブグラフ側の resolver 関数は通常解析でエントリーポイントとして現れるため、トレースは「gateway 側の鎖」と「サブグラフ側の鎖」に分かれて見える |
GraphQL スキーマ(.graphql)と resolver 名の突き合わせによるホップ接続 | 将来拡張(§4.2)。スキーマの field 名 ⇔ resolver 関数名の規約ベース照合で実現見込み |
6.6 Elixir(Phoenix)の gRPC クライアント検出
Elixir 製フロントエンド/サービス(Phoenix LiveView 等)からの RPC 呼び出しを検出する。 elixir-grpc の規約 <Package>.<Service>.Stub.<snake_case_rpc>(channel, req) が対象。
- 対象:
.exファイル(.exs= スクリプト/テスト/設定、.pb.ex= 生成コードは除外。deps/_build/はスキップ)。mix.exs をプロジェクトルートとして認識 - 関数抽出:
def/defp(複数クローズは最初の定義に集約)。呼び出し元の帰属は 「直近の def」への行ベース割り当て - alias 解決:
alias A.B.C/alias A.B.{C.D, E}(複数行も可)/alias A.B, as: X - RPC 突き合わせ: 解決後のモジュールパスが
.Stubで終わる呼び出しのみ対象。 直前のセグメントを service 名、関数名の snake→Pascal 変換を RPC 名として索引照合 (Stub.login→AuthService.Login)。曖昧なら接続せず warning - ローカル呼び出し: 同一ファイル内で定義された関数名のみ
callエッジで接続 (Phoenix のhandle_event → ヘルパー → Stub 呼び出しの鎖を繋ぐため) - Elixir 関数間のファイル横断呼び出し・HTTP ルーティングは対象外(将来拡張)
6.7 テストからの RPC 呼び出し検出
テストコードは依存グラフからは除外したまま(includeTests: false 既定)、 「本番からは呼ばれないがテストは呼んでいる RPC」を区別するための軽量パスを走らせる。
- 対象:
*_test.go(Go)、*_test.exs(Elixir)。JS/TS のテストは将来拡張 - Go: proto スタブの import と
.RpcName(の出現の突き合わせ / Elixir: §6.6 と同じ Stub 呼び出し検出 - 結果は RPC ノードの
meta.testCallers(ファイル数)とmeta.testFilesに記録 - ビューアでの表示: 本番呼び出しゼロの RPC は テスト呼び出しあり → 「テストのみ」(青)、完全にゼロ → 「未使用?」(赤)。 フロー画面には「テストからの呼び出し」一覧(クリックでテストコードを表示)
6.9 HTTP(REST / webhook)の検出
gRPC と同じく「定義」「呼び出し」「実装」の 3 点を集め、パスの正規化キーで突き合わせる。
- ルート定義: gin / echo / chi(
Group/Routeのプレフィックスを解決)/ gorilla mux (.Methods(...)を反映)/ net/http(Go 1.22 の"GET /path")/ Express / Fastify / Hono / Next.js App Router(app/**/route.tsのexport function GET)/ FastAPI(APIRouter(prefix=))/ Flask / Phoenix Router(get "/x", Ctrl, :actionとlive) - クライアント呼び出し:
http.Get/Post/NewRequest(WithContext)・fetch・axios・requests・httpx。URL は正規化する(スキームとホストを捨て、:id/{id}/${id}/%s/<int:id>を{}に寄せる)。文字列連結(base + "/users/" + id)は末尾にパラメータが 続くとみなす - 接続:
METHOD + パスで一致、無ければパスのみで一致。候補が 1 件のときだけ辺を張る (曖昧なら張らない)。呼び出し元は「その行を含む関数ノード」に帰属させる - ハンドラ実装: ルート登録の第 2 引数の識別子から、同一サービス内で名前が一意に決まる 関数へ
impl辺。users.Getのような同名メソッドはレシーバ変数名で曖昧解消する。 無名関数はインラインとして記録し「実装なし」とは扱わない - webhook: パスに
webhook/hookを含むルートは受信口とし、外部からの受信(HTTP)から 辺を張る。どのルートにも当たらない絶対 URL は外部への送信(HTTP)配下のホストノードへ。 受信と送信を別のトップレベルに分ける(1 つにまとめると外部 ⇄ サービスが循環になるため)
6.10 GraphQL の検出
- スキーマ:
*.graphql/*.gql/*.graphqlsと、コード中のgql\…`インライン SDL からtype Query/Mutation/Subscription(extend含む)のフィールドをgqlfield` ノード化 - リゾルバ実装: Go(gqlgen の
func (r *queryResolver) Field(...))、 TS/JS(resolvers = { Query: { field: ... } })からimpl辺 - クライアント操作:
gql\query|mutation|subscription`のルート直下の選択フィールドからgraphql` 辺(候補が一意のときだけ) - federation:
extend type X @key(...)を持つサブグラフから、Xを@key付きで定義する サブグラフへgraphql辺。スーパーグラフ側の設定ファイルは読まない(コードとスキーマが正)
6.8 静的解析の限界(明記事項)
以下は原理的に取りこぼす。ビューアの README・ヘルプにも明記する:
- インターフェース越しの呼び出し(Go の interface、TS の DI)は実装先を解決しない
- 高階関数・コールバック・リフレクション・動的 import のパス変数は追跡しない
- メッセージキュー等の非同期連携(Pub/Sub)は config.messaging を設定したときのみ検出する
- HTTP は「同じパスのルートが複数サービスにある」場合、どれか判定できないので接続しない (例: 各サブグラフが
/graphqlを持つ構成)。パスが変数だけで組まれている呼び出しも追えない - 取りこぼしがあっても「グラフに出ている依存は実在する」ことを優先する
7. データモデル(strata scan の出力 JSON)
{
"tool": "strata 0.1.0",
"name": "my-platform",
"root": "/abs/path",
"createdAt": "2026-07-24T00:00:00Z",
"nodes": [
{
"id": "services/user/internal/api#GetUser",
"label": "(*Server).GetUser",
"parent": "services/user/internal/api",
"kind": "service | module | dir | package | file | proto | func | rpc",
"lang": "go | ts | js | proto", // 任意
"loc": 42, // 任意(行数)
"meta": { "file": "services/user/internal/api/handler.go", "line": 31 } // 任意
}
],
"edges": [
{ "from": "...", "to": "...", "count": 3, "kind": "import | call | rpc | impl | proto" }
],
"warnings": [ "RPC名 'Get' が複数サービスに一致するため接続をスキップ: ..." ]
}
- ノード ID: 構造ノードはワークスペース相対 POSIX パス。関数は
親ID#関数名、 RPC はprotoファイルID#Service.Rpc。ワークスペースルート自体がプロジェクトなら. - サービスノード(設定由来)は
svc:+ 名前 - エッジは最小単位(関数、なければファイル/パッケージ)間で記録する。 上位階層(サービス間など)のエッジはビューア/check が親子関係から集約して導出する
- 決定的出力(ノード・エッジはソート済み)。差分レビューや将来の履歴比較に備える
8. ビューア仕様
8.1 レイアウト(ツリー + 左レーン配線 + トレースパネル)
┌─────────────────────────────────────────────────────┐
│ ツールバー: 表示深度 / ソート / 検索 / 違反のみ / 統計 │
├────────┬───────────────────────────────┬────────────┤
│ 配線 │ ツリー(縦リスト) │ 詳細パネル │
│ レーン │ ▾ [S] user-service [層 1] │ 選択要素の│
│ ┌──▶ │ ▾ [M] user (go) │ 依存一覧 │
│ │ │ ▾ [P] internal/handler │ (in/out) │
│ │ │ ƒ (*S).GetUser │ ────────── │
│ └───▶ │ [P] internal/store ←赤 │ トレース │
│ (色で │ ▸ ⬢ user.proto │ コール │
│ 向き) │ ▸ [S] order-service [層 0] │ ツリー │
└────────┴───────────────────────────────┴────────────┘
- 1 ノード = 1 行。インデントで階層、▸/▾ で展開・折りたたみ。既定の展開深度はモジュールまで
- 依存線(メトロ配線): すべて左のレーンを通り、▶ の先が依存先。 色で向きを表現: グレー = 下向き依存(正常)、ローズ = 上向き依存(レイヤー違反)
- 太さ = 集約された依存数(対数スケール)。kind
rpc/protoは破線(サービス境界を跨ぐ依存) - 折りたたまれたノードは配下すべてを代表し、エッジは表示中の代表ノードに集約される
- 行アイコン: ◆ サービス / ▣ モジュール / □ パッケージ・ディレクトリ / 📄 ファイル / ⬡ proto / ƒ 関数 / ⚡ RPC
8.2 レベル化ソート(既定)
- 各親の子集合ごとに、子孫間の生エッジを子同士のエッジに集約
- Tarjan の SCC で強連結成分(= 循環グループ)を検出
- 縮約 DAG 上で最長経路レベル付け(依存される側がレベル 0 = 最下段)
- レベル降順に配置(上 = 依存する側、下 = 依存される側)。同レベル内は名前順
- 循環グループの要素は赤背景。トップレベルにはレベル区切り線を表示
- 他のソート: 名前順 / サイズ(LOC)順
8.3 フォーカスとトレース(独自の核)
- ナビゲーション履歴: フォーカスの移動(依存一覧・経路・循環一覧などからのジャンプ)を 履歴に記録し、ツールバーの ← / →(または Alt+← / Alt+→)で 元のフォーカス位置とスクロール位置に戻れる / 進める(最大 100 件)。 他タブにいる場合は構造ビューに戻ってから復元する
- クリック = フォーカス(In/Out): その要素に接続する依存線のみ強調 (出力依存 = 緑、入力依存 = 青、その他は減光)。Esc で解除
- トレースモード: フォーカス要素から
- 「下流を辿る」: 推移的に到達可能な呼び出し先(call / rpc / impl を辿る)だけを表示
- 「上流を辿る」: 誰に呼ばれるか(逆向き)
- RPC 境界を越えるたびに詳細パネルのコールツリーに ⚡サービス名 の境界マークを挿入
- 循環に当たったら ↻ マークで打ち切り
- 詳細パネルのコールツリー: インデント表示・展開可能。クリックでツリー上の該当行へジャンプ、
meta.file:lineを表示しvscode://file/...リンクでエディタを開く - 検索: 部分一致。一致ノードの祖先を自動展開し、非一致を減光
- 表示深度: 「サービス概観」(深度1) / 「モジュール」 / 「全展開」 / 「全折りたたみ」
- 「違反のみ」トグル: 上向き依存の線のみ表示
- 構造ビューのフィルタ: 「サービス」コンボ(検索つき。1 つのトップレベルのサブツリーだけを表示)と 線種チップ(import / call / RPC境界 の ON/OFF)
- 要素の非表示: 行ホバーで出る ⊘ でそのサブツリーを非表示にできる(assets や設定ファイル等の ノイズ除去用)。ヘッダの「非表示 n」バッジで一括解除。プロジェクトごとに localStorage に保存
- 一覧が長いセレクト(サービス / 呼び出し元)は検索つきコンボボックス (入力で絞り込み、Enter で先頭候補を選択)
- 行ホバー: マウスを乗せた行に接続する依存線だけを強調し、他を減光(混みあった線の追跡用)。 依存線は始点に ●(依存元)、終点に ▶(依存先)が付く
- サービスサマリパネル: トップレベル(サービス/モジュール)を選択すると、 画面の展開状態に依存しないマイクロサービス単位の依存サマリを表示:
- 依存先/依存元サービスの一覧(⚡RPC 件数と code(import/call)件数の内訳つき)
- 公開 API の使用状況(RPC 総数 / 本番から呼ばれる / テストのみ / 未使用)+ API カタログへのリンク
- RPC への依存は「その RPC を実装しているサービス」に付け替えて集計する (共有 proto/ ディレクトリ構成でもサービス間の結合が正しく見える)
- 依存が見つからない場合は理由の注記を表示(RPC が本番から呼ばれていない、 メッセージキュー等の動的連携は検出対象外、など)
- 統計バー: ノード数 / エッジ数 / 循環グループ数 / 上向き依存数。循環バッジのクリックで一覧
- ダーク / ライトテーマ自動追従
8.4 配布形態
serve:/model.jsonを返すローカルサーバー(localhost バインドのみ)。リロードで再解析。 加えて/source?f=<ワークスペース相対パス>でソースコードを返す(§8.5 のビューア用。 ワークスペース外へのパストラバーサル禁止・対象拡張子のみ・2MB 上限)export: CSS/JS/モデルをすべてインライン化した自己完結 HTML(ネットワーク不要、共有可)。 ソース表示はサーバーが無いため不可(vscode://リンクで代替)
8.5 API タブ(proto カタログ + フロー + ソースビューア)★独自機能
「マイクロサービスの理解はまず API(proto)から」という導線のための専用タブ。 ツリーから proto を探し回らなくても、全 proto / service / RPC が 1 ページに集約される。
┌───────────────┬───────────────────────────────┬─────────────────────┐
│ カタログ │ フロー │ ソース │
│ 2proto・2svc・ │ ⚡ UserService.GetUser │ user.proto:8 │
│ 3RPC │ 呼び出し元(上流) │ 7 service User..{ │
│ proto/user/v1 │ ← ƒ[federation] user │ 8 rpc GetUser.. │ ← ハイライト
│ ⬡ user.proto │ ← ƒ[gateway] handleUser │ 9 rpc ListUsers..│
│ UserService │ フロー(実装 → 下流) │ │
│ ⚡GetUser │ ⚡ GetUser │ (クリックした関数の │
│ ⚡ListUsers │ → ƒ[user-service] GetUser │ 実コードが行番号・ │
│ 未使用? │ → ƒ store.Get │ 該当行付きで開く) │
│ proto/order.. │ → ƒ metrics.Count ↻循環 │ │
└───────────────┴───────────────────────────────┴─────────────────────┘
- カタログ(左): proto をディレクトリごとにグループ化し、service / RPC を一覧。 RPC ごとに呼び出し元サービス名を表示。実装ハンドラ未検出 = 「実装なし」、 呼び出し元ゼロ = 「未使用?」 のバッジで死んだ API を検出。検索ボックスで絞り込み
- カタログのフィルタ(大規模リポジトリ向け):
- ドロップダウン: 「サービス」(定義側)/「呼び出し元」(どこから呼ばれているか。
(テストから)も選べる)/「並び」(名前順 / 呼び出し数順) - 3状態チップ(クリックで 含む ✓ → 除外 ✕ → 解除 を巡回):
- 「使用状況」: 本番で使用 / テストのみ / 未使用(排他的な3区分。含むは OR、除外は常に適用)
- 「属性」: 実装なし / deprecated / stream(同上)
- グループ間・検索・ドロップダウンとは AND で組み合わせる (例: 「未使用 ✓ + 実装なし ✓」で定義だけが残った API を洗い出す)
- 「オプション」: テスト呼び出しを無視(本番コードだけで使用状況を判定。 テストのみの RPC は「未使用?」表示になり、フローのテスト欄も非表示)
- 検索はリクエスト/レスポンスのメッセージ型名にもヒットする(「この message を使う RPC」の逆引き)
- proto ファイル単位の折りたたみ(▸/▾ + 件数バッジ、「全て畳む / 全て開く」)。 検索・フィルタ中は一致を見せるため強制展開し、一致ゼロの proto は非表示
- 集計行にフィルタ後の件数(「表示中 n」)を表示
- ドロップダウン: 「サービス」(定義側)/「呼び出し元」(どこから呼ばれているか。
- フロー(中央): RPC 選択で表示。
- 上流: このRPC を呼ぶ関数を、そのさらに呼び出し元まで遡って表示(
←) - 下流:
⚡RPC → 実装ハンドラ → 呼び出し先関数 → …を入れ子で表示(→)。 サービス境界を越えるとサービス名チップ(実装 = 紫、その他 = 青)、循環は ↻ で打ち切り - 構造ビューの ⚡RPC 詳細パネルからも「API フローで開く」で遷移可能
- 上流: このRPC を呼ぶ関数を、そのさらに呼び出し元まで遡って表示(
- ソース(右): フロー内の関数 / proto / RPC をクリックすると
/sourceから実コードを取得し、 行番号付き・定義行ハイライトで表示。vscode://リンクでエディタも開ける - 経路探索(A → B がなぜ繋がっているか): ノードをフォーカスすると、サイドパネルの 「経路探索」で相手ノードを検索・指定できる。全エッジ(impl 含む)のグラフ上で BFS(最短)+ 深さ制限つき DFS により最大 5 本の経路を列挙(探索量に上限)。 各経路はサービス境界チップ・ホップ種別(↓ / ↓RPC / ↓実装)・呼出 file:line チップ付きで、 クリックでツリー内ジャンプ / ソース表示ができる。A・B がサービス等の場合はサブツリー全体を 始点/終点集合として扱う。影響調査・循環の切断計画・不審な依存の由来調査に使う
- フローの並び順: 同じ親の下の兄弟ノードは親の中での呼び出し行順に並べる (= コードを上から読む順序と一致する。呼び出し位置のないエッジは後ろに名前順)
- 呼び出し箇所ジャンプ: 解析時にエッジへ呼び出し位置(
sites: file:line、最大5件)を記録し、 フローの各ノードに「呼出 file:line」チップを表示。クリックすると 親がその関数を呼んでいる行へ直接飛べる(定義と呼び出し箇所の両方に行ける) - 定義ジャンプ: コード上を ⌘(Ctrl)+クリックすると、クリックした識別子を定義索引と 突き合わせて定義位置へ移動(同一ファイル → 同一サービス → 全体の順で解決)。 索引の対象: 関数 / RPC / proto の message・enum / Go の type / TS の interface・class・type・enum。ヘッダーの ← ボタンで参照元に戻れる(履歴 50 件) ※ 生成コード復元(
*_grpc.pb.go)のみのサービスは message 定義を持たないため対象外 - シンタックスハイライト: 依存なしの軽量トークナイザ(コメント / 文字列 / キーワード / 数値の 4 種、ブロックコメントは行を跨いで状態を持ち越す)。 対応: Go / TS / JS / proto / Elixir / YAML / JSON / SQL / shell / go.mod。ライト・ダーク両対応
- git 連携(⎇ blame): ソースパネルの「⎇ blame」で各行の最終変更コミットを ガター表示(短 SHA + 経過時間。30日以内 = 強調 / 1年以内 = 通常 / それ以前 = 減光。 同一コミットのブロック先頭にのみチップを表示)。チップをクリックすると コミットモーダル: メッセージ・作者・日時・色付き diff(
git show、400KB 上限)・ GitHub コミットリンク・PR リンク(squash(#123)/ merge コミットの規約から自動検出)。 サーバー側は/blame(line-porcelain)と/commit(SHA 検証付き)。 複合プロジェクトのシンボリックリンク越しでも実体パスで正しいリポジトリを引く - ファイルツリー: ソースパネルのヘッダーの 📁 でツリーペインを開閉。
/files(ワークスペースの閲覧可能ファイル一覧、SKIP_DIRS 除外・上限 2 万件)を元に ディレクトリを展開して任意のファイルを閲覧できる。ファイル名の絞り込み入力付き。 閲覧可能拡張子はコードに加え.md.json.yamlgo.modなどの設定・ドキュメント類も含む - ディープリンク:
#api= カタログ、#api=<RPC ID>= そのフローを直接開く(共有用)
8.9 アーキテクチャ図タブ
サービス(トップレベル)単位の「箱と矢印」ダイアグラム。一般的なアーキテクチャ図の見た目で システム全体を俯瞰する(ディープリンク: #diagram)。
- 自動レイアウト: レベル化(依存される側が下)で行=レイヤーを決め、 barycenter 法(既配置の隣接ノードの平均 x)で並べ替えて交差を減らす。 レイヤーごとに帯と「層 n」ラベル。どのサービスとも繋がらないものは「独立」帯にまとめる
- 箱: サービス名 + 公開 RPC 数(実装サービス帰属)+ 規模(LOC)
- 矢印: ⚡RPC 依存は破線 + 件数ラベル、コード依存は実線。上向き(レイヤー違反)はローズ。 RPC への依存は実装サービスに付け替えて集約(§8.3 と同じ規則)。⊘ 非表示ノードは図からも除外
- 操作: ホバーで関連エッジ・隣接ノードのみ強調、クリックでサービスサマリパネル (依存・公開 API・経路探索)を表示。フォーカス中の箱はアクセント色で強調
- ナビゲーション履歴は図ビューにも対応: パネル内のジャンプ(依存先クリック等)も 履歴に記録され、← / →(Alt+← / →)で図ビューのまま前のサービスに戻れる。 履歴は元のタブ(構造 / 図)ごと復元する。← / → ボタンは全タブで表示
8.8 エントリーポイントタブ
「どこから読み始めるか」のカタログ。解析時にエントリーポイントをマーキング(ノードの meta.entry)する:
| 種類 | 検出 |
|---|---|
| ▶ プロセス起動点 | Go の package main の func main(meta.entry: "main") |
| ▤ 画面 | Phoenix LiveView(use X, :live_view / use Phoenix.LiveView。defmacro を含むファイルは除外) |
| 操作イベント | LiveView の def handle_event("名前", …) をファイルの meta.events に収集 |
| ⚡ 公開 API | 既存の API カタログへのリンク |
- エントリーポイントをクリックすると構造ビューへ移動してフォーカス + 下流トレースを自動で開始 (「このエントリーポイントから何が起きるか」を 2 クリックで追える)。イベントチップは 該当ファイルの
handle_eventハンドラを起点にする - ディープリンク:
#entries - 将来: Go の HTTP ルート、cron・ワーカー起動点、GraphQL resolver
8.7 プロジェクト管理タブ
読み込むリポジトリ(ワークスペース)を複数登録して切り替えられる。
- レジストリ:
~/.config/strata/projects.json。serve起動時に指定したディレクトリは自動登録 - ビューアの「プロジェクト」タブ(ヘッダーのワークスペース名クリックでも開く): 登録一覧(表示中バッジ・存在しないパスの警告)、「開く」で切り替え、「追加」(パス入力、
~/可)、「削除」(一覧から外すだけ) - 切り替えは URL クエリ
?p=<絶対パス>で表現(ブックマーク・共有可能)。/model.json/files/sourceもpを受け付ける - セキュリティ:
pは登録済みプロジェクトのみ許可(未登録パスは 403)。 任意パスをソースビューアで読み出されることを防ぐ - 複合プロジェクト: 名前 + 複数リポジトリのパスを指定すると、
~/.config/strata/workspaces/<名前>/にシンボリックリンクを束ねたワークスペースを作り、 複数リポジトリを 1 つの依存グラフとして解析する(マルチリポのマイクロサービスや 複数案件の横断調査向け。gRPC のサービス間接続もリポジトリを跨いで解決される)。 削除はリンク置き場だけを片付け、実リポジトリには触れない - エクスポート HTML ではプロジェクトタブは非表示
8.6 ビジュアルデザイン(Strata ブランド)
- 地層メタファーを軸にした独自デザイン。配色はティール(#2dd4bf / #0d9488)× アンバー(#f59e0b / #b45309)の 2 色基調、ライト/ダーク自動追従
- ロゴ: 3 枚の地層(角丸バー)を重ねた SVG。favicon も同モチーフ
- 地層バンド: レベル化ソート時、トップレベルのレベルごとに背景を交互に塗り分け、 各層の先頭行に「層 n」タグを表示。アーキテクチャの層構造が視覚の主役になる
- 種別チップ: S(サービス)/ M(モジュール)/ P(パッケージ)/ ⬢(proto)/ ƒ(関数)/ ⚡(RPC)を 色付きの角丸チップで表示
- 依存線: メトロ路線図風の直交配線(角丸コーナー・丸端)。左レーン共通で、 色が向き(グレー = 下向き、ローズ = 上向き = 違反)、破線 = サービス境界(RPC / proto)
9. check / trace(CLI)仕様
9.1 check(CI 連携)
- 全親グループの兄弟レベル SCC を検出し、循環グループを報告。1 つでもあれば exit 1
--jsonで機械可読出力- ベースライン方式:
--baseline FILEで既知の循環を許容リストと照合し、 新規の循環だけで fail する(負債が残るリポジトリでも「悪化させない」CI を導入できる)。--update-baselineで現状を許容リストとして保存。解消済みの循環は更新を促す ※ ベースラインはノード ID(パス)ベースのため、大規模なリネーム後は--update-baselineで再生成する - 将来: レイヤールール検証
$ strata check .
✖ 循環依存: 2 グループ
[services/user/internal] store ⇄ metrics (2 edges)
[frontend/src/app] ui/page ⇄ lib/format (2 edges)
9.2 trace(端末でのコールツリー)
$ strata trace . 'frontend/src/api/client.ts#fetchUser'
ƒ fetchUser (frontend/src/api/client.ts:12)
└─ ⚡ UserService.GetUser ← サービス境界: user-service
└─ ƒ (*Server).GetUser (services/user/internal/handler/handler.go:31)
├─ ƒ store.Get (services/user/internal/store/store.go:8)
└─ ƒ metrics.Count (…) ↻ 循環
--upで逆方向(誰に呼ばれるか)。関数 ID は検索文字列でも可(一意に絞れなければ候補を表示)
9.3 report(アーキテクチャレポート)
strata report は README やオンボーディング資料にそのまま貼れる Markdown を生成する:
- サービス間依存図(mermaid): 実線 = gRPC/API 依存、点線 = コード依存、ラベル = 依存数。 RPC への依存は実装サービスに帰属(§8.3 と同じ規則)
- サービス別サマリ表: 規模(LOC)/ 公開 RPC / 本番から呼ばれる / テストのみ / 未使用
- 未使用の可能性がある API 一覧(棚卸し候補)
- 循環依存一覧
9.4 ref 指定つき読み込みと差分ビュー
全コマンド共通の --ref <git ref> で、作業ツリーではなくその ref の内容を解析する。
- git worktree 方式:
git worktree add --detachで一時ディレクトリに取り出して解析し、 終了時に必ず撤去する。clone しないので速く、利用者の作業ツリー・インデックスに触れない - ref は
rev-parse --verify <ref>^{commit}で解決する。英数字始まりのみ許可し、-始まり(git のオプションに化ける)は形式段階で弾く。git 呼び出しはすべてシェル非経由 - 解析対象がリポジトリのサブディレクトリのときは、worktree 側の同じサブディレクトリを見る。 パスは両方 realpath に揃えてから相対化する(macOS の
/var→/private/varのずれで worktree の外を指し、元の作業ツリーを解析してしまう事故があった) strata diff [dir] --ref <base>..<head>で 2 つの ref を直接比較する。head を省くと作業ツリーと比べる
ビューアの「差分」タブ(#diff)は同じ仕組みをサーバー経由で使う:
/refs— 比較に選べるブランチ・タグ・現在の HEAD(origin/HEADのような symref は除く)/diff?base=&head=— 2 つの ref を解析して §12 の差分を返す。head 省略時は作業ツリー。 head のコミットメッセージから PR 番号を復元できた場合は、リモート URL と併せて PR リンクを出す- 循環は増=悪(ローズ)/減=良(緑)、依存の増減はそれ自体に良し悪しが無いので中立色で示す
9.5 スタックトレース解析(ビューア)
ヘッダの「📋 トレース」から、panic・エラーログ・スタックトレースを貼り付けると:
- 各行の
ファイル:行(絶対パスはワークスペース相対へサフィックス一致で解決)と、 関数名(行内最後のName(を定義索引と照合)からフレームを解決 - 解決したフレームの一覧をクリックすると、該当コードを行ハイライト付きで開く
- 「ログを見て → コードを探して → 前後を理解する」を 1 ステップにする
10. 非機能要件
| 項目 | 要件 |
|---|---|
| 実行環境 | Node.js ≥ 22.18(TypeScript 直接実行)。macOS / Linux / Windows |
| 依存 | 実行時外部依存ゼロ(npm install 不要で node src/cli.ts が動く) |
| 規模 | 数千ファイル・関数数万ノード規模で scan 数秒〜十数秒、ビューア操作が実用的であること。既定の展開深度を浅くし、依存線の描画は表示中ノードのみに集約して描画量を抑える |
| 決定性 | 同一入力から同一モデル(タイムスタンプ以外)を出力 |
| セキュリティ | ソースを外部送信しない。serve は localhost バインドのみ |
11. リポジトリ構成(実装計画)
├── docs/SPEC.md # 本書
├── package.json # bin: strata(依存ゼロ)
├── src/
│ ├── cli.ts # コマンド分岐・引数解析
│ ├── scan.ts # 走査・プロジェクト検出・設定読込
│ ├── model.ts # ノード/エッジ型・Builder
│ ├── graph.ts # Tarjan SCC・循環検出・トレース(check/trace 用)
│ ├── analyzers/
│ │ ├── golang.ts # §5.2 + §6.2
│ │ ├── jsts.ts # §5.3 + §6.3
│ │ └── proto.ts # §5.4 + §6.4
│ ├── server.ts # serve
│ └── export.ts # 自己完結 HTML 生成
├── web/ # ビューア(素の HTML/CSS/JS、外部ライブラリ不使用)
│ ├── index.html
│ ├── style.css
│ └── app.js # §8 の描画・レベル化・集約・トレースロジック
└── examples/demo/ # 動作確認用サンプル(下記の検証計画を満たす構成)
12. 検証計画
examples/demo は仕事のシステムと同じレイアウト(§1)で作り、以下を意図的に仕込んで 全機能を通しで確認する:
examples/demo/
├── strata.config.json # サービス定義(gateway / federation / user / order)
├── proto/
│ ├── user/v1/user.proto # UserService { GetUser }、go_package 指定
│ └── order/v1/order.proto# OrderService { ListOrders }、user.proto を import
├── gateway/ # Go。user/order 両サービスの RPC を呼ぶ
├── federation/ # TS。resolver から user_pb スタブ経由で RPC を呼ぶ
└── services/
├── user/ # Go。UserService 実装。内部に循環(store ⇄ metrics)
└── order/ # Go。OrderService 実装。user の RPC を呼ぶ
- RPC 越えのコールチェーン:
gateway の handler → ⚡UserService.GetUser → user-service の (*Server).GetUser → store.Get()がトレースで一本に繋がること(federation の resolver → RPC も同様) - Go パッケージ循環 1 件(
store ⇄ metrics)、TS ファイル循環 1 件(federation 内)が赤表示され、strata checkが exit 1 で 2 件報告すること order-service → user.proto(Go スタブ import)とfederation → user.proto(user_pbimport)が kindproto/rpcの破線エッジで表示されること- proto → proto import(
order.proto → user.proto)が解決されること - サービス概観(深度 1)で
gateway → {user-service, order-service}、federation → user-service、order-service → user-serviceのサービス間依存図になること strata traceで RPC 実装をまたぐコールツリーが端末に出ることstrata exportの HTML が単体で開けること(オフライン)