@cuzfrog/pi-module-gates 0.13.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,9 @@ AI coding agents produce edits with limited context knowledge (myopia) — their
17
17
 
18
18
  The extension intercepts agent `write`/`edit` operations and enforces these contracts. Violations are blocked with a clear reason.
19
19
 
20
+ The attempt to add 2 public helper functions is blocked, forcing the agent to re-think the design.
21
+ ![Module Gate denial example](doc/module_gates_block.png)
22
+
20
23
  ### How it works
21
24
 
22
25
  1. **Indexing** — On session start, scans the project tree for descriptor files and builds a module index.
@@ -28,7 +31,7 @@ The extension intercepts agent `write`/`edit` operations and enforces these cont
28
31
  - **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)
29
32
  - **Import gate** (not implemented yet) — would the change introduce an import violating visibility scope?
30
33
 
31
- - System prompt: [system-prompt.md](src/context/system-prompt.ts)
34
+ - System prompt: [system-prompt.md](src/context/system-prompt.template.md)
32
35
  - Currently [supported languages](src/gates/checkers/index.ts): **TypeScript/JavaScript**, **Rust**, **Java**, **Go**, **Kotlin**, **Scala**
33
36
 
34
37
  ## Installation
@@ -127,6 +130,7 @@ Add a `module-gates` entry to `.pi/settings.json`:
127
130
  | `moduleDescriptorReadonly` | `true` | When `true`, descriptor files are readonly.|
128
131
  | `sourceRoot` | `"src/"` | Directory to scan for descriptor files and enforce gates. Set to `""` to scan from project root. |
129
132
  | `disableModuleInterfaceImportGate` | `false` | When `true`, imports will not be forced to be from module interface. |
133
+ | `disableSystemPrompt` | `false` | When `true`, skip injecting the module-gates hint into the agent's system prompt. |
130
134
 
131
135
  When no settings file exists or no `module-gates` key is present, defaults apply.
132
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuzfrog/pi-module-gates",
3
- "version": "0.13.3",
3
+ "version": "0.15.0",
4
4
  "description": "pi extension that controls the entropy of the codebase by enforcing code module boundaries.",
5
5
  "keywords": [
6
6
  "pi-package"
package/src/config.ts CHANGED
@@ -6,6 +6,7 @@ export type ModuleGateConfig = {
6
6
  moduleDescriptorReadonly: "file" | "frontmatter" | "off";
7
7
  sourceRoot: string;
8
8
  disableModuleInterfaceImportGate: boolean;
9
+ disableSystemPrompt: boolean;
9
10
  };
10
11
 
11
12
  const DEFAULTS: ModuleGateConfig = {
@@ -13,6 +14,7 @@ const DEFAULTS: ModuleGateConfig = {
13
14
  moduleDescriptorReadonly: "file",
14
15
  sourceRoot: "src/",
15
16
  disableModuleInterfaceImportGate: false,
17
+ disableSystemPrompt: false,
16
18
  };
17
19
 
18
20
  export function loadConfig(cwd: string): ModuleGateConfig {
@@ -0,0 +1,17 @@
1
+ ## Module gates (boundary enforcement)
2
+ This project uses `{{descriptorFileName}}`(case-insensitive) files to declare visibility, readonly and frozen rules that you should follow.
3
+ If you cannot comply, reconsider your design or raise to the user with tradeoffs if necessary.
4
+ Each `{{descriptorFileName}}` gates its branching point in the tree.
5
+ A `{{descriptorFileName}}` with a `visible` list means only entries in the list are allowed to be visible outside the module.
6
+
7
+ - Violations will be blocked.
8
+ {{#if descriptorReadonly}}- {{descriptorReadonly}}{{/if}}
9
+ {{#if moduleInterfaceImportGate}}- {{moduleInterfaceImportGate}}{{/if}}
10
+
11
+ ### Glossary
12
+ - `module`: a directory containing code;
13
+ - `external files`: files not in the module directory;
14
+ - `module interface`: the file representing the module surface, e.g. `index.ts` in Typescript, `mod.rs` in Rust;
15
+ - `readonly`: files are readonly;
16
+ - `frozen`: files cannot add new exports, but still editable;
17
+ - `visible`: visible from outside the module; files not in the module directory are outside the module;
@@ -1,3 +1,7 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
1
5
  import type { ModuleIndex } from "../types.ts";
2
6
  import type { ModuleGateConfig } from "../config.ts";
3
7
 
@@ -7,22 +11,38 @@ export function buildSystemPromptHint(
7
11
  descriptorFileName: string,
8
12
  config: ModuleGateConfig,
9
13
  ): string {
14
+ if (config.disableSystemPrompt) return systemPrompt;
10
15
  if (index.contracts.length === 0) return systemPrompt;
11
16
 
12
- const descriptorNote =
17
+ const descriptorReadonly =
13
18
  config.moduleDescriptorReadonly === "frontmatter"
14
- ? ` The frontmatter of \`${descriptorFileName}\` is readonly.`
19
+ ? `The frontmatter of \`${descriptorFileName}\` is readonly.`
15
20
  : config.moduleDescriptorReadonly === "file"
16
- ? ` The \`${descriptorFileName}\` file itself is readonly.`
21
+ ? `The \`${descriptorFileName}\` file itself is readonly.`
17
22
  : "";
18
23
 
19
- return systemPrompt + `
24
+ const moduleInterfaceImportGate = config.disableModuleInterfaceImportGate
25
+ ? ""
26
+ : "External files can only import through the module interface (e.g. `index.ts` in TypeScript, `mod.rs` in Rust).";
27
+
28
+ const section = applyTemplate(TEMPLATE, {
29
+ descriptorFileName,
30
+ descriptorReadonly,
31
+ moduleInterfaceImportGate,
32
+ });
20
33
 
21
- ## Module gates (boundary enforcement)
22
- This project uses \`${descriptorFileName}\`(case-insensitive) files to declare visibility, readonly and frozen rules that you should follow.
23
- If you cannot comply, reconsider your design, if impossible, raise to the user with tradeoffs.
24
- Each \`${descriptorFileName}\` gates its branching point in the tree.
25
- A \`${descriptorFileName}\` with a \`visible\` list means only entries in the list are allowed to be visible outside the module.
26
- \`readonly\` files are readonly; \`frozen\` files cannot add new exports.${descriptorNote}
27
- Violations will be blocked.`;
34
+ return systemPrompt + "\n\n" + section;
28
35
  }
36
+
37
+ const TEMPLATE = fs.readFileSync(
38
+ path.join(path.dirname(fileURLToPath(import.meta.url)), "system-prompt.template.md"),
39
+ "utf-8",
40
+ );
41
+
42
+ function applyTemplate(template: string, vars: Record<string, string>): string {
43
+ const ifBlock = /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
44
+ const variable = /\{\{(\w+)\}\}/g;
45
+ return template
46
+ .replace(ifBlock, (_match, name: string, body: string) => (vars[name] ? body : ""))
47
+ .replace(variable, (_match, name: string) => vars[name] ?? "");
48
+ }