Generates GitHub Actions workflow YAML (.github/workflows/ci.yml) with configurable stages for build, test, scan, Docker build, and registry push.
How this skill is triggered — by the user, by Claude, or both
Slash command
/devops-github-actions:pipeline-generationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Produces a complete `.github/workflows/ci.yml` for the resolved application language and registry configuration. Stages are individually toggled via the `stages` section of `.devops-agent.json`. The docker-build-push job is always emitted when registry config is present, and automatically depends on all enabled upstream stages.
Produces a complete .github/workflows/ci.yml for the resolved application language and registry configuration. Stages are individually toggled via the stages section of .devops-agent.json. The docker-build-push job is always emitted when registry config is present, and automatically depends on all enabled upstream stages.
Always write to .github/workflows/ci.yml. Create the .github/workflows/ directory if it does not exist. Do not overwrite an existing workflow file without confirmation unless invoked with --force.
All generated workflows use the following trigger block unless overridden in config:
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
| Config value | runs-on emitted |
|---|---|
runner.type: self-hosted (default) | [self-hosted, linux, x64] |
runner.type: hosted | ubuntu-latest |
The runner.labels array in config overrides the default label list for self-hosted runners. Document the label conventions in team guidelines (see ## Runner Config in defaults/coding-guidelines.md).
Stages are emitted in the following dependency order. Each stage after build is only emitted when its corresponding stages.*.enabled flag is true in config.
build
└── unit-test (stages.unitTest.enabled)
└── integration-test (stages.integrationTest.enabled)
└── sonar-scan (stages.sonarScan.enabled)
└── snyk-scan (stages.snykScan.enabled)
└── wiz-scan (stages.wizScan.enabled) ← part of docker-build-push job
└── docker-build-push (always, needs all enabled prior stages)
The docker-build-push job's needs: array is computed at generation time and includes only the job names that were enabled. If no optional stages are enabled, docker-build-push depends only on build.
The Wiz scan (wizcli docker scan) runs as a step inside docker-build-push after the Docker image is built, because it scans the image artifact directly. All other scans are independent parallel jobs.
| Language | Setup Action | Build Command |
|---|---|---|
| .NET | actions/setup-dotnet@v4 with dotnet-version | dotnet restore then dotnet build --no-restore --configuration Release |
| Node.js | actions/setup-node@v4 with node-version | npm ci && npm run build |
| Java | actions/setup-java@v4 with java-version and distribution: temurin | mvn package -DskipTests |
| Go | actions/setup-go@v4 with go-version | go build ./... |
| Python | actions/setup-python@v5 with python-version | pip install --no-cache-dir -r requirements.txt |
Language version is sourced from the languages.<lang>.version field in .devops-agent.json, or auto-detected from project files (e.g., global.json, .nvmrc, go.mod, .python-version).
Include actions/cache@v4 in the build job for each language's dependency directory:
| Language | Cache key | Cache path |
|---|---|---|
| .NET | ${{ hashFiles('**/*.csproj') }} | ~/.nuget/packages |
| Node.js | ${{ hashFiles('**/package-lock.json') }} | ~/.npm |
| Java | ${{ hashFiles('**/pom.xml') }} | ~/.m2 |
| Go | ${{ hashFiles('**/go.sum') }} | ~/go/pkg/mod |
| Python | ${{ hashFiles('**/requirements.txt') }} | ~/.cache/pip |
Before emitting docker-build-push, check for a Dockerfile in the project root. If none is found, automatically invoke the devops-dockerfile-generation skill to create one, then continue. Log the auto-invocation so it is visible in the agent's output:
[pipeline-generation] No Dockerfile found — invoking dockerfile-generation skill automatically.
Include a top-level env: block with registry coordinates sourced from config:
env:
REGISTRY: <defaults.registry.host>
IMAGE_NAME: ${{ github.repository }}
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: yourorg.azurecr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
unit-test:
needs: build
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
- name: Run unit tests
run: dotnet test --no-restore --configuration Release --logger trx
sonar-scan:
needs: build
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@v3
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
snyk-scan:
needs: build
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- name: Snyk Security Scan
uses: snyk/actions/dotnet@v1
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
docker-build-push:
needs: [unit-test, sonar-scan, snyk-scan]
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- name: Login to ACR
uses: azure/docker-login@v1
with:
login-server: ${{ env.REGISTRY }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build Docker image
run: docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
- name: Wiz CLI Image Scan
run: |
wizcli docker scan \
--image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--policy "default-image-policy"
env:
WIZ_CLIENT_ID: ${{ secrets.WIZ_CLIENT_ID }}
WIZ_CLIENT_SECRET: ${{ secrets.WIZ_CLIENT_SECRET }}
- name: Push Docker image
run: docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
When no optional stages are enabled, emit a minimal two-job workflow:
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: yourorg.azurecr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
docker-build-push:
needs: build
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- name: Login to ACR
uses: azure/docker-login@v1
with:
login-server: ${{ env.REGISTRY }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
The following fields in .devops-agent.json control pipeline generation:
{
"platform": "github-actions",
"runner": {
"type": "self-hosted",
"labels": ["self-hosted", "linux", "x64"]
},
"stages": {
"unitTest": { "enabled": true },
"integrationTest": { "enabled": false },
"sonarScan": { "enabled": true },
"snykScan": { "enabled": true },
"wizScan": { "enabled": false }
},
"defaults": {
"registry": {
"type": "acr",
"host": "yourorg.azurecr.io"
}
}
}
npx claudepluginhub gagandeepp/software-agent-teams --plugin devops-github-actionsCreates, edits, and verifies skills using a test-driven development approach with pressure scenarios and subagents.