Each snippet is one tool, one category. Follow these four steps:
1. Write the bash script — scripts/shell/<category>/<tool>.sh
#!/usr/bin/env bash
set -euo pipefail
ERROR_FOUND=0
# Validate config exists
if [ ! -f ".toolrc" ]; then
echo "::error::Config file .toolrc not found"
exit 1
fi
# Collect files
mapfile -t files < <(find . -type f -name "*.ext" \
-not -path "./_site/*" \
-not -path "./node_modules/*" \
-not -path "./.git/*")
# Graceful skip
if [ ${#files[@]} -eq 0 ]; then
echo "⚠️ No files found. Skipping."
exit 0
fi
# Check all files, accumulate errors
for file in "${files[@]}"; do
echo "ℹ️ Checking ${file#./}..."
tool "$file" || ERROR_FOUND=1
done
if [[ $ERROR_FOUND -eq 0 ]]; then
echo "✅ All files passed!"
else
echo "❌ Issues found!"
exit 1
fi2. Write BATS tests — tests/<category>/<tool>.bats
Cover: missing config, no files found, all pass, one fails.
See existing tests in tests/linters/ for examples.
3. Create the source YAML — scripts/CI/<category>/<tool>.yml
<tool>:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install <tool>
run: ...
- name: Run <tool>
run: bash scripts/shell/<category>/<tool>.sh4. Assemble and verify — generate the output file and check it's committed:
bash scripts/assemble-ci.sh scripts/CI/<category> scripts/shell/<category> CI/<category>
git diff CI/ # must be emptySee rules/process/ci-cd.md for the full reference. The short version:
runs-on: ubuntu-latestalwaysactions/checkout@v4must be the first step- Actions pinned to major version tag (
@v4,@v2) — never@latest - Config validated with
::error::before the tool runs - No files →
⚠️ ... Skipping.+exit 0 - Multi-file checks use the
ERROR_FOUND=0accumulator (neverset -eas a substitute) - Node.js:
npm ci, nevernpm install - PHP:
shivammathur/setup-php@v2+actions/cache@v4for Composer - Output emoji:
✅pass ·❌fail ·⚠️skip ·ℹ️per-file progress
# Install bats-core (macOS)
brew install bats-core
# Run all tests
bats --recursive tests/- Script is in
scripts/shell/<category>/ - Source YAML is in
scripts/CI/<category>/ - Assembled output is committed to
CI/<category>/ -
git diff CI/is clean after running the assembler - BATS tests added and passing
- All mandatory rules from
rules/process/ci-cd.mdsatisfied