Gemini CLI の拡張機能として使っていた Conductor が、プラグインに変わったと知ったのは、ちょうど進行中のトラックを二つ抱えていた週のことでした。agy plugins install の一行で入るらしい——と分かっても、指が止まります。plan.md の「作業中」の印がずれたり、spec.md が書き換わったりしたら、途中までの判断の履歴ごと失うことになるからです。
最初にお伝えしたいのは、移すものは実質ありませんということです。Conductor の成果物は、拡張機能の中ではなくリポジトリの conductor/ ディレクトリに置かれる Markdown です。Google の開発者ブログ(2026年7月16日)も、spec.md と plan.md は「どこにも行かない」と書いています。動くのは、それらを読み書きする側だけです。
それでも私は、個人開発で壁紙アプリの企画メモを Conductor に預けている身として、導入の前後に一度だけ状態を見比べることにしました。移行そのものより、「何も壊れていない」と言い切れる状態に自分を置くための作業です。
何が変わって、何が変わらないのか
公式ブログと Conductor リポジトリの README から確認できる範囲で整理します。
| 項目 | 変わらない | 変わる |
|---|---|---|
| 成果物の置き場所 | conductor/ 配下の product.md、tech-stack.md、workflow.md、tracks.md | なし |
| トラックの構成 | conductor/tracks/<id>/ の spec.md、plan.md、metadata.json | なし |
| 起動のしかた | 従来のスラッシュコマンドも使えると公式が説明 | 自然な会話でも動くようになった |
| 入れ方 | — | agy plugins install(Antigravity)、Claude Code ではマーケットプレイス登録 |
| プラグイン本体の置き場所 | — | グローバルなら ~/.gemini/config/plugins/、プロジェクト限定なら .agents/plugins/ |
プラグイン本体の場所と成果物の場所が別だ、という点が肝心です。アンインストールしても conductor/ は残りますし、逆に導入だけで conductor/ が書き換わる理由は、本来ありません。ただ、これは「書き換わらないはず」という設計上の話で、手元で確かめた話ではありません。確かめる道具を先に作りました。
導入の前後を見比べるスクリプト
やることは三つです。必須ファイルの有無、トラックごとの三つのファイルのハッシュ、plan.md のチェックボックス集計([ ] 未着手、[~] 作業中、[x] 完了)を JSON に保存し、あとで同じ計測と突き合わせます。
#!/usr/bin/env python3
"""conductor/ の状態を JSON に残し、プラグイン導入の前後で差分を取る。"""
import hashlib
import json
import re
import sys
from pathlib import Path
CORE = ["product.md", "product-guidelines.md", "tech-stack.md", "workflow.md", "tracks.md"]
BOX = re.compile(r"^\s*[-*]\s*\[( |x|~)\]", re.M) # [ ] 未着手 / [~] 作業中 / [x] 完了
def digest(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()[:12]
def snapshot(root: Path) -> dict:
base = root / "conductor"
out = {"missing_core": [c for c in CORE if not (base / c).exists()], "tracks": {}}
for tdir in sorted((base / "tracks").glob("*/")) if (base / "tracks").exists() else []:
entry = {"files": {}, "boxes": {" ": 0, "~": 0, "x": 0}}
for name in ("spec.md", "plan.md", "metadata.json"):
f = tdir / name
entry["files"][name] = digest(f) if f.exists() else None
plan = tdir / "plan.md"
if plan.exists():
for m in BOX.findall(plan.read_text(encoding="utf-8")):
entry["boxes"][m] += 1
out["tracks"][tdir.name] = entry
listed = (base / "tracks.md").read_text(encoding="utf-8") if (base / "tracks.md").exists() else ""
out["unlisted_tracks"] = [t for t in out["tracks"] if t not in listed]
return out
def diff(a: dict, b: dict) -> list:
msgs = []
for t in sorted(set(a["tracks"]) | set(b["tracks"])):
x, y = a["tracks"].get(t), b["tracks"].get(t)
if x is None or y is None:
msgs.append(f"{t}: {'追加' if x is None else '消失'}")
elif x != y:
changed = [n for n in x["files"] if x["files"][n] != y["files"][n]]
msgs.append(f"{t}: 変更 {changed}")
return msgs
if __name__ == "__main__":
mode, root = sys.argv[1], Path(sys.argv[2])
if mode == "save":
snap = snapshot(root)
Path(sys.argv[3]).write_text(json.dumps(snap, ensure_ascii=False, indent=1))
print(f"tracks={len(snap['tracks'])} missing_core={snap['missing_core']} unlisted={snap['unlisted_tracks']}")
elif mode == "compare":
before = json.loads(Path(sys.argv[3]).read_text())
msgs = diff(before, snapshot(root))
print("\n".join(msgs) if msgs else "差分なし")
sys.exit(1 if msgs else 0)ハッシュを 12 桁に切っているのは、人が目で見て比べられる長さにしたかったからです。セキュリティ用途ではないので、この長さで足ります。
試すために、トラックを二つ(dark-mode と login-fix)持つ小さなダミーの conductor/ を作り、保存と比較を回しました。
python3 conductor_snapshot.py save . before.json
# tracks=2 missing_core=[] unlisted=[]
python3 conductor_snapshot.py compare . before.json
# 差分なし
# plan.md の「作業中」を手で完了に変えたあと
python3 conductor_snapshot.py compare . before.json
# dark-mode_20261020: 変更 ['plan.md'] (終了コード 1)終了コードが 1 になるので、手順書やシェルスクリプトに組み込めます。逆に言えば、plan.md を普通に進めている最中は差分が出て当然です。比べるのは「導入の直前」と「導入の直後」の二点だけにしておきます。
導入の手順(私の順番)
- 進行中のトラックを一区切りまで進め、
git statusが空の状態にします。手が入った plan.md が未コミットのままだと、あとで差分の原因が自分なのかプラグインなのか分からなくなります。 python3 conductor_snapshot.py save . before.jsonを実行し、before.json はリポジトリの外(ホーム直下など)に置きます。リポジトリの中に置くと、それ自体が差分になります。agy plugins install https://github.com/gemini-cli-extensions/conductorを実行します。- もう一度
compareを実行し、「差分なし」とgit statusが空であることを両方確認します。 - 差分が出たら、
git diff conductor/を読みます。意図しない書き換えなら、そこで止めてgit checkout -- conductor/で戻します。
手順 4 で差分が出なければ、移行の主作業はそこで終わりです。残るのは使い方の側、つまり「従来のコマンドで続けるか、会話で続けるか」の選択です。
落とし穴と判断
トラックが一覧から漏れている場合があります。 unlisted_tracks は、conductor/tracks/ にディレクトリがあるのに tracks.md に名前が出てこないトラックを拾います。手で作ったトラックや、途中でコピーしたトラックに出ます。導入とは関係なく起きるので、前後の比較とは別に、保存時に一度見ておくと整理になります。
Gemini CLI 側の拡張機能は、そのままにするか外すかを決めます。 二つの経路から同じ conductor/ を書くと、plan.md のチェックボックスを両方が更新しうるからです。私は、しばらく併存させて、新しい側で一つトラックを最後まで回してから古い側を外す、という順番にしました。どちらが正しいと断言できる材料は持っていません。ただ、同じトラックを二つの道具から同時に進めない、という線引きだけは守っております。
トークン消費の増え方は公式も注意を書いています。 README には、仕様駆動の進め方は文脈の読み込みが増えるため、大きなプロジェクトでは消費が増えうると明記されています。会話で進められるようになった分、気軽に呼び出して消費が膨らむ、ということは起こりえます。
次にやること
プラグインへの移行は、ファイルを移す作業ではなく、ファイルが動いていないことを確かめる作業でした。導入の直前に保存し、直後に比べるだけです。この二回の実行は、合わせても一分とかかりません。最初に指が止まったのは、確かめる手段を持っていなかったからなのだと、いまは感じています。
まずは進行中のトラックを一つ選び、save を実行するところから始めていただければと思います。設定の持ち運びは 設定ファイルを開かずに MCP サーバーを足す — agy mcp サブコマンドで構成を持ち運ぶ に、無人実行の前に CLI の応答を確かめる方法は スケジュール実行の直前に、Antigravity CLI が本当に応答するかを1回だけ確かめる に書き残しています。