setup-ts-deep-modules について
TypeScriptリポジトリでdependency-cruiserを設定し、ディープモジュールの境界を適用するスキルです。公開するルートのエントリーポイント、非公開のサブフォルダー、循環チェック、テスト、文書を整えます。
主な機能
- 01dependency-cruiserの境界ルール設定
- 02外部からのインポートをエントリーポイントに限定
- 03パッケージ内部の相互インポートを許可
- 04依存関係の循環を検出
- 05深いインポートの失敗テストでルールを検証
- 06パッケージのひな型と境界文書を追加
活用シーン
- 01TypeScriptリポジトリでパッケージのカプセル化を適用
- 02深いインポートと依存関係の循環を防止
- 03再利用できるパッケージ構成とチェックを確立
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ leandrocfe/ skills --skill setup-ts-deep-modulesインストール後、Claude Code で/setup-ts-deep-modulesを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamldependency-cruiser.config.cjs
詳細
TS のディープモジュールをセットアップする
このリポジトリの各パッケージをディープモジュールにします。つまり、小さなインターフェースの背後に多くの振る舞いを隠します。パッケージの公開面はエントリーポイント、すなわちパッケージのルートにあるファイルです。サブフォルダー内のものはすべて隠されます。このスキルでは、dependency-cruiser と、エントリーポイントだけを入口にするルールを導入し、そのルールが実際に機能することを確認します。
用語(ディープモジュール、インターフェース、シーム、深さ)については、/codebase-design スキルを実行し、作業全体でその言葉遣いを使ってください。
このスキルで適用する形式
src/packages/
<name>/
index.ts ← um entry point (público). Importe este de fora.
client.ts ← outro entry point. Packages podem expor VÁRIOS.
lib/ ← implementação: escondida de fora, livre pra importar entre si.
tests/ ← testes co-localizados + fixtures (uma subpasta, então privada).
公開面はパッケージのルートファイルです。特定の index.ts に限定されません。慣例として、実装は lib/ に、テストは tests/ に配置し、すべてのパッケージが同じ2つのフォルダー構成になるようにします。ただし、ルール自体は一般的なものです。どのサブフォルダーにあるものもすべて非公開なので、フォルダーを追加するために設定を拡張することはありません。
ルールは4つあり、すべて error です。
- エントリーポイントの境界 — パッケージの外部にあるコード(アプリケーションコードや別のパッケージ)は、そのパッケージのエントリーポイント(ルートファイル)だけをインポートできます。サブフォルダー内のものはインポートできません。
- パッケージ内の自由なインポート — パッケージ自身のファイル同士は自由にインポートできます。
- エントリーポイント経由でのテスト —
<pkg>/tests/内のファイルは、あらゆるパッケージのエントリーポイントと、自身のtests/内のフィクスチャをインポートできます。ただし、どのパッケージのサブフォルダー内の実装も(自身のものも含めて)インポートできません。パッケージ間の統合テストは問題ありませんが、ディープインポートは禁止です。 - 循環依存なし — 依存関係の循環を禁止します。
バレルではなくエントリーポイント。 公開面はルートにあるすべてのファイルなので、パッケージはすべてを巨大な index.ts に集約する代わりに、複数の小さなエントリーポイント(index.ts、client.ts、server.ts)を公開できます。サブツリー全体を再エクスポートするバレルファイルは推奨されません。エントリーポイントは小さく保ち、実装はサブフォルダーに隠してください。
どのパッケージがどのパッケージに依存できるかというレイヤリングは、別の関心事です。設定内のコメントアウトされたスタブとして残し、このリポジトリで追加します。
手順
1. 環境を検出する
- パッケージマネージャー —
pnpm-lock.yamlがあれば pnpm、yarn.lockがあれば yarn、bun.lockbがあれば bun、いずれもなければ npm です。以下のすべてのコマンドでそれを使います(pnpm/yarn/npm run/bunx)。 - パッケージのルート —
src/があればsrc/packages、なければpackagesを使います。リポジトリにすでに明らかな別の慣例がある場合は、選択をユーザーに確認してください。 - 既存の設定 —
.dependency-cruiser.*ファイルがあるか確認します。存在する場合は上書きせず、4つのルールとオプションをマージして、追加した内容をユーザーに伝えてください。
完了条件: パッケージマネージャー、パッケージのルート、既存設定の有無がすべて判明していること。
2. dependency-cruiser をインストールする
検出したパッケージマネージャーを使って、dependency-cruiser を devDependency としてインストールします。
完了条件: dependency-cruiser が devDependencies に含まれていること。
3. 設定を書く
dependency-cruiser.config.cjs をリポジトリのルートに .dependency-cruiser.cjs としてコピーします。手順1で検出したルートに合わせて PACKAGES_ROOT を調整します。ルールはパスの深さに基づき、拡張子に依存しないため、ほかに変更する必要はありません。
完了条件: 正しい PACKAGES_ROOT を指定した .dependency-cruiser.cjs があり、4つの forbidden ルールがすべて含まれていること。
4. チェックに組み込む
depcruise <packages-root>(またはdepcruise src)を実行するlint:boundariesスクリプトを追加します。- リポジトリの包括的なコマンド、つまりすでに型チェックを実行しているコマンド(例:
check/ci/validateスクリプト)に組み込みます。tsconfigは変更せず、パスエイリアスも追加しないでください。 - 包括的なスクリプトがない場合は
lint:boundariesを追加し、CI に含めるようユーザーに伝えてください。
完了条件: lint:boundaries が存在し、型チェックと同じコマンドの一部として実行されること。
5. サンプル package のスキャフォールドを作成する
テンプレートとしてコピーして使える <packages-root>/example/ をコミットします。
index.ts— エントリーポイントです。内部ファイルに処理を委譲する関数をエクスポートします(package が明確に 深い 構造になるよう、単なるパススルーにはしません)。lib/impl.ts— サブフォルダー内の内部ファイルです。index.tsからインポートし、外部からは到達できないようにします。tests/example.test.ts—../index(エントリーポイント)のみをインポートし、公開関数に対するアサーションを行います。
これはコピーするか削除するための初期テンプレートであることをユーザーに伝えます。
完了条件: サンプル package が存在し、ルートのエントリーポイントを通じて動作を公開し、impl がサブフォルダー内に隠されていること。
6. ルールが機能することを証明する
これはスキル全体の完了条件です。違反があっても失敗しない設定には意味がありません。
lint:boundariesを実行します。クリーンなサンプルでは成功する必要があります。tests/example.test.tsに一時的にディープインポートを追加します(例:import { thing } from "../lib/impl")。もう一度lint:boundariesを実行します。tests-through-entrypointsで失敗する必要があります。- ディープインポートを元に戻します。もう一度実行すると成功する必要があります。
完了条件: 成功、ディープインポートで失敗、再び成功することを確認していること。手順 2 で失敗しない場合、ルールが正しく有効になっていません。完了する前に修正してください。
7. 規約を文書化する
package を置くフォルダー(<packages-root>/README.md)に、管理対象の package と同じ階層に README.md を作成し、次の内容を説明します。src/packages/<name>/ のレイアウト(ルートにエントリーポイント、実装に lib/、テストに tests/ を使用)、"package のエントリーポイント(ルートにあるファイル)からのみインポートすること"、および lint:boundaries の実行方法です。barrel file は明示的に避けるよう説明してください。サブツリー全体を index から再エクスポートするのではなく、小さなエントリーポイントを複数公開します。コピー用スニペットには、4 つのルールをそれぞれ 1 段落で記載します。
次に、リポジトリのエージェント指示ファイルから、この README へのコンテキストポインターを追加します。CLAUDE.md があればそれを、なければ AGENTS.md を使用します(どちらも存在しない場合は AGENTS.md を作成します)。1 行で十分です。例:Packages は deep modules です — 追加やインポートの前に [src/packages/README.md](./src/packages/README.md) を参照してください。 これにより、エージェントが境界ルールにぶつかる前にその存在を把握できます。
完了条件: <packages-root>/README.md が存在し、barrel を避けるよう説明していて、リポジトリの CLAUDE.md/AGENTS.md からリンクされていること。
注意事項
- 設定内のバックリファレンス
$1(dependency-cruiser のグループマッチング)によって、package は自身の内部にはアクセスでき、外部の package からはアクセスできないようになります。これを package ごとの個別ルールに分割しないでください。 - 公開と非公開は深さで決まります。package のルートにあるファイルはエントリーポイントで、サブフォルダー内のものはすべて非公開です。慣例的なサブフォルダーは実装用の
lib/とテスト用のtests/ですが、ルールで固定されているわけではありません。どのサブフォルダーも非公開になるため、新しいフォルダーを追加しても設定を変更する必要はありません。エントリーポイントの追加は、barrel を使わずにルートファイルを 1 つ追加するだけです。 - package はフラットです。ルート直下に子 package を 1 階層だけ置きます。package 内部のネストの深さに制限はありません。package の中に別の package を置くことはできません。
"type": "module"のリポジトリでも設定のmodule.exportsが動作するよう、.jsではなく.cjsを使用します。