CTS-KB

ハーネスが単一プロジェクトを卒業する — cts-harness プラグイン化・二層配布・バージョンピン【番外編】

⏱ 約 11 分で読めます
#Claude Code#ハーネスエンジニアリング#プラグイン#マルチプロジェクト展開#バージョン管理#ステアリング

📖 はじめに — エンジンを配る日

前回の番外編までは、エンジンの運用の話だった — CI の速度、モデルの世代交代、サブスク枠の経済。今回はエンジンそのものが cts-ec の外へ出る話である。

CTS の製品群は CTS-EC で終わらない。**CTS-POS(AI 駆動型 POS クラウド)・CTS-LOGI(3PL 物流)・CTS-AD(SNS 広告)**が続けて立ち上がる。4 プロジェクトはデータ基盤を共用しながら相互補完する製品群で、開発スタックも .NET + Angular + Python の CTS 共通だ。

ここで選択を迫られる。このシリーズが 3 か月かけて cts-ec に育てたハーネス — skills 16・agents 16 体・commands 12・hooks 10 本(+ 仕様固定テスト 5 本)・必須ルール 12 個 — を、新プロジェクトへどう持ち込むか。一番安直な答えはリポジトリごとのコピーだが、それは4 冊の帳簿が独立に進化を始めるということでもある。EC で直した hook のバグが POS に残る。POS で足したルールが LOGI に来ない。番外編 3 本ぶんの改良は、コピーした瞬間の断面で凍結される。差分地獄の未来が見えているなら、コピーの前に切り出すべきだ。

結果を先に書く。

項目中身
切り出し先共通リポジトリ cts-harness(marketplace 兼プラグイン・初版 v0.1.0 → 同日 v0.2.0)
プラグイン層skills 16 / agents 16 / commands 12 / MCP サーバ定義 — marketplace 登録だけで自動ロード
dist 層必須ルール 12 個 / hooks 10 本 / codex ツール / 運用ドキュメント 2 本 / settings 雛形 — sync.sh でコピーし各リポジトリに commit
隔離機構プロジェクトスコープの version + commit SHA ピン(dist 層は HARNESS_VERSION)
初回導入cts-pos(Phase 0・要件定義中)

ただし本稿の主役は「共通化しました」ではない。どこで層を切るか、そして何を共有しないか — 線引きのほうである。

🧱 二層分割 — プラグインで配れるもの、リポジトリの一部であるべきもの

Claude Code のプラグイン機構は、リポジトリ自体を marketplace にできる。cts-harness の場合、リポジトリ直下に marketplace 定義とプラグイン定義を置き、導入側は settings.json に marketplace の git URL とプラグイン名を書くだけ。これで skills / agents / commands / MCP サーバ定義が自動ロードされる。

