setup-ts-deep-modules について
TypeScript リポジトリで、パッケージのルートファイルを公開エントリーポイント、サブフォルダーを非公開実装として扱うスキルです。dependency-cruiser のルールを設定してチェックに組み込み、違反が検出されることも確認します。
主な機能
- 01パッケージの境界ルールを設定
- 02パッケージ内の相互インポートを許可
- 03テストのエントリーポイント経由を強制
- 04依存関係の循環を検出
- 05サンプルパッケージの作成とルール検証
- 06規約とエージェント向け案内を記録
活用シーン
- 01モノレポの実装詳細を非公開にする
- 02アプリや別パッケージからの深いインポートを防ぐ
- 03テストが公開インターフェースを使うか確認
- 04新規パッケージ用の構成テンプレートを用意
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vinvcn/ mattpocock-skills-zh-cn --skill setup-ts-deep-modulesインストール後、Claude Code で/setup-ts-deep-modulesを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamldependency-cruiser.config.cjs
詳細
TS ディープモジュールのセットアップ
repo 内の各 package を ディープモジュール にします。小さな interface で多くの振る舞いを隠します。Package の公開サーフェスはその エントリーポイント(package root 内の files)であり、すべての subfolders は非公開です。この skill では dependency-cruiser をインストールし、エントリーポイント経由でのみアクセスできるように強制する rules を追加して、それらの rules が実際に違反を検出することを証明します。
語彙(deep module、interface、seam、depth)は、Skill ツールを呼び出して codebase-design を指定することで得られます。全体を通じてその語彙を使用します。
強制する構成
src/packages/
<name>/
index.ts ← an entry point (public). Import this from outside.
client.ts ← another entry point. Packages may expose SEVERAL.
lib/ ← implementation: hidden from outside, free to import each other.
tests/ ← co-located tests + fixtures (a subfolder, so private).
公開サーフェスは package ごとの root files であり、指定された単一の index.ts ではありません。慣例として、implementation は lib/ に、tests は tests/ に配置し、すべての packages が同じ 2 つのフォルダー構成になるようにします。Rule 自体は汎用的です。どの subfolder にあるどの内容も非公開なので、新しい folder を追加するたびに config を拡張する必要はありません。
4 つの rules は、すべて error です。
- エントリーポイント境界 — package 外の code(app code または別の package)は、その package のエントリーポイント(root files)のみを import できます。subfolder 内の内容は一切 import できません。
- package 内の自由な参照 — package 自身の files は、互いに自由に import できます。
- エントリーポイント経由の tests —
<pkg>/tests/内の files は、任意の package のエントリーポイントと、自身のtests/fixtures を import できますが、(自身のものも含めて)どの package の subfolder 内部も import できません。package をまたぐ integration tests は許可されますが、deep imports は許可されません。 - 循環依存なし — dependency cycles は許可されません。
エントリーポイントであり、barrel ではありません。 公開サーフェスは各 root file なので、package はすべてを巨大な index.ts にまとめることなく、複数の小さなエントリーポイント(index.ts、client.ts、server.ts)を提供できます。subtree 全体を再エクスポートする barrel files は推奨されません。エントリーポイントは小さくし、implementation は subfolders に隠します。
Layering(どの packages がどの packages に依存できるか)は別の concern なので、config に commented stub を残し、現在の repo に合わせて設定します。
手順
1. 環境を検出する
- Package manager —
pnpm-lock.yamlがあれば pnpm、yarn.lockがあれば yarn、bun.lockbがあれば bun、それ以外は npm を使用します。以降のすべての command でこれを使用します(pnpm/yarn/npm run/bunx)。 - Packages root —
src/が存在する場合はsrc/packages、そうでなければpackagesを使用します。repo に明らかに異なる慣例がすでにある場合は、ユーザーに確認します。 - 既存の config —
.dependency-cruiser.*file を確認します。存在する場合は上書きせず、4 つの rules と options をマージし、何を追加したか説明します。
完了条件: package manager、packages root、既存 config の状態がすべて明確になっていること。
2. dependency-cruiser をインストールする
検出した package manager を使用して、dependency-cruiser を devDependency としてインストールします。
完了条件: dependency-cruiser が devDependencies に含まれていること。
3. config を作成する
dependency-cruiser.config.cjs を repo root にコピーし、.dependency-cruiser.cjs という名前にします。PACKAGES_ROOT を step 1 で検出した root に設定します。Rules は path depth に基づき、extension に依存しないため、ほかの調整は必要ありません。
完了条件: .dependency-cruiser.cjs が存在し、PACKAGES_ROOT が正しく設定され、4 つの forbidden rules が含まれていること。
4. チェックに組み込む
lint:boundariesscript を追加します:depcruise <packages-root>(またはdepcruise src)。- repo ですでに typecheck を実行している umbrella check command(
check/ci/validateなど)に組み込みます。tsconfigを変更したり、path aliases を追加したりしないでください。 - umbrella script がない場合は、
lint:boundariesを追加し、ユーザーに CI へ追加するよう伝えます。
完了条件: lint:boundaries が存在し、typecheck と同じ command で実行されること。
5. サンプル package をひな形として作成する
コピー用テンプレートとして <packages-root>/example/ を作成し、commit します。
index.ts— エントリーポイントです。internal file に処理を委譲する function を export し、この package が単なる pass-through ではなく、明らかに deep であることを示します。lib/impl.ts— subfolder 内に置く internal file です。index.tsから import し、外部からアクセスできないようにします。tests/example.test.ts—../index(エントリーポイント)のみを import し、public function を対象に assert します。
これはコピーまたは削除できる starter template であることをユーザーに伝えます。
完了条件: example package が存在し、root entry point を通じて振る舞いを公開し、impl を subfolder に隠していること。
6. Rules が違反を検出することを証明する
これは skill 全体の完了条件です。違反があっても失敗しない config に価値はありません。
lint:boundariesを実行します。clean な example は pass しなければなりません。- 一時的に
tests/example.test.tsにimport { thing } from "../lib/impl"などの deep import を追加します。lint:boundariesを再実行し、tests-through-entrypointsで fail しなければなりません。 - deep import を revert し、もう一度実行して pass することを確認します。
完了条件: pass、deep import 時の fail、復元後の pass を確認できていること。Step 2 で失敗しない場合は、wiring を先に修正してください。タスクを完了してはいけません。
7. Document the convention
在 packages folder(<packages-root>/README.md)中写 README.md,内容覆盖:src/packages/<name>/ layout(root 中是 entry points、lib/ 放 implementation、tests/ 放 tests)、“只通过 package 的 entry points(root files)import”,以及如何运行 lint:boundaries。明确 discourage barrel files,用多个小 entry points,而不是从一个 index re-export 整个 subtree。内容只保留 copy-me snippet,以及四条 rules 各一段。
再从 repo 的 agent-instructions file 指向它:优先 CLAUDE.md,否则 AGENTS.md;两者都不存在则创建 AGENTS.md。一行即可,例如:Packages are deep modules — see [src/packages/README.md](./src/packages/README.md) before adding or importing one. 这让 agent 能发现 boundary rule,而不是撞上它。
Done when: <packages-root>/README.md 存在且 discourages barrels,repo 的 CLAUDE.md/AGENTS.md 链接到它。
Notes
- Config 中的
$1back-references(dependency-cruiser group matching)让 package 能访问自己的 internals,同时阻止 outsiders;不要把它们展开成每 package 一条 rule。 - Public/private 由 depth 决定:package root files 是 entry points,subfolder 中的一切都是 private。Convention 是
lib/和tests/,但 rule 不 hardcode;新增 folder 无需改 config,新增 entry point 只需新增 root file,不需要 barrel。 - Packages 是 flat:root 下只有一层 immediate children。Package internals 可以任意深,但 package 不能包含另一个 package。
- 使用
.cjs(不是.js),确保即使 repo 使用"type": "module",config 的module.exports也能工作。