Integracion CI/CD

Guia completa para integrar intake en pipelines de integracion continua. Incluye ejemplos para GitHub Actions, GitLab CI, Jenkins y Azure DevOps.


Estrategia general

Que ejecutar en CI

AccionCuandoComando
Verificar specEn cada PR / pushintake verify specs/<spec>/ -p . -f junit
Exportar specAl mergear a mainintake export specs/<spec>/ -f <formato> -o .
Detectar driftProgramado (semanal)Comparar hashes de spec.lock.yaml con fuentes
Doctor checkProgramado / manualintake doctor
FeedbackCuando verify fallaintake feedback specs/<spec>/ -p .

Exit codes

CodigoSignificadoAccion CI
0Todos los checks requeridos pasaronJob pasa
1Al menos un check requerido falloJob falla
2Error de ejecucionJob falla (investigar)

GitHub Actions

Verificacion en PR

name: Verify Spec Compliance
on: [push, pull_request]

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-intake

      - name: Install intake
        run: pip install intake-ai-cli

      - name: Run acceptance checks
        run: intake verify specs/mi-feature/ -p . -f junit > test-results.xml

      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: spec-verification
          path: test-results.xml

      - name: Publish test results
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Spec Compliance
          path: test-results.xml
          reporter: java-junit

Workflow completo: verificar + exportar

name: Spec Pipeline
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install intake-ai-cli
      - run: intake verify specs/mi-feature/ -p . -f junit > test-results.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results
          path: test-results.xml

  export:
    needs: verify
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install intake-ai-cli
      - run: intake export specs/mi-feature/ -f claude-code -o .
      - uses: actions/upload-artifact@v4
        with:
          name: exported-spec
          path: |
            CLAUDE.md
            .intake/

Deteccion de spec drift (scheduled)

name: Spec Drift Detection
on:
  schedule:
    - cron: "0 9 * * 1"  # Lunes a las 9am

jobs:
  check-drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install intake-ai-cli

      - name: Check for stale specs
        id: drift
        run: |
          OUTPUT=$(intake show specs/mi-feature/ 2>&1)
          if echo "$OUTPUT" | grep -qi "stale\|changed"; then
            echo "drift=true" >> $GITHUB_OUTPUT
          else
            echo "drift=false" >> $GITHUB_OUTPUT
          fi

      - name: Create issue if drift detected
        if: steps.drift.outputs.drift == 'true'
        run: |
          gh issue create \
            --title "Spec drift detected: mi-feature" \
            --body "Las fuentes de la spec han cambiado. Regenerar con: \`intake add specs/mi-feature/ --regenerate\`" \
            --label "spec-drift"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Multiples specs en paralelo

jobs:
  verify:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        spec: [api-auth, api-payments, frontend-dashboard]
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install intake-ai-cli
      - run: intake verify specs/${{ matrix.spec }}/ -p . -f junit > results-${{ matrix.spec }}.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: results-${{ matrix.spec }}
          path: results-${{ matrix.spec }}.xml

GitLab CI

Pipeline basico

# .gitlab-ci.yml
stages:
  - verify

verify-spec:
  stage: verify
  image: python:3.12-slim
  before_script:
    - pip install intake-ai-cli
  script:
    - intake verify specs/mi-feature/ -p . -f junit > test-results.xml
  artifacts:
    when: always
    reports:
      junit: test-results.xml

GitLab muestra automaticamente los resultados JUnit en el widget de Merge Request.

Pipeline completo con cache

stages:
  - verify
  - export

.intake-base:
  image: python:3.12-slim
  cache:
    key: intake-pip
    paths:
      - .cache/pip
  variables:
    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
  before_script:
    - pip install intake-ai-cli

verify-spec:
  extends: .intake-base
  stage: verify
  script:
    - intake verify specs/mi-feature/ -p . -f junit > test-results.xml
  artifacts:
    when: always
    reports:
      junit: test-results.xml

export-spec:
  extends: .intake-base
  stage: export
  only:
    - main
  script:
    - intake export specs/mi-feature/ -f claude-code -o .
  artifacts:
    paths:
      - CLAUDE.md
      - .intake/

Jenkins

Jenkinsfile declarativo

pipeline {
    agent {
        docker {
            image 'python:3.12-slim'
        }
    }

    environment {
        ANTHROPIC_API_KEY = credentials('anthropic-api-key')
    }

    stages {
        stage('Install') {
            steps {
                sh 'pip install intake-ai-cli'
            }
        }

        stage('Verify') {
            steps {
                sh 'intake verify specs/mi-feature/ -p . -f junit > test-results.xml'
            }
            post {
                always {
                    junit 'test-results.xml'
                }
            }
        }

        stage('Export') {
            when {
                branch 'main'
            }
            steps {
                sh 'intake export specs/mi-feature/ -f claude-code -o .'
                archiveArtifacts artifacts: 'CLAUDE.md,.intake/**'
            }
        }
    }
}

