Skip to content
AI CraftsmanSUPERPOWERS

CI Integration

Zero-drift CI integration for GitHub Actions, GitLab CI, Bitbucket Pipelines, and Jenkins.

Last updated: Edit on GitHub

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.