Quality Gate
Run every lint, typecheck, and test step the project declares, in parallel, and report the outcome as a single table. This skill doesn't fix failures—it surfaces them with enough detail that the caller knows what to fix.
The gate's wiring — whether the Taskfile targets, .pre-commit-config.yaml, and ci.yml conform to spec/project/quality-gate/ — is the sibling quality-gate-enforcer agent's job: dispatch that read-only auditor when the question is whether the gate is wired right, not whether the code passes it.
Implements spec/project/quality-gate/ — the spec defines the gate composition contract, invocation requirements, and output shape. This skill binds those rules to the on-disk procedure.
German trigger phrases
This skill also triggers on equivalent German-language requests, including:
- "Quality-Gate ausführen"
- "vor dem Commit prüfen"
- "Linting und Tests laufen lassen"
User-language policy
Detect the user's language from their message and respond in it. The result table uses English column headers so the output stays diffable; prose around the table is localised.
Inputs
- Repo root: default is the current working directory.
- Scope override (optional): caller may restrict the run to a named subset — "lint only," "tests only," "typecheck only," "fast" (lint + typecheck, skip tests).
- Subroot filter (optional): in a monorepo, caller may name a subroot (
backend/, frontend/, packages/foo/) to scope the run.
Operations
1. Prefer Taskfile targets
If the repo root has Taskfile.yml or Taskfile.yaml, enumerate the declared targets:
task --list-all 2>/dev/null
Look for these target names, in order of preference:
| Category |
Preferred target |
Fallback targets |
| Composite |
check, quality, gate |
run the category-specific targets below instead |
| Lint |
lint |
lint:backend, lint:frontend, ruff, eslint |
| Typecheck |
typecheck, types |
tsc, mypy |
| Tests |
test |
test:unit, test:backend, test:frontend, pytest, vitest |
Rules for picking targets:
- If a composite target exists and clearly wraps lint + typecheck + tests (inspect its description and the commands it runs via
task --summary <name>), run only that target and report its single pass/fail as the whole gate. Record which target was used.
- Otherwise, pick one target per category and run them in parallel. If a category has no target, fall through to native tooling detection (Step 2) for just that category.
Record every chosen target in the report so the caller can see what ran.
2. Detect native tooling (fallback)
For any category not covered by a Taskfile target, detect the tooling from the manifests:
| Language |
Lint |
Typecheck |
Tests |
Python — pyproject.toml declares [tool.ruff] |
ruff check . |
mypy . or pyright only if configured |
pytest -q |
Python — pyproject.toml declares flake8 / pylint |
that linter |
same |
pytest -q |
Node — package.json has lint script |
npm run lint (or pnpm lint / yarn lint) |
tsc -b or tsc --noEmit if tsconfig.json exists |
npm test -- --run (vitest) or the declared test script |
Go — go.mod exists |
go vet ./... (and golangci-lint run if installed) |
go build ./... |
go test ./... |
Rust — Cargo.toml exists |
cargo clippy --all-targets -- -D warnings |
included in build |
cargo test |
Detection rules:
- Use whichever package manager the repo actually uses. Check the lockfile name:
package-lock.json → npm, pnpm-lock.yaml → pnpm, yarn.lock → yarn, uv.lock → uv, poetry.lock → poetry.
- Run each detected tool from the subroot its manifest lives in, not from the repo root, unless the Taskfile already scopes it.
- For monorepos, run a detection pass per subroot with a manifest, then fan out parallel runs.
If a category has no detectable tooling and no Taskfile target, record it as skipped: no tooling detected rather than claiming pass.
3. Run checks in parallel
Issue every chosen command in a single parallel batch. Each command must end with ; echo "EXIT:$?" so the exit code survives through redirects and shell wrappers. Honour these timeouts:
- Lint: 2 minutes.
- Typecheck: 5 minutes.
- Tests: 10 minutes (raise if the project's Taskfile target clearly documents a longer run).
If a command times out, report it as timeout in the status column—don't retry.
4. Parse each result
For every check, capture:
- Status:
pass, fail, skipped, or timeout.
- Exit code.
- Counters where parseable: number of lint errors and warnings (ruff/eslint JSON), number of type errors (tsc output), number of passed/failed tests (pytest/vitest/go test summary line).
- First failure snippet: for failing checks, the first 10 lines of the actual failure output — enough for the caller to triage without re-running.
Project-local conventions that the skill must honour when the Taskfile target already honours them:
- A Taskfile wraps
ruff with an ignore list → don't second-guess and run ruff directly.
- A Taskfile passes
--exclude or --testpaths → don't override.
When running tools directly (Step 2 fallback), don't add project-local ignores the skill doesn't know about. Report the raw tool output and let the caller decide.
5. Render the result table
| Check | Status | Runner | Details |
|---|---|---|---|
| Lint | pass/fail | `task lint` | <n> errors, <m> warnings (exit <code>) |
| Typecheck | pass/fail | `tsc -b` (detected) | <n> type errors (exit <code>) |
| Tests | pass/fail | `task test` | <passed>/<total> passed (exit <code>) |
The Runner column shows exactly what was invoked — the Taskfile target name or the detected command — so the caller can reproduce the failure locally.
The Details column should surface the underlying tool's exit code (the EXIT:$? value captured in Step 4) per spec/project/quality-gate/ §Timeouts and failure handling, so the caller can tell "lint found 3 errors" (exit 1) apart from "lint crashed" (exit > 1).
Below the table, for every fail or timeout row, append a one-paragraph excerpt from the captured output (≤10 lines, fenced in a code block). Group excerpts by check.
6. Overall verdict
- All
pass: one-line green summary (Quality gate passed — N checks green.).
- Any
fail / timeout: red summary naming the failed checks and pointing at the excerpts below the table.
fast scope (tests deliberately skipped): when the caller ran the fast scope (lint + typecheck, tests skipped), report the Tests row as skipped with the Details noting the deliberate fast-scope reason (for example skipped: fast scope (pre-commit), tests deferred), and have the verdict explicitly state that tests were skipped by design — for example Quality gate (fast scope) passed — lint + typecheck green; tests skipped (fast scope).. This is a distinct verdict note from skipped: no tooling detected below: the fast-scope skip is an intentional scoping decision, not a missing-tooling gap, and the caller must be able to tell the two apart per spec/project/quality-gate/ §Triggers and §Output shape. A fast run is never reported as a full pass.
- Any
skipped: no tooling detected: mention them explicitly in the summary so the caller can decide whether they're acceptable (a pure-Python repo genuinely has no frontend lint) or a misconfiguration (missing ruff config). Keep this distinct from the deliberate fast-scope skip above — the two skip reasons must never be collapsed into one undifferentiated skipped.
Examples
- Read
examples/01-task-targets-when-available.md when Taskfile targets exist and the skill should prefer them over direct tool invocation.
- Read
examples/02-native-fallback-no-taskfile.md when no Taskfile is present and the skill must detect and run native tooling directly.
- Read
examples/03-multi-language-monorepo.md when the repository has multiple language subroots that each need a separate parallel scan.
Gotchas
- Taskfile target may exist but wrap nothing:
task --list-all lists a target even if it contains no commands or calls a non-existent dependency; always inspect the target via task --summary <name> before relying on its output — an empty or broken target silently produces exit 0.
- Tool detection order is not deterministic across repos: a monorepo may carry both
pyproject.toml and package.json at the root, making the detected lint/test runner ambiguous; always select the runner per the subroot where its manifest lives and document which runner was chosen in the Runner column.
- Missing Taskfile target for a category is not a failure: when
task lint doesn't exist the skill falls through to native tooling — record skipped: no tooling detected rather than reporting fail; claiming fail without a detected tool is a false positive.
Hard rules
- Never fix failures automatically. This skill surfaces them; fixing is a separate step the caller owns.
- Never run checks sequentially when parallel execution is possible and the project doesn't explicitly forbid it. Parallel is the default so feedback is fast.
- Never claim
pass for a check that was skipped. skipped is a distinct status.
- Never report a
fast-scope run (tests deliberately skipped) as a plain pass, and never collapse the deliberate fast-scope skip into the same verdict note as skipped: no tooling detected. The verdict must distinguish "tests skipped by design (fast scope), with the reason recorded in Details" from "tests skipped because no test tooling was detected." Both surface in the verdict; they are never merged.
- Never apply a project-specific lint ignore the skill guessed at. Respect Taskfile targets; run the raw tool when falling back.
- Never retry a timed-out check; report the timeout so the caller can decide whether the timeout was wrong or the test truly hangs.
- Always prefer Taskfile targets over direct tool invocation when a suitable target exists. That keeps project conventions in charge.
- Always report exactly what was invoked in the Runner column so the caller can reproduce any failure locally.
- Always include enough of the failure output (≤10 lines per failing check) that the caller doesn't need to re-run to triage.
- When
spec/project/quality-gate/ and this skill disagree, the spec wins; this skill needs the update.
Why this is a skill, not an agent
This is a skill, not an agent, because:
- Orchestration role: quality gating is one step in a pre-commit / pre-PR flow; the output is meant to flow back into the main conversation so the caller can triage.
- Interactivity: the caller typically wants to decide what to do with a failure (fix now, defer, override) as part of the same conversation — skill bias.
- Context-window impact is acceptable: the output is compact (one table + short excerpts), so isolation wouldn't pay for itself.
- Counter-dimension: parallel execution is an agent-side dimension, but it's already achievable inside a skill via parallel
Bash calls — it doesn't force the agent choice.
1---2name: quality-gate3description: Runs the project's lint + typecheck + test gate in parallel, tabulates the results, and calls out exactly which checks failed so the caller can triage before a commit, a PR, or a release. Prefers repository-declared Taskfile targets (`task lint`, `task test`, `task typecheck`, `task check`) when they exist so project conventions and ignore lists are honoured; otherwise detects and runs the native tooling directly (ruff, pytest, eslint, tsc, vitest, go test, cargo test, and similar). Invoke when the user asks to "run the quality gate," "run lint and tests," "make sure CI will pass," "run all checks before committing," or equivalent German-language requests. Don't use for CVE scanning (`dependency-audit`) or license compliance (`license-check`), even when task lint wraps a security check and don't use for documentation builds (those are a separate concern).4---56# Quality Gate78Run every lint, typecheck, and test step the project declares, in parallel, and report the outcome as a single table. This skill doesn't fix failures—it surfaces them with enough detail that the caller knows what to fix.910The gate's *wiring* — whether the Taskfile targets, `.pre-commit-config.yaml`, and `ci.yml` conform to `spec/project/quality-gate/` — is the sibling `quality-gate-enforcer` agent's job: dispatch that read-only auditor when the question is whether the gate is wired right, not whether the code passes it.1112Implements `spec/project/quality-gate/` — the spec defines the gate composition contract, invocation requirements, and output shape. This skill binds those rules to the on-disk procedure.1314## German trigger phrases1516This skill also triggers on equivalent German-language requests, including:1718- "Quality-Gate ausführen"19- "vor dem Commit prüfen"20- "Linting und Tests laufen lassen"2122## User-language policy2324Detect the user's language from their message and respond in it. The result table uses English column headers so the output stays diffable; prose around the table is localised.2526## Inputs2728- **Repo root**: default is the current working directory.29- **Scope override** (optional): caller may restrict the run to a named subset — "lint only," "tests only," "typecheck only," "fast" (lint + typecheck, skip tests).30- **Subroot filter** (optional): in a monorepo, caller may name a subroot (`backend/`, `frontend/`, `packages/foo/`) to scope the run.3132## Operations3334### 1. Prefer Taskfile targets3536If the repo root has `Taskfile.yml` or `Taskfile.yaml`, enumerate the declared targets:3738```39task --list-all 2>/dev/null40```4142Look for these target names, in order of preference:4344| Category | Preferred target | Fallback targets |45|---|---|---|46| Composite | `check`, `quality`, `gate` | run the category-specific targets below instead |47| Lint | `lint` | `lint:backend`, `lint:frontend`, `ruff`, `eslint` |48| Typecheck | `typecheck`, `types` | `tsc`, `mypy` |49| Tests | `test` | `test:unit`, `test:backend`, `test:frontend`, `pytest`, `vitest` |5051Rules for picking targets:5253- If a **composite** target exists and clearly wraps lint + typecheck + tests (inspect its description and the commands it runs via `task --summary <name>`), run only that target and report its single pass/fail as the whole gate. Record which target was used.54- Otherwise, pick one target per category and run them **in parallel**. If a category has no target, fall through to native tooling detection (Step 2) for just that category.5556Record every chosen target in the report so the caller can see what ran.5758### 2. Detect native tooling (fallback)5960For any category not covered by a Taskfile target, detect the tooling from the manifests:6162| Language | Lint | Typecheck | Tests |63|---|---|---|---|64| Python — `pyproject.toml` declares `[tool.ruff]` | `ruff check .` | `mypy .` or `pyright` only if configured | `pytest -q` |65| Python — `pyproject.toml` declares flake8 / pylint | that linter | same | `pytest -q` |66| Node — `package.json` has `lint` script | `npm run lint` (or `pnpm lint` / `yarn lint`) | `tsc -b` or `tsc --noEmit` if `tsconfig.json` exists | `npm test -- --run` (vitest) or the declared `test` script |67| Go — `go.mod` exists | `go vet ./...` (and `golangci-lint run` if installed) | `go build ./...` | `go test ./...` |68| Rust — `Cargo.toml` exists | `cargo clippy --all-targets -- -D warnings` | included in build | `cargo test` |6970Detection rules:7172- Use whichever package manager the repo actually uses. Check the lockfile name: `package-lock.json` → npm, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `uv.lock` → uv, `poetry.lock` → poetry.73- Run each detected tool from the subroot its manifest lives in, not from the repo root, unless the Taskfile already scopes it.74- For monorepos, run a detection pass per subroot with a manifest, then fan out parallel runs.7576If a category has no detectable tooling and no Taskfile target, record it as `skipped: no tooling detected` rather than claiming pass.7778### 3. Run checks in parallel7980Issue every chosen command in a single parallel batch. Each command **must** end with `; echo "EXIT:$?"` so the exit code survives through redirects and shell wrappers. Honour these timeouts:8182- Lint: 2 minutes.83- Typecheck: 5 minutes.84- Tests: 10 minutes (raise if the project's Taskfile target clearly documents a longer run).8586If a command times out, report it as `timeout` in the status column—don't retry.8788### 4. Parse each result8990For every check, capture:9192- **Status**: `pass`, `fail`, `skipped`, or `timeout`.93- **Exit code**.94- **Counters where parseable**: number of lint errors and warnings (ruff/eslint JSON), number of type errors (tsc output), number of passed/failed tests (pytest/vitest/go test summary line).95- **First failure snippet**: for failing checks, the first 10 lines of the actual failure output — enough for the caller to triage without re-running.9697Project-local conventions that the skill **must** honour when the Taskfile target already honours them:9899- A Taskfile wraps `ruff` with an ignore list → don't second-guess and run `ruff` directly.100- A Taskfile passes `--exclude` or `--testpaths` → don't override.101102When running tools directly (Step 2 fallback), **don't** add project-local ignores the skill doesn't know about. Report the raw tool output and let the caller decide.103104### 5. Render the result table105106```107| Check | Status | Runner | Details |108|---|---|---|---|109| Lint | pass/fail | `task lint` | <n> errors, <m> warnings (exit <code>) |110| Typecheck | pass/fail | `tsc -b` (detected) | <n> type errors (exit <code>) |111| Tests | pass/fail | `task test` | <passed>/<total> passed (exit <code>) |112```113114The **Runner** column shows exactly what was invoked — the Taskfile target name or the detected command — so the caller can reproduce the failure locally.115116The **Details** column **should** surface the underlying tool's exit code (the `EXIT:$?` value captured in Step 4) per `spec/project/quality-gate/` §Timeouts and failure handling, so the caller can tell "lint found 3 errors" (exit 1) apart from "lint crashed" (exit > 1).117118Below the table, for every `fail` or `timeout` row, append a one-paragraph excerpt from the captured output (≤10 lines, fenced in a code block). Group excerpts by check.119120### 6. Overall verdict121122- **All `pass`**: one-line green summary (`Quality gate passed — N checks green.`).123- **Any `fail` / `timeout`**: red summary naming the failed checks and pointing at the excerpts below the table.124- **`fast` scope (tests deliberately skipped)**: when the caller ran the `fast` scope (lint + typecheck, tests skipped), report the Tests row as `skipped` with the **Details** noting the deliberate fast-scope reason (for example `skipped: fast scope (pre-commit), tests deferred`), and have the verdict explicitly state that tests were skipped by design — for example `Quality gate (fast scope) passed — lint + typecheck green; tests skipped (fast scope).`. This is a **distinct** verdict note from `skipped: no tooling detected` below: the fast-scope skip is an intentional scoping decision, not a missing-tooling gap, and the caller must be able to tell the two apart per `spec/project/quality-gate/` §Triggers and §Output shape. A `fast` run is never reported as a full `pass`.125- **Any `skipped: no tooling detected`**: mention them explicitly in the summary so the caller can decide whether they're acceptable (a pure-Python repo genuinely has no frontend lint) or a misconfiguration (missing `ruff` config). Keep this distinct from the deliberate fast-scope skip above — the two skip reasons must never be collapsed into one undifferentiated `skipped`.126127## Examples128129- Read `examples/01-task-targets-when-available.md` when Taskfile targets exist and the skill should prefer them over direct tool invocation.130- Read `examples/02-native-fallback-no-taskfile.md` when no Taskfile is present and the skill must detect and run native tooling directly.131- Read `examples/03-multi-language-monorepo.md` when the repository has multiple language subroots that each need a separate parallel scan.132133## Gotchas134135- **Taskfile target may exist but wrap nothing**: `task --list-all` lists a target even if it contains no commands or calls a non-existent dependency; always inspect the target via `task --summary <name>` before relying on its output — an empty or broken target silently produces exit 0.136- **Tool detection order is not deterministic across repos**: a monorepo may carry both `pyproject.toml` and `package.json` at the root, making the detected lint/test runner ambiguous; always select the runner per the subroot where its manifest lives and document which runner was chosen in the `Runner` column.137- **Missing Taskfile target for a category is not a failure**: when `task lint` doesn't exist the skill falls through to native tooling — record `skipped: no tooling detected` rather than reporting `fail`; claiming fail without a detected tool is a false positive.138139## Hard rules140141- **Never** fix failures automatically. This skill surfaces them; fixing is a separate step the caller owns.142- **Never** run checks sequentially when parallel execution is possible and the project doesn't explicitly forbid it. Parallel is the default so feedback is fast.143- **Never** claim `pass` for a check that was skipped. `skipped` is a distinct status.144- **Never** report a `fast`-scope run (tests deliberately skipped) as a plain `pass`, and never collapse the deliberate fast-scope skip into the same verdict note as `skipped: no tooling detected`. The verdict must distinguish "tests skipped by design (fast scope), with the reason recorded in Details" from "tests skipped because no test tooling was detected." Both surface in the verdict; they are never merged.145- **Never** apply a project-specific lint ignore the skill guessed at. Respect Taskfile targets; run the raw tool when falling back.146- **Never** retry a timed-out check; report the timeout so the caller can decide whether the timeout was wrong or the test truly hangs.147- **Always** prefer Taskfile targets over direct tool invocation when a suitable target exists. That keeps project conventions in charge.148- **Always** report exactly what was invoked in the **Runner** column so the caller can reproduce any failure locally.149- **Always** include enough of the failure output (≤10 lines per failing check) that the caller doesn't need to re-run to triage.150- When `spec/project/quality-gate/` and this skill disagree, the spec wins; this skill needs the update.151152## Why this is a skill, not an agent153154This is a skill, not an agent, because:155156- **Orchestration role**: quality gating is one step in a pre-commit / pre-PR flow; the output is meant to flow back into the main conversation so the caller can triage.157- **Interactivity**: the caller typically wants to decide what to do with a failure (fix now, defer, override) as part of the same conversation — skill bias.158- **Context-window impact is acceptable**: the output is compact (one table + short excerpts), so isolation wouldn't pay for itself.159- **Counter-dimension**: parallel execution is an agent-side dimension, but it's already achievable inside a skill via parallel `Bash` calls — it doesn't force the agent choice.