/generate-ci Skill
Generate GitHub Actions CI workflow files for the project at $ARGUMENTS (or current directory if not specified).
Follow each phase sequentially. Ask the user for confirmation before generating files.
Phase 1: Project Auto-Detection
Scope limitation: This skill targets single-package repositories. Monorepos (multiple
package.json/pyproject.tomlat different directory levels, or workspaces configured inpackage.json) are not supported. If a monorepo is detected, inform the user and stop — do not attempt to generate CI files.
Use Glob and Read tools to detect the project's language, package manager, and existing CI setup.
1-1. Detect project root
Set PROJECT_ROOT to $ARGUMENTS if provided, otherwise use the current working directory.
1-2. Language and tool detection
Check for the following files under PROJECT_ROOT:
| File pattern | Detected stack |
|---|---|
pyproject.toml |
Python |
requirements.txt or setup.py |
Python |
package.json + tsconfig.json |
TypeScript |
package.json (no tsconfig) |
Node.js |
*.tf or **/*.tf |
Terraform |
mise.toml |
Read for tool versions |
For Python, also check:
uv.lock→ package manager is uvpoetry.lock→ package manager is poetry- neither → assume pip
For Node.js/TypeScript, also check:
yarn.lock→ package manager is yarnpnpm-lock.yaml→ package manager is pnpmpackage-lock.json→ package manager is npm- If
package.jsonexists but no lockfile is present, inform the user: "⚠️ No lockfile detected (package-lock.json, yarn.lock, or pnpm-lock.yaml). The generated workflow usesnpm ci/yarn --frozen-lockfilewhich requires a committed lockfile. Please runnpm install/yarn install/pnpm installand commit the lockfile, then re-run /generate-ci." Stop generation and do not proceed to Phase 2.
For Python linter, check pyproject.toml:
[tool.ruff]section present → Ruff- default → Ruff (recommended)
For Python version, check in order:
mise.toml[tools] python = "X.Y"→ use that versionpyproject.toml[project] requires-python = ">=X.Y"→ use that minimum version- Neither found → default to
"3.12"
For Python test framework, check pyproject.toml for [tool.pytest] or [tool.pytest.ini_options] → pytest. Otherwise assume pytest as default.
For Python pytest configuration, check pyproject.toml [tool.pytest.ini_options]:
- If
addoptscontains coverage-related flags (--cov,--cov-report, etc.), the project already configures coverage. In this case, the generated workflow should only add--junitxmlfor CI artifacts, and rely on the project config for coverage reporting. - If no
addoptsor minimal flags, the generated workflow should include full coverage flags:--cov=<src_dir> --cov-branch --cov-report=xml --cov-report=term-missing --junitxml=test-results.xml
For Node.js/TypeScript version, check in order:
.nvmrcfile present → usenode-version-file: .nvmrcmise.toml[tools] node = "X"→ usenode-version: "X"package.jsonengines.nodefield → use that version- Neither found → default to
node-version: "22"(current LTS)
For Node.js/TypeScript test framework, read package.json scripts and devDependencies:
vitest→ Vitestjest→ Jest- default → Vitest (recommended)
For Node.js/TypeScript linter:
biome.jsonor@biomejs/biomein devDependencies → Biomeeslintin devDependencies → ESLint- default → Biome (recommended)
1-3. Detect source directories
For Python: look for src/ directory or check [tool.coverage.run] source in pyproject.toml.
If neither is found, look for a top-level Python package directory (a directory containing __init__.py).
If still not found, set source directory to . and add a note in Phase 1 summary asking the user to confirm.
For TypeScript: look for src/ directory or check tsconfig.json rootDir.
If not found, default to . and ask the user to confirm in Phase 1 summary.
1-4. Check existing CI
Use Glob to check if .github/workflows/ exists and list any existing workflow files.
1-5. Report detection summary
Present the detection summary to the user in this format:
Detected project configuration:
Language(s): Python + Terraform (or whatever was found)
Package manager: uv
Python version: 3.12 (from mise.toml / requires-python / default)
Test framework: pytest
Linter: Ruff
Source directory: src/
Terraform: Yes (*.tf files found)
Existing CI: None (or list existing files)
Is this correct? If not, describe any corrections (e.g. "package manager is pip, not uv").
Wait for user confirmation before proceeding to Phase 2.
Phase 2: Interactive CI Configuration
Ask the user a series of questions to configure the CI.
Implementation note: Use the AskUserQuestion tool for interactive questions. The tool supports at most 4 questions per call, with at most 4 options per question. Split into two calls if needed:
Call 1 (CI steps + workflow structure):
- Question 1 — "Which CI steps would you like? (select all that apply)"
multiSelect: true- "Lint + Test + Build + actionlint (essential; always recommended)"
- "Secret scanning (gitleaks)"
- "SAST (CodeQL + Trivy)"
- "Dependency audit + PR title check"
- Question 2 — "Workflow file structure?" (2 options)
- "Separate files: ci.yml + security.yml (recommended)"
- "Single file: ci.yml"
Call 2 (dependency management + visibility + security policy):
- Question 3 — "Dependency management automation?" (3 options)
- "Renovate (recommended)"
- "Dependabot (GitHub native)"
- "Skip"
- Question 4 — "Repository visibility?" (2 options)
- "Private / internal (default)"
- "Public (enables fork PR support)"
Ask security policy and GHAS availability as a follow-up call when option 1 includes SAST (i.e., user selected "SAST" or all options):
- "Security scan failure policy?" (3 options)
- "Advisory — CRITICAL findings appear in Security tab, CI never fails (recommended)"
- "Strict — Trivy exits with error on CRITICAL/HIGH/MEDIUM; CodeQL requires branch protection rule"
- "Log-only — all findings visible in Security tab, CI never fails"
- "GitHub Advanced Security (GHAS) available?" (2 options)
- "Yes — public repo or GHAS enabled: generate CodeQL job + SARIF upload (recommended)"
- "No — private repo without GHAS: omit CodeQL job; set Trivy SARIF upload to continue-on-error"
For Terraform-only projects: always generate terraform.yml separately regardless of workflow
structure choice.
Please answer the following to configure your CI:
1. Which CI steps do you want? (default: all)
- [x] Lint + Test + Build + actionlint (essential; always recommended)
- [x] Secret scanning (gitleaks)
- [x] SAST (CodeQL + Trivy)
- [x] Dependency audit + PR title check
Uncheck any you don't need.
2. Workflow file structure:
a) Separate files: ci.yml + security.yml [recommended]
b) Single file: ci.yml (all steps)
(For Terraform: always generates terraform.yml separately)
3. Dependency management automation:
a) Renovate (renovate.json) [recommended - more flexible]
b) Dependabot (.github/dependabot.yml) [GitHub native]
c) Skip
4. Repository visibility:
a) Private / internal [default]
b) Public (enables fork PR support with restricted permissions)
5. Security scan failure policy: (asked only when SAST is selected)
a) Advisory - findings appear in Security tab, CI never fails [recommended]
b) Strict - Trivy fails CI directly; CodeQL requires branch protection rule
c) Log-only - all findings visible in Security tab, CI never fails
6. GitHub Advanced Security (GHAS) available? (asked only when SAST is selected)
a) Yes - public repo or GHAS enabled: generate CodeQL job + SARIF upload [default]
b) No - private repo without GHAS: omit CodeQL job; Trivy SARIF upload set to continue-on-error
Wait for user responses before proceeding to Phase 3.
Phase 3: File Generation
Based on Phase 1 detection and Phase 2 answers, generate the workflow files using the Write tool.
Important security rules for ALL generated workflows:
- Pin ALL third-party Actions to their git SHA hash, with a version comment
- Add
permissions: {}at the workflow level to remove all default GITHUB_TOKEN permissions (equivalent topermissions: none); then explicitly grant the minimum requiredpermissions:at the job level (e.g.,contents: readfor jobs that useactions/checkout) - Always include
concurrency: cancel-in-progress: true(use${{ github.event_name == 'pull_request' }}forsecurity.ymlthat has ascheduletrigger, to avoid cancelling scheduled CVE scans) - Always include
timeout-minuteson every job - Use
hashFiles()in all cache keys
Use the templates below, substituting detected values.
Templates
Template: ci.yml (Python + uv)
# .github/workflows/ci.yml
# Actions versions are pinned to SHA for security.
# Dependabot/Renovate will keep them up to date automatically.
name: CI
on:
pull_request:
push:
branches: [main]
permissions: {} # Restrict default GITHUB_TOKEN; each job sets its own minimum permissions
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint & Format
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v5.4.0
with:
enable-cache: true
cache-dependency-glob: uv.lock
- name: Lint with Ruff
run: uv run ruff check --output-format=github .
- name: Format check with Ruff
run: uv run ruff format --check .
test:
name: Test & Coverage
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: write # Required for PR coverage comment
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v5.4.0
with:
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --all-extras
# NOTE: --cov=src measures ALL files in src/, including untested ones (shown as 0%)
# This gives an honest picture of total coverage. Adjust --cov=<your-src-dir> if needed.
- name: Run tests with coverage
run: |
uv run pytest \
--cov=src \
--cov-branch \
--cov-report=xml:coverage.xml \
--cov-report=term-missing \
--junitxml=test-results.xml
# Retry on failure to handle flaky tests (remove if tests are stable)
# To enable retries: uv add pytest-rerunfailures --dev
# then add: --reruns 2 --reruns-delay 5
- name: Upload coverage report
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
if: always()
with:
name: coverage-report
path: coverage.xml
retention-days: 7
- name: Post coverage comment on PR
if: github.event_name == 'pull_request'
uses: MishaKav/pytest-coverage-comment@81882822c5cd55bc8e856418ecb60c5c45c11247 # v1.1.52
with:
pytest-xml-coverage-path: coverage.xml
junitxml-path: test-results.xml
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v5.4.0
with:
enable-cache: true
cache-dependency-glob: uv.lock
- name: Build package
run: uv build
actionlint:
name: Lint GitHub Actions workflows
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run actionlint
uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2
Template: ci.yml (Python + pip)
# .github/workflows/ci.yml
# Actions versions are pinned to SHA for security.
# Dependabot/Renovate will keep them up to date automatically.
name: CI
on:
pull_request:
push:
branches: [main]
permissions: {} # Restrict default GITHUB_TOKEN; each job sets its own minimum permissions
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint & Format
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@0b93645bdc8f3c7c6f8d3cf81c2a3a0e5e68a3a3 # v5.3.0
with:
python-version: "3.12"
cache: pip
- name: Install dependencies
run: pip install -e .[dev]
- name: Lint with Ruff
run: python -m ruff check --output-format=github .
- name: Format check with Ruff
run: python -m ruff format --check .
test:
name: Test & Coverage
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: write # Required for PR coverage comment
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@0b93645bdc8f3c7c6f8d3cf81c2a3a0e5e68a3a3 # v5.3.0
with:
python-version: "3.12"
cache: pip
- name: Install dependencies
run: pip install -e .[dev]
# NOTE: --cov=src measures ALL files in src/, including untested ones (shown as 0%)
# This gives an honest picture of total coverage. Adjust --cov=<your-src-dir> if needed.
- name: Run tests with coverage
run: |
python -m pytest \
--cov=src \
--cov-branch \
--cov-report=xml:coverage.xml \
--cov-report=term-missing \
--junitxml=test-results.xml
- name: Upload coverage report
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
if: always()
with:
name: coverage-report
path: coverage.xml
retention-days: 7
- name: Post coverage comment on PR
if: github.event_name == 'pull_request'
uses: MishaKav/pytest-coverage-comment@81882822c5cd55bc8e856418ecb60c5c45c11247 # v1.1.52
with:
pytest-xml-coverage-path: coverage.xml
junitxml-path: test-results.xml
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@0b93645bdc8f3c7c6f8d3cf81c2a3a0e5e68a3a3 # v5.3.0
with:
python-version: "3.12"
cache: pip
- name: Install build tool
run: pip install build
- name: Build package
run: python -m build
actionlint:
name: Lint GitHub Actions workflows
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run actionlint
uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2
Template: ci.yml (Python + poetry)
Generate the same structure as the uv variant, but replace setup/install/run steps in each job. The common setup block for all poetry jobs:
- uses: actions/setup-python@0b93645bdc8f3c7c6f8d3cf81c2a3a0e5e68a3a3 # v5.3.0
with:
python-version: "3.12"
cache: poetry # Cache dependencies based on poetry.lock
- name: Install Poetry
# Pin to a specific version for reproducibility, e.g. "poetry==2.1.3"
run: pip install "poetry>=2.0,<3.0"
- name: Install dependencies
run: poetry install --with dev
Lint job — after the common setup block, add:
- name: Lint with Ruff
run: poetry run ruff check --output-format=github .
- name: Format check with Ruff
run: poetry run ruff format --check .
Test job — after the common setup block, replace the pytest step with:
- name: Run tests with coverage
run: |
poetry run pytest \
--cov=src \
--cov-branch \
--cov-report=xml:coverage.xml \
--cov-report=term-missing \
--junitxml=test-results.xml
Build job — after the common setup block, replace the build step with:
- name: Build package
run: poetry build
The actionlint job is identical to the uv variant.
Template: ci.yml (TypeScript + npm + Biome)
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
permissions: {} # Restrict default GITHUB_TOKEN; each job sets its own minimum permissions
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint & Format
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version-file: .nvmrc # or: node-version: "22"
cache: npm
- run: npm ci
- name: Lint with Biome
run: npx biome check --reporter=github .
# Biome annotations appear inline in PR diffs
test:
name: Test & Coverage
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
# NOTE: vitest.config.ts must set coverage.include to cover ALL src files,
# including untested ones. Example: include: ['src/**/*.ts']
- name: Run tests with coverage
run: npm test
- name: Upload coverage
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
if: always()
with:
name: coverage-report
path: coverage/
retention-days: 7
- name: Post coverage report on PR
if: github.event_name == 'pull_request'
uses: davelosert/vitest-coverage-report-action@2500dafcee7dd64f85ab689c0b83798a8359770e # v2.9.3
with:
# "all" shows Changed Files + Unchanged Files sections (full project coverage visibility).
# "changes" (default) shows only PR-modified files.
file-coverage-mode: all
# Note: this action is maintained by a GitHub employee; actively maintained as of 2025
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- run: npm run build
actionlint:
name: Lint GitHub Actions workflows
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2
Template: ci.yml (TypeScript + npm + ESLint)
Replace the lint job from the Biome template with:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pull-requests: write # for reviewdog PR review comments
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- uses: reviewdog/action-eslint@b19663f0faf1d4b0f1e9f92821f530a836b4b317 # v1.34.0
with:
reporter: github-pr-review # posts inline review comments on PR
eslint_flags: "src/"
Template: ci.yml (TypeScript + yarn or pnpm)
Same as the npm + Biome or npm + ESLint templates, but replace actions/setup-node cache and install steps:
yarn:
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version-file: .nvmrc # or: node-version: "22"
cache: yarn
- name: Install dependencies
run: yarn install --frozen-lockfile
pnpm:
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddde # v4.1.0
with:
run_install: false
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version-file: .nvmrc # or: node-version: "22"
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
Replace all npm run commands with yarn or pnpm run respectively.
Replace npm ci with yarn install --frozen-lockfile or pnpm install --frozen-lockfile.
Note:
pnpm/action-setupmust be added beforeactions/setup-nodefor thecache: pnpmoption to work.
Template: security.yml
# .github/workflows/security.yml
# Runs on PR, push to main, and daily schedule to catch newly disclosed CVEs.
name: Security
on:
pull_request:
push:
branches: [main]
schedule:
- cron: "0 2 * * *" # Daily at 02:00 UTC
workflow_dispatch:
permissions: {} # Restrict default GITHUB_TOKEN; each job sets its own minimum permissions
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Do NOT cancel scheduled runs (they monitor for newly disclosed CVEs).
# Only cancel duplicate PR builds.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
secret-scan:
name: Secret Scanning
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pull-requests: read # Required by gitleaks-action to list PR commits on pull_request events
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # full history for secret scanning
- name: Run gitleaks
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} # required only for paid license
dependency-audit:
name: Dependency Audit
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# NOTE: When generating this workflow, uncomment ONLY the block matching the detected
# ecosystem below. Remove all other commented blocks. Do not leave placeholders in the
# generated workflow.
# --- Python (uv) ---
# Requires: uv add pip-audit --dev
# - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v5.4.0
# - run: uv sync && uv run pip-audit
# --- Python (pip) ---
# - uses: actions/setup-python@0b93645bdc8f3c7c6f8d3cf81c2a3a0e5e68a3a3 # v5.3.0
# with: { python-version: "3.12", cache: pip }
# - run: pip install -e .[dev] pip-audit && pip-audit
# --- Python (poetry) ---
# Requires: poetry add pip-audit --group dev
# - uses: actions/setup-python@0b93645bdc8f3c7c6f8d3cf81c2a3a0e5e68a3a3 # v5.3.0
# with: { python-version: "3.12", cache: poetry }
# - run: pip install "poetry>=2.0,<3.0" && poetry install --with dev && poetry run pip-audit
# --- Node.js (npm) ---
# - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
# with: { node-version-file: .nvmrc, cache: npm }
# - run: npm ci && npm audit --audit-level=high
# --- Node.js (yarn) ---
# Note: yarn audit is yarn v1 only; yarn v2+ (berry) does not support this command
# - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
# with: { node-version-file: .nvmrc, cache: yarn }
# - run: yarn install --frozen-lockfile && yarn audit --level high
# --- Node.js (pnpm) ---
# - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddde # v4.1.0
# - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
# with: { node-version-file: .nvmrc, cache: pnpm }
# - run: pnpm install --frozen-lockfile && pnpm audit --audit-level high
# (Uncomment the block matching your language and package manager)
sast:
name: SAST (CodeQL)
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
security-events: write # Required for SARIF upload to Security tab
strategy:
fail-fast: false
matrix:
# Set to detected language(s): python, javascript, typescript
language: [python]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Initialize CodeQL
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
languages: ${{ matrix.language }}
# `queries:` controls which rule suite runs:
# security-extended = ADVISORY / LOG-ONLY (default, broad security rules)
# security-and-quality = STRICT (adds quality rules, wider coverage)
# NOTE: CodeQL never fails the action itself based on alert count.
# Alerts always appear in the GitHub Security tab.
# To block PR merges on CodeQL findings, enable "Code scanning" in branch protection rules
# and configure the severity threshold in repository Settings > Code security.
queries: security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
- name: Analyze
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
category: "/language:${{ matrix.language }}"
trivy:
name: Trivy FS Scan
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write # Required for SARIF upload
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run Trivy
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
scan-type: fs
format: sarif
output: trivy-results.sarif
# ADVISORY policy: only CRITICAL and HIGH
severity: "CRITICAL,HIGH"
# For STRICT: exit-code: '1' (below)
# For LOG-ONLY: remove exit-code line
exit-code: "0" # change to '1' for STRICT mode
- name: Upload Trivy results to Security tab
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
if: always()
with:
sarif_file: trivy-results.sarif
pr-title:
name: PR Title (Conventional Commits)
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.event_name == 'pull_request'
permissions:
pull-requests: read
steps:
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Template: terraform.yml
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths:
- "**.tf"
- "**/.tflint.hcl"
push:
branches: [main]
paths:
- "**.tf"
permissions: {} # Restrict default GITHUB_TOKEN; each job sets its own minimum permissions
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
terraform-check:
name: Terraform Lint & Validate
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: write # Required for reviewdog inline PR comments
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0
- name: Terraform Format Check
run: terraform fmt -check -recursive
# If this fails: run `terraform fmt -recursive` locally and commit
- name: Terraform Init
run: terraform init -backend=false
- name: Terraform Validate
run: terraform validate
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.tflint.d/plugins
key: tflint-${{ runner.os }}-${{ hashFiles('.tflint.hcl') }}
- uses: terraform-linters/setup-tflint@4cb9feea73331a35b422df102992a03a44a3bb33 # v6.2.1
- name: Init tflint
run: tflint --init
- name: Run tflint (push — log only)
if: github.event_name != 'pull_request'
run: tflint --recursive --format compact
- name: Run tflint (PR — inline comments via reviewdog)
if: github.event_name == 'pull_request'
uses: reviewdog/action-tflint@54a5e5aed57dcfbb4662ec548de876df33d6288d # v1.25.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review # posts tflint errors as inline review comments on PR diff
flags: "--recursive"
fail_on_error: "true"
terraform-security:
name: Terraform Security Scan
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run Trivy for IaC
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
scan-type: config
format: sarif
output: trivy-iac.sarif
severity: "CRITICAL,HIGH"
exit-code: "0"
- name: Upload results to Security tab
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
if: always()
with:
sarif_file: trivy-iac.sarif
actionlint:
name: Lint GitHub Actions workflows
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run actionlint
uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2
Template: renovate.json
Note on
group:allNonMajor: This preset bundles all non-major updates into a single weekly PR, which reduces PR noise but makes it harder to isolate regressions caused by individual dependency updates. If the user prefers fine-grained PRs (one per package), remove"group:allNonMajor"fromextends. Ask the user during Phase 2 if they have a preference, or default to grouped (recommended for most projects).
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":semanticCommits",
"group:allNonMajor",
"schedule:weekly"
],
"labels": ["dependencies"],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security", "dependencies"]
},
"packageRules": [
{
"matchManagers": ["github-actions"],
"pinDigests": true,
"automerge": false,
"labels": ["dependencies", "github-actions"]
}
]
}
Template: .github/dependabot.yml
# .github/dependabot.yml
# Dependabot keeps Actions SHA-pinned versions up to date automatically.
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "04:00"
labels:
- "dependencies"
- "github-actions"
# Keeps all Actions in .github/workflows/ up to date
# Python (uv) - uncomment if applicable
# - package-ecosystem: "pip"
# directory: "/"
# schedule:
# interval: "weekly"
# labels:
# - "dependencies"
# Node.js / TypeScript - uncomment if applicable
# - package-ecosystem: "npm"
# directory: "/"
# schedule:
# interval: "weekly"
# labels:
# - "dependencies"
Phase 3 Execution Instructions
SHA の鮮度確認 (任意) テンプレートの SHA は時間とともに陳腐化します。Renovate/Dependabot 未導入の場合や 重要なアップデートが疑われる場合は、生成前に以下で最新 SHA を確認してください:
# 1. 最新リリースタグを取得 gh api repos/{owner}/{repo}/releases/latest --jq '.tag_name' # 2. タグの SHA を取得(通常タグ) gh api repos/{owner}/{repo}/git/ref/tags/{tag} --jq '.object.sha' # 3. annotated tag の場合はさらに dereference gh api repos/{owner}/{repo}/git/tags/{sha} --jq '.object.sha'Renovate/Dependabot を導入済みであれば自動更新されるため、この手順は省略可能です。
Step 1: Determine files to generate
Based on Phase 2 choices, decide which files to create:
| Condition | Files to generate |
|---|---|
| Python + uv detected | ci.yml (Python + uv variant) |
| Python + poetry detected | ci.yml (Python + poetry variant) |
| Python + pip (fallback) | ci.yml (Python + pip variant) |
| TypeScript + npm + Biome | ci.yml (TypeScript + npm + Biome variant) |
| TypeScript + npm + ESLint | ci.yml (TypeScript + npm + ESLint variant) |
| TypeScript + yarn + Biome | ci.yml (TypeScript + npm + Biome variant, substituting yarn steps per yarn template block) |
| TypeScript + yarn + ESLint | ci.yml (TypeScript + npm + ESLint variant, substituting yarn steps per yarn template block) |
| TypeScript + pnpm + Biome | ci.yml (TypeScript + npm + Biome variant, substituting pnpm steps per pnpm template block) |
| TypeScript + pnpm + ESLint | ci.yml (TypeScript + npm + ESLint variant, substituting pnpm steps per pnpm template block) |
| Node.js (no TypeScript) | Same as TypeScript variant; set CodeQL language to javascript; omit tsc build steps and any tsconfig.json references |
| Terraform detected | terraform.yml |
| Security steps selected | security.yml |
| Renovate selected | renovate.json |
| Dependabot selected | .github/dependabot.yml |
Combined projects (Python + Terraform):
If both Python and Terraform are detected, generate both ci.yml (Python variant) and terraform.yml as separate files.
Do NOT merge them into a single file — keeping them separate allows independent triggering via paths filters.
Combined projects (Python + TypeScript):
If both Python and TypeScript are detected in the same repository, generate a single ci.yml that contains separate jobs for each language:
- Python jobs:
lint-python,test-python,build-python - TypeScript jobs:
lint-ts,test-ts,build-ts - One shared
actionlintjob at the end Use the respective language templates for each job group's steps.
Step 2: Customize templates
Before writing files, substitute these values from Phase 1 detection:
Package manager commands (
uv run,pip,npm ci,yarn,pnpm install)For yarn: replace
cache: npm→cache: yarn;npm ci→yarn install --frozen-lockfile;npm run→yarnFor pnpm: add
pnpm/action-setupbeforeactions/setup-node; replacecache: npm→cache: pnpm;npm ci→pnpm install --frozen-lockfile;npm run→pnpm runSource directory in
--cov=<src_dir>andcoverage.includePython pytest command: If
pyproject.toml[tool.pytest.ini_options].addoptsalready contains coverage flags, simplify the workflow pytest command to only:pytest --junitxml=test-results.xml(rely on project config for coverage). Otherwise, use the full template with--cov=<src_dir> --cov-branch --cov-report=xml --cov-report=term-missing --junitxml=test-results.xmlPython version from
mise.tomlorpyproject.tomlrequires-pythonNode.js version: if
.nvmrcexists →node-version-file: .nvmrc; otherwise →node-version: "<detected-version>"(fallback:"22") Remove thenode-version-filekey from templates if.nvmrcis absentCodeQL language (
python,javascript,typescript)If repository is Public, add this to fork PR-sensitive jobs (coverage comment):
# Restrict on fork PRs (no write permissions available from forks) if: github.event.pull_request.head.repo.full_name == github.repositoryGHAS availability from Phase 2 (applies when SAST was selected):
GHAS = Yes: Generate the full
sastjob (CodeQL) and Trivyupload-sarifwithoutcontinue-on-errorGHAS = No: Omit the
sastjob entirely; addcontinue-on-error: trueto Trivyupload-sarifsteps with a comment explaining the requirement:- name: Upload Trivy results to Security tab uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 if: always() with: sarif_file: trivy-results.sarif # SARIF upload requires GitHub Advanced Security for private repos. # Enable GHAS and remove continue-on-error to surface findings in the Security tab. continue-on-error: true
Step 3: Apply security failure policy from Phase 2
Advisory (default):
- CodeQL:
queries: security-extended— alerts appear in Security tab, CI never fails on findings - Trivy:
exit-code: "0",severity: "CRITICAL,HIGH"
Strict:
- CodeQL:
queries: security-and-quality— broader rule coverage; alerts still only appear in Security tab. To actually block PR merges, configure branch protection rules to require the "CodeQL" status check and set the alert severity threshold in repository Settings → Code security → Code scanning. - Trivy:
exit-code: "1",severity: "CRITICAL,HIGH,MEDIUM"— Trivy does fail the action directly
Log-only:
- CodeQL:
queries: security-extended, no change — findings visible in Security tab only - Trivy:
exit-code: "0",severity: "CRITICAL,HIGH,MEDIUM,LOW"
Step 4: Check for existing workflows
If .github/workflows/ already contains files, warn the user:
⚠️ Existing workflow files found: ci.yml
Overwrite? (yes / no / rename to ci-new.yml)
Wait for confirmation before writing.
Step 5: Write files
Use the Write tool to create each file. Use the absolute path resolved from PROJECT_ROOT.
Create the .github/workflows/ directory if it does not exist.
Step 6: Summary
After writing all files, display a summary:
✅ Generated files:
.github/workflows/ci.yml — Lint + Test (coverage PR comment) + Build + actionlint
.github/workflows/security.yml — Secret scan + CodeQL + Trivy + PR title check
.github/workflows/terraform.yml — fmt + validate + tflint + Trivy IaC scan
renovate.
…(truncated)