module-gates

agent
Security Audit
Fail
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Fail
  • process.env — Environment variable access in bin/module-gates.mjs
  • process.env — Environment variable access in scripts/gh-bot.mjs
  • network request — Outbound network request in scripts/gh-bot.mjs
  • crypto private key — Private key handling in scripts/gh-bot.mjs
  • spawnSync — Synchronous process spawning in src/bootstrap-jiti.mjs
  • process.env — Environment variable access in src/bootstrap-jiti.mjs
  • spawnSync — Synchronous process spawning in src/bridges/claude/pre-tool-use.test.ts
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

controls the entropy of the codebase by enforcing code module boundaries.

README.md

module-gates - Constraints liberate, liberties constrain.

License: MIT
GitHub stars
GitHub last commit
GitHub repo size
CI

English · 简体中文 · 日本語

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.

Approach

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

  • 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 reason.

The attempt to add 2 public helper functions is blocked, forcing the agent to re-think the design.
Module Gate denial example

How it works

  1. Indexing — On session start, scans the project tree for descriptor files and builds a module index.
  2. System prompt — Injects a hint so the agent knows to respect descriptor file conventions.
  3. Gating — On every write/edit, checks:
    • Readonly gate — is the target file locked?
    • No-new-exports gate — would the change add new exports to a file in the no-new-exports 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

pi install npm:@cuzfrog/module-gates

Or load directly for a single session:

pi -e npm:@cuzfrog/module-gates

Claude Code

As a plugin, from this repository's marketplace (no login required — public repo):

/plugin marketplace add cuzfrog/module-gates
/plugin install module-gates@cuzfrog

On the first hook invocation the plugin installs its runtime dependencies into its data directory.

Or as plain hooks wired into a project (requires the package installed in the project):

npm install --save-dev @cuzfrog/module-gates
npx module-gates install-claude

This writes PreToolUse and SessionStart hooks into .claude/settings.json; npx module-gates uninstall-claude removes them. The SessionStart hook injects the system prompt hint automatically.

Or reuse an existing pi installation by pointing hooks at it manually in ~/.claude/settings.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"
          }
        ]
      }
    ]
  }
}

The pi install directory may differ; locate run.mjs under your pi npm root. The SessionStart hook (system prompt injection) is optional — PreToolUse alone enforces the 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. A MODULE.md only enforces its own immediate directory.

Readonly constraints

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

Any prose for the agent to better understand the module.

No-new-exports constraints

no-new-exports: [mod.rs]

No-new-exports files cannot change their surface size: no new exports or public entries are allowed. The file body is still editable.

A skill module-no-new-exports-all has been included to populate no-new-exports entries in modules.

Scenario Behavior
No MODULE.md Module is unconstrained — nothing is gated.
Malformed YAML frontmatter The module is left unguarded and an info notification is emitted.

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 — e.g. .pi/settings.json, .claude/settings.json.

{
  "module-gates": {
    "moduleDescriptorFileName": "MODULE.md",
    "moduleDescriptorReadonly": "off",
    "sourceRoots": ["src/"],
    "outputModuleProseOnBlock": false
  }
}
Option Default Description
moduleDescriptorFileName MODULE.md File name used for module descriptors (case-insensitive)
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.
outputModuleProseOnBlock false When true, the violating module descriptor's prose is appended to the block message so the agent sees the contract context. Disabled by default to keep the error message concise.

When no settings file exists or no module-gates key is present, defaults apply.

Troubleshooting

Prompt:

Check if PreToolUse hook `module-gates` is triggered and runs expectedly.

License

MIT

Author

Cause Chung ([email protected])

Reviews (0)

No results found