◉ANTIGRAVITY LABEN
記事一覧/アプリ開発
▣ アプリ開発/2026-10-14中級

ドキュメントのないコードを仕様書に起こす前に、読ませる順番を fan-in で決めておく

ドキュメントのない既存コードをエージェントに読ませて仕様書にするとき、最初に渡すファイルをどう決めるか。import の被参照数(fan-in)とルート定義ファイルから「読ませる順番」を出す 36 行の Python と、77 ファイルの Next.js プロジェクトで測った割合をまとめます。

antigravity464legacy-codespecificationfan-inpython27

先日、ある Zenn の記事で「ドキュメントがまったく無い Rails アプリをエージェントに解析させ、仕様書にした」という体験談を読みました。読み終えて最初に浮かんだのは、感心よりも先に、「自分なら最初の一手で何を渡すだろう」という疑問でした。

数年前に自分で書いたアプリでも、半年触らないと入口がどこだったか分からなくなります。そこでフォルダごとエージェントに渡したくなるのですが、私はこれで一度、読ませすぎて要約がぼやけたことがあります。

渡す量は、読ませたい深さに合わせて決めます。 いまはこの線引きにしています。

ここからは、読ませる順番を「勘」ではなく数字で出すために書いた小さなスクリプトと、手元のプロジェクトで測った結果をお伝えします。

最初に読ませるのは「よく呼ばれるファイル」と「入口」

仕様書に起こしたいとき、必要なのは全ファイルではありません。読む価値が高いのは、おおむね次の 2 種類です。

  1. 入口: ルーティング、ページ、ミドルウェア、設定。外から見える振る舞いが決まる場所です。
  2. 被参照数が多いファイル: 他の多くのファイルが import している共通部品。ここを誤解すると、仕様書全体がずれます。

後者は、import 文を数えれば機械的に出せます。ファイルごとに「何本のファイルから import されているか」を数えたものを、ここでは fan-in と呼びます。

数えるスクリプト(標準ライブラリのみ・36 行)

TypeScript / JavaScript のプロジェクトを対象にした版です。相対 import と @/ エイリアスだけを解決し、外部パッケージは数えません。

#!/usr/bin/env python3
"""reading_order.py — 既存コードの「読ませる順番」を fan-in とエントリポイントで出す。標準ライブラリのみ。"""
import os, re, sys, json, collections
root = os.path.abspath(sys.argv[1]); top_n = int(sys.argv[2]) if len(sys.argv) > 2 else 12
SKIP = {"node_modules", ".git", ".next", "dist", "build", "public", "content", "__pycache__"}
EXT = (".ts", ".tsx", ".js", ".mjs", ".jsx")
ENTRY = re.compile(r"(^|/)(page|route|layout|middleware|proxy)\.(t|j)sx?$")
IMP = re.compile(r"""(?:from|import)\s*\(?\s*['"]([^'"]+)['"]""")
files, size = [], {}
for d, ds, fs in os.walk(root):
    ds[:] = [x for x in ds if x not in SKIP]
    for f in fs:
        if f.endswith(EXT):
            p = os.path.relpath(os.path.join(d, f), root); files.append(p); size[p] = os.path.getsize(os.path.join(root, p))
fset = set(files)
def resolve(src, spec):
    if spec.startswith("@/"): base = os.path.join("src", spec[2:])
    elif spec.startswith("."): base = os.path.normpath(os.path.join(os.path.dirname(src), spec))
    else: return None
    for c in [base] + [base + e for e in EXT] + [os.path.join(base, "index" + e) for e in EXT]:
        if c in fset: return c
fan = collections.Counter()
for p in files:
    try: t = open(os.path.join(root, p), encoding="utf-8").read()
    except Exception: continue
    for m in set(IMP.findall(t)):
        r = resolve(p, m)
        if r and r != p: fan[r] += 1
entries = [p for p in files if ENTRY.search(p)]
total = sum(size.values())
ranked = [p for p, _ in fan.most_common(top_n)]
pick = list(dict.fromkeys(ranked + entries[:top_n]))
pb = sum(size[p] for p in pick)
print(json.dumps({"files": len(files), "total_bytes": total, "entries": len(entries),
  "picked": len(pick), "picked_bytes": pb, "ratio_pct": round(pb * 100 / total, 1)}, ensure_ascii=False))
