Skip to main content

moai graph NEW

更新 2026-10-03 9分で読めます GitHub で編集 ↗
NEW · v3.1.1

コードベースの関係をひとつの成果物に集めて逆方向の問いに答えるツールです。「このパッケージを直すとどこまで揺れるか」「この SPEC は実際のコードとつながっているか」— こうした問いは grep では答えが出ず、関係が集まっていてはじめて答えられます。

情報
一行要約: moai graph build が codemaps·@MX タグ·SPEC·レポートに散らばった関係を .moai/project/graph/edges.jsonl の1ファイルに集め、moai graph query がそのファイルに逆方向の問合せをします。

なぜ必要か

MoAI-ADK は関係情報をすでに複数の層に持っています — codemaps のインポートグラフ、コード内の @MX:SPEC タグ、SPEC 文書の依存宣言、レポートのマイルストーン記録。問題は、これらの層がそれぞれ別のファイルに散らばっている点です。「このコードを直すとどの SPEC が影響を受けるか」を問うには、インポート方向(@MX:SPEC タグのあるファイルをインポートするファイル)と SPEC 依存の方向を同じグラフの中で逆向きにたどる必要があります。edges.jsonl はそのひとつのグラフです。

moai graph build

bash
$ moai graph build

インポートエッジ·@MX:SPEC 接続·SPEC 間依存を集めて .moai/project/graph/edges.jsonl に記録します。同じ git HEAD で2回実行すれば同じ内容が出るよう、決定論的に動作します。問合せは常にこの成果物を読むので、問合せの前にまず build を回しておく必要があります。

ドキュメント層に加えて、コードから直接抽出したエッジを上乗せします — 関数呼び出し(code-call)とインポート(code-import)で、既存のドキュメントエッジは一切変わりません。インポート先は go.mod のモジュールパスを剥がしてリポジトリローカルのパッケージに正規化し、codemaps のインポートグラフと同じ領域を指させます。呼び出しをどの水準(グレード)で解決したかは16言語すべてについて公表します。両層が同じ関係について食い違う場合、どちらも捨てずに disagrees_with マーカーとともに残し、--all-disagreements はデフォルトで抑止した方向(コードは発見したが文書が沈黙したローカル依存)まで表示します。

moai graph query

1回の呼び出しに与えるセレクタはちょうど1つだけです。

セレクタ問い答
--callers <ノード>このパッケージ/SPEC を直接依存している対象は?逆方向の隣人 — インポートするパッケージ、依存する SPEC、@MX:SPEC タグの付いたコードファイル
--blast <ノード>ここを直すとどこまで揺れるか?逆方向エッジを幅優先で洗った(BFS)影響半径。@MX:SPEC エッジは双方向に伝播し、コードファイルが実装する SPEC まで届きます
--fanin [--limit N]最もよく使われるパッケージは?インポートのファンイン順位
--debt-fanin [--limit N]@MX:DEBT の付いた対象はどれだけ呼び込まれているか?@MX:DEBT タグの対象を、根拠のある呼び出しファンインの降順で一覧 — ファイルスコープの DEBT はファンイン 0 に (self) 表示で併せて出ます
--specs-no-codeコードとつながっていない SPEC は?edges.jsonl に @MX:SPEC エッジが0個の SPEC の一覧
--milestones-no-cardカードなしで通過したマイルストーンは?カード交差検査の行がカードを主張していないか、主張したカードが生存するバックログキューにないマイルストーン
bash
$ moai graph query --callers SPEC-FOO-001
$ moai graph query --blast internal/config
$ moai graph query --fanin --limit 20
$ moai graph query --debt-fanin
$ moai graph query --specs-no-code
$ moai graph query --milestones-no-card

--edges <パス>で別の edges.jsonl を指したり、ルート引数で別のプロジェクトルートを指定したりできます。

問合せの前に、機械的な層(@MX インデックス · edges.jsonl)が古くなっていれば先に更新してから答えます。内容ハッシュが変わったファイルだけを再パースするため、コミットしていない編集も答えに反映されます。更新コストが gate.yaml の update_budget_ms(デフォルト2000ms)を超えたら警告だけを出し、答えは続けます。答えを計算したツリールートとコミット(または dirty フィンガープリント)が常に stderr に一緒に出るので、どのツリーの答えか取り違える余地がありません。

moai graph check

bash
$ moai graph check
codemaps  metric=described-source-diff value=0 threshold=40 verdict=fresh
mx-index  metric=inventory-content-diff value=0 threshold=1 verdict=fresh
edges     metric=source-fingerprint-mismatch value=0 threshold=0 verdict=fresh

グラフの3層 — codemaps · @MX インデックス · edges.jsonl — がコードにどれだけ遅れているかを、層ごとに自分の指標で測り、fresh / stale / absent の判定を返します。codemaps はコンテンツアンカー — codemaps 本文が最後に実際に変わった地点 — 以降に変わった記述対象ファイル数(戻した変更は0と数えます)、@MX インデックスは内容ハッシュが変わったファイル数、edges.jsonl はソースフィンガープリントの不一致を見ます。