Jenkinsfile con multiples specs

pipeline {
    agent any
    stages {
        stage('Verify All Specs') {
            parallel {
                stage('API Auth') {
                    steps {
                        sh 'intake verify specs/api-auth/ -p . -f junit > results-auth.xml'
                    }
                }
                stage('API Payments') {
                    steps {
                        sh 'intake verify specs/api-payments/ -p . -f junit > results-payments.xml'
                    }
                }
            }
            post {
                always {
                    junit 'results-*.xml'
                }
            }
        }
    }
}

Azure DevOps

azure-pipelines.yml

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: UsePythonVersion@0
    inputs:
      versionSpec: '3.12'

  - script: pip install intake-ai-cli
    displayName: 'Install intake'

  - script: intake verify specs/mi-feature/ -p . -f junit > test-results.xml
    displayName: 'Verify spec compliance'

  - task: PublishTestResults@2
    condition: always()
    inputs:
      testResultsFormat: 'JUnit'
      testResultsFiles: 'test-results.xml'
      testRunTitle: 'Spec Compliance'

Formatos de reporte para CI

JUnit XML

El formato mas compatible con CI. Todos los sistemas lo soportan:

intake verify specs/mi-feature/ -p . -f junit > test-results.xml
PlataformaComo se consume
GitHub Actionsdorny/test-reporter action o actions/upload-artifact
GitLab CIartifacts.reports.junit (aparece en MR widget)
Jenkinsjunit post step (aparece en dashboard)
Azure DevOpsPublishTestResults@2 task

JSON

Para integraciones personalizadas:

intake verify specs/mi-feature/ -p . -f json > results.json

Extraer metricas con jq:

# Total de checks
jq '.total_checks' results.json

# Solo nombres de checks fallidos
jq '.results[] | select(.passed == false) | .name' results.json

# Resumen: passed/failed
jq '{passed: .passed, failed: .failed, all_ok: .all_required_passed}' results.json

Pre-commit hooks

Ver Despliegue > Pre-commit hooks para configuracion detallada.

Resumen rapido:

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: intake-verify
        name: Verify spec compliance
        entry: intake verify specs/mi-feature/ -p . --fail-fast -t structure
        language: system
        pass_filenames: false
        files: ^src/

Deteccion de spec drift

Que es

“Spec drift” ocurre cuando las fuentes de requisitos cambian pero la spec no se regenera. El spec.lock.yaml almacena hashes SHA-256 de las fuentes originales, permitiendo detectar cambios.

Cuando verificar

  • Semanal (cron job): detectar drift por fuentes que cambian sin regenerar
  • En PR: si un PR modifica una fuente listada en spec.lock.yaml, alertar que la spec puede estar desactualizada

Implementacion

El patron general:

# 1. intake show detecta staleness automaticamente
intake show specs/mi-feature/

# 2. Si las fuentes cambiaron, regenerar
intake add specs/mi-feature/ --regenerate

# 3. Revisar cambios
intake diff specs/mi-feature-old/ specs/mi-feature/

Ver los ejemplos de GitHub Actions y GitLab CI arriba para implementaciones en CI.


Notificaciones

Slack (webhook)

# GitHub Actions: notificar en Slack si verify falla
- name: Notify Slack on failure
  if: failure()
  run: |
    curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
      -H "Content-Type: application/json" \
      -d '{
        "text": "Spec verification failed for mi-feature",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Spec Verification Failed*\nSpec: `mi-feature`\nBranch: `${{ github.ref_name }}`\nPR: ${{ github.event.pull_request.html_url }}"
            }
          }
        ]
      }'

Microsoft Teams (webhook)

- name: Notify Teams on failure
  if: failure()
  run: |
    curl -X POST "${{ secrets.TEAMS_WEBHOOK }}" \
      -H "Content-Type: application/json" \
      -d '{
        "title": "Spec Verification Failed",
        "text": "La spec mi-feature no paso los checks de aceptacion en la branch ${{ github.ref_name }}"
      }'

Tips

Cache de dependencias

Cachear pip reduce ~30 segundos por ejecucion:

# GitHub Actions
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-intake-${{ hashFiles('**/requirements*.txt') }}

# GitLab CI
cache:
  key: intake-pip
  paths:
    - .cache/pip

verify.sh sin instalar intake

El exporter generic genera un verify.sh standalone. En CI donde no quieres instalar intake:

# Una vez: generar verify.sh y commitearlo
intake export specs/mi-feature/ -f generic -o output/
git add output/verify.sh
git commit -m "Add standalone verify script"

# En CI: ejecutar directamente (sin intake)
chmod +x output/verify.sh
./output/verify.sh .

Tradeoff: menos flexible que intake verify (sin tags, sin JUnit), pero zero dependencias.

Verificacion solo de estructura

Para checks rapidos en pre-commit, ejecutar solo checks de estructura (sin tests):

intake verify specs/mi-feature/ -p . -t structure --fail-fast