> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing rules

> The rule format, the actions the guard recognizes, and how team, personal and repository rules combine

A rule names an **action**, optional conditions on it (**where**), what to do (**verdict**), and what to tell the agent (**say**).

```yaml theme={null}
rules:
  - id: no-admin-merge
    say: Never merge with --admin; branch protection is not to be bypassed.
    action: gh.pr_merge
    where: {admin: "true"}
    verdict: deny
```

| Field | Required | Meaning |
| - | - | - |
| `id` | Yes | Lowercase letters, digits, `.`, `_` and `-`. Shown in reports and used by `guard allow` |
| `say` | Yes | Your words. The agent sees them when the rule stops it |
| `action` | Yes | An action kind from the table below. A glob works: `git.*` |
| `where` | No | Conditions on the action's fields; all must match |
| `verdict` | Yes | `deny` (the agent is told no) or `ask` (a person decides) |
| `shadow` | No | `true` to count when the rule would fire without enforcing it. Useful to try a rule on a team first |

## Conditions

Each `where` value is matched against one field:

* `"main|master"` — any of the alternatives.
* `"*/migrations/*"` — `*` matches any run of characters, including `/`; `?` matches one character.
* `"!main|dev"` — a leading `!` negates the whole value: anything except main or dev.
* A field the action doesn't have never matches, so the rule doesn't fire.

## Actions

Commands are parsed, not pattern-matched. `cd`, `git -C`, `bash -c`, `eval`, `env` and `sudo` wrappers are followed. Writes through `>`, `tee`, `cp`, `mv` and `sed -i` count as file writes.

| Action | When | Fields |
| - | - | - |
| `git.push` | `git push` | `remote`, `branch` (the current branch when none is named), `force`, `delete`, `all` |
| `git.discard` | `reset --hard`, `clean`, `restore`, `checkout --` | `how` |
| `git.branch_delete` | `git branch -D` (or `--delete --force`) | |
| `git.stash` | `git stash` | `op` (`push`, `drop`, `clear`…), `tagged` |
| `git.commit`, `git.merge`, `git.rebase`, `git.cherry-pick`, `git.revert` | Those subcommands | |
| `gh.pr_merge` | `gh pr merge` | `method` (`merge`, `squash`, `rebase`), `admin`, `auto` |
| `gh.pr_create` | `gh pr create` | `base` (`@default` when not given) |
| `gh.workflow_run` | `gh workflow run` | `workflow`, `ref`, `inputs` |
| `file.write` | Edit and write tools, and shell writes | `path`, `via` (`redirect`, `tee`, `cp`, `mv`, `sed`) |
| `file.read` | The read tool, and `cat`-style reads | `path` |
| `shell.rm_recursive` | `rm -r` / `rm -rf` | `path` |
| `mcp.call` | A tool from an MCP server | `server`, `tool` |
| `shell.unparsed` | A command that couldn't be parsed | |

Inside a git repository, every action also has `repo` (`owner/name`, from the `origin` remote) and `current_branch`. File and directory actions have `dir`.

<Tip>
  Test a rule before you save it. `synheart guard check <command>` prints the action and its fields, and which rule would fire.
</Tip>

## Where rules come from

Rules load in this order. When two have the same `id`, the first one wins.

1. **Built-in rules.** An agent can't grant exceptions, edit rule files or turn the guard off. These can't be overridden.
2. **Team rules**, set by an admin under **Agents → Team rules** and synced to each machine.
3. **Your own rules**, in `~/.synheart/guard/rules.yaml` (`synheart guard init` writes a starter set).
4. **Exceptions** you granted with `synheart guard allow`.
5. **Repository rules**, in `.synheart/guard.yaml` at the repository root.

So a personal or repository file can add rules, but can't redefine a team rule.

## A team example

```yaml theme={null}
rules:
  - id: no-force-push-protected
    say: Never force-push to main, master, dev or production.
    action: git.push
    where: {force: "true", branch: "main|master|dev|production"}
    verdict: deny

  - id: no-direct-push-protected
    say: Changes reach main, dev and production through pull requests, not direct pushes.
    action: git.push
    where: {branch: "main|dev|production"}
    verdict: deny

  - id: no-admin-merge
    say: Never use admin privileges to bypass branch protection.
    action: gh.pr_merge
    where: {admin: "true"}
    verdict: deny

  - id: ask-discard-work
    say: Ask before discarding uncommitted work.
    action: git.discard
    verdict: ask

  - id: ask-rm-recursive
    say: Ask before deleting directories outside temp and build folders.
    action: shell.rm_recursive
    where: {path: "!/tmp/*|*/node_modules|*/node_modules/*|*/dist|*/build|*/target"}
    verdict: ask

  - id: ask-read-secrets
    say: Ask before reading secret files (.env, keys).
    action: file.read
    where: {path: "*/.env|*/.env.*|*.pem|*.key|*/id_rsa*|*/id_ed25519*"}
    verdict: ask

  - id: ask-migrations
    say: Database migrations are reviewed by the platform team; ask before changing one.
    action: file.write
    where: {path: "*/migrations/*"}
    verdict: ask
```

## When rules are too strict

**Agents → Team** lists rules people always let through when asked. Those are candidates to relax, so developers are asked less. Before turning a new rule on for everyone, add `shadow: true` and watch the *would fire* column for a week.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.