だが、ハーネスの全部品がこの経路に乗るわけではない。hooks は settings.json から ./scripts/hooks/*.sh の相対パスで呼ばれる実行物であり、共通ルール文書 CLAUDE-shared.md は各リポジトリの CLAUDE.md 冒頭から @import される参照先だ。どちらもリポジトリ内に実ファイルとして存在することが前提の部品である。ここを無理にプラグイン側へ寄せず、sync.sh で各リポジトリへコピーして commit する dist 層として割り切った。

配布経路中身更新が効く瞬間
プラグイン層marketplace 登録 → 自動ロードskills / agents / commands / MCP 定義claude plugin update を叩いたとき
dist 層sync.sh コピー → 各リポジトリで commitCLAUDE-shared.md(必須ルール 12 個)/ hooks / codex ツール / 運用ドキュメント / settings 雛形sync 後の commit が merge されたとき

dist 層をリポジトリに commit する設計には、狙い以上の副産物があった。hooks は clone すれば動く。 プラグインの導入状態にも Claude Code のキャッシュにも依存しない。ハーネスのうち「破ってはいけないルールを物理で止める」部分 — main 直 push ガード、テストスタンプ、codex レビューゲート(TDD×ハーネス編の三層ゲート) — が、追加機構なしの素のリポジトリで成立しているのは、事故耐性として悪くない。

📜 ホストリポジトリ契約 —「静かに exit 0」で Phase 0 でも動く

プラグインの commands / hooks は、ホスト側リポジトリが一定のパスを供給している前提で動く。この前提は暗黙にせず、README に契約として明文化した。

ホストが供給するもの供給元
scripts/hooks/・steering 自動ロード・codex ツールsync.sh(dist 層)
全テストランナー(/test が使用)各リポジトリが実装
アクティブステアリング・全体仕様(北極星)各リポジトリ
dotnet / frontend / python のコード配置各リポジトリ

問題は、導入初日のリポジトリはこの契約をほぼ満たせないことだ。初回導入先の cts-pos は Phase 0 — 主要機能の仕様が未確定で、dotnet/ も frontend/ もまだ存在しない。そこで hooks 側に、対象ディレクトリが存在しなければ静かに exit 0 するガードを全数入れた。契約不履行を騒がず、履行が始まった瞬間からゲートが効き始める。挙動変更として許容したのは 1 点だけ — フロントエンド構成チェックは CI ゲート未導入リポジトリでは skip する。

結果、cts-pos の CLAUDE.md は @import 1 行 + POS 固有ルール 2 本(仕様未確定機能の推測実装禁止 / 共用データ基盤に触れる変更の他プロジェクト影響分析必須)だけの薄さで立ち上がった。共通ルール 12 個は import 先が供給し、POS 側には POS の事情だけが書いてある。CLAUDE.md の薄さは、共通化が正しく効いていることの観測値である。

🚫 共有してはいけないもの — 線引きのほうが設計だった

共有リストより先に確定させたのは、共有禁止リストだ。

共有しないもの理由
業務固有スキル(EC のモール API スキル等)ドメイン知識は各プロジェクトの資産。共通層に業務が混じると全員が EC 方言を話し始める
.claude/agent-memory/エージェントの学習内容は業務固有。本体は共通でも、学びは各リポジトリに積む
.steering/ と docs/(運用ドキュメント 2 本を除く)ステアリングは各プロジェクトの意思決定履歴であって、配布物ではない
settings の permissions・プロジェクト固有 hooks許可リストは各プロジェクトのリスクの関数。雛形は配るが、確定は各リポジトリ

このうち agent-memory の線は、導入直後からさっそく機構どおりに働いている。cts-pos では、プラグイン配布の design-reviewer エージェントが北極星ドキュメントのレビューで学んだ内容を、POS リポジトリ側の agent-memory に積み始めた。エージェント本体は共通・学習はプロジェクト別 — この分離が崩れると、EC のドメイン知識が POS の設計判断に混入する。

汎用化の実作業も記録しておく。EC 固有の固有名詞(社内システム名・モール API・STG DB 接続ルール)の除去または「cts-ec の例」への格下げ、cts-ec ADR 参照の出典注記化、テストスタンプのパスを /tmp/<リポジトリ名>-* 導出へ統一(cts-ec では従来パスと完全互換 — 母体を壊さない移行)、スキルに紛れ込んでいた上流サンプル由来の無関係ファイルの削除。地味な掃除だが、これを怠ると「共通ハーネス」は名ばかりで、中身は EC の抜け殻になる。

📌 バージョンピン —「EC だけ先行しても他は動かない」の実体

リリースの運用は単純だ。MR → merge → CHANGELOG 追記 → git tag vX.Y.Z(プラグイン定義の version と一対一で対応させる)。ただし「タグで呼ぶから安全」という説明は正確ではない。隔離を実際に担っているのはタグではなく、インストール側の台帳である。

Claude Code はプラグインを導入すると、インストール台帳(~/.claude/plugins/installed_plugins.json)にプロジェクトスコープでバージョンとコミット SHA を記録し、実行時にはバージョン付きキャッシュのスナップショットを読む。導入直後の cts-pos の台帳はこうなっている。

"cts-harness@cts": [
  {
    "scope": "project",
    "installPath": "~/.claude/plugins/cache/cts/cts-harness/0.1.0",
    "version": "0.1.0",
    "gitCommitSha": "bd42b2e0...",
    "projectPath": ".../cts-pos"
  }
]

この構造から、欲しかった性質が 3 つ導かれる。

  1. main が進んでも何も起きない。 実行時に読まれるのはインストール時点のスナップショットだ。EC 都合の変更が cts-harness の main に入っても、POS が claude plugin update を叩くまで POS の挙動は 1 bit も変わらない
  2. バージョン併存が成立する。 キャッシュパスにバージョンが含まれるため、同一マシン上で EC = 0.2.0 / POS = 0.1.0 が並んで動ける
  3. dist 層にも台帳がある。 sync.sh が配布時に HARNESS_VERSION を書き込む。プラグイン層・dist 層の両方で「このリポジトリはどの版のハーネスか」が観察可能になる

その上で、運用原則を README に明文化した — 一括自動伝播はしない。各プロジェクトが CHANGELOG を読み、任意のタイミングで plugin update + sync 再実行で追従する。git tag は依存を固定する装置ではなく、CHANGELOG・プラグイン version・台帳の記録を一致させるためのリリース目印である。「Tag 付きで呼ぶ」の実体は、この二段構え — リリース目印としてのタグと、隔離を担うプロジェクト別ピン — だ。

🔍 検証の台帳 — 「未実走」が同日中に 1 つ消えた

evidence-gate の流儀で、本稿の主張の検証状態を分けて閉じる — つもりだった。初稿のこの節には「バージョンアップサイクルは未実走(タグは v0.1.0 の 1 本だけ)。実証は初回バージョンアップ時に追記する」と書いてあったのだが、その数時間後に初回サイクルが実走したので、台帳を更新して閉じる。

  • 観察済み(初稿時点): version + SHA のプロジェクト別ピン、バージョン付きキャッシュ、cts-pos での hooks 静音ガード動作、agent-memory の分離。いずれも現物のファイルと挙動で確認した
  • 実走済み(同日追記): バージョンアップサイクル。前回番外編の 3 効率化(read-only 上流調査・段別モデル・verifier バッチ化)を共通層へ移植して v0.2.0 を切った — EC 固有実装として生まれた改良が、翌日にはハーネスの共通資産へ昇格した格好だ。リリースは cts-pos に何も起こさず(台帳は 0.1.0 のピンのまま)、POS 側で claude plugin update + sync 再実行を明示的に叩いたときだけ 0.2.0 へ動いた。更新後はインストール台帳・HARNESS_VERSION の両方が 0.2.0・新 SHA で一致し、テストも通った。「main が進んでも、叩くまで何も起きない」は、設計意図から観測事実になった
  • これから: cts-ec 自身のプラグイン切替。母体である EC は現時点でリポジトリ内ハーネスのまま運用しており、プラグイン導入済みは cts-pos のみ。切り出し時に互換を保った(テストスタンプの従来パス互換)のは、この移行を差分最小で行うためでもある。そして「EC = 0.2.0 / POS = 0.1.0」のような消費プロジェクト間のバージョン分岐は、消費者が 2 つになって初めて本番で観測できる — EC 切替後の宿題として残る

🧾 まとめ — 配れる形にして初めて、ハーネスは資産になる

学びを 3 行に圧縮する。

  1. 共通化の設計は「何を共有するか」より「何を共有しないか」で決まる。 agent-memory・業務スキル・permissions・ステアリングを各リポジトリに固定したことで、共有層には「どのプロジェクトでも同じであるべきもの」だけが残った。禁止リストを先に確定させると、共有リストは自動的に決まる
  2. 配布経路は 1 本に統一しない。 自動ロードが正しい層(skills / agents / commands)と、リポジトリの一部であることが正しい層(hooks・共通ルール)では、正解の配布機構が違う。無理に片方へ寄せず二層に割り、それぞれにバージョン台帳を持たせれば運用は濁らない
  3. 「影響しない」は機構にしてから主張する。 非強制追従はスローガンではなく、プロジェクトスコープの version + SHA ピンという観察可能な実体に落とした。未実走の部分は未実走と書く — そう書いた初稿の数時間後に初回サイクルが実走し、主張は観測に変わった。検証が済むまで書かない、済んだら即日書き換える。検証エンジンを配る話で、検証状態を粉飾したら台無しである

終章は「完成とは、自分の劣化を自分のデータで検出するループが閉じること」と書いた。そのループはこれまで cts-ec の中で閉じていた。本稿は、そのエンジンが単一プロジェクトを卒業する準備の記録である。次にこのエンジンの記事を書くときは、4 冊の帳簿が同じ骨格の上で、それぞれのページを進んでいるはずだ。

📚 シリーズ記事(ステアリング駆動開発・実践編)

序章

  1. AI 駆動開発が積み上げる技術的負債

第I部: 技術的負債ドメイン別の実録(総点検ガイド + 8 ドメイン・全 9 回)

  1. リファクタリング総点検ガイド — 7 つの観点と進め方
  2. DDD/SOLID/BC 編 — god class 一掃と境界の機械ガード
  3. SAGA 編 — 新アーキテクチャ挑戦と再発ゲート
  4. Atomic Design リファクタ編 — 47 page 新規移植
  5. Storybook × a11y 実機編
  6. テスト品質・網羅性編
  7. Python ジョブ群編
  8. パフォーマンス編
  9. セキュリティ編

第II部: 検証エンジン(クロスファミリー検証・全 6 回)

  1. 総論 — マルチ LLM の 2 系統と見取り図
  2. 裏取り編 — +18.1pt 論文の検証
  3. 設計編 — finder/verifier 分業と逆順禁止
  4. TDD×ハーネス編 — テスト保護と三層ゲート
  5. 実装編 — Claude Code の中から Codex を動かす
  6. モデル戦略編 — ティア割当と「買うか組むか」

終章

  1. クロスファミリー検証の最終型 —「正しく作る」から「自分で正しさを測り直す」へ — 検証エンジンに「計測」の層をはめ、自己修正ループを閉じる

番外編

  1. CI テスト 29 分 → 5.6 分 — 検証エンジンの平時運用実録 — 実測が有力仮説を殺し、退行前より速くなった 1 日
  2. GPT-5.6 sol 切替の当日実録 — もう一つのデフォルト追従 — CLI 更新が黙って替える finder と、当日中の再計測
  3. 遊休 90% の枠に仕事を振る — Codex 上流調査と verifier バッチ化 — 逆順禁止の境界を ADR で確定し、読む仕事を遊休枠へ移した 1 日
  4. ハーネスが単一プロジェクトを卒業する — プラグイン化と二層配布(本記事)
  5. 69分で5リリース — Opus 5 当日対応が暴いた共通ハーネスの死角 — 新モデル対応を起点に、配布・文書・CI の回帰を二消費者で検出した 69 分

関連記事