pi-module-gates - Constraints liberate, liberties constrain.
Experimental pi cli extension that controls the entropy of the codebase by enforcing code module boundaries.
It helps combat slop generation and code architecture degradation.
- Claude Code supported via a hook, see below.
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:
readonly — files and directories the agent must not touch
sealed — files where no new exports are allowed (body still editable)
visible — the set of exports allowed to be added or modified in that module
The extension intercepts agent write/edit operations and enforces these contracts. Violations are blocked with a clear reason.
The attempt to add 2 public helper functions is blocked, forcing the agent to re-think the design.

How it works
- Indexing — On session start, scans the project tree for descriptor files and builds a module index.
- System prompt — Injects a hint so the agent knows to respect descriptor file conventions.
- Gating — On every write/edit, checks:
- Readonly gate — is the target file locked?
Sealed gate — would the change add new exports to a file in the
sealed list?
- Export gate — would the change introduce an export not in the
visible list?
- Module interface import gate — external files can only import from the module not internal files, i.e. re-exports from
index.ts or mod.rs. A child module may import from a parent module's internal files (not recommended but allowed). (Only Typescript/JavaScript and Rust are supported)
- Import gate (not implemented yet) — would the change introduce an import violating visibility scope?
Installation
pi install npm:@cuzfrog/pi-module-gates
Or load directly for a single session:
pi -e npm:@cuzfrog/pi-module-gates
Module Descriptor Semantics
A module descriptor is a Markdown file (default name: MODULE.md) placed in a directory. You can piggy-back on your module context file for example CONTEXT.md.
Readonly constraints
---
readonly: [mod.rs]
---
Any prose for the agent to better understand the module.
Sealed constraints
sealed: [mod.rs]
Sealed files cannot change their surface size: no new exports or public entries are allowed. The file body is still editable.
A skill module-seal-all has been included to auto-seal modules.
Visibility whitelist (under redesign)
visible:
- greet
- sub/mod1/Foo
or:
visible:
- path: my_function
modifier: pub(crate)
visible key absent or no MODULE.md | Module is unconstrained — exports are not gated. Equivalent to null internally. |
visible: [] | Module is fully closed — no new exports may be added. Editing existing exports is still allowed. |
| Malformed YAML frontmatter | The module is left unguarded and an info notification is emitted. |
Export gating
project/
MODULE.md visible: [Foo, Bar]
src/
MODULE.md visible: [Bar, Baz]
app.ts ← checked against `src/MODULE.md` only
A MODULE.md only enforces exports within its immediate directory.
Import gating (not implemented yet)
visible:
- sub/Tool
visible:
- Bar
A MODULE.md semantically gates exposures at the module level it resides.
Configuration
Add a module-gates entry to .pi/settings.json:
{
"module-gates": {
"moduleDescriptorFileName": "MODULE.md",
"moduleDescriptorReadonly": "file",
"sourceRoot": "src/"
}
}
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. |
sourceRoot | "src/" | Directory to scan for descriptor files and enforce gates. Set to "" to scan from project root. |
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. |
When no settings file exists or no module-gates key is present, defaults apply.
Claude Code Support
Install
Add the following to .claude/settings.json in the current project, pointing the PreToolUse hook at the installed binary.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "bun ${CLAUDE_PROJECT_DIR}/node_modules/@cuzfrog/pi-module-gates/src/claude/pre-tool-use.ts",
"statusMessage": "Module gate checking edit..."
}
]
}
]
}
}
If pi-module-gates is already installed in pi global dir, you can use below path instead:
~/.pi/agent/npm/node_modules/@cuzfrog/pi-module-gates/src/claude/pre-tool-use.ts
System prompt
You need to add system-prompt.md manually to your context.
Configuration
Claude Code uses the same .pi/settings.json#module-gates block as the pi extension. See the Configuration section above.
Troubleshooting
Prompt:
Check if PreToolUse hook `pi-module-gates` is triggered and runs expectedly.
License
MIT
Author
Cause Chung (cuzfrog@gmail.com)