GitLab CI Quality Gates (ci-templates)
Wires the already-built, versioned components from
educlopezcinetic/ci-templates
into a project, plus the one piece that repo doesn't cover yet (PHP static
analysis), and sets branch protection to match how the project is really
worked. Validated end-to-end against educlopezcinetic/prestashop-demo and
laravel-demo (real deliberate-failure smoke tests for every gate, not just
"pipeline ran").
The stack-specific include blocks and static-analysis job live in
references/ — this file is the shared decision tree and gotchas, identical
regardless of stack. Load the matching reference file for Step 1 and Step 2:
references/prestashop.mdreferences/laravel.mdreferences/astro.mdreferences/nextjs.md- (No other stack references exist yet — add one following the same shape when a new stack actually needs this, don't build ahead of need.)
Step 0 — ask, don't assume: blocking or informational?
This is the one decision that changes everything else. Ask the user:
"¿Este proyecto lo trabajás solo con hábito de PR, o el equipo mergea ramas directo a
testsin MR?"
- Solo / personal workflow, real PR habit → gates are blocking.
Enable
only_allow_merge_if_pipeline_succeeds, protecttest/mainwith push access "No one" (merge via MR only). This is the Eduardo-personal-project case — see Step 3. - Team client project (coworkers merge branches straight to
test, no MR habit yet) → gates are informational only. Do NOT enableonly_allow_merge_if_pipeline_succeedsand do NOT restrict push access ontest/main— that would silently block a coworker's normal workflow the moment they push, with no warning, for a policy they never agreed to. The pipeline still shows red/green on every push as a signal; it just never blocks anything. Skip Step 3 entirely for this case.
Never flip a real client project to blocking without the user explicitly confirming the whole team knows and is ready for it.
Step 1 — wire the ci-templates components
Open the reference file matching the project's stack (references/prestashop.md
or references/laravel.md) for the exact include: block and workflow:
rules to use.
Always verify the current tag before wiring — do not assume the version in the reference file is still latest:
glab api "projects/educlopezcinetic%2Fci-templates/repository/tags"
Check the ci-templates repo's own README for the current, authoritative
input list per component before wiring — it's the source of truth; this
skill and its references just point you at it and add the missing
static-analysis piece.
Step 2 — static analysis (repo-local, not a component yet)
ci-templates doesn't have a PHPStan/Larastan component yet — add it as a
repo-local job per the stack's reference file. Critical gotcha: every
ci-templates component job declares needs: [] on purpose (see the repo's
own README) so one gate failing doesn't silently skip the others via classic
stage-based blocking. Any repo-local job you add alongside them — like this
one — MUST also declare needs: [], or it will get marked skipped (not
run, not failed, just invisible) the first time an earlier-stage gate fails.
Confirmed as a real bug in prestashop-demo before this skill existed —
don't repeat it.
Step 3 — branch protection + merge checks (blocking case ONLY)
Only do this after Step 0 confirmed the project is solo/personal with a real PR habit. Skip entirely for team client projects.
enc=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "<namespace>/<project>")
# Create + protect a `test` branch (pre-prod), merge-only:
glab api -X POST "projects/$enc/repository/branches" --field "branch=test" --field "ref=main"
glab api -X POST "projects/$enc/protected_branches" --field "name=test" --field "push_access_level=0" --field "merge_access_level=40"
# Block merge on red pipeline:
glab api -X PUT "projects/$enc" --field "only_allow_merge_if_pipeline_succeeds=true"
push_access_level=0 ("No one") is deliberate — it forces even the repo
owner through a Merge Request, which is the whole point if the goal is
building the habit. If that's too strict for how the user actually wants to
work, use 40 (Maintainer) instead and say so explicitly — don't silently
soften it.
If the project follows Cinetic's Intervals-task branching model (task branch
off main/master, merged to test first for pre-prod validation, then
the same task branch — never test itself — merged to main once the
client approves), the workflow: rules already cover both merge targets:
merge_request_event fires regardless of which branch the MR targets, so
gates run identically whether the MR goes into test or main. No extra
config needed for this branching model specifically.
Gotchas (stack-agnostic)
needs: []on every repo-local job you add next to ci-templates components — omitting it causes silentskipped(notfailed) status the first time an earlier gate fails. See Step 2.- Pin the exact
ci-templatestag (@vX.Y.Z), never@main. If you're the one maintainingci-templatesitself, also protect its tags (Settings → Repository → Protected tags, patternv*, Maintainer-only) — GitLab tags are mutable by default, and an unprotected tag is a single point of failure across every project that includes it. - No zizmor-equivalent exists for GitLab CI. The closest tool is
gitlab-ci-verify(ShellCheck overscript:blocks) — small, low-adoption. The real defenses are GitLab-native: protected CI/CD variables are never exposed to non-protected-branch or fork-originated pipelines by default; the actual gotcha is a maintainer manually running a fork MR's pipeline in the parent project — review the.gitlab-ci.ymldiff first if you ever do that. - GitLab Free tier is enough for all of this.
only_allow_merge_if_pipeline_succeedsis a plain repository setting, not a Premium feature. What Free tier doesn't have is GitHub-style named required-status-checks or MR approval rules — the whole pipeline is the unit; if it's green, every gate in it passed. - CI minutes are limited on GitLab.com's standard tier (shared runners).
All the ci-templates gates are already designed around this: secret-scan
and dependency-audit are MR-diff/lockfile-scoped (not full-repo), and
dependency-audit-*also runs on scheduled pipelines rather than every push where it makes sense. Don't add a full test/build matrix across multiple PHP/framework versions on a client project's real CI — that's an OSS-scale practice (see PrestaShop core/Laravel core's own CI), not appropriate for a small team's per-minute budget. One job pinned to the actual production PHP/framework version is enough. - Dependency-audit findings on a fresh/clean install are normal, not a setup mistake — see the stack reference file for the specific "safe to hand-patch vs. wait for upstream" version-range logic before touching anything a scan finds.
Known gaps — researched, not built yet
External research across large OSS PrestaShop/Laravel/Astro/Next.js projects (astro, starlight, next.js, cal.com, formbricks, laravel/framework, spatie/laravel-permission, filamentphp/filament) surfaced practices this skill doesn't cover yet. Don't build these speculatively — add them when a real project's needs justify the CI-minute cost, same reasoning as everything else here:
- Dependabot/Renovate-equivalent — near-universal in every repo studied, currently absent here. GitLab's own Dependency Scanning / a self-hosted Renovate bot both fit; hasn't been evaluated yet for this team's scale.
- Severity-threshold hard-fail on dependency audits —
cal.comrunsaudit --severity criticalas an actual blocking gate rather than a passive report.dependency-audit-node/dependency-audit-phphere already support anaudit-levelinput (see references) — using it as a hard gate rather than leaving it at a lenient default is a cheap upgrade worth considering per-project. - Free-tier SAST (CodeQL/Semgrep-equivalent) — used at scale by astro/formbricks/cloudflare-docs for any project touching auth/user data. No direct GitLab-native equivalent evaluated yet — flagged as a candidate, not scoped.