Levers: droost.workflow.yml
A single repo-root file, droost.workflow.yml, decides what "pass" means for this repository. drush droost:workflow:install writes one measured to your layout.
mode: agentic # agentic | interactive
preset: custom # custom | factory | light
enforcement: soft # hard | soft | off — the hooks, mid-run only
require_run: hard # hard | soft | off — custom-code edits with NO active run
gates:
phpcs: { on: true, standard: "Drupal,DrupalPractice" }
phpstan: { on: true, level: 6 } # 0-9 | max
# both static gates accept paths: "web/modules/custom,web/themes/custom"
phpunit: { on: true }
mutation: { on: false, msi_min: 0 }
playwright: { on: false }
coverage: { on: false, min: 0 }
rendered_check: { on: true, routes: "/,/events" }
config_clean: { on: true } # a fresh cex produces zero diff
wiki_fresh: { on: true } # the project's own docs still match the code
# custom:
# semgrep: { on: true, phase: code, cmd: "semgrep scan --error --quiet" }
max_gate_retries: 2Why a file, not Drupal configuration
Four reasons. The agent must be able to read its own levers while the site is mid-build or broken. A plain Claude Code or Codex user reads the same file with no site at all. It is dev tooling and belongs with the code it gates. And it belongs in review, where loosening a gate shows up as a diff.
Presets
A preset is a base, not an alternative to per-gate control; explicit gates: entries are applied over it.
factory: everything on, strict. The full EARS spec; enforcement defaultshard.light: the same phases at lighter weight: a shorter spec in the same shape, the mandatory trio with phpstan at level 2, the rendered check; enforcement defaultssoft. Nothing skips.custom: the values shown above; the ergonomic middle, and what the installer writes.
Anything that does not name a preset resolves to factory. An earlier revision defaulted a file that exists to custom, which meant touch droost.workflow.yml silently turned mutation, Playwright and coverage off. If you want the gentler set, name it, so the choice is visible in a diff.
What is not a lever
- The phases. Every run walks plan → code → test → complete.
- The mandatory trio.
phpcs,phpstanandphpunitcannot be turned off; they are the toolchain Drupal core develops with. Their tuning levers still apply. Anon: falseis recorded as a deprecation notice and superseded. A repository that cannot run one yet gets an honest answer (tool missing, config missing, or a labeled "nothing to analyse yet") rather than a pass. - The seeker's authority.
seekers: { on: false }exists, on by default; when on, its ledger is parsed, never self-reported.
Unknown keys are errors
A loader that shrugs at phpstain: hands back a run with static analysis quietly disabled and a report that says everything passed. Every unknown setting, gate, option, phase, mode and preset is refused by name:
droost.workflow.yml: unknown gate "phpstain" (known: phpcs, phpstan, phpunit,
mutation, playwright, coverage, rendered_check, config_clean, wiki_fresh)Thresholds never imply on: writing coverage.min without coverage.on leaves the gate where the preset put it. Something at the config path that is not a readable regular file is an error, not an absent config.
Ownership
Re-running the installer refreshes only the directories the pack owns (marked with .droost-workflow-pack). Your droost.workflow.yml is never refreshed: it is version-controlled intent you wrote.