This guide walks you through installing pi-steering, creating your first rule file, and verifying that the guardrail behaves correctly — blocking the dangerous command while leaving safe look-alike strings untouched. By the end you will have a workingDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/cad0p/pi-steering-hooks/llms.txt
Use this file to discover all available pages before exploring further.
no-force-push rule enforced at the AST level, plus the default git plugin’s no-main-commit protection active automatically.
During the PoC period,
pi-steering has not yet been published to npm. Install from a local clone instead:git clone https://github.com/cad0p/pi-steering-hooks.git
cd pi-steering-hooks
pnpm install
pnpm --filter pi-steering build # dist/ is gitignored — build first
pi install ./packages/pi-steering
After code changes to the
pi-steering source itself, rebuild before restarting:pi install <local-path> only registers the path — it does not run a build. The package is compiled ("main": "./dist/index.js") and dist/ is gitignored, so edits to src/ take effect only after a rebuild and a full pi restart. /reload alone is not sufficient for compiled extension code.Create the file at the root of your project. This is where your project-local rules live. The loader walks from the launch directory up to
$HOME and merges every .pi/steering/index.ts it finds, so project-level rules compose with any user-level rules you define higher up.import { defineConfig } from "pi-steering";
export default defineConfig({
rules: [
{
name: "no-force-push",
tool: "bash",
field: "command",
// `(?!-)` rules out `--force-with-lease` — `\b` alone would match
// it, since `-` is a non-word character and `--force\b` sees a
// word boundary between `e` and `-`.
pattern: /^git\s+push.*--force(?!-)/,
reason: "Force-push rewrites history. Use --force-with-lease if needed.",
},
],
});
defineConfig is the recommended entry point. It threads generics through your config so that rule names, plugin names, observer names, and when.happened.event references are all type-checked at tsc --noEmit time — typos become compile errors rather than silent misconfiguration.Restart pi to load the new extension config. The bridge factory runs at startup, walks up from your launch directory, finds
.pi/steering/index.ts, and wires the evaluator and observer dispatcher onto pi’s lifecycle events.Agent: git push --force
[steering:no-force-push@user] Force-push rewrites history. Use --force-with-lease if needed.
All three trigger the same
no-force-push rule because the wrapper commands are expanded during the AST walk, exposing the inner git push --force ref.What the Defaults Add Automatically
Two built-in bundles are layered onto every config automatically, unless you opt out:DEFAULT_RULES — four domain-agnostic safety rails active on every pi session:
| Rule | What it blocks |
|---|---|
no-force-push | git push --force / -f (allows --force-with-lease) |
no-hard-reset | git reset --hard |
no-rm-rf-slash | rm -rf / in all flag permutations |
no-long-running-commands | Dev servers, watchers, nodemon, vite dev, etc. |
DEFAULT_PLUGINS — the git plugin (pi-steering/plugins/git), which contributes:
- Predicates:
branch,upstream,commitsAhead,hasStagedChanges,isClean,remote - Rules:
no-main-commit(blocks commits directly tomain,master,mainline,trunk) - Trackers:
branch(in-chaingit checkout/git switchawareness)
no-main-commit rule is overridable per commit with an inline comment:
Hot reload. Editing
.pi/steering/index.ts and running /reload inside pi picks up your config changes without a pi restart. The loader cache-busts the dynamic import so Node’s ESM module map cannot serve a stale copy.What /reload does not pick up: edits to a plugin’s compiled dist/index.js. Compiled modules in node_modules are cached for the process lifetime. Plugin authors who want hot-reload during development should ship .ts source as the package entry (Node 22+ native type-stripping: set "main": "./src/index.ts", allowImportingTsExtensions: true, noEmit: true).