Skip to main content

Documentation 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.

pi-steering-flags is the first official external plugin for the pi-steering ecosystem. It ships two predicates — requiresFlag and allowlistedFlagsOnly — that encode flag-presence and flag-allowlist checks that don’t belong in the core engine. Which flags count as equivalent (short + long + env)? How aggressive should default-deny be? What exactly counts as a “flag”? These are opinionated policy questions that belong in a plugin so they can iterate on their own release cadence without committing the engine to answers about every CLI’s conventions.

Install

pnpm add pi-steering-flags
pi-steering-flags declares pi-steering as a peer dependency. Install both together.

Quickstart

// .pi/steering/index.ts
import { defineConfig } from "pi-steering";
import flagsPlugin, { INFO_ONLY } from "pi-steering-flags";

export default defineConfig({
  plugins: [flagsPlugin],
  rules: [
    // Block `aws` invocations without --profile or AWS_PROFILE env.
    {
      name: "aws-requires-profile",
      tool: "bash",
      field: "command",
      pattern: /^aws\s+[a-z]/,
      unless: /^aws\s+(sts\s+get-caller-identity|configure)\b/,
      when: {
        requiresFlag: { flag: "--profile", env: "AWS_PROFILE" },
      },
      reason: "Always specify --profile (or AWS_PROFILE=) — never rely on the default profile.",
    },
    // Default-deny flag gating for `cr`.
    {
      name: "cr-allowlisted-flags-only",
      tool: "bash",
      field: "command",
      pattern: /^cr\b/,
      unless: INFO_ONLY,
      when: {
        allowlistedFlagsOnly: {
          allow: ["--all", "--description", "--reviewers"],
        },
      },
      reason:
        "Only --all, --description, --reviewers are allowed with `cr`. " +
        "Everything else should be inferred from the commit message.",
    },
  ],
});

when.requiresFlag

The rule fires (command is blocked) when none of the listed flag or env-var equivalents appear in the evaluated command. Use it to enforce that a required flag is always supplied. ShorthandrequiresFlag: "--profile" is equivalent to requiresFlag: { flag: "--profile" }:
when: { requiresFlag: "--profile" }
Object form:
flag
string
One required flag. The rule blocks when this flag is absent.
flags
string[]
Any one of several flags (OR). The rule blocks when none of the listed flags appear.
env
string
An environment-variable alternative (bare VAR=value shell prefix). The rule passes when this env assignment appears, even if the flag itself is absent.
envs
string[]
Any one of several env-var alternatives (OR). The rule passes when any one appears.
At least one of flag, flags, env, or envs must be specified. A malformed arg (empty object) does not fire — fail-open for the rule author’s benefit (better than a silent always-block). Examples:
// Single flag required:
when: { requiresFlag: "--profile" }

// Flag OR env-var equivalent:
when: { requiresFlag: { flag: "--profile", env: "AWS_PROFILE" } }

// Any one of several short/long flag variants:
when: { requiresFlag: { flags: ["-n", "--namespace"] } }

// Flag OR any one of several env-var alternatives:
when: {
  requiresFlag: {
    flag: "--region",
    envs: ["AWS_REGION", "AWS_DEFAULT_REGION"],
  },
}

when.allowlistedFlagsOnly

The rule fires when any --prefixed token in the command is NOT in the allowlist. Use it for default-deny flag gating — the command may only use the explicitly approved flags, and any unknown flag triggers the block.
allow
string[]
required
The set of permitted flags. Long flags (--flag) automatically match their --flag=value attached-value form. Short flags (-n, -h) do not get auto-prefix matching.
allowPrefixes
string[]
Explicit prefixes to allow for attached-value short flags. For example, allowPrefixes: ["-o"] permits -ofoo, -obar, etc.
Positional arguments (tokens not starting with -) are ignored.
when: {
  allowlistedFlagsOnly: {
    allow: ["--all", "--description", "--reviewers"],
    // Implicitly matches: --description=... and --reviewers=...
  },
}

Helper functions

When the built-in predicates aren’t enough, reach for these helpers inside when.condition. All helpers are quote-aware — they read .value before falling back to .text and handle undefined input gracefully.
  • hasFlag(args, flag) — returns true when the flag appears in args, in either bare form or flag=value attached-value form.
  • getFlagValue(args, flag) — returns the flag’s value from a separated flag value pair or an attached flag=value form; returns null when the flag is absent.
  • hasEnvAssignment(envAssignments, name) — returns true when the named env variable appears as a literal VAR=value shell assignment.
  • INFO_ONLY — a regex matching -h, --help, -v, and --version. Use it in Rule.unless to carve out informational invocations from a rule entirely.
Example using getFlagValue inside when.condition:
import { getFlagValue, hasEnvAssignment, hasFlag } from "pi-steering-flags";

when: {
  condition: async (ctx) => {
    if (ctx.input.tool !== "bash") return false;
    const path = getFlagValue(ctx.input.args, "--description");
    if (path === null) return false;
    const result = await ctx.exec("test", ["-f", path], { cwd: ctx.cwd });
    return result.exitCode !== 0;
  },
}
This example checks whether the file supplied to --description actually exists, blocking the command when the file is missing. ctx.exec is memoized per (cmd, args, cwd) within a single tool call, so the test call doesn’t re-fork a process if another rule already ran the same check.

Build docs developers (and LLMs) love