diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3585e85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +package-lock.json +dist/ +*.log +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index be5dae0..088357d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `@inceptionstack/pi-branch-enforcer` are documented here. +## [3.3.0] — 2026-05-14 + +### Added +- **Runtime kill-switch** — enforcement can now be disabled without restarting the agent + - File-based: create `~/.pi-branch-enforcer/disabled` to disable; remove to re-enable + - Env var: `PI_BRANCH_ENFORCER_DISABLED=1` (process-scope only) + - Checked on every `tool_call`, so toggling takes effect on the next bash command + - Designed for use with roundhouse `/toggle-enforce-branches` (immediate, persisted across restarts) + ## [3.2.1] — 2026-05-10 ### Fixed diff --git a/README.md b/README.md index ae6fc69..9e94ef0 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,30 @@ Detects when a scripting language executes a file (e.g. `node /tmp/script.js`): None needed. Works out of the box. Protected branches are `main` and `master`. +## Disabling at runtime + +The extension honors a runtime kill-switch — useful when an automation needs +to temporarily bypass enforcement (e.g. release scripts, recovery scenarios) +without restarting the agent. + +**File-based (persistent):** +```bash +# Disable +mkdir -p ~/.pi-branch-enforcer && touch ~/.pi-branch-enforcer/disabled + +# Re-enable +rm ~/.pi-branch-enforcer/disabled +``` + +**Env var (process-scope only):** +```bash +PI_BRANCH_ENFORCER_DISABLED=1 pi ... +``` + +The file is checked on every `bash` tool call, so toggling takes effect on +the **next** command — no agent restart required. Roundhouse exposes this +as the `/toggle-enforce-branches` Telegram command. + ## License Apache-2.0 diff --git a/index.ts b/index.ts index 96bdbe8..cc7970f 100644 --- a/index.ts +++ b/index.ts @@ -16,10 +16,30 @@ */ import { type ExtensionAPI, isToolCallEventType } from "@earendil-works/pi-coding-agent"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { homedir } from "node:os"; /** Branch names that are protected from direct commits and pushes. */ const PROTECTED_BRANCHES = new Set(["main", "master"]); +/** + * Runtime kill-switch path. If this file exists OR the env var + * `PI_BRANCH_ENFORCER_DISABLED=1` is set, the extension fails open + * (returns immediately on every tool_call). Lets external tooling + * (e.g. roundhouse `/toggle-enforce-branches`) disable enforcement + * without restarting the agent. + * + * File-based switch is preferred over env var because it survives + * agent restarts and works for already-running processes. + */ +const DISABLED_MARKER_PATH = join(homedir(), ".pi-branch-enforcer", "disabled"); + +function isDisabled(): boolean { + if (process.env.PI_BRANCH_ENFORCER_DISABLED === "1") return true; + try { return existsSync(DISABLED_MARKER_PATH); } catch { return false; } +} + const BRANCH_FIX_INSTRUCTIONS = `Use a feature branch for all changes.\n\n` + `To fix:\n` + @@ -34,6 +54,9 @@ const JUDGE_MODEL = "us.anthropic.claude-haiku-4-5-20251001-v1:0"; export default function (pi: ExtensionAPI) { pi.on("tool_call", async (event, ctx) => { if (!isToolCallEventType("bash", event)) return; + // Runtime kill-switch — checked on every call so toggling takes effect + // immediately without restarting the agent. + if (isDisabled()) return; const cmd = event.input.command ?? ""; // Tier 1: Fast regex for direct git commands diff --git a/package.json b/package.json index a2b2d94..686af3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inceptionstack/pi-branch-enforcer", - "version": "3.2.2", + "version": "3.3.0", "description": "Pi extension — prevents git commit/push directly to main/master, enforces branch workflow", "keywords": [ "pi",