@cuzfrog/pi-module-gates 0.13.0 → 0.13.2
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 +2 -2
- package/package.json +2 -2
- package/skills/module-freeze-all/SKILL.md +1 -2
- package/skills/module-freeze-all/scripts/freeze-all.d.mts +1 -0
- package/skills/module-freeze-all/scripts/freeze-all.mjs +20 -5
- package/src/gates/checkers/typescript.ts +13 -1
- package/src/gates/module-interface-import-gate.ts +6 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ The extension intercepts agent `write`/`edit` operations and enforces these cont
|
|
|
25
25
|
- **Readonly gate** — is the target file locked?
|
|
26
26
|
**Fronzen gate** — is there any surface change to the target file?
|
|
27
27
|
- **Export gate** — would the change introduce an export not in the `visible` list?
|
|
28
|
-
- **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`. (Only Typescript/JavaScript and Rust are supported)
|
|
28
|
+
- **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
29
|
- **Import gate** (not implemented yet) — would the change introduce an import violating visibility scope?
|
|
30
30
|
|
|
31
31
|
- System prompt: [system-prompt.md](src/context/system-prompt.ts)
|
|
@@ -126,7 +126,7 @@ Add a `module-gates` entry to `.pi/settings.json`:
|
|
|
126
126
|
| `moduleDescriptorFileName` | `MODULE.md` | File name used for module descriptors (case-insensitive) |
|
|
127
127
|
| `moduleDescriptorReadonly` | `true` | When `true`, descriptor files are readonly.|
|
|
128
128
|
| `sourceRoot` | `"src/"` | Directory to scan for descriptor files and enforce gates. Set to `""` to scan from project root. |
|
|
129
|
-
| `disableModuleInterfaceImportGate` | `false` |
|
|
129
|
+
| `disableModuleInterfaceImportGate` | `false` | When `true`, imports will not be forced to be from module interface. |
|
|
130
130
|
|
|
131
131
|
When no settings file exists or no `module-gates` key is present, defaults apply.
|
|
132
132
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cuzfrog/pi-module-gates",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "pi extension that controls the entropy of the codebase by enforcing code module boundaries.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@earendil-works/pi-coding-agent": "*"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
25
|
+
"@earendil-works/pi-coding-agent": "0.79.8",
|
|
26
26
|
"@types/node": "22.19.19",
|
|
27
27
|
"typescript": "5.9.3",
|
|
28
28
|
"vitest": "4.1.7"
|
|
@@ -9,7 +9,7 @@ argument-hint: <user-instructions>
|
|
|
9
9
|
- Find out module descriptor filename in the context.
|
|
10
10
|
- Find out source root in the context or from configurations.
|
|
11
11
|
- Derive scripts args from user instructions.
|
|
12
|
-
- Call the script to scans the source tree for module descriptors and auto-populates their `frozen` entries with
|
|
12
|
+
- Call the script to scans the source tree for module descriptors and auto-populates their `frozen` entries with code files in each module directory.
|
|
13
13
|
|
|
14
14
|
## Script Usage
|
|
15
15
|
|
|
@@ -30,6 +30,5 @@ Options:
|
|
|
30
30
|
3. Adds those files to the `frozen` frontmatter field
|
|
31
31
|
4. Preserves existing `frozen` entries, other fields in the frontmatter, and body prose
|
|
32
32
|
|
|
33
|
-
|
|
34
33
|
## Extra user instructions:
|
|
35
34
|
"$ARGUMENTS"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SUPPORTED_EXTENSIONS: Set<string>;
|
|
@@ -3,14 +3,26 @@
|
|
|
3
3
|
* Auto-freeze all files in module descriptors.
|
|
4
4
|
*
|
|
5
5
|
* Scans module descriptor files under a source root and populates each
|
|
6
|
-
* descriptor's `frozen` field with every direct file in the module
|
|
7
|
-
* Files in nested sub-modules (directories with their own
|
|
8
|
-
* excluded from the parent.
|
|
6
|
+
* descriptor's `frozen` field with every direct code file in the module
|
|
7
|
+
* directory. Files in nested sub-modules (directories with their own
|
|
8
|
+
* descriptor) are excluded from the parent.
|
|
9
9
|
*/
|
|
10
10
|
import { readFileSync, writeFileSync, readdirSync, existsSync } from "node:fs";
|
|
11
|
-
import { resolve, relative, join, dirname } from "node:path";
|
|
11
|
+
import { resolve, relative, join, dirname, extname } from "node:path";
|
|
12
|
+
import { pathToFileURL } from "node:url";
|
|
12
13
|
import { parseArgs } from "node:util";
|
|
13
14
|
|
|
15
|
+
const SUPPORTED_EXTENSIONS = new Set([
|
|
16
|
+
".ts", ".tsx", ".js", ".jsx",
|
|
17
|
+
".rs",
|
|
18
|
+
".java",
|
|
19
|
+
".go",
|
|
20
|
+
".kt", ".kts",
|
|
21
|
+
".scala", ".sc",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
export { SUPPORTED_EXTENSIONS };
|
|
25
|
+
|
|
14
26
|
function main() {
|
|
15
27
|
const { values } = parseArgs({
|
|
16
28
|
options: {
|
|
@@ -320,6 +332,7 @@ function listDirectFiles(modDir, descriptorName, allModuleDirs) {
|
|
|
320
332
|
// Non-module subdirs are skipped too to avoid granularity issues
|
|
321
333
|
} else {
|
|
322
334
|
if (entry.name.toLowerCase() === lowerDesc) continue;
|
|
335
|
+
if (!SUPPORTED_EXTENSIONS.has(extname(entry.name).toLowerCase())) continue;
|
|
323
336
|
files.push(entry.name);
|
|
324
337
|
}
|
|
325
338
|
}
|
|
@@ -351,4 +364,6 @@ function arraysEqual(a, b) {
|
|
|
351
364
|
return sortedA.every((v, i) => v === sortedB[i]);
|
|
352
365
|
}
|
|
353
366
|
|
|
354
|
-
|
|
367
|
+
if (import.meta.url === pathToFileURL(resolve(process.argv[1] ?? "")).href) {
|
|
368
|
+
main();
|
|
369
|
+
}
|
|
@@ -19,7 +19,9 @@ function extractExports(src: string): Signature[] {
|
|
|
19
19
|
),
|
|
20
20
|
].map((m) => ({ name: m[1] }));
|
|
21
21
|
|
|
22
|
-
for (const m of src.matchAll(
|
|
22
|
+
for (const m of src.matchAll(
|
|
23
|
+
/^export\s*(?:type\s+)?\{\s*([^}]+)\s*\}\s*from/gm,
|
|
24
|
+
)) {
|
|
23
25
|
const inner = m[1];
|
|
24
26
|
for (const entry of inner.split(",")) {
|
|
25
27
|
const trimmed = entry.trim();
|
|
@@ -37,5 +39,15 @@ function extractExports(src: string): Signature[] {
|
|
|
37
39
|
results.push({ name: m[1] });
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
for (const m of src.matchAll(/^export\s*\*\s+from/gm)) {
|
|
43
|
+
results.push({ name: "*" });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const m of src.matchAll(
|
|
47
|
+
/^export\s+default\s+(?!function|class|const|let|var|type|interface|enum|abstract|async|declare)([a-zA-Z_]\w*)/gm,
|
|
48
|
+
)) {
|
|
49
|
+
results.push({ name: m[1] });
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
return results;
|
|
41
53
|
}
|
|
@@ -63,7 +63,7 @@ function checkViolation(
|
|
|
63
63
|
if (isInterfaceFile(resolved)) return undefined;
|
|
64
64
|
|
|
65
65
|
const sourceModule = findOwningModule(path.join(fileDir, "dummy.ts"), index);
|
|
66
|
-
if (sourceModule && sourceModule
|
|
66
|
+
if (sourceModule && isDescendantOrSelf(sourceModule, targetModule)) return undefined;
|
|
67
67
|
|
|
68
68
|
const relTarget = path.relative(cwd, resolved);
|
|
69
69
|
const relModule = path.relative(cwd, targetModule);
|
|
@@ -146,3 +146,8 @@ function isInterfaceFile(absPath: string): boolean {
|
|
|
146
146
|
|
|
147
147
|
return false;
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
function isDescendantOrSelf(child: string, ancestor: string): boolean {
|
|
151
|
+
if (child === ancestor) return true;
|
|
152
|
+
return child.startsWith(ancestor + path.sep);
|
|
153
|
+
}
|