Integration Guide¶
Quick-start workflow files are in the README. Every input and default is in Workflows.
The scripts table lists the generators (generate-workflow.sh, generate-agents-md.sh, install-hooks.sh, generate-baseline.sh, generate-badges.sh). They are idempotent, so re-run them as you enable more checks. The steps below are the manual route.
C++ Setup¶
1. Docker image¶
All C++ checks and tests run inside your Docker dev container, in CI and locally. The image is the single source of truth. Every CI check must be reproducible by running the same script in the container.
The image needs:
- clang-tidy (14+ recommended)
- cppcheck (2.10+ recommended)
- clang-format, if
enable_clang_format: true - cmake and a build toolchain
- project dependencies (libraries, headers, ROS2 packages)
compile_commands.jsonatcompile_commands_path
The repo is mounted at source_mount (default /workspace/src). A non-root user in the image needs read access to that mount.
2. Copy configs¶
Copy what you need from configs/ into the repo root:
cp configs/.clang-tidy .clang-tidy
cp configs/.clang-format .clang-format
cp configs/cppcheck.suppress cppcheck.suppress
cp configs/.clang-tidy-naming .clang-tidy-naming
cp configs/naming-exceptions.txt naming-exceptions.txt
.clang-tidy is required. The rest are optional. The workflow uses your config when a config input points at it, and the tool defaults otherwise. Config inputs take paths relative to the repo root:
with:
clang_tidy_config: .clang-tidy
cppcheck_suppress: tools/cppcheck.suppress
clang_format_config: .clang-format
file_naming_exceptions: tools/naming-exceptions.txt
3. Enable checks one at a time¶
Start with the defaults (clang-tidy, cppcheck) and add checks as the codebase is ready:
jobs:
cpp:
uses: PavelGuzenfeld/standard/.github/workflows/cpp-quality.yml@main
with:
docker_image: ghcr.io/your-org/your-image:latest
cppcheck_include_file: cppcheck.include
cppcheck_suppress: cppcheck.suppress
enable_clang_format: true
enable_file_naming: true
ban_cout: true
ban_new: true
enforce_doctest: true
enable_flawfinder: true
enable_sarif: true
enable_hardening: true
permissions:
contents: read
pull-requests: write
security-events: write
security-events: write is needed for SARIF upload. The sanitizer, TSan, coverage and IWYU jobs each take an enable_* flag and a script input, listed in C++ quality.
4. SAST¶
CodeQL and Infer are templates: copy configs/ci-codeql.yml or configs/ci-infer.yml to .github/workflows/. CodeQL runs on push, PR and weekly. Infer runs on push to main.
5. Agent instructions¶
Edit the "Opt-in" section to match the checks you enabled.
6. Copilot code review¶
cp configs/.github/copilot-instructions.md .github/copilot-instructions.md
mkdir -p .github/instructions
cp configs/.github/instructions/cpp.instructions.md .github/instructions/
cp configs/.github/instructions/python.instructions.md .github/instructions/
The files use applyTo frontmatter, so each language file applies only to its own files. Drop the one you do not need. Turn on the reviewer under Settings > Copilot > Code review, or request @copilot on a PR. See GitHub's custom instructions guide.
7. Pre-commit hooks¶
Or by hand:
cp configs/.pre-commit-config.yaml .pre-commit-config.yaml
pip install pre-commit
pre-commit install
8. CMake presets and warnings¶
cp configs/CMakePresets-sanitizers.json CMakePresets.json
cp configs/cmake-warnings.cmake cmake/cmake-warnings.cmake
Presets and flags are described in SDLC.
9. Fuzzing¶
The template calls the reusable fuzz.yml, which runs ClusterFuzzLite. Add a .clusterfuzzlite/ directory with a Dockerfile and build.sh that build your harnesses; without the Dockerfile the job is skipped with a notice. Harness:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
my_parser(data, size);
return 0;
}
Python Setup¶
jobs:
python:
uses: PavelGuzenfeld/standard/.github/workflows/python-quality.yml@main
with:
python_linter: ruff
fail_under: 80
permissions:
contents: read
pull-requests: write
sast:
uses: PavelGuzenfeld/standard/.github/workflows/sast-python.yml@main
with:
enable_codeql: true
permissions:
contents: read
pull-requests: write
security-events: write
Use python_linter: flake8 for ROS2/ament. fail_under defaults to 100, meaning zero violations on changed lines. ruff_select overrides select and ignore in pyproject.toml. Extra Semgrep rule sets go in semgrep_rules, for example 'p/python p/owasp-top-ten p/django'.
With agent-sdlc¶
agent-sdlc gates the coding agent's session and each commit: rules, a diff-scoped mutation gate, commit-message and scope checks. standard gates the PR. This repo runs both.
Do the standard setup first, then add agent-sdlc. cp configs/AGENTS.md and cp configs/.pre-commit-config.yaml overwrite the file, so running them after mutation-gate rules sync drops the rules block, and rules-check fails until you sync again.
standard-ci init --preset recommended
cp configs/AGENTS.md AGENTS.md
cp configs/.pre-commit-config.yaml .pre-commit-config.yaml
# then add the agent-sdlc repo: entry to .pre-commit-config.yaml
touch .mutation-gate.toml
mutation-gate rules sync
pre-commit install
pre-commit install --hook-type commit-msg
With an mkdocs docs/ folder, allowlist it in .mutation-gate.toml, or no-new-docs blocks every new page:
The reusable pre-commit.yml doesn't enforce agent-sdlc's diff checks. Under pre-commit run --from-ref, mutation-gate and no-new-docs read an empty index and pass, and the commit-msg hooks don't run. Run them with --range in their own job, as in agent-sdlc's usage guide. The mutation gate itself needs the project's test environment and has no CI mode.
Customization¶
cppcheck include file¶
cppcheck.include lists one include directory per line. Blank lines are ignored.
Exclusion file¶
.standards-exclude lists path prefixes to skip, one per line, for vendored code, submodules and build output. All checks respect it.
File naming exceptions¶
One regex per line, matched against path segments.
Package naming¶
C++ packages follow include/<package_name>/ with a snake_case name. enable_file_naming: true checks every path segment. include/nav_utils/ passes. include/NavUtils/ and include/flightController/ fail.
ROS2 and colcon¶
If the image already has compile_commands.json:
with:
docker_image: ghcr.io/your-org/ros2-dev:humble
source_setup: 'source /opt/ros/humble/install/setup.bash'
compile_commands_path: build/your_package
cppcheck_include_file: cppcheck.include
runner: '"self-hosted"'
If CI must build it, use a pre-analysis script and a build cache:
with:
docker_image: ghcr.io/your-org/ros2-dev:humble
source_setup: 'source /opt/ros/humble/setup.bash'
compile_commands_path: build
pre_analysis_script: .github/scripts/pre-analysis.sh
build_cache_key: clang-tidy-build-${{ hashFiles('**/CMakeLists.txt', '**/package.xml') }}
runner: '"self-hosted"'
.github/scripts/pre-analysis.sh:
#!/usr/bin/env bash
set -euo pipefail
colcon build \
--cmake-args -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
--event-handlers console_cohesion+
python3 -c "
import json, glob
merged, seen = [], set()
for f in sorted(glob.glob('build/*/compile_commands.json')):
for entry in json.load(open(f)):
key = entry.get('file', '')
if key not in seen:
seen.add(key)
merged.append(entry)
json.dump(merged, open('build/compile_commands.json', 'w'), indent=2)
"
On a cache hit only changed packages rebuild.
Self-hosted runners¶
runner takes a JSON string or array on every workflow: runner: '"self-hosted"'.
Troubleshooting¶
| Symptom | Fix |
|---|---|
| "No C++ files changed" | Default extensions are cpp hpp h cc cxx. Set file_extensions: 'cpp hpp h cc cxx c' to add more. |
| clang-tidy: "compile_commands.json not found" | compile_commands_path is a directory inside the container, relative to source_mount. For /workspace/src/build/my_pkg/compile_commands.json use build/my_pkg. |
| cppcheck: "file not found" for system headers | Set cppcheck_includes or cppcheck_include_file. |
| PR comments missing | Add pull-requests: write. SARIF upload also needs security-events: write. |
| diff-quality too strict | Lower fail_under (default 100). 80 allows a 20% violation rate on changed lines. |
| Hardening: "No ELF binaries found" | Set hardening_binary_paths (default build-hardened/bin/*), for example 'build-hardened/bin/* build-hardened/lib/*.so'. A custom hardening_script must write binaries to those paths. |
Auto-Release¶
auto-release.yml bumps the version on every push to main from conventional commit prefixes. It creates an annotated git tag and a GitHub Release with generated notes, and optionally a SLSA provenance attestation. No commit is made to main, so there is no loop. The git tag is the version of record.
.github/workflows/release.yml:
name: Release
on:
push:
branches: [main]
permissions:
contents: write
id-token: write
attestations: write
jobs:
release:
uses: PavelGuzenfeld/standard/.github/workflows/auto-release.yml@main
with:
enable_provenance: true
enable_provenance defaults to false. When true it attests each release with actions/attest-build-provenance. default_bump (default patch) applies when no prefix matches. Optional secrets app_id and app_private_key (GitHub App credentials): when both are set, release events trigger downstream workflows.
The workflow finds the latest tag with git tag -l 'v*' --sort=-v:refname, scans commits since it, and takes the highest bump. With no v* tag the first release is v0.0.1.
| Prefix | Example | Bump |
|---|---|---|
feat!: or BREAKING CHANGE: |
feat!: redesign API |
major |
feat: or feat(scope): |
feat(auth): add OAuth |
minor |
| Anything else | fix: null pointer |
patch |
Security Hygiene¶
These steps raise your OpenSSF Scorecard score.
cp configs/SECURITY.md SECURITY.md
mkdir -p .github
cp configs/dependabot.yml .github/dependabot.yml
Fill the TODO placeholders in SECURITY.md with a contact and response times. The Dependabot template watches github-actions. Uncomment pip, npm, cargo or docker as needed. For Renovate, or for Conan dependencies, run scripts/generate-workflow.sh --dependency-bot renovate (or both) to write .github/renovate.json.
Add these to your infra-lint workflow call:
with:
enable_dangerous_workflows: true
enable_binary_artifacts: true
enable_gitleaks: true
gitleaks_config: .gitleaks.toml
- Dangerous workflows: flags
pull_request_targetmisuse and injection such as${{ github.event.pull_request.title }}inrun:steps. Locally:./scripts/check-dangerous-workflows.sh .github/workflows/. - Binary artifacts: flags committed
.exe,.dll,.so,.jar,.pyc,.whland similar. - Gitleaks: scans only the commits in the PR range. Add allowlists in
.gitleaks.toml:
Allstar¶
Allstar enforces GitHub platform settings (branch protection, collaborator access) from config files. This repo's workflows check code. The two complement each other. Install the Allstar app first.
Per repo:
| Policy | Enforces |
|---|---|
branch_protection.yaml |
PR approvals, no force push, dismiss stale reviews |
security.yaml |
SECURITY.md exists |
binary_artifacts.yaml |
No committed binaries |
dangerous_workflow.yaml |
No pull_request_target injection |
outside.yaml |
No admin access for outside collaborators |
actions.yaml |
Required or denied GitHub Actions |
Org-wide: create a repo named .allstar in the org and copy configs/.allstar/*.yaml into it. In allstar.yaml, comment out optIn and set:
In branch_protection.yaml, set action: fix to configure branch protection instead of only opening issues.
Trend Dashboard¶
trend-dashboard.yml queries the GitHub Actions API for the last lookback_days days of runs of the standard workflows it finds in the repo. It buckets job results by week and writes a table of pass rates with trend arrows to the workflow summary. Inputs are in Trend dashboard.
.github/workflows/trends.yml:
name: Trend Dashboard
on:
schedule:
- cron: '0 9 * * 1'
workflow_dispatch:
jobs:
trends:
uses: PavelGuzenfeld/standard/.github/workflows/trend-dashboard.yml@main
with:
slack_webhook_url: ${{ secrets.SLACK_TRENDS_WEBHOOK }}
post_to_discussions: true
permissions:
actions: read
contents: read
discussions: write
Drop slack_webhook_url to skip Slack. Drop post_to_discussions and discussions: write to skip Discussions.