The git plugin is pi-steering’s default-on plugin, registered automatically viaDocumentation 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.
DEFAULT_PLUGINS in every config. It gates commits to protected branches through the no-main-commit and no-main-commit-github rules, and extends the when: clause with six predicates that expose git repository state — branch name, upstream tracking ref, commits-ahead count, staged change presence, working-tree cleanliness, and remote URL. All six predicates are walker-aware: when a bash chain contains git checkout feat && git commit, the commit evaluates against feat, not against whatever branch was current before the chain ran.
Predicates
when.branch — match the current git branch
when.branch — match the current git branch
Matches the current branch name against a pattern. The predicate performs a three-way discrimination on tracker state before ever shelling out:Array form (OR-of-matches — fires when the branch matches any listed pattern):Spread form with
- Tracker-resolved value — a
git checkout <X>orgit switch <X>with a statically-resolvable target was observed earlier in the same bash chain. The pattern is matched againstXdirectly. - Tracker unknown — a checkout was observed but the target was dynamic (e.g.
git checkout $VAR). TheonUnknownpolicy applies without shelling out, becausegit branch --show-currentat this point would return the pre-checkout branch and silently defeat the walker. - Tracker missing — no branch-changing command appeared in the chain. The predicate shells out via
git branch --show-currentinctx.cwd.
onUnknown (defaults to "block" — fail-closed):onUnknown: "block" means: if the branch cannot be determined (dynamic checkout, exec failure, detached HEAD), the predicate reports “match” so the rule still fires. Use "allow" only when you want to permit commands on branches that can’t be resolved.when.upstream — match the upstream tracking branch
when.upstream — match the upstream tracking branch
Matches the current branch’s configured upstream (resolved via
git rev-parse --abbrev-ref @{upstream}). A branch without an upstream set causes a non-zero exit; the predicate then applies the onUnknown policy (default "block").There is no tracker for upstream today — upstream configuration isn’t changed by in-chain git commands at a rate that justifies modelling it statically. The per-tool-call exec cache ensures multiple upstream-gated rules share one git call.The predicate includes a walker-unknown-cwd guard: when the walker cannot statically resolve the effective cwd (cd "$VAR/pkg" && ...), shelling out would query the wrong repository. In that case the predicate surfaces "unknown" and the engine’s onUnknown policy projects to a definite verdict.when.commitsAhead — match a commits-ahead count
when.commitsAhead — match a commits-ahead count
Matches when the number of commits ahead of a revision (default Spread form:
@{upstream}) satisfies every supplied comparator. At least one of eq, gt, or lt must be specified; all provided comparisons must pass (AND semantics).Bare form — commitsAhead: N is sugar for { eq: N }:eq?: number— exact equality (count === eq)gt?: number— strict greater-than (count > gt)lt?: number— strict less-than (count < lt)wrt?: string— git revision to count against; defaults to"@{upstream}"
false (rule skips) on exec failure or non-numeric output. Pair with upstream for fail-closed behavior when no upstream is configured.when.hasStagedChanges — match staged change presence
when.hasStagedChanges — match staged change presence
Boolean predicate. Fires when the presence or absence of staged changes matches the declared value. Resolved via Returns
git diff --cached --quiet: exit 0 means no staged changes, exit 1 means staged changes exist.false on exec failure rather than blocking (fail-open on error). Apply the spread form with onUnknown: "block" or layer with upstream if you need fail-closed behavior. Includes the walker-unknown-cwd guard — when the effective cwd is unresolvable, the predicate surfaces "unknown" rather than querying the wrong repository.when.isClean — match working-tree cleanliness
when.isClean — match working-tree cleanliness
Boolean predicate. Fires when the working tree’s cleanliness at Returns
ctx.cwd matches the declared value. Resolved via git status --porcelain: empty stdout means clean.false on exec failure. Includes the same walker-unknown-cwd guard as hasStagedChanges.when.remote — match the origin remote URL
when.remote — match the origin remote URL
Matches the Same arg shapes as
origin remote URL against a pattern (resolved via git config --get remote.origin.url). Useful for rules that should only apply in specific repositories — for example, detecting GitHub repos to emit PR-flow guidance.branch (bare pattern, array, or spread with onUnknown). Non-zero exit from git (no origin configured) applies the onUnknown policy (default "block"). Includes the walker-unknown-cwd guard.Built-in rules
no-main-commit
Blocks git commit on branches matching main, master, mainline, or trunk. The pattern anchors on the git commit subcommand and is bypass-proof against common wrapper forms — sh -c 'git commit', git -C /path commit, and git checkout main && git commit all trigger the rule.
The rule is overridable on a per-invocation basis via an inline comment:
no-main-commit-github
A specialization of no-main-commit for github.com clones. It adds when: { remote: { pattern: /github\.com[/:]/, onUnknown: "allow" } } alongside the same protected-branch check, and emits PR-flow guidance in the block reason (gh pr merge) instead of the generic feature-branch reminder.
First-match-wins ordering is load-bearing. no-main-commit-github is registered before no-main-commit in the plugin’s rule array. On a github clone on a protected branch both rules match — first-match-wins routes the github-flavored reason to github users. On non-github contexts the remote: predicate doesn’t match and the engine falls through to the generic rule.
The remote: predicate uses onUnknown: "allow" so that repositories without an origin remote (fresh-init repos, repos with upstream but no origin) fall through to the generic no-main-commit rather than emitting misleading PR-flow guidance.
Opting out
All three opt-out paths require an explicitplugins: [gitPlugin] import so defineConfig’s generics can type-check the rule and plugin names. DEFAULT_PLUGINS gives runtime registration but doesn’t extend the TypeScript inference.
Customizing the rule
The named exports frompi-steering/plugins/git include noMainCommit and noMainCommitGithub. Spread-and-override lets you reuse all fields — pattern, when, tool, field, noOverride — and only replace what you need.
as const satisfies Rule preserves literal types so defineConfig’s cross-reference checks on happened.event, observer, and other fields still run on the replacement.
Branch tracker
The git plugin registers abranch tracker under Plugin.trackers. The walker threads this tracker through every ref in a bash chain, so git checkout feat && git commit evaluates the commit rule against feat — the branch the agent is checking out to — not the branch that was current before the chain ran.
The tracker performs a three-way discrimination on its stored value:
- Value — a
git checkout <name>orgit switch <name>with a statically-resolvable target was observed; the tracker holds the resolved name. - Unknown — a checkout was observed but the target was dynamic (
git checkout $VAR); the tracker holds"unknown"to signal “a change happened but I can’t name the new value.” - Missing (sentinel
NO_CHECKOUT_IN_CHAIN) — no branch-changing command appeared in the chain; the predicate falls back to shelling out.
Tracker extensions
The git plugin extends the built-in cwd tracker with a--git-dir= and --work-tree= flag parser registered under trackerExtensions.cwd.git. This lets the walker correctly track the effective working directory when git is invoked with explicit directory overrides — for example, git --git-dir=/repo/.git --work-tree=/repo commit evaluates the commit at /repo, not at the shell’s ambient cwd.