CI Integration
Zero-drift CI integration for GitHub Actions, GitLab CI, Bitbucket Pipelines, and Jenkins.
Zero-Drift Principle
The core promise: the same rules that run in your Claude Code hooks run in CI. No divergence. No “works on my machine” validation failures.
The CI adapter system reads your .craft-config.yml and applies the same rule severity configuration that your hooks use locally.
Generate CI Config
/craftsman:ci export
This detects your provider from .craft-config.yml and generates the workflow file.
GitHub Actions
Generated at .github/workflows/craftsman-quality-gate.yml:
name: Craftsman Quality Gate
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
quality-gate:
name: Craftsman Quality Gate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
if: ${{ env.CRAFTSMAN_PACK_SYMFONY == 'true' }}
uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
tools: phpstan
- name: Setup Node
if: ${{ env.CRAFTSMAN_PACK_REACT == 'true' }}
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- name: Install dependencies
run: |
[ -f composer.json ] && composer install --no-dev || true
[ -f package.json ] && npm ci || true
- name: Run Craftsman Quality Gate
run: bash ci/craftsman-ci.sh
env:
CRAFTSMAN_CONFIG: ".craft-config.yml"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Annotate PR with violations
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const violations = JSON.parse(process.env.CRAFTSMAN_VIOLATIONS || '[]');
for (const v of violations) {
github.rest.pulls.createReviewComment({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
body: `**Craftsman:** ${v.message} (\`${v.rule}\`)`,
path: v.file,
line: v.line,
});
}
PR annotations
The GitHub adapter annotates Pull Requests directly with inline comments on the violated lines. No need to search through CI logs.
GitLab CI
Generated at .gitlab-ci.craftsman.yml:
craftsman-quality-gate:
stage: test
image: php:8.4-cli
before_script:
- apt-get update -qq && apt-get install -y -qq git curl jq sqlite3
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install --no-dev 2>/dev/null || true
script:
- bash ci/craftsman-ci.sh
variables:
CRAFTSMAN_CONFIG: ".craft-config.yml"
CRAFTSMAN_PROVIDER: "gitlab"
artifacts:
reports:
junit: craftsman-report.xml
when: always
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Post violations as MR comments
after_script:
- |
if [ -f craftsman-violations.json ] && [ "$CI_MERGE_REQUEST_IID" != "" ]; then
bash ci/adapters/gitlab.sh annotate
fi
Bitbucket Pipelines
Generated at bitbucket-pipelines.yml (or appended to existing):
pipelines:
default:
- step:
name: Craftsman Quality Gate
image: php:8.4-cli
caches:
- composer
- node
script:
- apt-get update -qq && apt-get install -y -qq git curl jq sqlite3
- bash ci/craftsman-ci.sh
after-script:
- |
if [ "$BITBUCKET_PR_ID" != "" ] && [ $BITBUCKET_EXIT_CODE -ne 0 ]; then
bash ci/adapters/bitbucket.sh annotate
fi
Jenkins
Generated as a Jenkinsfile stage to add to your pipeline:
stage('Craftsman Quality Gate') {
agent {
docker {
image 'php:8.4-cli'
}
}
environment {
CRAFTSMAN_CONFIG = '.craft-config.yml'
CRAFTSMAN_PROVIDER = 'jenkins'
}
steps {
sh 'apt-get update -qq && apt-get install -y -qq git curl jq sqlite3'
sh 'bash ci/craftsman-ci.sh'
}
post {
failure {
script {
def violations = readJSON file: 'craftsman-violations.json'
violations.each { v ->
// Add to build description
currentBuild.description = "${currentBuild.description ?: ''}\n${v.rule}: ${v.file}:${v.line}"
}
}
}
}
}
CI Adapter Architecture
The adapter pattern enables zero-drift across providers:
craftsman-ci.sh (orchestrator)
↓
Detect provider (CRAFTSMAN_PROVIDER or auto-detect)
↓
Load .craft-config.yml rules
↓
Run validations (same as local hooks)
├── Level 1: Regex rules
├── Level 2: PHPStan / ESLint
└── Level 3: deptrac / dependency-cruiser
↓
adapters/github.sh → Annotate PR, set check status
adapters/gitlab.sh → Post MR comments, set pipeline status
adapters/bitbucket.sh → Post PR comments
adapters/generic.sh → Exit code only (for Jenkins, etc.)
Check CI Status
/craftsman:ci status
Shows the last CI run for the current branch, including:
- Overall pass/fail
- Violation count by level
- Link to CI run
Rules must match
If your project .craft-config.yml overrides global rules, commit it to your repository so CI uses the same configuration. Never rely on ~/.claude/.craft-config.yml in CI: it’s a local file.