測定の起点はスタンプされたコミットではなくコンテンツアンカーです。作業ツリーの codemaps 本文がスタンプ時点の本文と違えばアンカーはスタンプコミット自身であり(content_anchor_source=working-tree-differs-from-stamp)、一致していればスタンプ自身の履歴で本文に最後に触れたコミットがアンカーになります(last-body-change)。アンカーとその出所は stderr と --json に content_anchor · content_anchor_source として出ます。本文をそのままにして moai graph stamp codemaps だけ打ち直しても測定窓が0に戻らないのはこのためです — ゲートが赤いときはスタンプを打ち直すのではなく、/moai codemaps で本文を作り直してからスタンプしてください。

各成果物は provenance ブロックで、どのツリー·コミットの産物かを明かします。ブロックのないものは absent — 判断不能を fresh と偽らず、absent も失敗です。新しいワークツリーにはそもそもこれらの成果物がないため、すべて absent として報告されます。終了コードは 0(すべて fresh)· 1(stale または absent)· 2(システムエラー)で、プリコミット品質ゲートの graph-freshness ステップと CI の graph-freshness ジョブがこの値をそのまま消費します。しきい値は gate.yaml の graph_freshness セクションで調整します。

mtime はどこでも読みません。新しいチェックアウトはすべての mtime を初期化するため、mtime 基準の指標は再生成直後と誤判します — だからここにある指標は内容ハッシュと git diff、フィンガープリントだけです。

stale 判定には原因の内訳が付くようになりました。codemaps 層が stale になると、stderr にはこの変更自身が寄与したファイル数(contribution)と測定基準のコミット(contribution_base、通常は HEAD^1)がまず出力され、続いてドリフトを起こした経路が最大10件まで列挙され、それ以上は ... and N more にまとめられます。--json では同じ情報が contribution · contribution_base · driving_paths · driving_paths_omitted フィールドとして出ます。この red を単に引き継いだだけか(寄与0)、自分で起こしたのか(寄与>0)を stderr 一行で見分けられます。

moai graph stamp codemaps

bash
$ moai graph stamp codemaps
OK: stamped .moai/project/codemaps/provenance.json
provenance: tree=/path/to/project commit=1a2b3c4d5e6

codemaps を再生成した後の最後のステップとして実行します。文書の内容は /moai codemaps が整えますが、その内容がどのツリー状態を記述しているかはこのコマンドが provenance.json に記録します。moai graph check はこの記録を出発点にコンテンツアンカーを解決し、codemaps 層を判定します。

マージを生き残るコミットの指定 (--commit)

bash
$ moai graph stamp codemaps --commit "$(git merge-base HEAD origin/main)"
OK: stamped .moai/project/codemaps/provenance.json
provenance: tree=/path/to/project commit=1a2b3c4d5e6

フラグなしで実行すると、スタンプは現在チェックアウトしている HEAD を記録します。機能ブランチではこれが落とし穴です。このリポジトリはプルリクエストをスカッシュマージで統合するため、ブランチのコミット — HEAD も含め — は main の履歴に一切入りません。ブランチローカルの HEAD を指すスタンプは、スカッシュマージが決まった瞬間に孤児になり、その後に開くすべてのプルリクエストが graph-freshness の赤判定(not comparable、exit 2)を受け継ぎます。この失敗は実際に一度発生し、0d15864ae90b インシデントとして追跡されました。

--commit <rev> は git rev-parse の式(フル sha、短縮ハッシュ、ref 名のどれでも)を受け取り、フル sha に解決してそのまま記録します。上の merge-base レシピが安全な形です。git merge-base HEAD origin/main は main の祖先なのでスカッシュマージを生き残り、同時に分岐点での described ソースと内容が一致するため、他の PR が統合した変更まで自分のドリフトとして数えられません。 ただし実際の測定起点はこのコミットではなくコンテンツアンカーなので、本文がそれより前のコミット以降変わっていなければ、その前のコミットから測ります。ブランチローカルの HEAD で再スタンプしないでください。また、described ソースに未コミットの変更がある状態で --commit を使うと即座に拒否されます — コミット指定とコンテンツフィンガープリントは異なる正直性の主張であり、スキーマのアンカーは一つしか持てないからです。

この規律は CI が機械的に支えます。graph-freshness ワークフローは鮮度判定を報告する前に、追跡されているスタンプのコミットがプルリクエストのベースブランチの祖先かどうかを検証します。孤児になるスタンプは、マージ後の無名の exit 2 ではなく、名前付きでその場で失敗します。

2つのセレクタへの注意書き

注意
--specs-no-code: 「未接続」は「未実装」ではありません。大部分の SPEC は文書・ルール・ハーネスを出すだけで、コードがなくても完成です。この一覧は欠陥リストではなくカバレッジの地図として読んでください。
注意
--milestones-no-card: バックログキューはカードが終わると(done)行を消します。そのため「キューにない」は終わったカードと、そもそも発行されなかったカードを一緒に束ねます。各項目は git log --oneline --grep 'merge: tNN' で判定してください — コミットがあれば通過、なければ新しいカードの候補です。grep 0件も「作業をしなかった」という意味ではありません。カードが新しい id で再発行されている可能性があるので、新しいカードを切る前に系譜を確認してください。

関連ドキュメント

  • ファクトリーモード — マイルストーンとカードの交差検査が見張るカードの流れ
  • /moai mx — @MX タグと @MX:SPEC 接続の原本
  • Navigator — 設計決定·SPEC·シンボルを束ねるもうひとつのグラフ(nav-graph.json)