for p in ranked: print(f"fan-in {fan[p]:>3}  {size[p]:>6}B  {p}")

使い方は python3 reading_order.py <プロジェクトのルート> 8 です。第 2 引数は fan-in 上位から何本を採るかを表します。出力の 1 行目に、全体のファイル数・総バイト数・採用ファイル数・採用バイト数・割合が JSON で出ます。

77 ファイルのプロジェクトで測った結果

手元の Next.js プロジェクト(TypeScript / JavaScript が 77 ファイル、合計 496,852 バイト。記事本文のデータとビルド成果物は除外)で、採る本数を変えて 3 回ずつ走らせました。実行は 1 回 0.03 秒で、3 回とも同じ値でした。

fan-in 上位の本数採用ファイル数(入口を含む)採用バイト数全体に対する割合
81657,42911.6%
122489,73018.1%
2040243,84249.1%

上位 8 本に入口を足した 16 ファイルで、全体の約 1 割強に収まりました。ところが 20 本まで広げると、割合は一気に約半分になります。12 本から 20 本のあいだに、サイズの大きいファイルが混ざったからです。

実際、fan-in 上位 8 本から、目立つ 4 本を抜き出します(順位は 1・2・3・8 位)。

fan-inバイト数ファイル
197,491src/lib/content.ts
96,983src/config/pricing.ts
71,051src/components/ui/LevelBadge.tsx
215,129src/config/gone-slugs.ts

最後の行が、このスクリプトの限界を教えてくれました。fan-in は 2 と小さいのに、15,129 バイトもあります。中身は URL の一覧データで、仕様を読み解く役には立たないファイルです。数字だけで機械的に採ると、こういう「大きいが読んでも得るものが少ない」ファイルが紛れ込みます。

数字は叩き台、最後は目で外す

この結果から、私は次の運用にしました。

  • 最初は上位 8 本と入口だけにして、全体の 1 割強に抑えます
  • 一覧を見て、データ置き場・生成物・定数表を手で外します
  • 下書きに「この関数はどこから来たのか」と分からない箇所が出たら、そのとき初めて採用本数を 12、20 と広げます

広げる判断を「下書きで詰まった箇所」に結びつけておくと、読ませすぎを防げます。

なお、今回確かめたのはスクリプトの出力だけです。エージェントに実際に読ませて、仕様書の質がどう変わるかまでは測っていません。そこは別の機会に、同じ題材で並べて比べるつもりでおります。

次の一歩

まず、いま気になっている古いプロジェクトで、上のスクリプトを 8 本設定で 1 度走らせてみてください。出てきた一覧に「これは読ませても意味がない」と感じるファイルがいくつあるか。その数が、あなたのプロジェクトで手作業の除外が必要になる量の目安になります。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

▣ アプリ開発2026-04-23
Antigravity Python API を本番で落とさない — リトライ・タイムアウト・サーキットブレーカー実装ガイド
Antigravity で開発する Google Gen AI SDK アプリケーションを本番環境で安定稼働させるための、リトライ・タイムアウト・サーキットブレーカー・バックプレッシャー設計を、動作する Python コードと運用経験から体系的に解説します。
▣ アプリ開発2026-10-08
agy CLI をコンテナで動かす前に、擬似 agy で確かめておく 3 つの安全網
Antigravity CLI(agy)を Docker や Cloud Run のジョブから呼ぶ前に、インストーラーの取りこぼし・承認待ちの固まり・終了コード 0 の空振りを、擬似 agy で再現して潰します。公式 docs の記述と、手元で実行した結果だけを根拠に組み立てた手順です。
▣ アプリ開発2026-09-10
iOS 27 の配信日が決まってから、私が最初に grep したのは AGENTS.md でした
iOS 27 と iPadOS 27 の配信は9月14日です。版数の文字列はソースだけでなく、エージェントが読む設定ファイルにも残ります。両方を数えるスクリプトと、keyword 検索では拾えない一行の話を書き残します。