{
 "id": "gitlab-ci",
 "kind": "skill",
 "name": "GitLab CI/CD",
 "description": "Write, validate and debug GitLab CI/CD pipelines (.gitlab-ci.yml): stages, jobs, rules, artifacts, environments and deployments.",
 "version": "1.0.0",
 "author": "Hexa Hub",
 "files": {
  "SKILL.md": "---\nname: gitlab-ci\ndescription: Write, validate and debug GitLab CI/CD pipelines (.gitlab-ci.yml): stages, jobs, rules, artifacts, environments and deployments.\ntitle: GitLab CI/CD\nicon: tabler:brand-gitlab\ncategory: CI/CD\ntriggers: gitlab, gitlab-ci, pipeline, ci/cd, runner, stage, artifacts, glab, merge request, .gitlab-ci.yml\nmarkers: glab, gitlab-ci\nrecipes: gitlab.ci-lint, gitlab.pipeline-status, check.yamllint, git.status, git.diff, git.log\nrelated: docker-compose, terraform, ansible\n---\n\n# GitLab CI/CD\n\n## Workflow\n1. Read `.gitlab-ci.yml` and every file it `include`s (the code map lists them). Follow the existing stage names and job naming.\n2. Edit. Validate with `gitlab.ci-lint` (asks GitLab itself, needs `glab` logged in) and `check.yamllint` for plain YAML mistakes.\n3. To see what happened on a branch: `gitlab.pipeline-status`; for failed jobs read the job log the user pastes or the GitLab page they point to.\n4. Say what changed and what the lint said. A pipeline only proves itself when it runs on GitLab; say that you could not run it.\n\nExample: `run_recipe {\"recipe\":\"gitlab.ci-lint\",\"args\":{}}`\n\n## Structure\n```yaml\nstages: [build, test, deploy]\ndefault:\n  image: alpine:3.20\nbuild-app:\n  stage: build\n  script: [make build]\n  artifacts: { paths: [dist/], expire_in: 1 week }\ntest-app:\n  stage: test\n  needs: [build-app]\n  script: [make test]\ndeploy-test:\n  stage: deploy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n  environment: test\n  script: [./deploy.sh test]\n```\n\n## Habits\n- **`rules:` not `only:`/`except:`**; never mix them in one job. Typical rules: `if: $CI_PIPELINE_SOURCE == \"merge_request_event\"`, `if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH`.\n- `needs:` lets a job start as soon as its inputs exist; `artifacts` pass files between stages; `cache` (keyed by lockfile) speeds up dependency installs and is not for passing results.\n- Deployments: `environment:` set, production jobs `when: manual` and limited to the default branch or protected tags. Use `resource_group:` so two deploys to one environment never run together.\n- Reuse with `extends:`, `include:` (local, project, template) and hidden jobs (`.base:`); `!reference [.job, script]` to reuse part of a job.\n- A `script` line containing `: ` must be quoted or use a block (`- |`), otherwise YAML reads a mapping.\n- Pin images and tool versions. Set `interruptible: true` on tests so new pushes cancel old pipelines.\n\n## Secrets\nNever write tokens or passwords in the file. They are **CI/CD variables** (masked, and protected for production), referenced as `$NAME`. Tell the user which variables the job needs and where to set them (Settings, CI/CD, Variables). Do not echo variables in scripts.\n\n## Typical failures\n| Symptom | Cause |\n|---|---|\n| \"jobs:x config should contain either only/except or rules\" | both used in one job |\n| Job does not start | `rules` evaluate to nothing for this branch, or the runner tags do not match |\n| \"No such file\" in a later stage | the file was not listed in `artifacts:paths` or expired |\n| Variable empty | variable is protected and the branch is not, or the name differs |\n"
 }
}
