diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..7b0eb73 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,128 @@ +name: benchmark + +# Reusable performance benchmark + regression alert for BOTH tiers. Sets up the +# toolchain, runs a caller bench command that writes an output file, then +# github-action-benchmark compares against history and fails on regression. +# History is stored on a gh-pages branch, so the caller grants contents: write. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + language: + description: 'Toolchain to set up: python, rust, node, go, or none.' + type: string + default: 'python' + working_directory: + type: string + default: '.' + python_version: + type: string + default: '3.13' + node_version: + type: string + default: '22' + rust_toolchain: + type: string + default: 'stable' + go_version: + type: string + default: 'stable' + bench_command: + description: 'Command that runs benchmarks and writes output_file_path.' + type: string + default: '' + tool: + description: 'github-action-benchmark tool: cargo, pytest, benchmarkjs, go, googlecpp, ...' + type: string + default: 'pytest' + output_file_path: + type: string + default: 'output.json' + alert_threshold: + type: string + default: '200%' + fail_on_alert: + type: boolean + default: true + auto_push: + description: 'Store history on gh-pages (requires contents: write).' + type: boolean + default: true + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 30 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + benchmark: + name: benchmark (${{ inputs.language }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: write + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Python + if: ${{ inputs.language == 'python' }} + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: ${{ inputs.python_version }} + + - name: Set up Node.js + if: ${{ inputs.language == 'node' }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ inputs.node_version }} + + - name: Set up Rust + if: ${{ inputs.language == 'rust' }} + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + toolchain: ${{ inputs.rust_toolchain }} + + - name: Set up Go + if: ${{ inputs.language == 'go' }} + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version: ${{ inputs.go_version }} + + - name: Run benchmarks + if: ${{ inputs.bench_command != '' }} + env: + BENCH_COMMAND: ${{ inputs.bench_command }} + run: bash -c "$BENCH_COMMAND" + + - name: Compare and alert + uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1 + with: + tool: ${{ inputs.tool }} + output-file-path: ${{ inputs.output_file_path }} + github-token: ${{ secrets.GITHUB_TOKEN }} + alert-threshold: ${{ inputs.alert_threshold }} + fail-on-alert: ${{ inputs.fail_on_alert }} + auto-push: ${{ inputs.auto_push }} + comment-on-alert: false diff --git a/.github/workflows/coverage-gate.yml b/.github/workflows/coverage-gate.yml new file mode 100644 index 0000000..4302849 --- /dev/null +++ b/.github/workflows/coverage-gate.yml @@ -0,0 +1,99 @@ +name: coverage-gate + +# Reusable coverage upload for BOTH tiers: harden-runner, a caller-provided +# coverage command, and a Codecov upload (free on public; token-gated on +# private) with an optional Coveralls upload. Coverage thresholds are enforced by +# the caller's codecov.yml, not here. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + working_directory: + type: string + default: '.' + coverage_command: + description: 'Command (bash) that produces a coverage report. Empty to skip.' + type: string + default: '' + files: + description: 'Coverage file(s) to upload (comma-separated). Empty lets Codecov search.' + type: string + default: '' + flags: + type: string + default: '' + fail_ci_if_error: + type: boolean + default: true + use_codecov: + type: boolean + default: true + use_coveralls: + type: boolean + default: false + coveralls_file: + description: 'Coverage file for Coveralls, e.g. coverage/lcov.info.' + type: string + default: '' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 20 + secrets: + codecov_token: + required: false + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + coverage: + name: coverage + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Produce coverage + if: ${{ inputs.coverage_command != '' }} + env: + COVERAGE_COMMAND: ${{ inputs.coverage_command }} + run: bash -c "$COVERAGE_COMMAND" + + - name: Upload to Codecov + if: ${{ inputs.use_codecov }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.codecov_token }} + files: ${{ inputs.files }} + flags: ${{ inputs.flags }} + fail_ci_if_error: ${{ inputs.fail_ci_if_error }} + + - name: Upload to Coveralls + if: ${{ inputs.use_coveralls }} + uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: ${{ inputs.coveralls_file }} diff --git a/.github/workflows/cpp-ci.yml b/.github/workflows/cpp-ci.yml new file mode 100644 index 0000000..67bddbf --- /dev/null +++ b/.github/workflows/cpp-ci.yml @@ -0,0 +1,140 @@ +name: cpp-ci + +# Reusable C/C++ CI for BOTH tiers: harden-runner, optional apt packages and +# ccache, clang-format + cppcheck static gates, and caller-provided +# configure/build/test commands passed via env. Defaults target a CMake project; +# override the commands for Make/Meson/Bazel. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + working_directory: + type: string + default: '.' + apt_packages: + description: 'Extra apt packages (space-separated), e.g. "cppcheck clang-tidy ninja-build".' + type: string + default: '' + enable_ccache: + description: 'Restore/save a ccache cache (wire -DCMAKE_CXX_COMPILER_LAUNCHER=ccache to use it).' + type: boolean + default: false + ccache_key: + type: string + default: 'cpp-ci' + configure_command: + description: 'Configure command (bash). Empty to skip.' + type: string + default: 'cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug' + build_command: + description: 'Build command (bash). Empty to skip.' + type: string + default: 'cmake --build build --parallel' + test_command: + description: 'Test command (bash). Empty to skip.' + type: string + default: 'ctest --test-dir build --output-on-failure' + format_check: + description: 'Run clang-format --dry-run -Werror over format_paths.' + type: boolean + default: false + format_paths: + description: 'Space-separated globs for clang-format when format_check is true.' + type: string + default: 'src/**/*.cpp src/**/*.hpp' + cppcheck: + description: 'Run cppcheck (add "cppcheck" to apt_packages).' + type: boolean + default: false + cppcheck_args: + type: string + default: '--enable=warning,portability --error-exitcode=1 .' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 30 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + cpp: + name: cpp + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Install apt packages + if: ${{ inputs.apt_packages != '' }} + env: + APT_PACKAGES: ${{ inputs.apt_packages }} + run: | + sudo apt-get update + # shellcheck disable=SC2086 + sudo apt-get install -y --no-install-recommends $APT_PACKAGES + + - name: Set up ccache + if: ${{ inputs.enable_ccache }} + uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23 + with: + key: ${{ inputs.ccache_key }} + + - name: Format check + if: ${{ inputs.format_check }} + env: + FORMAT_PATHS: ${{ inputs.format_paths }} + run: | + # shellcheck disable=SC2086 + clang-format --dry-run -Werror $FORMAT_PATHS + + - name: Configure + if: ${{ inputs.configure_command != '' }} + env: + CONFIGURE_COMMAND: ${{ inputs.configure_command }} + run: bash -c "$CONFIGURE_COMMAND" + + - name: Build + if: ${{ inputs.build_command != '' }} + env: + BUILD_COMMAND: ${{ inputs.build_command }} + run: bash -c "$BUILD_COMMAND" + + - name: cppcheck + if: ${{ inputs.cppcheck }} + env: + CPPCHECK_ARGS: ${{ inputs.cppcheck_args }} + run: | + # shellcheck disable=SC2086 + cppcheck $CPPCHECK_ARGS + + - name: Test + if: ${{ inputs.test_command != '' }} + env: + TEST_COMMAND: ${{ inputs.test_command }} + run: bash -c "$TEST_COMMAND" + + - name: Summary + run: echo "C/C++ CI (configure + build + test) passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/dart-flutter-ci.yml b/.github/workflows/dart-flutter-ci.yml new file mode 100644 index 0000000..884f151 --- /dev/null +++ b/.github/workflows/dart-flutter-ci.yml @@ -0,0 +1,102 @@ +name: dart-flutter-ci + +# Reusable Dart/Flutter CI for BOTH tiers: harden-runner, pinned flutter-action +# with build cache, format + analyze gates, and a caller-provided test command +# passed via env (no `${{ inputs.* }}` inline in run:). + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + flutter_channel: + description: 'flutter-action channel: stable, beta, or master.' + type: string + default: 'stable' + flutter_version: + description: 'Exact Flutter version (e.g. 3.35.0) or empty for the channel default.' + type: string + default: '' + working_directory: + type: string + default: '.' + pub_get_command: + description: 'Dependency resolution command (bash). Empty to skip.' + type: string + default: 'flutter pub get' + format_check: + description: 'Run dart format --set-exit-if-changed.' + type: boolean + default: true + analyze: + description: 'Run flutter analyze.' + type: boolean + default: true + test_command: + description: 'Test/verify command (bash).' + type: string + default: 'flutter test' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 20 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + dart-flutter: + name: dart-flutter (${{ inputs.flutter_channel }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: ${{ inputs.flutter_channel }} + flutter-version: ${{ inputs.flutter_version }} + cache: true + + - name: Resolve dependencies + if: ${{ inputs.pub_get_command != '' }} + env: + PUB_GET_COMMAND: ${{ inputs.pub_get_command }} + run: bash -c "$PUB_GET_COMMAND" + + - name: Format check + if: ${{ inputs.format_check }} + run: dart format --output=none --set-exit-if-changed . + + - name: Analyze + if: ${{ inputs.analyze }} + run: flutter analyze + + - name: Run tests + env: + TEST_COMMAND: ${{ inputs.test_command }} + FLUTTER_CHANNEL: ${{ inputs.flutter_channel }} + run: | + bash -c "$TEST_COMMAND" + echo "Flutter ${FLUTTER_CHANNEL} format + analyze + tests passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/docs-quality.yml b/.github/workflows/docs-quality.yml new file mode 100644 index 0000000..2730d4a --- /dev/null +++ b/.github/workflows/docs-quality.yml @@ -0,0 +1,82 @@ +name: docs-quality + +# Reusable docs quality gate for BOTH tiers: harden-runner, link checking +# (lychee), spell checking (typos), and Markdown linting (markdownlint-cli2). +# Every tool is free on every tier. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + working_directory: + type: string + default: '.' + lychee: + type: boolean + default: true + lychee_args: + description: 'Arguments passed to lychee.' + type: string + default: '--no-progress --max-concurrency 8 "**/*.md"' + typos: + type: boolean + default: true + typos_files: + type: string + default: '.' + markdownlint: + type: boolean + default: true + markdownlint_globs: + type: string + default: '**/*.md' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 15 + +permissions: {} + +jobs: + docs-quality: + name: docs-quality + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Link check (lychee) + if: ${{ inputs.lychee }} + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0 + with: + args: ${{ inputs.lychee_args }} + fail: true + token: ${{ secrets.GITHUB_TOKEN }} + workingDirectory: ${{ inputs.working_directory }} + + - name: Spell check (typos) + if: ${{ inputs.typos }} + uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0 + with: + files: ${{ inputs.typos_files }} + + - name: Markdown lint + if: ${{ inputs.markdownlint }} + uses: DavidAnson/markdownlint-cli2-action@8de2aa07cae85fd17c0b35642db70cf5495f1d25 # v24.0.0 + with: + globs: ${{ inputs.markdownlint_globs }} diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml new file mode 100644 index 0000000..6545dfb --- /dev/null +++ b/.github/workflows/fuzzing.yml @@ -0,0 +1,78 @@ +name: fuzzing + +# Reusable fuzzing for BOTH tiers. Defaults to Rust cargo-fuzz (libFuzzer) on a +# nightly toolchain with a bounded time budget - run it on a schedule. Override +# the commands for other harnesses (e.g. ClusterFuzzLite, libFuzzer, AFL++). + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + working_directory: + type: string + default: '.' + rust_toolchain: + type: string + default: 'nightly' + install_command: + description: 'Install the fuzzer. Empty to skip.' + type: string + default: 'cargo install cargo-fuzz --locked' + fuzz_command: + description: 'Run fuzzing with a bounded budget.' + type: string + default: 'cargo fuzz run fuzz_target_1 -- -max_total_time=60' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 30 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + fuzz: + name: fuzz + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + toolchain: ${{ inputs.rust_toolchain }} + + - name: Install fuzzer + if: ${{ inputs.install_command != '' }} + env: + INSTALL_COMMAND: ${{ inputs.install_command }} + run: bash -c "$INSTALL_COMMAND" + + - name: Run fuzzing + env: + FUZZ_COMMAND: ${{ inputs.fuzz_command }} + run: | + bash -c "$FUZZ_COMMAND" + echo "Fuzzing run completed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/grype-scan.yml b/.github/workflows/grype-scan.yml new file mode 100644 index 0000000..604ad8d --- /dev/null +++ b/.github/workflows/grype-scan.yml @@ -0,0 +1,62 @@ +name: grype-scan + +# Reusable Grype vulnerability (SCA) scan for BOTH tiers. Free on private-free. +# Scans a filesystem path (or SBOM) and fails at/above the severity cutoff. +# Gate-only: table output, no SARIF upload. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + path: + description: 'Filesystem path to scan.' + type: string + default: '.' + severity_cutoff: + description: 'Fail at/above: negligible, low, medium, high, or critical.' + type: string + default: 'high' + only_fixed: + type: boolean + default: false + fail_build: + type: boolean + default: true + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 15 + +permissions: {} + +jobs: + grype: + name: grype + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Grype scan + uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 + with: + path: ${{ inputs.path }} + fail-build: ${{ inputs.fail_build }} + severity-cutoff: ${{ inputs.severity_cutoff }} + only-fixed: ${{ inputs.only_fixed }} + output-format: table diff --git a/.github/workflows/hadolint-ci.yml b/.github/workflows/hadolint-ci.yml new file mode 100644 index 0000000..82d4f0c --- /dev/null +++ b/.github/workflows/hadolint-ci.yml @@ -0,0 +1,60 @@ +name: hadolint-ci + +# Reusable Dockerfile linter (hadolint) for BOTH tiers. Free everywhere. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + dockerfile: + type: string + default: 'Dockerfile' + recursive: + description: 'Lint every Dockerfile in the tree.' + type: boolean + default: false + failure_threshold: + description: 'Fail at/above: error, warning, info, or style.' + type: string + default: 'warning' + ignore: + description: 'Comma/space-separated rule codes to ignore, e.g. "DL3008 DL3059".' + type: string + default: '' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 10 + +permissions: {} + +jobs: + hadolint: + name: hadolint + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: hadolint + uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 + with: + dockerfile: ${{ inputs.dockerfile }} + recursive: ${{ inputs.recursive }} + failure-threshold: ${{ inputs.failure_threshold }} + ignore: ${{ inputs.ignore }} diff --git a/.github/workflows/iac-scan.yml b/.github/workflows/iac-scan.yml new file mode 100644 index 0000000..2bad309 --- /dev/null +++ b/.github/workflows/iac-scan.yml @@ -0,0 +1,67 @@ +name: iac-scan + +# Reusable Infrastructure-as-Code static analysis (Checkov) for BOTH tiers. +# Free everywhere. Scans Terraform, Kubernetes, CloudFormation, Helm, ARM, and +# more. Gate-only: CLI output, no SARIF upload. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + directory: + type: string + default: '.' + framework: + description: 'Checkov framework filter, e.g. all, terraform, kubernetes, cloudformation.' + type: string + default: 'all' + soft_fail: + description: 'Report findings without failing the job.' + type: boolean + default: false + quiet: + type: boolean + default: true + skip_check: + description: 'Comma-separated check IDs to skip, e.g. "CKV_AWS_1,CKV_AWS_2".' + type: string + default: '' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 15 + +permissions: {} + +jobs: + iac-scan: + name: iac-scan + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Checkov + uses: bridgecrewio/checkov-action@99bb2caf247dfd9f03cf984373bc6043d4e32ebf # v12.1347.0 + with: + directory: ${{ inputs.directory }} + framework: ${{ inputs.framework }} + soft_fail: ${{ inputs.soft_fail }} + quiet: ${{ inputs.quiet }} + skip_check: ${{ inputs.skip_check }} + output_format: cli diff --git a/.github/workflows/kotlin-android-ci.yml b/.github/workflows/kotlin-android-ci.yml new file mode 100644 index 0000000..c19c583 --- /dev/null +++ b/.github/workflows/kotlin-android-ci.yml @@ -0,0 +1,100 @@ +name: kotlin-android-ci + +# Reusable Kotlin/Android CI for BOTH tiers: harden-runner, pinned setup-java + +# setup-gradle (with Gradle cache), optional Android SDK, and caller-provided +# lint/build commands passed via env. Works for pure-JVM Kotlin and Android. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + java_version: + type: string + default: '21' + java_distribution: + type: string + default: 'temurin' + working_directory: + type: string + default: '.' + setup_android: + description: 'Install the Android SDK command-line tools (needed for Android modules).' + type: boolean + default: false + android_packages: + description: 'Extra sdkmanager packages (space-separated). Empty installs the defaults.' + type: string + default: '' + lint_command: + description: 'Lint command (bash), e.g. "./gradlew ktlintCheck detekt". Empty to skip.' + type: string + default: '' + build_command: + description: 'Build/test command (bash).' + type: string + default: './gradlew build' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 30 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + kotlin-android: + name: kotlin-android (java ${{ inputs.java_version }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner && runner.os == 'Linux' }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up JDK + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 + with: + java-version: ${{ inputs.java_version }} + distribution: ${{ inputs.java_distribution }} + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + + - name: Set up Android SDK + if: ${{ inputs.setup_android }} + uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 + with: + packages: ${{ inputs.android_packages }} + + - name: Lint + if: ${{ inputs.lint_command != '' }} + env: + LINT_COMMAND: ${{ inputs.lint_command }} + run: bash -c "$LINT_COMMAND" + + - name: Build + env: + BUILD_COMMAND: ${{ inputs.build_command }} + JAVA_VER: ${{ inputs.java_version }} + run: | + bash -c "$BUILD_COMMAND" + echo "Kotlin/Android (JDK ${JAVA_VER}) build passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/mutation-testing.yml b/.github/workflows/mutation-testing.yml new file mode 100644 index 0000000..9ffa0ac --- /dev/null +++ b/.github/workflows/mutation-testing.yml @@ -0,0 +1,101 @@ +name: mutation-testing + +# Reusable mutation testing for BOTH tiers. Sets up the toolchain for the chosen +# language (python -> mutmut, rust -> cargo-mutants, node -> Stryker) and runs +# caller-provided install/mutation commands passed via env. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + language: + description: 'Toolchain to set up: python, rust, or node.' + type: string + default: 'python' + working_directory: + type: string + default: '.' + python_version: + type: string + default: '3.13' + node_version: + type: string + default: '22' + rust_toolchain: + type: string + default: 'stable' + install_command: + description: 'Install the mutation tool, e.g. "pip install mutmut" / "cargo install cargo-mutants --locked" / "npm ci".' + type: string + default: '' + mutation_command: + description: 'Run mutation testing, e.g. "mutmut run" / "cargo mutants" / "npx stryker run".' + type: string + default: '' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 45 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + mutation: + name: mutation (${{ inputs.language }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Python + if: ${{ inputs.language == 'python' }} + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: ${{ inputs.python_version }} + + - name: Set up Node.js + if: ${{ inputs.language == 'node' }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ inputs.node_version }} + + - name: Set up Rust + if: ${{ inputs.language == 'rust' }} + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + toolchain: ${{ inputs.rust_toolchain }} + + - name: Install mutation tool + if: ${{ inputs.install_command != '' }} + env: + INSTALL_COMMAND: ${{ inputs.install_command }} + run: bash -c "$INSTALL_COMMAND" + + - name: Run mutation testing + env: + MUTATION_COMMAND: ${{ inputs.mutation_command }} + run: | + bash -c "$MUTATION_COMMAND" + echo "Mutation testing passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/osv-scan.yml b/.github/workflows/osv-scan.yml new file mode 100644 index 0000000..4b34911 --- /dev/null +++ b/.github/workflows/osv-scan.yml @@ -0,0 +1,80 @@ +name: osv-scan + +# Reusable OSV-Scanner SCA for BOTH tiers. Free on private-free repos, where +# GitHub dependency review is a paid GHAS feature. Pinned, checksum-verified +# binary; gate-only: known vulnerabilities fail the job (no SARIF upload). + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + working_directory: + type: string + default: '.' + scan_args: + description: 'Arguments after "osv-scanner scan source", e.g. "--recursive ." or "--lockfile=path".' + type: string + default: '--recursive .' + osv_scanner_version: + type: string + default: '2.4.0' + osv_scanner_sha256: + description: 'SHA256 of osv-scanner_linux_amd64 from the release SHA256SUMS.' + type: string + default: '15314940c10d26af9c6649f150b8a47c1262e8fc7e17b1d1029b0e479e8ed8a0' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 15 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + osv-scan: + name: osv-scan + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Install OSV-Scanner (pinned + checksum-verified) + env: + OSV_VERSION: ${{ inputs.osv_scanner_version }} + OSV_SHA256: ${{ inputs.osv_scanner_sha256 }} + run: | + set -euo pipefail + curl -fsSL -o /tmp/osv-scanner \ + "https://github.com/google/osv-scanner/releases/download/v${OSV_VERSION}/osv-scanner_linux_amd64" + echo "${OSV_SHA256} /tmp/osv-scanner" | sha256sum -c - + install -m 0755 /tmp/osv-scanner /usr/local/bin/osv-scanner + osv-scanner --version + + - name: OSV scan + env: + OSV_SCAN_ARGS: ${{ inputs.scan_args }} + run: | + # shellcheck disable=SC2086 + osv-scanner scan source $OSV_SCAN_ARGS + echo "OSV-Scanner SCA passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/pr-hygiene.yml b/.github/workflows/pr-hygiene.yml new file mode 100644 index 0000000..e128eb8 --- /dev/null +++ b/.github/workflows/pr-hygiene.yml @@ -0,0 +1,110 @@ +name: pr-hygiene + +# Reusable PR-hygiene bundle. Each check is an independently toggled job with +# least-privilege permissions. Trigger from pull_request (commitlint, pr-title, +# labeler) and/or schedule (stale). The caller must grant the union of the +# enabled jobs' permissions. Every tool is free on every tier. + +on: + workflow_call: + inputs: + commitlint: + type: boolean + default: true + commitlint_config: + description: 'Path to a commitlint config. Empty uses the repository default.' + type: string + default: '' + pr_title: + type: boolean + default: true + labeler: + type: boolean + default: false + labeler_config: + type: string + default: '.github/labeler.yml' + stale: + type: boolean + default: false + days_before_stale: + type: number + default: 60 + days_before_close: + type: number + default: 14 + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 10 + +permissions: {} + +jobs: + commitlint: + if: ${{ inputs.commitlint }} + name: commitlint + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + persist-credentials: false + - name: commitlint + uses: wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed # v6.2.1 + with: + configFile: ${{ inputs.commitlint_config }} + + pr-title: + if: ${{ inputs.pr_title }} + name: pr-title + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + steps: + - name: Validate PR title (Conventional Commits) + uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + labeler: + if: ${{ inputs.labeler }} + name: labeler + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + pull-requests: write + steps: + - name: Label pull request + uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 + with: + configuration-path: ${{ inputs.labeler_config }} + + stale: + if: ${{ inputs.stale }} + name: stale + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + issues: write + pull-requests: write + steps: + - name: Mark stale issues and pull requests + uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 + with: + days-before-stale: ${{ inputs.days_before_stale }} + days-before-close: ${{ inputs.days_before_close }} diff --git a/.github/workflows/qt-ci.yml b/.github/workflows/qt-ci.yml new file mode 100644 index 0000000..f3dfe81 --- /dev/null +++ b/.github/workflows/qt-ci.yml @@ -0,0 +1,126 @@ +name: qt-ci + +# Reusable Qt (C++) CI for BOTH tiers: harden-runner, pinned install-qt-action +# (aqtinstall) with cache, optional ccache, and caller-provided +# configure/build/test commands passed via env. Defaults target a CMake project. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + qt_version: + description: 'Qt version spec for aqtinstall, e.g. 6.8.* or 6.8.3.' + type: string + default: '6.8.*' + qt_host: + description: 'aqtinstall host (linux, mac, windows). Empty auto-detects from the runner.' + type: string + default: '' + qt_target: + description: 'aqtinstall target: desktop, android, or ios.' + type: string + default: 'desktop' + qt_arch: + description: 'aqtinstall arch (empty picks the host default).' + type: string + default: '' + qt_modules: + description: 'Space-separated extra Qt modules, e.g. "qtcharts qtmultimedia".' + type: string + default: '' + working_directory: + type: string + default: '.' + enable_ccache: + type: boolean + default: false + ccache_key: + type: string + default: 'qt-ci' + configure_command: + description: 'Configure command (bash). Empty to skip.' + type: string + default: 'cmake -S . -B build -DCMAKE_BUILD_TYPE=Release' + build_command: + description: 'Build command (bash). Empty to skip.' + type: string + default: 'cmake --build build --parallel' + test_command: + description: 'Test command (bash). Empty to skip.' + type: string + default: 'ctest --test-dir build --output-on-failure' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 40 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + qt: + name: qt (${{ inputs.qt_version }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Install Qt + uses: jurplel/install-qt-action@48d3ad6db93f3627c8ee7a0454bc6f3744f7e730 # v4.3.1 + with: + version: ${{ inputs.qt_version }} + host: ${{ inputs.qt_host }} + target: ${{ inputs.qt_target }} + arch: ${{ inputs.qt_arch }} + modules: ${{ inputs.qt_modules }} + cache: true + + - name: Set up ccache + if: ${{ inputs.enable_ccache }} + uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23 + with: + key: ${{ inputs.ccache_key }} + + - name: Configure + if: ${{ inputs.configure_command != '' }} + env: + CONFIGURE_COMMAND: ${{ inputs.configure_command }} + run: bash -c "$CONFIGURE_COMMAND" + + - name: Build + if: ${{ inputs.build_command != '' }} + env: + BUILD_COMMAND: ${{ inputs.build_command }} + run: bash -c "$BUILD_COMMAND" + + - name: Test + if: ${{ inputs.test_command != '' }} + env: + TEST_COMMAND: ${{ inputs.test_command }} + run: bash -c "$TEST_COMMAND" + + - name: Summary + env: + QT_VERSION: ${{ inputs.qt_version }} + run: echo "Qt ${QT_VERSION} configure + build + test passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/r-ci.yml b/.github/workflows/r-ci.yml new file mode 100644 index 0000000..2dd7f2a --- /dev/null +++ b/.github/workflows/r-ci.yml @@ -0,0 +1,90 @@ +name: r-ci + +# Reusable R CI for BOTH tiers: harden-runner, pinned r-lib setup-r (public RSPM +# binaries), and caller-provided install/lint/test commands passed via env. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + r_version: + type: string + default: 'release' + working_directory: + type: string + default: '.' + use_public_rspm: + description: 'Use the public RStudio Package Manager for fast binary installs.' + type: boolean + default: true + install_command: + description: 'Dependency install command (bash). Empty to skip.' + type: string + default: "Rscript -e 'install.packages(c(\"lintr\", \"testthat\"))'" + lint: + description: 'Run lintr::lint_dir().' + type: boolean + default: false + test_command: + description: 'Test/check command (bash).' + type: string + default: "Rscript -e 'testthat::test_local()'" + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 30 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + r: + name: r (${{ inputs.r_version }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner && runner.os == 'Linux' }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up R + uses: r-lib/actions/setup-r@d3c5be51b12e724e68f33216ca3c148b66d5f0b6 # v2 + with: + r-version: ${{ inputs.r_version }} + use-public-rspm: ${{ inputs.use_public_rspm }} + + - name: Install dependencies + if: ${{ inputs.install_command != '' }} + env: + INSTALL_COMMAND: ${{ inputs.install_command }} + run: bash -c "$INSTALL_COMMAND" + + - name: Lint + if: ${{ inputs.lint }} + run: Rscript -e 'lintr::lint_dir()' + + - name: Test + env: + TEST_COMMAND: ${{ inputs.test_command }} + run: | + bash -c "$TEST_COMMAND" + echo "R checks passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/semgrep-ci.yml b/.github/workflows/semgrep-ci.yml new file mode 100644 index 0000000..6daa0f3 --- /dev/null +++ b/.github/workflows/semgrep-ci.yml @@ -0,0 +1,84 @@ +name: semgrep-ci + +# Reusable Semgrep OSS SAST for BOTH tiers. Free on private-free repos, where +# CodeQL is a paid GHAS feature. Gate-only: findings fail the job and no SARIF is +# uploaded, so no code-scanning (security-events) permission is required. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + working_directory: + type: string + default: '.' + config: + description: 'Semgrep ruleset(s): p/ci, p/default, p/security-audit, or a local path.' + type: string + default: 'p/ci' + semgrep_version: + description: 'Exact semgrep version to pin (empty installs the latest release).' + type: string + default: '' + extra_args: + type: string + default: '' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 20 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + semgrep: + name: semgrep + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '3.13' + + - name: Install Semgrep + env: + SEMGREP_VERSION: ${{ inputs.semgrep_version }} + run: | + if [ -n "$SEMGREP_VERSION" ]; then + python -m pip install --disable-pip-version-check "semgrep==${SEMGREP_VERSION}" + else + python -m pip install --disable-pip-version-check semgrep + fi + + - name: Semgrep scan + env: + SEMGREP_CONFIG: ${{ inputs.config }} + SEMGREP_EXTRA_ARGS: ${{ inputs.extra_args }} + run: | + # shellcheck disable=SC2086 + semgrep scan --error --metrics off --config "$SEMGREP_CONFIG" $SEMGREP_EXTRA_ARGS + echo "Semgrep SAST passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/sql-ci.yml b/.github/workflows/sql-ci.yml new file mode 100644 index 0000000..02bbcdf --- /dev/null +++ b/.github/workflows/sql-ci.yml @@ -0,0 +1,101 @@ +name: sql-ci + +# Reusable SQL CI for BOTH tiers: harden-runner, pinned setup-python, and +# sqlfluff lint (dialect-aware) plus an optional caller command passed via env. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + python_version: + type: string + default: '3.13' + working_directory: + type: string + default: '.' + dialect: + description: 'sqlfluff dialect: ansi, postgres, mysql, sqlite, bigquery, snowflake, tsql, ...' + type: string + default: 'ansi' + paths: + description: 'Path or directory to lint.' + type: string + default: '.' + sqlfluff_version: + description: 'Exact sqlfluff version to pin (empty installs the latest release).' + type: string + default: '' + lint: + description: 'Run sqlfluff lint.' + type: boolean + default: true + extra_command: + description: 'Additional command (bash), e.g. "sqlfluff format --diff". Empty to skip.' + type: string + default: '' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 15 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + sql: + name: sql (${{ inputs.dialect }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: ${{ inputs.python_version }} + + - name: Install sqlfluff + env: + SQLFLUFF_VERSION: ${{ inputs.sqlfluff_version }} + run: | + if [ -n "$SQLFLUFF_VERSION" ]; then + python -m pip install --disable-pip-version-check "sqlfluff==${SQLFLUFF_VERSION}" + else + python -m pip install --disable-pip-version-check sqlfluff + fi + + - name: Lint + if: ${{ inputs.lint }} + env: + SQL_DIALECT: ${{ inputs.dialect }} + SQL_PATHS: ${{ inputs.paths }} + run: | + sqlfluff lint --dialect "$SQL_DIALECT" "$SQL_PATHS" + echo "sqlfluff lint (${SQL_DIALECT}) passed." >> "$GITHUB_STEP_SUMMARY" + + - name: Extra command + if: ${{ inputs.extra_command != '' }} + env: + EXTRA_COMMAND: ${{ inputs.extra_command }} + run: bash -c "$EXTRA_COMMAND" diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml new file mode 100644 index 0000000..c7fd8c8 --- /dev/null +++ b/.github/workflows/swift-ci.yml @@ -0,0 +1,100 @@ +name: swift-ci + +# Reusable Swift CI. Defaults to a macOS runner (Swift + Xcode preinstalled; +# iOS/macOS builds bill at a 10x minute multiplier). Optional swift-actions +# setup for Linux/pinned Swift, optional SwiftLint + swift-format gates, and +# caller-provided build/test commands passed via env. harden-runner runs only on +# Linux (it is a Linux/paid feature). + +on: + workflow_call: + inputs: + runner: + type: string + default: 'macos-latest' + swift_version: + description: 'Swift version for swift-actions/setup-swift (Linux/pinned). Empty uses the runner toolchain.' + type: string + default: '' + working_directory: + type: string + default: '.' + swiftlint: + description: 'Install (brew) and run SwiftLint. macOS runners only.' + type: boolean + default: false + swift_format: + description: 'Run "swift format lint" over the tree.' + type: boolean + default: false + build_command: + description: 'Build command (bash). Empty to skip.' + type: string + default: 'swift build' + test_command: + description: 'Test command (bash).' + type: string + default: 'swift test' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 30 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + swift: + name: swift + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner && runner.os == 'Linux' }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Swift + if: ${{ inputs.swift_version != '' }} + uses: swift-actions/setup-swift@7ca6abe6b3b0e8b5421b88be48feee39cbf52c6a # v2.4.0 + with: + swift-version: ${{ inputs.swift_version }} + + - name: SwiftLint + if: ${{ inputs.swiftlint && runner.os == 'macOS' }} + run: | + brew install swiftlint + swiftlint --strict + + - name: swift-format + if: ${{ inputs.swift_format }} + run: swift format lint --recursive --strict . + + - name: Build + if: ${{ inputs.build_command != '' }} + env: + BUILD_COMMAND: ${{ inputs.build_command }} + run: bash -c "$BUILD_COMMAND" + + - name: Test + env: + TEST_COMMAND: ${{ inputs.test_command }} + run: | + bash -c "$TEST_COMMAND" + echo "Swift build + test passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/terraform-ci.yml b/.github/workflows/terraform-ci.yml index 0ab3108..01b1d08 100644 --- a/.github/workflows/terraform-ci.yml +++ b/.github/workflows/terraform-ci.yml @@ -10,6 +10,7 @@ on: type: string default: 'ubuntu-latest' terraform_version: + description: 'Terraform version constraint for setup-terraform. Pin an exact version or range for reproducible CI (see setup-terraform docs); "latest" always resolves to the newest release.' type: string default: 'latest' working_directory: diff --git a/.github/workflows/web-ci.yml b/.github/workflows/web-ci.yml new file mode 100644 index 0000000..73aa5bd --- /dev/null +++ b/.github/workflows/web-ci.yml @@ -0,0 +1,101 @@ +name: web-ci + +# Reusable HTML/CSS/web CI for BOTH tiers: harden-runner, pinned setup-node, and +# caller-provided lint/test commands passed via env. The default lint step runs +# stylelint + htmlhint; callers can add Lighthouse CI or pa11y via test_command. + +on: + workflow_call: + inputs: + runner: + type: string + default: 'ubuntu-latest' + node_version: + type: string + default: '22' + working_directory: + type: string + default: '.' + cache: + description: 'setup-node cache: npm, yarn, pnpm, or empty to disable.' + type: string + default: '' + cache_dependency_path: + type: string + default: '' + install_command: + description: 'Dependency install command (bash). Empty to skip.' + type: string + default: '' + lint_command: + description: 'Lint command (bash). Empty to skip.' + type: string + default: 'npx --yes stylelint "**/*.{css,scss}" --allow-empty-input && npx --yes htmlhint "**/*.html"' + test_command: + description: 'Additional test/verify command (bash), e.g. Lighthouse CI or pa11y. Empty to skip.' + type: string + default: '' + enable_harden_runner: + type: boolean + default: true + timeout_minutes: + type: number + default: 15 + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + web: + name: web (node ${{ inputs.node_version }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: ${{ inputs.timeout_minutes }} + permissions: + contents: read + defaults: + run: + working-directory: ${{ inputs.working_directory }} + steps: + - name: Harden runner + if: ${{ inputs.enable_harden_runner }} + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ inputs.node_version }} + cache: ${{ inputs.cache }} + cache-dependency-path: ${{ inputs.cache_dependency_path }} + + - name: Install dependencies + if: ${{ inputs.install_command != '' }} + env: + INSTALL_COMMAND: ${{ inputs.install_command }} + run: bash -c "$INSTALL_COMMAND" + + - name: Lint + if: ${{ inputs.lint_command != '' }} + env: + LINT_COMMAND: ${{ inputs.lint_command }} + run: bash -c "$LINT_COMMAND" + + - name: Run tests + if: ${{ inputs.test_command != '' }} + env: + TEST_COMMAND: ${{ inputs.test_command }} + run: bash -c "$TEST_COMMAND" + + - name: Summary + env: + NODE_VER: ${{ inputs.node_version }} + run: echo "web-ci (Node ${NODE_VER}) lint + tests passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/CHANGELOG.md b/CHANGELOG.md index a1695e5..f88fc7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ ## [Unreleased] +## [0.3.0] - 2026-07-08 + +### Added + +- Language packs: Dart/Flutter, C/C++, Qt, Kotlin/Android, Swift, R, HTML/CSS, + and SQL reusable workflows (joining Python, Node, Go, Rust, Java, .NET, + container, and Terraform). +- Quality gates: `coverage-gate` (Codecov/Coveralls), `docs-quality` + (lychee/typos/markdownlint), and `pr-hygiene` + (commitlint/PR-title/labeler/stale). +- Free SAST/SCA/IaC for every tier including private-free: Semgrep OSS, + OSV-Scanner, Grype, hadolint, and Checkov (all gate-only, no security-events). +- Advanced testing: `mutation-testing`, `fuzzing` (cargo-fuzz), and `benchmark` + (github-action-benchmark regression alert). +- Level-3 opt-in caller examples: AI code review (Claude Code Action) and + release automation (release-please). +- `docs/15-language-and-quality-packs.md` and `examples/` subdirectories + (`languages/`, `quality/`, `security/`, `testing/`, `level3/`). + +### Changed + +- Catalog grows to 67 capabilities and 38 pinned tools; every new third-party + action is SHA-pinned with a version comment and verified against its + `action.yml` input contract. +- `terraform-ci` documents `terraform_version` pinning for reproducible CI. +- Generated docs re-dated to 2026-07-08. + ## [0.2.4] - 2026-07-04 ### Added diff --git a/README.md b/README.md index f0cbab7..8d89336 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,18 @@ The machine-readable source of truth is [`catalog/capabilities.yml`](catalog/cap Generated mirrors live in [`docs/generated/`](docs/generated/) and are checked by `scripts/generate_docs.py --check`. +### Extended packs (July 2026) + +Beyond the security suite, the library ships language packs (Python, Node, Go, +Rust, Java, .NET, **Dart/Flutter, C/C++, Qt, Kotlin/Android, Swift, R, HTML/CSS, +SQL**), quality gates (coverage, docs-quality, PR-hygiene), free SAST/SCA/IaC for +every tier (Semgrep, OSV-Scanner, Grype, hadolint, Checkov — free even on +private-free, where CodeQL and dependency review are paid), advanced testing +(mutation, fuzzing, benchmark), and opt-in Level-3 patterns (AI code review, +release-please). See +[`docs/15-language-and-quality-packs.md`](docs/15-language-and-quality-packs.md) +and copy-paste callers under [`examples/`](examples/). + ## Usage Always pin by **full commit SHA** (tags are mutable). Dependabot bumps the SHA. @@ -193,7 +205,7 @@ docs/generated/ catalog-derived matrices (do not edit by hand) rulesets/ branch/tag/push ruleset specs ISSUE_TEMPLATE/ issue forms scripts/ static validators (validate_all.py) + attestation verifier -examples/ copy-paste caller workflows per tier +examples/ copy-paste callers: per-tier + languages/ quality/ security/ testing/ infra/ level3/ ``` ## Conventions diff --git a/VERSION b/VERSION index abd4105..0d91a54 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.4 +0.3.0 diff --git a/catalog/capabilities.yml b/catalog/capabilities.yml index 51af880..0adc37e 100644 --- a/catalog/capabilities.yml +++ b/catalog/capabilities.yml @@ -243,7 +243,7 @@ capabilities: private_free: free private_paid: available workflow: .github/workflows/cross-platform-smoke.yml - example: null + example: examples/infra/cross-platform.yml required_permissions: - "contents: read" required_settings: [] @@ -782,7 +782,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/python-ci.yml - example: null + example: examples/languages/python.yml required_permissions: - "contents: read" required_settings: [] @@ -802,7 +802,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/node-ci.yml - example: null + example: examples/languages/node.yml required_permissions: - "contents: read" required_settings: [] @@ -821,7 +821,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/go-ci.yml - example: null + example: examples/languages/go.yml required_permissions: - "contents: read" required_settings: [] @@ -840,7 +840,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/rust-ci.yml - example: null + example: examples/languages/rust.yml required_permissions: - "contents: read" required_settings: [] @@ -859,7 +859,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/java-ci.yml - example: null + example: examples/languages/java.yml required_permissions: - "contents: read" required_settings: [] @@ -878,7 +878,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/dotnet-ci.yml - example: null + example: examples/languages/dotnet.yml required_permissions: - "contents: read" required_settings: [] @@ -897,7 +897,7 @@ capabilities: private_free: free private_paid: available workflow: .github/workflows/container-ci.yml - example: null + example: examples/infra/container.yml required_permissions: - "contents: read" - "security-events: write" @@ -920,7 +920,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/terraform-ci.yml - example: null + example: examples/infra/terraform.yml required_permissions: - "contents: read" required_settings: [] @@ -940,7 +940,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/docs-ci.yml - example: null + example: examples/infra/docs.yml required_permissions: - "contents: read" required_settings: [] @@ -959,7 +959,7 @@ capabilities: private_free: conditional private_paid: available workflow: .github/workflows/monorepo-changed-paths.yml - example: null + example: examples/infra/monorepo.yml required_permissions: - "contents: read" required_settings: [] @@ -970,3 +970,447 @@ capabilities: last_verified: "2026-07-04" sources: - "https://docs.github.com/en/actions/using-workflows/reusing-workflows" + + - id: dart-flutter-ci + name: Dart/Flutter CI + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/dart-flutter-ci.yml + example: examples/languages/dart-flutter.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Private-repo runner minutes are metered beyond the included free allotment" + - "iOS/macOS builds require macOS runners billed at a 10x minute multiplier" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://docs.flutter.dev/deployment/cd" + - "https://github.com/subosito/flutter-action" + + - id: web-ci + name: HTML/CSS/web CI + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/web-ci.yml + example: examples/languages/web.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Default lint uses npx to fetch stylelint/htmlhint at runtime; pin them in package.json for reproducible CI" + - "Private-repo runner minutes are metered beyond the included free allotment" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://stylelint.io/" + - "https://htmlhint.com/" + + - id: sql-ci + name: SQL CI + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/sql-ci.yml + example: examples/languages/sql.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Pin sqlfluff_version for reproducible linting; the default installs the latest release" + - "Private-repo runner minutes are metered beyond the included free allotment" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://docs.sqlfluff.com/" + - "https://github.com/sqlfluff/sqlfluff" + + - id: cpp-ci + name: C/C++ CI + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/cpp-ci.yml + example: examples/languages/cpp.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "clang-format/cppcheck are opt-in and require the tools present (add cppcheck to apt_packages)" + - "Private-repo runner minutes are metered beyond the included free allotment" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://cmake.org/cmake/help/latest/manual/ctest.1.html" + - "https://clang.llvm.org/docs/ClangFormat.html" + + - id: qt-ci + name: Qt CI + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/qt-ci.yml + example: examples/languages/qt.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "aqtinstall downloads Qt at runtime; pin qt_version for reproducible builds" + - "Private-repo runner minutes are metered beyond the included free allotment" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/jurplel/install-qt-action" + - "https://doc.qt.io/qt-6/cmake-manual.html" + + - id: kotlin-android-ci + name: Kotlin/Android CI + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/kotlin-android-ci.yml + example: examples/languages/kotlin-android.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "ktlint/detekt run as project-defined Gradle tasks supplied via lint_command" + - "Private-repo runner minutes are metered beyond the included free allotment" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/gradle/actions" + - "https://developer.android.com/build" + + - id: swift-ci + name: Swift CI + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/swift-ci.yml + example: examples/languages/swift.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Defaults to a macOS runner billed at a 10x minute multiplier; SwiftLint runs on macOS only" + - "harden-runner only runs on the Linux leg (it is a Linux/paid feature)" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://www.swift.org/documentation/" + - "https://github.com/realm/SwiftLint" + + - id: r-ci + name: R CI + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/r-ci.yml + example: examples/languages/r.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Package dependencies install via a caller-provided install_command" + - "Private-repo runner minutes are metered beyond the included free allotment" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/r-lib/actions" + - "https://lintr.r-lib.org/" + + - id: coverage-gate + name: Coverage gate + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/coverage-gate.yml + example: examples/quality/coverage-gate.yml + required_permissions: + - "contents: read" + required_settings: + - "CODECOV_TOKEN secret (required for private repos)" + risks: + - "Coverage thresholds are enforced by the caller's codecov.yml, not this workflow" + - "Private-repo runner minutes are metered beyond the included free allotment" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://docs.codecov.com/docs" + - "https://github.com/codecov/codecov-action" + + - id: docs-quality + name: Docs quality (links, spelling, markdown) + cluster: actions-core + status: ga + public_oss: free + private_free: free + private_paid: available + workflow: .github/workflows/docs-quality.yml + example: examples/quality/docs-quality.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Link checking hits the network; flaky external hosts can fail lychee (tune lychee_args)" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/lycheeverse/lychee-action" + - "https://github.com/crate-ci/typos" + + - id: pr-hygiene + name: PR hygiene (commitlint, PR title, labeler, stale) + cluster: community-dx + status: ga + public_oss: free + private_free: free + private_paid: available + workflow: .github/workflows/pr-hygiene.yml + example: examples/quality/pr-hygiene.yml + required_permissions: + - "contents: read" + - "pull-requests: write (labeler and stale jobs)" + - "issues: write (stale job)" + required_settings: [] + risks: + - "labeler on fork PRs needs pull_request_target, which this library intentionally avoids; use it for same-repo PRs" + - "Callers must grant the union of the enabled jobs' permissions" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/wagoid/commitlint-github-action" + - "https://github.com/actions/labeler" + + - id: semgrep-sast + name: Semgrep OSS SAST + cluster: security-scanning + status: ga + public_oss: free + private_free: free + private_paid: available + workflow: .github/workflows/semgrep-ci.yml + example: examples/security/semgrep.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Gate-only (no SARIF); use CodeQL/GHAS for code-scanning alerts on paid tiers" + - "Registry rulesets (p/ci) are fetched at runtime; pin semgrep_version for reproducibility" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://semgrep.dev/docs/" + - "https://github.com/semgrep/semgrep" + + - id: osv-scanner-sca + name: OSV-Scanner SCA + cluster: supply-chain + status: ga + public_oss: free + private_free: free + private_paid: available + workflow: .github/workflows/osv-scan.yml + example: examples/security/osv-scan.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Checksum-pinned binary; bump osv_scanner_version and osv_scanner_sha256 together" + - "Gate-only free alternative to GitHub dependency review on private-free repos" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://google.github.io/osv-scanner/" + - "https://github.com/google/osv-scanner" + + - id: grype-sca + name: Grype vulnerability scan + cluster: supply-chain + status: ga + public_oss: free + private_free: free + private_paid: available + workflow: .github/workflows/grype-scan.yml + example: examples/security/grype-scan.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Gate-only (table output); wire SARIF + code scanning separately on paid tiers" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/anchore/grype" + - "https://github.com/anchore/scan-action" + + - id: hadolint-dockerfile + name: hadolint Dockerfile lint + cluster: security-scanning + status: ga + public_oss: free + private_free: free + private_paid: available + workflow: .github/workflows/hadolint-ci.yml + example: examples/security/hadolint.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Tune failure_threshold and ignore to match your Dockerfile policy" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/hadolint/hadolint" + - "https://github.com/hadolint/hadolint-action" + + - id: iac-scan-checkov + name: IaC scan (Checkov) + cluster: security-scanning + status: ga + public_oss: free + private_free: free + private_paid: available + workflow: .github/workflows/iac-scan.yml + example: examples/security/iac-scan.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Gate-only (CLI output); wire SARIF upload separately for code-scanning alerts" + - "Broadens Terraform-CI coverage to Kubernetes/CloudFormation/Helm/ARM" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://www.checkov.io/" + - "https://github.com/bridgecrewio/checkov-action" + + - id: mutation-testing + name: Mutation testing + cluster: actions-core + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/mutation-testing.yml + example: examples/testing/mutation-testing.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "Mutation runs are slow; schedule them rather than gating every PR" + - "Private-repo runner minutes are metered beyond the included free allotment" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://mutmut.readthedocs.io/" + - "https://github.com/sourcefrog/cargo-mutants" + + - id: fuzzing + name: Fuzzing + cluster: security-scanning + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/fuzzing.yml + example: examples/testing/fuzzing.yml + required_permissions: + - "contents: read" + required_settings: [] + risks: + - "cargo-fuzz needs a nightly toolchain; bound each run with -max_total_time" + - "For continuous coverage-guided fuzzing, integrate ClusterFuzzLite" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://rust-fuzz.github.io/book/cargo-fuzz.html" + - "https://google.github.io/clusterfuzzlite/" + + - id: benchmark + name: Performance benchmark + regression alert + cluster: observability + status: ga + public_oss: free + private_free: conditional + private_paid: available + workflow: .github/workflows/benchmark.yml + example: examples/testing/benchmark.yml + required_permissions: + - "contents: write (gh-pages history when auto_push is true)" + required_settings: [] + risks: + - "Storing history needs contents: write and a gh-pages branch; set auto_push false for compare-only runs" + - "Benchmark noise on shared runners can cause false regressions; tune alert_threshold" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/benchmark-action/github-action-benchmark" + - "https://bencher.dev/" + + - id: ai-code-review + name: AI code review + cluster: ai-agentic + status: ga + public_oss: conditional + private_free: conditional + private_paid: conditional + workflow: null + example: examples/level3/ai-review.yml + required_permissions: + - "contents: read" + - "pull-requests: write" + required_settings: + - "Model API key stored as a secret (e.g. ANTHROPIC_API_KEY), or a GitHub App (CodeRabbit)" + risks: + - "AI reviews are advisory; keep human review as the merge gate" + - "CodeRabbit is free for public OSS; Claude/Qodo consume paid model tokens" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/anthropics/claude-code-action" + - "https://github.com/qodo-ai/pr-agent" + + - id: release-automation + name: Release automation (release-please / changesets) + cluster: releases-packages + status: ga + public_oss: free + private_free: free + private_paid: available + workflow: null + example: examples/level3/release-please.yml + required_permissions: + - "contents: write" + - "pull-requests: write" + required_settings: [] + risks: + - "Complements, not replaces, the tag-driven release-supply-chain workflow" + - "release-please opens a release PR; changesets suits monorepos with per-package versioning" + deprecations: null + last_verified: "2026-07-08" + sources: + - "https://github.com/googleapis/release-please-action" + - "https://github.com/changesets/action" diff --git a/catalog/tools.yml b/catalog/tools.yml index 6752f7e..e3846b7 100644 --- a/catalog/tools.yml +++ b/catalog/tools.yml @@ -181,3 +181,224 @@ tools: used_by: - .github/workflows/release-supply-chain.yml last_verified: "2026-07-04" + + - id: flutter-action + name: subosito/flutter-action + homepage: "https://github.com/subosito/flutter-action" + kind: action + current_version: "v2.23.0" + pin: "subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2" + used_by: + - .github/workflows/dart-flutter-ci.yml + last_verified: "2026-07-08" + + - id: install-qt-action + name: jurplel/install-qt-action + homepage: "https://github.com/jurplel/install-qt-action" + kind: action + current_version: "v4.3.1" + pin: "jurplel/install-qt-action@48d3ad6db93f3627c8ee7a0454bc6f3744f7e730" + used_by: + - .github/workflows/qt-ci.yml + last_verified: "2026-07-08" + + - id: ccache-action + name: hendrikmuhs/ccache-action + homepage: "https://github.com/hendrikmuhs/ccache-action" + kind: action + current_version: "v1.2.23" + pin: "hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03" + used_by: + - .github/workflows/cpp-ci.yml + - .github/workflows/qt-ci.yml + last_verified: "2026-07-08" + + - id: setup-gradle + name: gradle/actions/setup-gradle + homepage: "https://github.com/gradle/actions" + kind: action + current_version: "v6.2.0" + pin: "gradle/actions@3f131e8634966bd73d06cc69884922b02e6faf92" + used_by: + - .github/workflows/kotlin-android-ci.yml + last_verified: "2026-07-08" + + - id: setup-android + name: android-actions/setup-android + homepage: "https://github.com/android-actions/setup-android" + kind: action + current_version: "v4.0.1" + pin: "android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699" + used_by: + - .github/workflows/kotlin-android-ci.yml + last_verified: "2026-07-08" + + - id: setup-swift + name: swift-actions/setup-swift + homepage: "https://github.com/swift-actions/setup-swift" + kind: action + current_version: "v2.4.0" + pin: "swift-actions/setup-swift@7ca6abe6b3b0e8b5421b88be48feee39cbf52c6a" + used_by: + - .github/workflows/swift-ci.yml + last_verified: "2026-07-08" + + - id: setup-r + name: r-lib/actions/setup-r + homepage: "https://github.com/r-lib/actions" + kind: action + current_version: "v2" + pin: "r-lib/actions@d3c5be51b12e724e68f33216ca3c148b66d5f0b6" + used_by: + - .github/workflows/r-ci.yml + last_verified: "2026-07-08" + + - id: codecov-action + name: codecov/codecov-action + homepage: "https://github.com/codecov/codecov-action" + kind: action + current_version: "v7.0.0" + pin: "codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f" + used_by: + - .github/workflows/coverage-gate.yml + last_verified: "2026-07-08" + + - id: coveralls-action + name: coverallsapp/github-action + homepage: "https://github.com/coverallsapp/github-action" + kind: action + current_version: "v2.3.6" + pin: "coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b" + used_by: + - .github/workflows/coverage-gate.yml + last_verified: "2026-07-08" + + - id: lychee-action + name: lycheeverse/lychee-action + homepage: "https://github.com/lycheeverse/lychee-action" + kind: action + current_version: "v2.8.0" + pin: "lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411" + used_by: + - .github/workflows/docs-quality.yml + last_verified: "2026-07-08" + + - id: typos-action + name: crate-ci/typos + homepage: "https://github.com/crate-ci/typos" + kind: action + current_version: "v1.48.0" + pin: "crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14" + used_by: + - .github/workflows/docs-quality.yml + last_verified: "2026-07-08" + + - id: markdownlint-cli2-action + name: DavidAnson/markdownlint-cli2-action + homepage: "https://github.com/DavidAnson/markdownlint-cli2-action" + kind: action + current_version: "v24.0.0" + pin: "DavidAnson/markdownlint-cli2-action@8de2aa07cae85fd17c0b35642db70cf5495f1d25" + used_by: + - .github/workflows/docs-quality.yml + last_verified: "2026-07-08" + + - id: commitlint-action + name: wagoid/commitlint-github-action + homepage: "https://github.com/wagoid/commitlint-github-action" + kind: action + current_version: "v6.2.1" + pin: "wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed" + used_by: + - .github/workflows/pr-hygiene.yml + last_verified: "2026-07-08" + + - id: semantic-pull-request + name: amannn/action-semantic-pull-request + homepage: "https://github.com/amannn/action-semantic-pull-request" + kind: action + current_version: "v6.1.1" + pin: "amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50" + used_by: + - .github/workflows/pr-hygiene.yml + last_verified: "2026-07-08" + + - id: labeler + name: actions/labeler + homepage: "https://github.com/actions/labeler" + kind: action + current_version: "v6.1.0" + pin: "actions/labeler@f27b608878404679385c85cfa523b85ccb86e213" + used_by: + - .github/workflows/pr-hygiene.yml + last_verified: "2026-07-08" + + - id: stale + name: actions/stale + homepage: "https://github.com/actions/stale" + kind: action + current_version: "v10.3.0" + pin: "actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899" + used_by: + - .github/workflows/pr-hygiene.yml + last_verified: "2026-07-08" + + - id: osv-scanner + name: osv-scanner + homepage: "https://github.com/google/osv-scanner" + kind: cli + current_version: "v2.4.0" + pin: null + used_by: + - .github/workflows/osv-scan.yml + last_verified: "2026-07-08" + + - id: semgrep + name: semgrep + homepage: "https://github.com/semgrep/semgrep" + kind: cli + current_version: "v1.168.0" + pin: null + used_by: + - .github/workflows/semgrep-ci.yml + last_verified: "2026-07-08" + + - id: anchore-scan-action + name: anchore/scan-action + homepage: "https://github.com/anchore/scan-action" + kind: action + current_version: "v7.4.0" + pin: "anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2" + used_by: + - .github/workflows/grype-scan.yml + last_verified: "2026-07-08" + + - id: hadolint-action + name: hadolint/hadolint-action + homepage: "https://github.com/hadolint/hadolint-action" + kind: action + current_version: "v3.3.0" + pin: "hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5" + used_by: + - .github/workflows/hadolint-ci.yml + last_verified: "2026-07-08" + + - id: checkov-action + name: bridgecrewio/checkov-action + homepage: "https://github.com/bridgecrewio/checkov-action" + kind: action + current_version: "v12.1347.0" + pin: "bridgecrewio/checkov-action@99bb2caf247dfd9f03cf984373bc6043d4e32ebf" + used_by: + - .github/workflows/iac-scan.yml + last_verified: "2026-07-08" + + - id: github-action-benchmark + name: benchmark-action/github-action-benchmark + homepage: "https://github.com/benchmark-action/github-action-benchmark" + kind: action + current_version: "v1.22.1" + pin: "benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba" + used_by: + - .github/workflows/benchmark.yml + last_verified: "2026-07-08" diff --git a/docs/00-overview.md b/docs/00-overview.md index 53c2880..47e5fa9 100644 --- a/docs/00-overview.md +++ b/docs/00-overview.md @@ -67,6 +67,13 @@ For end-to-end caller examples per tier, see the tier docs and the repository | `terraform-ci.yml` | Terraform fmt/validate/plan | Both | | `docs-ci.yml` | Docs lint/link-check/build | Both | | `monorepo-changed-paths.yml` | Changed-path filtering for monorepos | Both | +| `dart-flutter-ci.yml` · `cpp-ci.yml` · `qt-ci.yml` · `kotlin-android-ci.yml` · `swift-ci.yml` · `r-ci.yml` · `web-ci.yml` · `sql-ci.yml` | Language packs (Dart/Flutter, C/C++, Qt, Kotlin/Android, Swift, R, web, SQL) | Both | +| `coverage-gate.yml` · `docs-quality.yml` · `pr-hygiene.yml` | Coverage, docs quality, PR hygiene | Both | +| `semgrep-ci.yml` · `osv-scan.yml` · `grype-scan.yml` · `hadolint-ci.yml` · `iac-scan.yml` | Free SAST/SCA/IaC (incl. private-free) | Both | +| `mutation-testing.yml` · `fuzzing.yml` · `benchmark.yml` | Mutation testing, fuzzing, benchmark | Both | + +> The July 2026 language/quality/security/testing packs are documented in +> [15 Language & quality packs](15-language-and-quality-packs.md). > zizmor is split into two callers: `zizmor-sarif.yml` (uploads to code > scanning, for public and GHAS) and `zizmor-no-sarif.yml` (fails the job on @@ -87,10 +94,11 @@ For end-to-end caller examples per tier, see the tier docs and the repository [12 Community & DX](12-community-dx.md) - Tooling: [13 External tools](13-external-tools.md) · [14 AI / agentic workflows](14-ai-agentic-workflows.md) +- Packs: [15 Language & quality packs](15-language-and-quality-packs.md) - Horizon: [Watchlist 2026](watchlist-2026.md) - Security deep-dive: [pull_request_target / pwn requests](security/pull-request-target.md) - Generated from catalog: [capability matrix](generated/capability-matrix.md) · [workflow inventory](generated/workflow-inventory.md) --- -Last verified: 2026-07-04 +Last verified: 2026-07-08 diff --git a/docs/15-language-and-quality-packs.md b/docs/15-language-and-quality-packs.md new file mode 100644 index 0000000..36a8162 --- /dev/null +++ b/docs/15-language-and-quality-packs.md @@ -0,0 +1,77 @@ +# Language & quality packs (July 2026 expansion) + +This page documents the reusable packs added in the July 2026 expansion. Every +pack follows the library conventions (top-level `permissions: {}`, SHA-pinned +actions with version comments, env-indirected caller commands, `timeout-minutes`, +harden-runner gating) and is validated by `scripts/validate_all.py`. The +machine-readable source of truth is +[`catalog/capabilities.yml`](../catalog/capabilities.yml); the full matrix is in +[generated/capability-matrix.md](generated/capability-matrix.md). + +Tier legend: **Public** = free on public repos; **Private-free** = free on +private repos (no paid GHAS); **Private-paid** = available with GHAS. Language +build packs consume metered runner minutes on private repos (marked +*conditional* in the catalog) but need no paid feature. Private-free callers set +`enable_harden_runner: false` — harden-runner is a Linux/paid feature on private. + +## Language packs + +Dual-tier, caller-command-driven with sensible defaults. + +| Pack | Workflow | Example | +| --- | --- | --- | +| Dart/Flutter | `dart-flutter-ci.yml` | [dart-flutter](../examples/languages/dart-flutter.yml) | +| C/C++ | `cpp-ci.yml` | [cpp](../examples/languages/cpp.yml) | +| Qt | `qt-ci.yml` | [qt](../examples/languages/qt.yml) | +| Kotlin/Android | `kotlin-android-ci.yml` | [kotlin-android](../examples/languages/kotlin-android.yml) | +| Swift | `swift-ci.yml` | [swift](../examples/languages/swift.yml) | +| R | `r-ci.yml` | [r](../examples/languages/r.yml) | +| HTML/CSS/web | `web-ci.yml` | [web](../examples/languages/web.yml) | +| SQL | `sql-ci.yml` | [sql](../examples/languages/sql.yml) | + +These join the existing Python, Node, Go, Rust, Java, .NET, container, and +Terraform packs. Swift defaults to a macOS runner (10x minute multiplier); its +harden-runner step and SwiftLint run on the appropriate OS only. + +## Quality gates + +| Pack | Workflow | Tiers | Example | +| --- | --- | --- | --- | +| Coverage (Codecov/Coveralls) | `coverage-gate.yml` | all (token on private) | [coverage-gate](../examples/quality/coverage-gate.yml) | +| Docs quality (lychee/typos/markdownlint) | `docs-quality.yml` | free everywhere | [docs-quality](../examples/quality/docs-quality.yml) | +| PR hygiene (commitlint/PR-title/labeler/stale) | `pr-hygiene.yml` | free everywhere | [pr-hygiene](../examples/quality/pr-hygiene.yml) | + +## Free security (SAST / SCA / IaC) + +Free on **every** tier, including private-free where CodeQL and dependency review +are paid GHAS. All are gate-only (no SARIF upload, so no `security-events` +permission is required). + +| Pack | Tool | Workflow | Example | +| --- | --- | --- | --- | +| SAST | Semgrep OSS | `semgrep-ci.yml` | [semgrep](../examples/security/semgrep.yml) | +| SCA | OSV-Scanner | `osv-scan.yml` | [osv-scan](../examples/security/osv-scan.yml) | +| SCA | Grype | `grype-scan.yml` | [grype-scan](../examples/security/grype-scan.yml) | +| Dockerfile | hadolint | `hadolint-ci.yml` | [hadolint](../examples/security/hadolint.yml) | +| IaC | Checkov | `iac-scan.yml` | [iac-scan](../examples/security/iac-scan.yml) | + +## Advanced testing + +| Pack | Workflow | Example | +| --- | --- | --- | +| Mutation testing (mutmut/cargo-mutants/Stryker) | `mutation-testing.yml` | [mutation-testing](../examples/testing/mutation-testing.yml) | +| Fuzzing (cargo-fuzz; ClusterFuzzLite noted) | `fuzzing.yml` | [fuzzing](../examples/testing/fuzzing.yml) | +| Benchmark + regression alert | `benchmark.yml` | [benchmark](../examples/testing/benchmark.yml) | + +## Level-3 opt-in (self-contained examples) + +Delivered as self-contained caller examples (no reusable workflow), since they +wrap fast-moving third-party services and are opt-in. + +| Pattern | Example | Notes | +| --- | --- | --- | +| AI code review | [ai-review](../examples/level3/ai-review.yml) | Claude Code Action; CodeRabbit (free OSS) / Qodo alternatives. Advisory — human review stays the merge gate. | +| Release automation | [release-please](../examples/level3/release-please.yml) | Complements the tag-driven attested release; changesets for monorepos. | + +--- +Last verified: 2026-07-08 diff --git a/docs/generated/capability-matrix.md b/docs/generated/capability-matrix.md index 563e79e..13f03e0 100644 --- a/docs/generated/capability-matrix.md +++ b/docs/generated/capability-matrix.md @@ -4,29 +4,44 @@ | Capability | Cluster | Status | Public OSS | Private free | Private paid | Workflow | Example | | --- | --- | --- | --- | --- | --- | --- | --- | | actionlint workflow linter (`actionlint`) | actions-core | ga | free | free | available | `.github/workflows/actionlint.yml` | `-` | -| Cross-platform smoke test (`cross-platform-smoke`) | actions-core | ga | free | free | available | `.github/workflows/cross-platform-smoke.yml` | `-` | -| Docs CI (`docs-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/docs-ci.yml` | `-` | -| .NET CI (`dotnet-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/dotnet-ci.yml` | `-` | -| Go CI (`go-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/go-ci.yml` | `-` | -| Java CI (`java-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/java-ci.yml` | `-` | -| Monorepo changed-paths filter (`monorepo-changed-paths`) | actions-core | ga | free | conditional | available | `.github/workflows/monorepo-changed-paths.yml` | `-` | -| Node.js CI (`node-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/node-ci.yml` | `-` | +| Coverage gate (`coverage-gate`) | actions-core | ga | free | conditional | available | `.github/workflows/coverage-gate.yml` | `examples/quality/coverage-gate.yml` | +| C/C++ CI (`cpp-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/cpp-ci.yml` | `examples/languages/cpp.yml` | +| Cross-platform smoke test (`cross-platform-smoke`) | actions-core | ga | free | free | available | `.github/workflows/cross-platform-smoke.yml` | `examples/infra/cross-platform.yml` | +| Dart/Flutter CI (`dart-flutter-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/dart-flutter-ci.yml` | `examples/languages/dart-flutter.yml` | +| Docs CI (`docs-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/docs-ci.yml` | `examples/infra/docs.yml` | +| Docs quality (links, spelling, markdown) (`docs-quality`) | actions-core | ga | free | free | available | `.github/workflows/docs-quality.yml` | `examples/quality/docs-quality.yml` | +| .NET CI (`dotnet-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/dotnet-ci.yml` | `examples/languages/dotnet.yml` | +| Go CI (`go-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/go-ci.yml` | `examples/languages/go.yml` | +| Java CI (`java-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/java-ci.yml` | `examples/languages/java.yml` | +| Kotlin/Android CI (`kotlin-android-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/kotlin-android-ci.yml` | `examples/languages/kotlin-android.yml` | +| Monorepo changed-paths filter (`monorepo-changed-paths`) | actions-core | ga | free | conditional | available | `.github/workflows/monorepo-changed-paths.yml` | `examples/infra/monorepo.yml` | +| Mutation testing (`mutation-testing`) | actions-core | ga | free | conditional | available | `.github/workflows/mutation-testing.yml` | `examples/testing/mutation-testing.yml` | +| Node.js CI (`node-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/node-ci.yml` | `examples/languages/node.yml` | | Lightweight static validation (`private-static-validation`) | actions-core | ga | free | free | available | `.github/workflows/private-static.yml` | `-` | -| Python CI (`python-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/python-ci.yml` | `-` | -| Rust CI (`rust-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/rust-ci.yml` | `-` | +| Python CI (`python-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/python-ci.yml` | `examples/languages/python.yml` | +| Qt CI (`qt-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/qt-ci.yml` | `examples/languages/qt.yml` | +| R CI (`r-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/r-ci.yml` | `examples/languages/r.yml` | +| Rust CI (`rust-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/rust-ci.yml` | `examples/languages/rust.yml` | +| SQL CI (`sql-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/sql-ci.yml` | `examples/languages/sql.yml` | | Actions step-level parallel execution (`step-level-parallel-execution`) | actions-core | preview | free | free | available | `-` | `-` | -| Terraform CI (`terraform-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/terraform-ci.yml` | `-` | +| Swift CI (`swift-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/swift-ci.yml` | `examples/languages/swift.yml` | +| Terraform CI (`terraform-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/terraform-ci.yml` | `examples/infra/terraform.yml` | +| HTML/CSS/web CI (`web-ci`) | actions-core | ga | free | conditional | available | `.github/workflows/web-ci.yml` | `examples/languages/web.yml` | | GitHub Agentic Workflows (`agentic-workflows`) | ai-agentic | preview | conditional | conditional | conditional | `-` | `-` | +| AI code review (`ai-code-review`) | ai-agentic | ga | conditional | conditional | conditional | `-` | `examples/level3/ai-review.yml` | | GitHub Models (`github-models`) | ai-agentic | retiring | conditional | conditional | conditional | `-` | `-` | +| PR hygiene (commitlint, PR title, labeler, stale) (`pr-hygiene`) | community-dx | ga | free | free | available | `.github/workflows/pr-hygiene.yml` | `examples/quality/pr-hygiene.yml` | | OIDC cloud publishing (`oidc-cloud-publishing`) | deployments | ga | free | free | available | `-` | `-` | | Actions cache security (read-only untrusted cache) (`actions-cache-security`) | governance | ga | free | free | available | `-` | `-` | | Branch protection (classic) (`branch-protection-classic`) | governance | deprecated | free | conditional | available | `-` | `-` | | Merge queue (`merge-queue`) | governance | ga | conditional | unavailable | conditional | `-` | `-` | | Repository rulesets (`rulesets`) | governance | ga | free | conditional | available | `-` | `-` | | Workflow execution protections (`workflow-execution-protections`) | governance | preview | free | conditional | conditional | `-` | `-` | +| Performance benchmark + regression alert (`benchmark`) | observability | ga | free | conditional | available | `.github/workflows/benchmark.yml` | `examples/testing/benchmark.yml` | | Immutable releases (`immutable-releases`) | releases-packages | ga | free | free | available | `-` | `-` | | npm trusted publishing (`npm-trusted-publishing`) | releases-packages | ga | free | free | available | `-` | `examples/release/npm-trusted-publishing.yml` | | PyPI trusted publishing (`pypi-trusted-publishing`) | releases-packages | ga | free | free | available | `-` | `examples/release/pypi-trusted-publishing.yml` | +| Release automation (release-please / changesets) (`release-automation`) | releases-packages | ga | free | free | available | `-` | `examples/level3/release-please.yml` | | Release supply chain (SBOM + attest + publish) (`release-supply-chain`) | releases-packages | ga | free | free | available | `.github/workflows/release-supply-chain.yml` | `examples/public-oss/release.yml` | | GitHub-hosted runner governance controls (`hosted-runner-governance-controls`) | runners | preview | conditional | conditional | conditional | `-` | `-` | | Larger and GPU hosted runners (`larger-gpu-runners`) | runners | ga | paid | unavailable | available | `-` | `-` | @@ -34,21 +49,27 @@ | Self-hosted runners (`self-hosted-runners`) | runners | ga | conditional | free | available | `-` | `-` | | Standard GitHub-hosted runners (`standard-hosted-runners`) | runners | ga | free | conditional | available | `-` | `-` | | CodeQL code scanning (`codeql-code-scanning`) | security-scanning | ga | free | unavailable | available | `.github/workflows/public-codeql.yml` | `-` | -| Container build and Trivy scan (`container-scan-trivy`) | security-scanning | ga | free | free | available | `.github/workflows/container-ci.yml` | `-` | +| Container build and Trivy scan (`container-scan-trivy`) | security-scanning | ga | free | free | available | `.github/workflows/container-ci.yml` | `examples/infra/container.yml` | | Copilot Autofix for code scanning (`copilot-autofix`) | security-scanning | ga | free | unavailable | available | `-` | `-` | +| Fuzzing (`fuzzing`) | security-scanning | ga | free | conditional | available | `.github/workflows/fuzzing.yml` | `examples/testing/fuzzing.yml` | | Gitleaks secret scan (`gitleaks-secret-scan`) | security-scanning | ga | free | free | available | `.github/workflows/secret-scan.yml` | `-` | +| hadolint Dockerfile lint (`hadolint-dockerfile`) | security-scanning | ga | free | free | available | `.github/workflows/hadolint-ci.yml` | `examples/security/hadolint.yml` | +| IaC scan (Checkov) (`iac-scan-checkov`) | security-scanning | ga | free | free | available | `.github/workflows/iac-scan.yml` | `examples/security/iac-scan.yml` | | GitHub native secret scanning (`native-secret-scanning`) | security-scanning | ga | free | unavailable | available | `-` | `-` | | OpenSSF Scorecard SARIF (`ossf-scorecard`) | security-scanning | ga | free | unavailable | conditional | `.github/workflows/public-scorecard.yml` | `-` | | OpenSSF Scorecard JSON artifact (`ossf-scorecard-json`) | security-scanning | ga | free | unavailable | conditional | `.github/workflows/public-scorecard-json.yml` | `examples/public-oss/scorecard.yml` | +| Semgrep OSS SAST (`semgrep-sast`) | security-scanning | ga | free | free | available | `.github/workflows/semgrep-ci.yml` | `examples/security/semgrep.yml` | | zizmor workflow security scanner (`zizmor`) | security-scanning | ga | free | free | available | `.github/workflows/zizmor-sarif.yml` | `-` | | zizmor workflow security scanner (no SARIF) (`zizmor-no-sarif`) | security-scanning | ga | free | free | available | `.github/workflows/zizmor-no-sarif.yml` | `examples/private-free/security.yml` | | Artifact attestations (`artifact-attestations`) | supply-chain | ga | free | free | available | `.github/workflows/release-supply-chain.yml` | `-` | | Dependabot version and security updates (`dependabot`) | supply-chain | ga | free | free | available | `-` | `-` | | Dependency review (`dependency-review`) | supply-chain | ga | free | unavailable | available | `.github/workflows/public-dependency-review.yml` | `examples/public-oss/dependency-review.yml` | +| Grype vulnerability scan (`grype-sca`) | supply-chain | ga | free | free | available | `.github/workflows/grype-scan.yml` | `examples/security/grype-scan.yml` | | StepSecurity Harden-Runner egress control (`harden-runner`) | supply-chain | ga | free | conditional | conditional | `-` | `-` | | Open source license compliance (`license-compliance-preview`) | supply-chain | preview | unavailable | unavailable | conditional | `-` | `-` | +| OSV-Scanner SCA (`osv-scanner-sca`) | supply-chain | ga | free | free | available | `.github/workflows/osv-scan.yml` | `examples/security/osv-scan.yml` | | SBOM generation (`sbom-generation`) | supply-chain | ga | free | free | available | `.github/workflows/release-supply-chain.yml` | `-` | | SLSA build provenance (`slsa-build-provenance`) | supply-chain | ga | free | free | available | `.github/workflows/release-supply-chain.yml` | `-` | --- -Last generated: 2026-07-04 +Last generated: 2026-07-08 diff --git a/docs/generated/workflow-inventory.md b/docs/generated/workflow-inventory.md index 2b22cac..49b1660 100644 --- a/docs/generated/workflow-inventory.md +++ b/docs/generated/workflow-inventory.md @@ -4,28 +4,47 @@ | Workflow | Capability IDs | Statuses | | --- | --- | --- | | `.github/workflows/actionlint.yml` | `actionlint` | ga | +| `.github/workflows/benchmark.yml` | `benchmark` | ga | | `.github/workflows/ci.yml` | internal | internal | | `.github/workflows/container-ci.yml` | `container-scan-trivy` | ga | +| `.github/workflows/coverage-gate.yml` | `coverage-gate` | ga | +| `.github/workflows/cpp-ci.yml` | `cpp-ci` | ga | | `.github/workflows/cross-platform-smoke.yml` | `cross-platform-smoke` | ga | +| `.github/workflows/dart-flutter-ci.yml` | `dart-flutter-ci` | ga | | `.github/workflows/docs-ci.yml` | `docs-ci` | ga | +| `.github/workflows/docs-quality.yml` | `docs-quality` | ga | | `.github/workflows/dotnet-ci.yml` | `dotnet-ci` | ga | +| `.github/workflows/fuzzing.yml` | `fuzzing` | ga | | `.github/workflows/go-ci.yml` | `go-ci` | ga | +| `.github/workflows/grype-scan.yml` | `grype-sca` | ga | +| `.github/workflows/hadolint-ci.yml` | `hadolint-dockerfile` | ga | +| `.github/workflows/iac-scan.yml` | `iac-scan-checkov` | ga | | `.github/workflows/java-ci.yml` | `java-ci` | ga | +| `.github/workflows/kotlin-android-ci.yml` | `kotlin-android-ci` | ga | | `.github/workflows/monorepo-changed-paths.yml` | `monorepo-changed-paths` | ga | +| `.github/workflows/mutation-testing.yml` | `mutation-testing` | ga | | `.github/workflows/node-ci.yml` | `node-ci` | ga | +| `.github/workflows/osv-scan.yml` | `osv-scanner-sca` | ga | +| `.github/workflows/pr-hygiene.yml` | `pr-hygiene` | ga | | `.github/workflows/private-static.yml` | `private-static-validation` | ga | | `.github/workflows/public-codeql.yml` | `codeql-code-scanning` | ga | | `.github/workflows/public-dependency-review.yml` | `dependency-review` | ga | | `.github/workflows/public-scorecard-json.yml` | `ossf-scorecard-json` | ga | | `.github/workflows/public-scorecard.yml` | `ossf-scorecard` | ga | | `.github/workflows/python-ci.yml` | `python-ci` | ga | +| `.github/workflows/qt-ci.yml` | `qt-ci` | ga | +| `.github/workflows/r-ci.yml` | `r-ci` | ga | | `.github/workflows/release-supply-chain.yml` | `artifact-attestations`, `sbom-generation`, `slsa-build-provenance`, `release-supply-chain` | ga | | `.github/workflows/release.yml` | internal | internal | | `.github/workflows/rust-ci.yml` | `rust-ci` | ga | | `.github/workflows/secret-scan.yml` | `gitleaks-secret-scan` | ga | +| `.github/workflows/semgrep-ci.yml` | `semgrep-sast` | ga | +| `.github/workflows/sql-ci.yml` | `sql-ci` | ga | +| `.github/workflows/swift-ci.yml` | `swift-ci` | ga | | `.github/workflows/terraform-ci.yml` | `terraform-ci` | ga | +| `.github/workflows/web-ci.yml` | `web-ci` | ga | | `.github/workflows/zizmor-no-sarif.yml` | `zizmor-no-sarif` | ga | | `.github/workflows/zizmor-sarif.yml` | `zizmor` | ga | --- -Last generated: 2026-07-04 +Last generated: 2026-07-08 diff --git a/examples/infra/container.yml b/examples/infra/container.yml new file mode 100644 index 0000000..0c86f99 --- /dev/null +++ b/examples/infra/container.yml @@ -0,0 +1,18 @@ +# Container/filesystem Trivy scan caller (any tier). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: container +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: container-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + container: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/container-ci.yml@ + with: + scan_type: fs + scan_ref: '.' + severity: 'CRITICAL,HIGH' diff --git a/examples/infra/cross-platform.yml b/examples/infra/cross-platform.yml new file mode 100644 index 0000000..65e9264 --- /dev/null +++ b/examples/infra/cross-platform.yml @@ -0,0 +1,17 @@ +# Cross-platform smoke test caller (any tier). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: cross-platform +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: cross-platform-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + smoke: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/cross-platform-smoke.yml@ + with: + os_list: '["ubuntu-latest","macos-latest","windows-latest"]' + command: 'python -c "import sys; print(sys.platform)"' diff --git a/examples/infra/docs.yml b/examples/infra/docs.yml new file mode 100644 index 0000000..2b53ff7 --- /dev/null +++ b/examples/infra/docs.yml @@ -0,0 +1,16 @@ +# Docs CI (broken relative-link check) caller (any tier). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: docs +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: docs-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + docs: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/docs-ci.yml@ + with: + docs_glob: 'docs/**/*.md' diff --git a/examples/infra/monorepo.yml b/examples/infra/monorepo.yml new file mode 100644 index 0000000..5b14266 --- /dev/null +++ b/examples/infra/monorepo.yml @@ -0,0 +1,20 @@ +# Monorepo changed-paths router (any tier). Replace @. +# The reusable emits outputs.result (JSON of matched filters) that downstream +# jobs gate on with `needs: changes` + `if: fromJSON(needs.changes.outputs.result).backend`. +name: monorepo +on: + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: monorepo-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + changes: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/monorepo-changed-paths.yml@ + with: + filters: | + backend: + - 'backend/**' + frontend: + - 'frontend/**' diff --git a/examples/infra/terraform.yml b/examples/infra/terraform.yml new file mode 100644 index 0000000..ff1acb6 --- /dev/null +++ b/examples/infra/terraform.yml @@ -0,0 +1,17 @@ +# Terraform fmt/init/validate caller (any tier). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: terraform +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: terraform-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + terraform: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/terraform-ci.yml@ + with: + terraform_version: '1.13.0' # pin for reproducible CI + working_directory: infra diff --git a/examples/languages/cpp.yml b/examples/languages/cpp.yml new file mode 100644 index 0000000..31f0bcb --- /dev/null +++ b/examples/languages/cpp.yml @@ -0,0 +1,18 @@ +# C/C++ CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: cpp +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: cpp-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + cpp: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/cpp-ci.yml@ + with: + apt_packages: 'cppcheck ninja-build' + format_check: true + cppcheck: true diff --git a/examples/languages/dart-flutter.yml b/examples/languages/dart-flutter.yml new file mode 100644 index 0000000..9f8f56d --- /dev/null +++ b/examples/languages/dart-flutter.yml @@ -0,0 +1,17 @@ +# Dart/Flutter CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: dart-flutter +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: dart-flutter-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + dart-flutter: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/dart-flutter-ci.yml@ + with: + flutter_channel: stable + test_command: 'flutter test --coverage' diff --git a/examples/languages/dotnet.yml b/examples/languages/dotnet.yml new file mode 100644 index 0000000..3705066 --- /dev/null +++ b/examples/languages/dotnet.yml @@ -0,0 +1,16 @@ +# .NET CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: dotnet +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: dotnet-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + dotnet: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/dotnet-ci.yml@ + with: + test_command: 'dotnet test' diff --git a/examples/languages/go.yml b/examples/languages/go.yml new file mode 100644 index 0000000..eb61065 --- /dev/null +++ b/examples/languages/go.yml @@ -0,0 +1,16 @@ +# Go CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: go +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: go-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + go: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/go-ci.yml@ + with: + test_command: 'go test -race ./...' diff --git a/examples/languages/java.yml b/examples/languages/java.yml new file mode 100644 index 0000000..92bc84e --- /dev/null +++ b/examples/languages/java.yml @@ -0,0 +1,16 @@ +# Java CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: java +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: java-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + java: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/java-ci.yml@ + with: + test_command: 'mvn -B verify' diff --git a/examples/languages/kotlin-android.yml b/examples/languages/kotlin-android.yml new file mode 100644 index 0000000..79b0b8c --- /dev/null +++ b/examples/languages/kotlin-android.yml @@ -0,0 +1,17 @@ +# Kotlin/Android CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: kotlin-android +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: kotlin-android-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + kotlin-android: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/kotlin-android-ci.yml@ + with: + lint_command: './gradlew ktlintCheck detekt' + build_command: './gradlew build' diff --git a/examples/languages/node.yml b/examples/languages/node.yml new file mode 100644 index 0000000..a5745e4 --- /dev/null +++ b/examples/languages/node.yml @@ -0,0 +1,18 @@ +# Node.js CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: node +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: node-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + node: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/node-ci.yml@ + with: + node_version: '22' + cache: npm + test_command: 'npm test' diff --git a/examples/languages/python.yml b/examples/languages/python.yml new file mode 100644 index 0000000..a6996f6 --- /dev/null +++ b/examples/languages/python.yml @@ -0,0 +1,19 @@ +# Python CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: python +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: python-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + python: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/python-ci.yml@ + with: + python_version: '3.13' + cache: pip + install_command: 'python -m pip install -e ".[test]"' + test_command: 'pytest' diff --git a/examples/languages/qt.yml b/examples/languages/qt.yml new file mode 100644 index 0000000..509c5d0 --- /dev/null +++ b/examples/languages/qt.yml @@ -0,0 +1,17 @@ +# Qt (C++) CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: qt +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: qt-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + qt: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/qt-ci.yml@ + with: + qt_version: '6.8.*' + qt_modules: 'qtcharts' diff --git a/examples/languages/r.yml b/examples/languages/r.yml new file mode 100644 index 0000000..d7bd323 --- /dev/null +++ b/examples/languages/r.yml @@ -0,0 +1,17 @@ +# R CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: r +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: r-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + r: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/r-ci.yml@ + with: + lint: true + test_command: "Rscript -e 'testthat::test_local()'" diff --git a/examples/languages/rust.yml b/examples/languages/rust.yml new file mode 100644 index 0000000..60927f1 --- /dev/null +++ b/examples/languages/rust.yml @@ -0,0 +1,18 @@ +# Rust CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: rust +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: rust-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + rust: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/rust-ci.yml@ + with: + components: 'clippy,rustfmt' + build_command: 'cargo build --locked --all-targets' + test_command: 'cargo test --locked' diff --git a/examples/languages/sql.yml b/examples/languages/sql.yml new file mode 100644 index 0000000..374d44f --- /dev/null +++ b/examples/languages/sql.yml @@ -0,0 +1,17 @@ +# SQL CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: sql +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: sql-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + sql: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/sql-ci.yml@ + with: + dialect: postgres + paths: 'migrations' diff --git a/examples/languages/swift.yml b/examples/languages/swift.yml new file mode 100644 index 0000000..5d2a6ae --- /dev/null +++ b/examples/languages/swift.yml @@ -0,0 +1,17 @@ +# Swift CI caller. Uses a macOS runner (billed at a 10x minute multiplier). +# Replace @ with a pinned full commit SHA. +name: swift +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: swift-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + swift: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/swift-ci.yml@ + with: + runner: macos-latest + swiftlint: true diff --git a/examples/languages/web.yml b/examples/languages/web.yml new file mode 100644 index 0000000..f286f4c --- /dev/null +++ b/examples/languages/web.yml @@ -0,0 +1,17 @@ +# HTML/CSS/web CI caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: web +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: web-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + web: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/web-ci.yml@ + with: + node_version: '22' + cache: npm diff --git a/examples/level3/ai-review.yml b/examples/level3/ai-review.yml new file mode 100644 index 0000000..76e58c8 --- /dev/null +++ b/examples/level3/ai-review.yml @@ -0,0 +1,30 @@ +# AI code review (Level 3, opt-in). Self-contained caller; replace @ with +# pinned full commit SHAs. This example uses the Claude Code Action. Same-shape +# alternatives: +# - CodeRabbit: install the GitHub App (free for public OSS); no workflow needed. +# - Qodo Merge / PR-Agent: uses: qodo-ai/pr-agent@ with your model key. +# AI review is advisory - keep human review as the merge gate. Store the model +# key as a secret; never inline it. +name: ai-review +on: + pull_request: + branches: [main] +permissions: {} +concurrency: + group: ai-review-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + review: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: anthropics/claude-code-action@ + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + # See the action docs for the current review-mode inputs (prompt / claude_args). diff --git a/examples/level3/release-please.yml b/examples/level3/release-please.yml new file mode 100644 index 0000000..04664e8 --- /dev/null +++ b/examples/level3/release-please.yml @@ -0,0 +1,24 @@ +# Automated releases with release-please (opt-in). Self-contained caller; replace +# @ with a pinned full commit SHA. This complements - does not replace - the +# tag-driven release-supply-chain workflow: release-please opens a release PR that +# bumps VERSION/CHANGELOG, and merging it can push the tag that triggers the +# attested release. Monorepo alternative: changesets (uses: changesets/action@). +name: release-please +on: + push: + branches: [main] +permissions: {} +concurrency: + group: release-please-${{ github.ref }} + cancel-in-progress: false +jobs: + release-please: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + steps: + - uses: googleapis/release-please-action@ + with: + release-type: simple diff --git a/examples/quality/coverage-gate.yml b/examples/quality/coverage-gate.yml new file mode 100644 index 0000000..6c6f74a --- /dev/null +++ b/examples/quality/coverage-gate.yml @@ -0,0 +1,19 @@ +# Coverage upload caller (any tier). Replace @ with a pinned full commit SHA. +# Public repos may upload tokenless; private repos need CODECOV_TOKEN. +name: coverage +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: coverage-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + coverage: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/coverage-gate.yml@ + with: + coverage_command: 'pip install -e ".[test]" && pytest --cov --cov-report=xml' + files: coverage.xml + secrets: + codecov_token: ${{ secrets.CODECOV_TOKEN }} diff --git a/examples/quality/docs-quality.yml b/examples/quality/docs-quality.yml new file mode 100644 index 0000000..17fef8b --- /dev/null +++ b/examples/quality/docs-quality.yml @@ -0,0 +1,14 @@ +# Docs quality caller (any tier). Replace @ with a pinned full commit SHA. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: docs-quality +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: docs-quality-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + docs-quality: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/docs-quality.yml@ diff --git a/examples/quality/pr-hygiene.yml b/examples/quality/pr-hygiene.yml new file mode 100644 index 0000000..c352499 --- /dev/null +++ b/examples/quality/pr-hygiene.yml @@ -0,0 +1,14 @@ +# PR-hygiene caller. Replace @ with a pinned full commit SHA. +# Defaults run commitlint + PR-title (contents: read). To enable labeler/stale, +# turn them on and widen the caller job permissions (pull-requests: write, and +# issues: write for stale). +name: pr-hygiene +on: + pull_request: + branches: [main] + types: [opened, edited, synchronize, reopened] +permissions: {} +jobs: + pr-hygiene: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/pr-hygiene.yml@ diff --git a/examples/security/grype-scan.yml b/examples/security/grype-scan.yml new file mode 100644 index 0000000..18b791d --- /dev/null +++ b/examples/security/grype-scan.yml @@ -0,0 +1,16 @@ +# Grype SCA caller (any tier — free on private-free). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: grype-scan +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: grype-scan-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + grype: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/grype-scan.yml@ + with: + severity_cutoff: high diff --git a/examples/security/hadolint.yml b/examples/security/hadolint.yml new file mode 100644 index 0000000..2fd7041 --- /dev/null +++ b/examples/security/hadolint.yml @@ -0,0 +1,16 @@ +# hadolint Dockerfile lint caller (any tier). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: hadolint +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: hadolint-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + hadolint: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/hadolint-ci.yml@ + with: + recursive: true diff --git a/examples/security/iac-scan.yml b/examples/security/iac-scan.yml new file mode 100644 index 0000000..507f237 --- /dev/null +++ b/examples/security/iac-scan.yml @@ -0,0 +1,16 @@ +# Checkov IaC scan caller (any tier). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: iac-scan +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: iac-scan-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + iac-scan: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/iac-scan.yml@ + with: + framework: terraform diff --git a/examples/security/osv-scan.yml b/examples/security/osv-scan.yml new file mode 100644 index 0000000..4281ab0 --- /dev/null +++ b/examples/security/osv-scan.yml @@ -0,0 +1,16 @@ +# OSV-Scanner SCA caller (any tier — free on private-free). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: osv-scan +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: osv-scan-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + osv-scan: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/osv-scan.yml@ + with: + scan_args: '--recursive .' diff --git a/examples/security/semgrep.yml b/examples/security/semgrep.yml new file mode 100644 index 0000000..12f7b85 --- /dev/null +++ b/examples/security/semgrep.yml @@ -0,0 +1,16 @@ +# Semgrep OSS SAST caller (any tier — free on private-free). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: semgrep +on: + push: { branches: [main] } + pull_request: { branches: [main] } +permissions: {} +concurrency: + group: semgrep-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + semgrep: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/semgrep-ci.yml@ + with: + config: p/ci diff --git a/examples/testing/benchmark.yml b/examples/testing/benchmark.yml new file mode 100644 index 0000000..8aa75b3 --- /dev/null +++ b/examples/testing/benchmark.yml @@ -0,0 +1,15 @@ +# Benchmark + regression alert caller. Needs contents: write for gh-pages history. +# Replace @ with a pinned full commit SHA. +name: benchmark +on: + push: { branches: [main] } +permissions: {} +jobs: + benchmark: + permissions: { contents: write } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/benchmark.yml@ + with: + language: python + tool: pytest + bench_command: 'pytest --benchmark-only --benchmark-json=output.json' + output_file_path: output.json diff --git a/examples/testing/fuzzing.yml b/examples/testing/fuzzing.yml new file mode 100644 index 0000000..507b141 --- /dev/null +++ b/examples/testing/fuzzing.yml @@ -0,0 +1,13 @@ +# Fuzzing caller (scheduled). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: fuzzing +on: + workflow_dispatch: + schedule: [{ cron: "0 4 * * *" }] +permissions: {} +jobs: + fuzz: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/fuzzing.yml@ + with: + fuzz_command: 'cargo fuzz run fuzz_target_1 -- -max_total_time=300' diff --git a/examples/testing/mutation-testing.yml b/examples/testing/mutation-testing.yml new file mode 100644 index 0000000..a20a6f6 --- /dev/null +++ b/examples/testing/mutation-testing.yml @@ -0,0 +1,15 @@ +# Mutation testing caller (any tier). Replace @. +# Private-free callers add `with: { enable_harden_runner: false }`. +name: mutation-testing +on: + workflow_dispatch: + schedule: [{ cron: "0 3 * * 1" }] +permissions: {} +jobs: + mutation: + permissions: { contents: read } + uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/mutation-testing.yml@ + with: + language: python + install_command: 'pip install mutmut' + mutation_command: 'mutmut run' diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index a7868c0..1827a05 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -8,7 +8,6 @@ from __future__ import annotations import argparse -import sys from pathlib import Path from typing import Any @@ -59,7 +58,7 @@ def capability_matrix(caps: list[dict[str, Any]]) -> str: example=example, ) ) - lines.extend(["", "---", "Last generated: 2026-07-04", ""]) + lines.extend(["", "---", "Last generated: 2026-07-08", ""]) return "\n".join(lines) @@ -86,7 +85,7 @@ def workflow_inventory(caps: list[dict[str, Any]]) -> str: ids = ", ".join(f"`{cap['id']}`" for cap in caps_for_workflow) or "MISSING" statuses = ", ".join(sorted({cap["status"] for cap in caps_for_workflow})) or "MISSING" lines.append(f"| `{rel}` | {ids} | {statuses} |") - lines.extend(["", "---", "Last generated: 2026-07-04", ""]) + lines.extend(["", "---", "Last generated: 2026-07-08", ""]) return "\n".join(lines)