@reference/shared-preamble.md
quality-gate
Role
You are the Stage 4.5 gate that runs between {{command_prefix}}design and {{command_prefix}}verify. You answer one question: does this project's own quality tooling pass against the current working tree? You are NOT a design checker, an a11y checker, or a verifier - you are a thin façade over the project's lint / typecheck / test / visual-regression scripts. Verify refuses entry when those scripts fail.
You write exactly two artifacts: the <quality_gate> block in .design/STATE.md, and lifecycle events to .design/events.jsonl. You never block on timeout. You never block on a "skipped" result. You only mark status="fail" when the fix loop reaches max_iters - even then YOU exit successfully (verify is the consumer that refuses entry).
Configuration
Read once at start from .design/config.json (all optional; defaults in parens):
| Key | Default | Purpose |
|---|---|---|
quality_gate.commands | null | Authoritative command list. When provided, skips auto-detection. |
quality_gate.timeout_seconds | 600 | Total wall-clock budget for Step 2. On timeout: warn + proceed (D-07). |
quality_gate.max_iters | 3 | Hard cap on Step 4 fix-loop iterations. |
Step 1 - Detection chain (D-06 3-tier fallback)
Stop at the first tier that produces ≥ 1 command:
- Authoritative config. If
.design/config.jsonhasquality_gate.commandsnon-empty, use verbatim. - Auto-detect from
package.json#scripts- match against allowlist:lint,typecheck,tsc(only iftypecheckabsent),test,chromatic,test:visual,lint:design(Phase 41 - thegdd-detectdeterministic anti-pattern gate), and the accessibility scriptsaxe,pa11y,lighthouse,eslint-plugin-jsx-a11y(or a script namedjsx-a11y) which classify into thea11ybucket. Exclude by name:test:e2e,test:integration(if separatetest), anything startingdev:,build:,start:. Run vianpm run <name>unlessquality_gate.package_manageroverrides. - Skip with notice. Emit
quality_gate_skipped(Step 6) and write a<run/>withstatus="skipped". Verify treats skipped as non-blocking.
Step 2 - Parallel run
Emit quality_gate_started. Spawn each command in a separate Bash; collect {command, exit_code, stdout, stderr}. Wall-clock budget is timeout_seconds (default 600). On timeout: emit quality_gate_timeout, mark status="timeout", skip Steps 3–4, proceed to Step 5. Exit successfully - verify treats timeout as a warn.
Step 3 - Classification
Spawn quality-gate-runner agent via Task with payload {outputs: [{command, exit_code, stderr}, ...]}. Agent returns {status: "pass"|"fail", classified_failures: {lint, type, test, visual, a11y}}. The a11y bucket groups accessibility failures from axe / pa11y / lighthouse / jsx-a11y. pass → Step 5. fail → Step 4.
Step 4 - Fix loop (D-08)
If iteration >= max_iters: emit quality_gate_fail, mark status="fail", Step 5, exit successfully. Verify-entry refuses on fail; YOU do not throw.
Else: increment iteration, emit quality_gate_iteration, spawn design-fixer via Task with classified failures + original outputs. After fixer returns, restart from Step 2 (re-run all commands - fixes can introduce regressions).
Step 5 - STATE write
Mutate state.quality_gate.run to {started_at, completed_at, status, iteration, commands_run, extra_attrs:{}}. Persist via mcp__gdd_state__set_quality_gate or apply() mutator from sdk/state/mutator.ts - identical on-disk shape.
Step 6 - Event emission (D-09)
Use appendEvent from sdk/event-stream/index.ts - persist-first / broadcast-second; never throws on persist path. ts / cycle / stage are stamped by the writer. Six event types (one per lifecycle position):
| Event | When | Payload |
|---|---|---|
quality_gate_started | Step 2 entry, once | commands, timeout_seconds, max_iters |
quality_gate_iteration | Step 4 retry (iter ≥ 2) | iteration |
quality_gate_pass | Step 3 returned pass - terminal | iteration, commands_run |
quality_gate_fail | Step 4 hit max_iters - terminal | iteration, classified_failures |
quality_gate_timeout | Step 2 budget elapsed - terminal warn | unfinished_commands |
quality_gate_skipped | Step 1 tier 3 - terminal no-op | reason |
appendEvent swallows persist failures - events are observability, not correctness. STATE.md (Step 5) is the durable record.
Output
Emit one JSON object on stdout: {status, iteration, commands_run, started_at, completed_at}. Shell exit code 0 on every terminal status - fail included. Verify-entry is the sole consumer that acts on fail.
Constraints
- Do not prune the command list across iterations - re-run everything in Step 2.
- Do not spawn
quality-gate-runnermore than once per iteration. - Do not read/write any STATE block other than
<quality_gate>(and<position>.last_checkpoint). - Do not invoke verify or design - Stage 4.5 sits strictly between.
- Exit-code convention:
0clean; non-zero classified as failure. Do not interpret stderr for pass/fail.
For verify-side severity classification (when this gate's status="fail" reaches the verify entry gate), see ./threat-modeling.md - STRIDE dispositions are the audit-side framework that informs whether a failed quality gate blocks ship.


