Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# module-gates - Constraints liberate, liberties constrain.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/cuzfrog/module-gates)](https://github.com/cuzfrog/module-gates/stargazers)
[![GitHub last commit](https://img.shields.io/github/last-commit/cuzfrog/module-gates/main)](https://github.com/cuzfrog/module-gates/commits/main)
[![GitHub repo size](https://img.shields.io/github/repo-size/cuzfrog/module-gates)](https://github.com/cuzfrog/module-gates)
[![CI](https://github.com/cuzfrog/module-gates/actions/workflows/ci.yml/badge.svg)](https://github.com/cuzfrog/module-gates/actions/workflows/ci.yml)

[English](README.md) · [简体中文](README.zh.md) · **日本語**

モジュール境界を強制することでコードベースのエントロピーを制御し、コードのSlopsと戦うためのフック。

対応しているエージェントフレームワーク:
- **pi** — pi 拡張機能
- **Claude Code** — プラグイン、または CLI でインストールしたプレーンフック

他のエージェント(qwen-code、cursor 等)への対応はブリッジを追加することで実現できます。

### アプローチ

**モジュール契約をガードレールとして。** 各ディレクトリには記述子ファイル(デフォルト `MODULE.md`)を含めることができ、以下を宣言します:

- `readonly` — ファイルを編集不可にする
- `no-new-exports` — 新しいエクスポートを禁止(ファイル本体は編集可能)

拡張機能はエージェントの `write`/`edit` 操作を傍受し、これらの契約を強制します。違反は理由とともにブロックされます。

2 つのパブリックヘルパー関数を追加しようとする試みはブロックされ、エージェントは設計を再考せざるを得なくなる。
![モジュールゲート拒否の例](doc/module_gates_block.png)

### 仕組み

1. **Indexing** — セッション開始時に、プロジェクトツリーの記述子ファイルをスキャンし、モジュールインデックスを構築する。
2. **System prompt** — エージェントが記述子ファイルの構約を認識できるようにヒントを注入する。
3. **Gating** — 書き込み/編集のたびにチェックする:
- **Readonly gate** — 対象ファイルはロックされているか?
- **No-new-exports gate** — 変更が `no-new-exports` リスト内のファイルに新しいエクスポートを追加するか?
- **Module interface import gate** — 外部ファイルはモジュールインターフェース(TypeScript の `index.ts`、Rust の `mod.rs` など)を通じてのみインポートできる。子モジュールは親モジュールの内部ファイルをインポートできる(推奨はされないが許可されている)。(TypeScript/JavaScript と Rust のみ対応)
- **Import gate**(未実装)— 変更が可視性スコープに違反するインポートを導入するか?

- System prompt: [system-prompt.md](src/core/context/system-prompt.template.md)
- 現在 [対応言語](src/core/gates/checkers/index.ts):**TypeScript/JavaScript**、**Rust**、**Java**、**Go**、**Kotlin**、**Scala**

## インストール

### pi
```bash
pi install npm:@cuzfrog/module-gates
```
または単一セッションで直接読み込む:
```bash
pi -e npm:@cuzfrog/module-gates
```

### Claude Code

このリポジトリのマーケットプレイスからプラグインとして(ログイン不要 — 公開リポジトリ):
```
/plugin marketplace add cuzfrog/module-gates
/plugin install module-gates@cuzfrog
```
初回のフック実行時に、プラグインはランタイム依存関係をデータディレクトリにインストールする。

または、通常のフックとしてプロジェクトに接続する(プロジェクトにパッケージをインストールする必要あり):
```bash
npm install --save-dev @cuzfrog/module-gates
npx module-gates install-claude
```
これは `PreToolUse` と `SessionStart` フックを `.claude/settings.json` に書き込む;`npx module-gates uninstall-claude` で削除する。`SessionStart` フックは自動的にシステムプロンプトヒントを注入する。

または既存の pi インストールを再利用し、`~/.claude/settings.json` でフックを手動で指す:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "node \"$HOME/.pi/agent/npm/node_modules/@cuzfrog/module-gates/src/bridges/claude/run.mjs\" pre-tool-use"
}
]
}
],
"SessionStart": [
{
"matcher": "startup|resume|clear",
"hooks": [
{
"type": "command",
"command": "node \"$HOME/.pi/agent/npm/node_modules/@cuzfrog/module-gates/src/bridges/claude/run.mjs\" session-start"
}
]
}
]
}
}
```
pi のインストールディレクトリは異なる場合がある;pi npm ルテム下の `run.mjs` を探す。`SessionStart` フック(システムプロンプト注入)は省略可能 — `PreToolUse` のみでゲートを強制する。

## モジュール記述子の意味論

モジュール記述子はディレクトリに配置される Markdown ファイル(デフォルト名:`MODULE.md`)である。モジュールコンテキストファイル(例:`CONTEXT.md`)に乗っ取ることができる。`MODULE.md` は自身のディレクトリのみを強制する。

### 読み取り専用の制約

```markdown
---
readonly: [mod.rs]
---

