# Make it yours

> Add your own checks to the workflow: a gate at a step, or a house rule about how your team builds. Each is a file or one line in droost.yml.

droost's own checks cover code style, code mistakes, tests and the browser.
Your team has rules on top of that. You add them as files in the `droost/`
folder, named in `droost/droost.yml`. There are four kinds:

| You want to... | Use |
|---|---|
| run your own check at a step, and stop the run if it fails | a **gate** (this page) |
| write down how your team builds, and hold every change to it | a **house rule** (this page) |
| be told when a run starts, moves on or finishes | a **hook** ([Hooks and walls](https://droost.org/docs/hooks-and-walls.md)) |
| check or stop the agent's edits, commands or tool calls | a **wall** ([Hooks and walls](https://droost.org/docs/hooks-and-walls.md)) |

All of them follow the same rules:

- **They are files committed in your repository.** droost runs nothing else
  and downloads nothing.
- **Each runs as its own process**, so a crash in yours cannot take the run
  down. A `.php` file runs under `php`; anything else runs directly.
- **They get JSON on standard input and answer with an exit code.**
- **Silence is never a pass.** A file that is missing, crashes or times out
  counts as "could not run", never as passed.
- **They cannot change during a run.** While a run is open the guard refuses
  edits to `droost/droost.yml` and to these files, so a change to your rules
  shows up in review, between runs.

## Your own gates

### One line

If your check is already a command, name it with `cmd:`:

```yaml
gates:
  semgrep: { phase: code, cmd: "semgrep --error --config auto web/modules/custom" }
  home_up: { phase: test, cmd: "curl -fsS -o /dev/null https://my-site.ddev.site/" }
```

Exit 0 passes; anything else fails. Make sure the command's exit code really
means pass or fail: a tool that prints a problem and still exits 0 can never
fail the gate.

### A file

A file can read what the run changed and check only that:

```yaml
gates:
  no-debug: { phase: code, run: droost/gates/no-debug.php }
```

```php
<?php
// droost/gates/no-debug.php: fail if a changed PHP file calls dpm() or var_dump().
$run = json_decode(stream_get_contents(STDIN), true);
$root = getenv('DROOST_PROJECT_ROOT');

$php = array_filter($run['changed_files'] ?? [], fn ($f) => str_ends_with($f, '.php'));
if ($php === []) {
  echo "No PHP files changed.\n";
  exit(4);                       // ran, but had nothing to check
}
foreach ($php as $file) {
  if (preg_match('/\b(dpm|var_dump)\s*\(/', (string) @file_get_contents("$root/$file"))) {
    echo "Debug call left in $file\n";
    exit(1);                     // failed
  }
}
echo "No debug calls in " . count($php) . " file(s).\n";
exit(0);                         // passed
```

**It receives** on standard input: `run_id`, `phase`, `base_commit` and
`changed_files` (relative to the project root). In the environment:
`DROOST_PROJECT_ROOT` and `DROOST_GATE`.

**Its exit code** means: `0` passed, `1` failed (the agent gets your output and
fixes the cause), `4` ran but had nothing to check. Anything else, a crash or
a timeout means it could not run. The **last** line you print is the gate's
summary in the report, so put the verdict there.

**Its options:** `phase` (`code`, `test`, or `"code,test"`), `run` or `cmd`,
`mode` (`block` to stop the run, or `report` to note the failure and move on),
`timeout` in seconds (600 by default), and `steered_by` (your gate's config
files: if a run edits one, the report says so).

Try a gate without starting a run:

```bash
tools/droost/vendor/bin/droost workflow:gate --phase=code
```

## House rules

droost never tells the agent how to build a Drupal site. Your team does,
in house rules:

```yaml
rules:
  house:
    - { id: HR1, rule: 'New markup lives in single-directory components.', judge: gate, check: sdc_markup, mode: block }
    - { id: HR2, rule: 'Landing pages are built in Canvas, never as a View page.', judge: seeker }
    - { id: HR3, rule: 'Custom code never queries the database directly.', judge: gate, cmd: "! grep -n 'Drupal::database'", files: '*.php', mode: block }
```

The agent reads every rule before it plans. Each one has a **judge**:

- **`judge: gate`** is for a rule a machine can check. It runs over the files
  the run changed and nothing else, so old code never fails a new run. Use the
  built-in `check: sdc_markup` (a new Twig template in a custom module or theme
  must live in a component, and a Canvas code component fails it too), or a
  `cmd:` of your own, which gets the changed files as its arguments
  (`files:` narrows them). Exit 0 means the rule held.
- **`judge: seeker`** is for a rule only judgment can read. The reviewing
  agent checks each change against it and cites the rule's id.

`mode: block` stops the run when a rule breaks. `mode: report` (the default)
notes the break and lets the run go on: start there, see how often a rule
fires, then switch it to `block`. If the right change has to break a rule,
the agent asks you. An exception you approve, recorded in the spec, stops the
reviewer grading the break as serious; a gate-judged rule at `block` still
fails, so set that rule to `report` for the change.

`droost workflow:rules` prints the rules the open run is held to.
