/** * Protected Paths Extension * * Forked from upstream `protected-paths.ts` with this repo's substrate in mind. * Blocks `write` and `edit` tool calls targeting paths that look like: * - Secrets / credentials (`.env`, `auth.json`) * - VCS internals (`.git/`) * - Dependency dirs (`node_modules/`, `__pycache__/`, `.venv/`) * - The pi harness substrate itself (compose file, Dockerfile, entrypoint, * pi settings) — agents should propose changes via discussion, not edit * these silently. * * `bash` is intentionally not gated here; use `confirm-destructive.ts` for that. * The agent can still *read* protected paths. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; /** Substring matches against the target path (forward-slashed). Order-insensitive. */ const PROTECTED_PATTERNS = [ // Secrets ".env", "auth.json", // VCS ".git/", // Dependency / build dirs "node_modules/", "__pycache__/", ".venv/", // Pi harness substrate "docker-compose.yml", "docker-compose.yaml", // Framework-owned compose layer (the pi service itself), and the COMMITTED // generated bays artifact: the agent shapes parked services by editing // recipes and running `garaje park` — never by hand-editing compose. "compose.framework.yaml", "compose.bays.yaml", // The COMMITTED generated pi-image toolchain: same contract as // compose.bays.yaml — the agent shapes it by editing a bay's recipe and // running `garaje park`, never by hand-editing the artifact. "pi/toolchain/", "pi/Dockerfile", "pi/entrypoint.sh", // Framework substrate now ships as the @garaje/base package. "packages/base/", ".pi/settings.json", ]; function isProtected(path: string): boolean { const normalized = path.replace(/\\/g, "/"); return PROTECTED_PATTERNS.some((pat) => normalized.includes(pat)); } export default function protectedPathsExtension(pi: ExtensionAPI) { pi.on("tool_call", async (event, ctx) => { if (event.toolName !== "write" && event.toolName !== "edit") { return undefined; } const path = event.input.path as string; if (!isProtected(path)) { return undefined; } if (ctx.hasUI) { ctx.ui.notify(`Blocked write to protected path: ${path}`, "warning"); } return { block: true, reason: `Path "${path}" is protected by packages/base/extensions/protected-paths.ts. Discuss the change with the user before editing.`, }; }); }