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) |
| check or stop the agent's edits, commands or tool calls | a wall (Hooks and walls) |
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
.phpfile runs underphp; 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.ymland 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::
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:
gates:
no-debug: { phase: code, run: droost/gates/no-debug.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); // passedIt 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:
tools/droost/vendor/bin/droost workflow:gate --phase=codeHouse rules
droost never tells the agent how to build a Drupal site. Your team does, in house rules:
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: gateis 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-incheck: 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 acmd:of your own, which gets the changed files as its arguments (files:narrows them). Exit 0 means the rule held.judge: seekeris 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.