エージェントがモジュールをよりよく理解するための説明文。
```

### 追加エクスポート禁止の制約

```yaml
no-new-exports: [mod.rs]
```
追加エクスポート禁止のファイルは、その表面サイズを変更できない:新しいエクスポートや公開エントリは許可されない。ファイル本体は引き続き編集可能。

[module-no-new-exports-all](skills/module-no-new-exports-all) スキルが含まれており、モジュール内に追加エクスポート禁止エントリを記入する。

| シナリオ | 動作 |
|----------|------|
| `MODULE.md` なし | モジュールは制約なし — 何もゲートされない。 |
| YAML frontmatter 形式が不正 | モジュールは保護されず、通知が発行される。 |

## 設定

正規のアージェント非依存の場所は `.module-gates/config.json`(ファイル全体が設定であり、ラッパーキーはない)。そのファイルが存在しない場合、各ブリッジはエージェント設定ファイルの `module-gates` キーに下記する — 例えば `.pi/settings.json`、`.claude/settings.json`。

```json
{
"module-gates": {
"moduleDescriptorFileName": "MODULE.md",
"moduleDescriptorReadonly": "off",
"sourceRoots": ["src/"],
"outputModuleProseOnBlock": false
}
}
```

| オプション | デフォルト | 説明 |
|-----------|-----------|------|
| `moduleDescriptorFileName` | `MODULE.md` | モジュール記述子のファイル名(大文字小文字を無視) |
| `moduleDescriptorReadonly` | `"off"` | `"file"` は記述子全体を読み取り専用にし、`"frontmatter"` は YAML frontmatter のみをロック(ボディの説明文は編集可能)、`"off"` は記述子の読み取り専用を無効にする。`true`/`false` も後方互換性のために受け入れられる。 |
| `sourceRoots` | `["src/"]` | 記述子ファイルをスキャンし、ゲートを強制するディレクトリ。単一のルートには文字列を、配列には複数のルート(例:マルチリポジトリで `["packages/app/src/", "packages/lib/src/"]`)を指定できる。`[""]` でプロジェクトルートからスキャンする。レガシーの単数形 `sourceRoot`(文字列)は引き続きサポートされる。 |
| `disableModuleInterfaceImportGate` | `false` | `true` の場合、インポートはモジュールインターフェースから強制されない。 |
| `disableSystemPrompt` | `false` | `true` の場合、エージェントのシステムプロンプトへのモジュールゲートヒントの注入をスキップする。 |
| `outputModuleProseOnBlock` | `false` | `true` の場合、違反モジュール記述子の説明文がブロックメッセージに追加され、エージェントが契約のコンテキストを確認できるにする。デフォルトでは無効で、エラーメッセージを簡潔に保つ。 |

設定ファイルが存在しない場合、または `module-gates` キーが存在しない場合は、デフォルトが適用される。

## トラブルシューティング
ヒント:
```
PreToolUse hook `module-gates` がトリガーされ、期待通りに実行されているか確認してください。
```

## ライセンス

MIT

## 作者
Cause Chung (cuzfrog@gmail.com)
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
# module-gates - Constraints liberate, liberties constrain.

Coding-agent extension that controls the entropy of the codebase by enforcing code module boundaries.
It helps combat slop generation and code architecture degradation.
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/cuzfrog/module-gates)](https://github.com/cuzfrog/module-gates/stargazers)
[![GitHub last commit](https://img.shields.io/github/last-commit/cuzfrog/module-gates/main)](https://github.com/cuzfrog/module-gates/commits/main)
[![GitHub repo size](https://img.shields.io/github/repo-size/cuzfrog/module-gates)](https://github.com/cuzfrog/module-gates)
[![CI](https://github.com/cuzfrog/module-gates/actions/workflows/ci.yml/badge.svg)](https://github.com/cuzfrog/module-gates/actions/workflows/ci.yml)

The core is agent-agnostic; per-agent **bridges** connect it to each agent:
**English** · [简体中文](README.zh.md) · [日本語](README.ja.md)

Hooks that controls the entropy of the codebase by enforcing module boundaries, helping combat slops.

Supported agent harnesses:
- **pi** — pi extension
- **Claude Code** — plugin, or plain hooks installed by the CLI

Adding support for another agent (qwen-code, cursor, ...) means adding a bridge.

## Problem

AI coding agents produce edits with limited context knowledge (myopia) — their changes may leak implementation details, and break architectural contracts (slop).

### Approach

**Module contracts as guardrails.** Each directory can contain a descriptor file that declares:
**Module contracts as guardrails.** Each directory can contain a descriptor file (default `MODULE.md`) that declares:

- `readonly` — files and directories the agent must not touch
- `readonly` — files cannot be edited
- `no-new-exports` — files where no new exports are allowed (body still editable)

The extension intercepts agent `write`/`edit` operations and enforces these contracts. Violations are blocked with a clear reason.
The extension intercepts agent `write`/`edit` operations and enforces these contracts. Violations are blocked with a reason.

The attempt to add 2 public helper functions is blocked, forcing the agent to re-think the design.
![Module Gate denial example](doc/module_gates_block.png)
Expand Down Expand Up @@ -124,17 +127,15 @@ A skill [module-no-new-exports-all](skills/module-no-new-exports-all) has been i
| No `MODULE.md` | Module is unconstrained — nothing is gated. |
| Malformed YAML frontmatter | The module is left unguarded and an info notification is emitted. |

> The `visible` export whitelist was removed pending a redesign — see [doc/visible.md](doc/visible.md).

## Configuration

The canonical agent-independent location is `.module-gates/config.json` (the whole file is the config, no wrapper key). When it is absent, each bridge falls back to the agents' settings files under a `module-gates` key — pi reads `.pi/settings.json` then `.claude/settings.json`; Claude Code reads `.claude/settings.json` then `.pi/settings.json`. The first existing source wins.
The canonical agent-independent location is `.module-gates/config.json` (the whole file is the config, no wrapper key). When it is absent, each bridge falls back to the agents' settings files under a `module-gates` key — e.g. `.pi/settings.json`, `.claude/settings.json`.

```json
{
"module-gates": {
"moduleDescriptorFileName": "MODULE.md",
"moduleDescriptorReadonly": "file",
"moduleDescriptorReadonly": "off",
"sourceRoots": ["src/"],
"outputModuleProseOnBlock": false
}
Expand All @@ -144,7 +145,7 @@ The canonical agent-independent location is `.module-gates/config.json` (the who
| Option | Default | Description |
|--------|---------|-------------|
| `moduleDescriptorFileName` | `MODULE.md` | File name used for module descriptors (case-insensitive) |
| `moduleDescriptorReadonly` | `"frontmatter"` | `"file"` makes the whole descriptor readonly; `"frontmatter"` locks only the YAML frontmatter (body prose stays editable); `"off"` disables descriptor readonly. `true`/`false` are also accepted for backward compatibility. |
| `moduleDescriptorReadonly` | `"off"` | `"file"` makes the whole descriptor readonly; `"frontmatter"` locks only the YAML frontmatter (body prose stays editable); `"off"` disables descriptor readonly. `true`/`false` are also accepted for backward compatibility. |
| `sourceRoots` | `["src/"]` | Directories to scan for descriptor files and enforce gates. Pass a single string for one root, or an array for multiple roots (e.g. monorepos with `["packages/app/src/", "packages/lib/src/"]`). Use `[""]` to scan from the project root. Legacy singular `sourceRoot` (string) is still accepted. |
| `disableModuleInterfaceImportGate` | `false` | When `true`, imports will not be forced to be from module interface. |
| `disableSystemPrompt` | `false` | When `true`, skip injecting the module-gates hint into the agent's system prompt. |
Expand Down
Loading
Loading