Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
07 Sep 2026 · 23 mnt baca ·Artikel 39 / 208
Go

Spec Kit GitHub Actions Golang: CI Pipeline Validasi Spec Otomatis

Cara mengintegrasikan GitHub Spec Kit ke GitHub Actions untuk CI pipeline validasi spec Golang. Three-stage pipeline, cost control, matrix testing, dan PR automation.

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

Mengintegrasikan Spec Kit ke GitHub Actions mengubah validasi spec dari ritual manual yang mudah dilupakan menjadi CI pipeline otomatis yang berjalan di setiap PR. Alih-alih mengandalkan reviewer untuk mengingat mengecek AC compliance satu per satu, kita serahkan pekerjaan repetitif itu ke mesin — dan menyisakan waktu reviewer untuk keputusan yang benar-benar butuh penilaian manusia.

Di artikel ini kita bangun three-stage CI pipeline untuk project Golang: spec check statis tanpa biaya API, LLM audit untuk AC compliance, dan quality gate standar Go. Kita juga bahas cost control, matrix testing untuk monorepo, PR automation, dan troubleshooting yang sering muncul saat specify dijalankan di runner CI.


19.1 Three-Stage CI Pipeline dengan Spec Kit

Sebelum menyentuh YAML, penting memahami kenapa validasi dipecah menjadi tiga tahap dengan urutan spesifik. Diagram berikut memetakan ketiga stage beserta durasi dan biayanya, dari yang paling murah ke yang paling komprehensif.

text
 1Stage 1: Spec Check (Fast, ~2 menit)
 2  - Apakah spec file ada untuk feature branch?
 3  - Apakah spec syntax valid?
 4  - Apakah semua ACs memiliki format yang benar?
 5  - Tanpa LLM → tidak ada biaya API
 6
 7Stage 2: Spec Audit (LLM-powered, ~5 menit)
 8  - specify audit --feature [name]
 9  - AC compliance check dengan AI analysis
10  - Architecture pattern check
11  - Score threshold enforcement
12
13Stage 3: Quality Gate (Comprehensive, ~10 menit)
14  - go test -race ./...
15  - golangci-lint run ./...
16  - Coverage check ≥ 85%
17  - Build verification
18
19Total runtime: ~17 menit — acceptable untuk developer workflow

Urutan ini bukan kebetulan: stage yang murah dan cepat berjalan lebih dulu supaya kegagalan obvious tertangkap tanpa membakar kuota LLM. Baru setelah spec lolos check statis, kita “membayar” untuk audit AI yang lebih dalam.


19.2 Complete Workflow: spec-ci.yml

Berikut workflow lengkap yang mengimplementasikan ketiga stage tersebut sebagai job yang saling bergantung. Perhatikan bagaimana needs dan if mengatur agar stage berikutnya hanya berjalan ketika stage sebelumnya relevan.

yaml
  1# .github/workflows/spec-ci.yml
  2name: Spec CI Pipeline
  3
  4on:
  5  pull_request:
  6    branches: [main, develop]
  7  push:
  8    branches: [main]
  9
 10env:
 11  GO_VERSION: '1.22'
 12  NODE_VERSION: '20'
 13  MIN_AUDIT_SCORE: '85'
 14
 15jobs:
 16  # ─── Stage 1: Spec Check ──────────────────
 17  spec-check:
 18    name: "Stage 1: Spec File Check"
 19    runs-on: ubuntu-latest
 20    outputs:
 21      has_spec: ${{ steps.check.outputs.has_spec }}
 22      feature: ${{ steps.extract.outputs.feature }}
 23
 24    steps:
 25      - uses: actions/checkout@v4
 26
 27      - name: Extract feature name
 28        id: extract
 29        run: |
 30          BRANCH="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}"
 31          if [[ "$BRANCH" == feature/* ]]; then
 32            FEATURE=$(echo "$BRANCH" | sed 's/feature\/[A-Z0-9-]*-//')
 33            echo "feature=$FEATURE" >> $GITHUB_OUTPUT
 34          else
 35            echo "feature=" >> $GITHUB_OUTPUT
 36          fi
 37
 38      - name: Check spec exists
 39        id: check
 40        run: |
 41          FEATURE="${{ steps.extract.outputs.feature }}"
 42          if [ -n "$FEATURE" ] && [ -d ".specify/features/$FEATURE" ]; then
 43            echo "has_spec=true" >> $GITHUB_OUTPUT
 44            echo "✅ Spec found: .specify/features/$FEATURE"
 45
 46            # Additional static checks (no API needed)
 47            SPEC=".specify/features/$FEATURE/spec.md"
 48
 49            # Check required sections
 50            for section in "Acceptance Criteria" "Edge Cases" "Out of Scope"; do
 51              if ! grep -q "$section" "$SPEC"; then
 52                echo "❌ Missing section: $section"
 53                exit 1
 54              fi
 55            done
 56
 57            # Check minimum ACs
 58            AC_COUNT=$(grep -c "^- AC" "$SPEC" || echo "0")
 59            if [ "$AC_COUNT" -lt "3" ]; then
 60              echo "❌ Too few ACs: $AC_COUNT (minimum: 3)"
 61              exit 1
 62            fi
 63
 64            echo "✅ Spec has $AC_COUNT ACs, all required sections present"
 65          else
 66            echo "has_spec=false" >> $GITHUB_OUTPUT
 67            if [[ "${{ github.head_ref }}" == feature/* ]]; then
 68              echo "⚠️  Feature branch without spec — consider adding spec"
 69            fi
 70          fi
 71
 72      - name: Validate spec YAML/Markdown syntax
 73        if: steps.check.outputs.has_spec == 'true'
 74        run: |
 75          # Install markdownlint for basic syntax check
 76          npm install -g markdownlint-cli
 77          FEATURE="${{ steps.extract.outputs.feature }}"
 78          markdownlint ".specify/features/$FEATURE/spec.md" \
 79            --config .markdownlintrc 2>/dev/null || \
 80            echo "⚠️ Minor markdown issues found (non-blocking)"
 81
 82  # ─── Stage 2: Spec Audit ──────────────────
 83  spec-audit:
 84    name: "Stage 2: LLM Spec Audit"
 85    needs: spec-check
 86    if: needs.spec-check.outputs.has_spec == 'true'
 87    runs-on: ubuntu-latest
 88
 89    steps:
 90      - uses: actions/checkout@v4
 91
 92      - name: Setup Node.js
 93        uses: actions/setup-node@v4
 94        with:
 95          node-version: ${{ env.NODE_VERSION }}
 96
 97      - name: Setup Go
 98        uses: actions/setup-go@v5
 99        with:
100          go-version: ${{ env.GO_VERSION }}
101          cache: true
102
103      - name: Install Spec Kit
104        run: npm install -g @github/spec-kit
105
106      - name: Run spec audit
107        id: audit
108        env:
109          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
110        run: |
111          FEATURE="${{ needs.spec-check.outputs.feature }}"
112
113          # Run audit dan capture output
114          specify audit --feature "$FEATURE" --output github-summary
115
116          # Get numerical score
117          SCORE=$(specify audit --feature "$FEATURE" --score --format number)
118          echo "score=$SCORE" >> $GITHUB_OUTPUT
119          echo "feature=$FEATURE" >> $GITHUB_OUTPUT
120
121          echo "## Spec Audit Results" >> $GITHUB_STEP_SUMMARY
122          echo "Score: **$SCORE/100**" >> $GITHUB_STEP_SUMMARY
123
124          if [ "$SCORE" -lt "$MIN_AUDIT_SCORE" ]; then
125            echo "❌ Score $SCORE < minimum $MIN_AUDIT_SCORE"
126            exit 1
127          fi
128
129          echo "✅ Spec audit passed: $SCORE/100"
130
131      - name: Post audit comment to PR
132        if: always() && github.event_name == 'pull_request'
133        uses: actions/github-script@v7
134        with:
135          script: |
136            const score = '${{ steps.audit.outputs.score }}' || 'N/A';
137            const feature = '${{ steps.audit.outputs.feature }}';
138            const minScore = '${{ env.MIN_AUDIT_SCORE }}';
139            const passed = parseInt(score) >= parseInt(minScore);
140            const icon = passed ? '✅' : '❌';
141
142            const body = [
143              '## 🔍 Spec Audit Report',
144              '',
145              `${icon} **Spec Audit Score: ${score}/100** (minimum: ${minScore})`,
146              `Feature: \`${feature}\``,
147              '',
148              passed
149                ? `All ACs verified. Code complies with specification.`
150                : `Score below threshold. Fix spec compliance issues before merging.`,
151              '',
152              `*Run locally: \`specify audit --feature ${feature}\` for details.*`
153            ].join('\n');
154
155            await github.rest.issues.createComment({
156              issue_number: context.issue.number,
157              owner: context.repo.owner,
158              repo: context.repo.repo,
159              body
160            });
161
162  # ─── Stage 3: Quality Gate ────────────────
163  quality-gate:
164    name: "Stage 3: Quality Gate"
165    needs: [spec-check, spec-audit]
166    if: always() && needs.spec-check.result == 'success'
167    runs-on: ubuntu-latest
168
169    steps:
170      - uses: actions/checkout@v4
171
172      - uses: actions/setup-go@v5
173        with:
174          go-version: ${{ env.GO_VERSION }}
175          cache: true
176
177      - name: Build
178        run: go build ./...
179
180      - name: Test with race detector
181        run: go test -race -timeout 5m ./...
182
183      - name: Coverage check
184        run: |
185          go test -coverprofile=coverage.out ./...
186          COVERAGE=$(go tool cover -func=coverage.out | \
187            grep total | awk '{print $3}' | tr -d '%')
188          echo "Coverage: ${COVERAGE}%"
189          echo "coverage=${COVERAGE}" >> $GITHUB_OUTPUT
190
191          if (( $(echo "$COVERAGE < 85" | bc -l) )); then
192            echo "❌ Coverage ${COVERAGE}% < 85% minimum"
193            exit 1
194          fi
195          echo "✅ Coverage: ${COVERAGE}%"
196
197      - name: Lint
198        uses: golangci/golangci-lint-action@v6
199        with:
200          version: latest
201          args: --timeout=5m
202
203      - name: Summary
204        if: always()
205        run: |
206          echo "## Quality Gate Summary" >> $GITHUB_STEP_SUMMARY
207          echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
208          echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
209          echo "| Build | ✅ |" >> $GITHUB_STEP_SUMMARY
210          echo "| Tests (race) | ✅ |" >> $GITHUB_STEP_SUMMARY
211          echo "| Coverage | ✅ |" >> $GITHUB_STEP_SUMMARY
212          echo "| Lint | ✅ |" >> $GITHUB_STEP_SUMMARY

Kunci dari workflow ini ada pada outputs di spec-check yang diteruskan lewat needs — feature name diekstrak sekali dari nama branch, lalu dipakai ulang di stage audit tanpa perlu logika duplikat. Dengan begitu satu file YAML sudah cukup untuk menegakkan disiplin spec-first end-to-end.


19.3 Fast Spec Check tanpa LLM (< 30 detik)

Untuk feedback yang lebih instan di pre-push atau staging branch, kita bisa jalankan versi check statis yang lebih tebal tanpa memanggil LLM sama sekali. Snippet berikut memeriksa section wajib, jumlah AC minimum, status klarifikasi, dan keberadaan plan.

yaml
 1- name: Fast spec check (no API)
 2  run: |
 3    FEATURE="${FEATURE}"
 4    SPEC=".specify/features/$FEATURE/spec.md"
 5
 6    echo "--- Fast Spec Check ---"
 7
 8    # 1. Required sections
 9    SECTIONS=("Acceptance Criteria" "Edge Cases" "Out of Scope" "User Stories")
10    for s in "${SECTIONS[@]}"; do
11      if grep -q "$s" "$SPEC"; then
12        echo "✅ Section: $s"
13      else
14        echo "❌ Missing: $s"
15        FAIL=1
16      fi
17    done
18
19    # 2. AC count minimum
20    AC_COUNT=$(grep -cE "^[-*] AC[0-9]+" "$SPEC" || echo "0")
21    if [ "$AC_COUNT" -lt "5" ]; then
22      echo "❌ Too few ACs: $AC_COUNT (minimum: 5)"
23      FAIL=1
24    else
25      echo "✅ ACs: $AC_COUNT"
26    fi
27
28    # 3. Clarifications answered (jika ada)
29    CLARI=".specify/features/$FEATURE/clarifications.md"
30    if [ -f "$CLARI" ]; then
31      UNANSWERED=$(grep -cE "^Q[0-9]+:" "$CLARI" || echo "0")
32      ANSWERED=$(grep -cE "^A[0-9]+:" "$CLARI" || echo "0")
33      if [ "$UNANSWERED" -gt "$ANSWERED" ]; then
34        echo "⚠️ Unanswered clarifications: $(($UNANSWERED - $ANSWERED))"
35      else
36        echo "✅ All clarifications answered"
37      fi
38    fi
39
40    # 4. Plan file exists
41    if [ -f ".specify/features/$FEATURE/plan.md" ]; then
42      echo "✅ Plan exists"
43    else
44      echo "⚠️ Plan not yet created"
45    fi
46
47    if [ "$FAIL" = "1" ]; then exit 1; fi
48    echo "✅ Fast spec check passed"

Karena hanya bergantung pada grep dan pengecekan file, langkah ini selesai dalam hitungan detik dan gratis — ideal sebagai gerbang pertama yang menyaring kesalahan sepele sebelum stage LLM yang berbayar dipanggil.


19.4 Cost Control di CI

Stage audit memakai LLM API, jadi menjalankannya di setiap push tanpa filter akan memboroskan biaya. Langkah berikut menentukan secara proaktif kapan audit LLM benar-benar perlu dijalankan berdasarkan jenis branch dan file yang berubah.

yaml
 1# Hanya jalankan LLM audit jika ada perubahan yang relevan
 2- name: Determine if LLM audit needed
 3  id: should-audit
 4  run: |
 5    BRANCH="${{ github.head_ref }}"
 6
 7    # Hanya untuk feature branches
 8    if [[ "$BRANCH" != feature/* ]]; then
 9      echo "run=false" >> $GITHUB_OUTPUT
10      echo "Reason: not a feature branch"
11      exit 0
12    fi
13
14    # Cek apakah spec atau impl berubah
15    SPEC_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}... | \
16      grep -c "\.specify/" || echo "0")
17    IMPL_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}... | \
18      grep -cE "^internal/|^cmd/" || echo "0")
19
20    if [ "$SPEC_CHANGED" -gt "0" ] || [ "$IMPL_CHANGED" -gt "0" ]; then
21      echo "run=true" >> $GITHUB_OUTPUT
22      echo "Reason: spec or impl changed ($SPEC_CHANGED spec, $IMPL_CHANGED impl files)"
23    else
24      echo "run=false" >> $GITHUB_OUTPUT
25      echo "Reason: no spec or impl changes detected"
26    fi
27
28- name: Run LLM audit (conditional)
29  if: steps.should-audit.outputs.run == 'true'
30  env:
31    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
32  run: specify audit --feature "$FEATURE"

Dengan gating seperti ini, PR yang hanya menyentuh dokumentasi atau konfigurasi tidak memicu audit AI — biaya LLM hanya keluar saat ada perubahan spec atau implementasi yang memang layak divalidasi.


19.5 Branch-Based Workflow

Tidak semua branch butuh tingkat audit yang sama: PR ke develop cukup audit ringan, sedangkan PR ke main layak audit komprehensif. Reusable workflow berikut menerima parameter audit_mode sehingga satu definisi bisa dipakai dengan intensitas berbeda.

yaml
 1# Reusable workflow untuk spec check
 2# .github/workflows/reusable-spec-check.yml
 3on:
 4  workflow_call:
 5    inputs:
 6      feature:
 7        required: true
 8        type: string
 9      audit_mode:
10        required: false
11        type: string
12        default: "basic"  # basic | comprehensive | fast
13
14jobs:
15  audit:
16    runs-on: ubuntu-latest
17    steps:
18      - uses: actions/checkout@v4
19      - run: npm install -g @github/spec-kit
20      - name: Run audit
21        env:
22          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
23        run: |
24          MODE="${{ inputs.audit_mode }}"
25          FEATURE="${{ inputs.feature }}"
26
27          case "$MODE" in
28            comprehensive) specify audit --feature "$FEATURE" --comprehensive ;;
29            fast)          specify audit --feature "$FEATURE" --fast ;;
30            *)             specify audit --feature "$FEATURE" ;;
31          esac

Setelah reusable workflow terdefinisi, caller cukup memilih mode secara dinamis berdasarkan target branch. Ekspresi kondisional berikut memetakan main ke mode komprehensif dan sisanya ke basic.

yaml
1# PR ke develop: basic audit
2# PR ke main: comprehensive audit
3- uses: ./.github/workflows/reusable-spec-check.yml
4  with:
5    feature: ${{ env.FEATURE }}
6    audit_mode: ${{ github.base_ref == 'main' && 'comprehensive' || 'basic' }}

Pola ini menjaga biaya tetap proporsional dengan risiko: perubahan yang menuju production mendapat scrutiny penuh, sementara iterasi harian tetap ringan dan cepat.


19.6 Matrix Testing untuk Monorepo

Di monorepo dengan banyak service, mengaudit semuanya di setiap PR jelas mubazir. Workflow berikut mendeteksi service mana yang berubah lewat paths-filter, lalu menjalankan audit hanya untuk service tersebut sebagai matrix.

yaml
 1jobs:
 2  detect-changed:
 3    outputs:
 4      services: ${{ steps.detect.outputs.services }}
 5    steps:
 6      - uses: dorny/paths-filter@v3
 7        id: detect
 8        with:
 9          list-files: json
10          filters: |
11            order: ['services/order-service/**']
12            product: ['services/product-service/**']
13            notification: ['services/notification-service/**']
14
15      - id: detect
16        run: |
17          SERVICES=$(echo '${{ toJSON(steps.filter.outputs) }}' | \
18            python3 -c "
19          import json, sys
20          filters = json.load(sys.stdin)
21          changed = [k for k,v in filters.items() if v == 'true']
22          print(json.dumps(changed))
23          ")
24          echo "services=$SERVICES" >> $GITHUB_OUTPUT
25
26  audit-services:
27    needs: detect-changed
28    if: needs.detect-changed.outputs.services != '[]'
29    strategy:
30      matrix:
31        service: ${{ fromJson(needs.detect-changed.outputs.services) }}
32      fail-fast: false  # Audit semua service meski satu fail
33
34    steps:
35      - uses: actions/checkout@v4
36      - run: npm install -g @github/spec-kit
37      - name: Audit ${{ matrix.service }}
38        env:
39          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
40        run: |
41          cd services/${{ matrix.service }}
42          FEATURE=$(extract_feature_from_branch)
43          specify audit --feature "$FEATURE" || true  # Non-blocking for now

fail-fast: false memastikan kegagalan di satu service tidak membatalkan audit service lain — kamu mendapat gambaran lengkap dalam satu run alih-alih harus retrigger berkali-kali.


19.7 Spec Status Badge di README

Skor audit akan lebih terasa jika visibel di README sebagai badge yang selalu update. Workflow berikut menghitung rata-rata skor seluruh feature saat push ke main, lalu menulis ulang badge di README secara otomatis.

yaml
 1# .github/workflows/spec-badge.yml
 2on:
 3  push:
 4    branches: [main]
 5
 6jobs:
 7  update-badge:
 8    runs-on: ubuntu-latest
 9    steps:
10      - uses: actions/checkout@v4
11      - run: npm install -g @github/spec-kit
12      - name: Get overall score
13        env:
14          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
15        run: |
16          AVG_SCORE=$(specify audit --all --score --format json | \
17            python3 -c "
18          import json, sys
19          scores = json.load(sys.stdin)
20          avg = sum(s['score'] for s in scores) / len(scores)
21          print(int(avg))
22          ")
23
24          COLOR="green"
25          [ "$AVG_SCORE" -lt "90" ] && COLOR="yellow"
26          [ "$AVG_SCORE" -lt "85" ] && COLOR="red"
27
28          # Update badge in README
29          sed -i "s|spec-audit-[0-9]*%25-[a-z]*|spec-audit-${AVG_SCORE}%25-${COLOR}|g" README.md
30
31          git config --global user.email "ci@santekno.com"
32          git config --global user.name "CI Bot"
33          git commit -am "chore: update spec audit badge [skip ci]" || true
34          git push || true

Badge yang otomatis ini menjadikan kualitas spec sebagai metrik yang terlihat oleh seluruh tim — sebuah nudge sosial yang halus namun efektif untuk menjaga skor tetap tinggi.


19.8 Spec Audit PR Decoration

Selain badge, kita bisa memperkaya PR itu sendiri dengan label dan judul yang mencerminkan skor audit. Script berikut menambahkan label sesuai rentang skor dan menyisipkan skor ke judul PR.

yaml
 1- name: Generate PR decoration
 2  if: github.event_name == 'pull_request'
 3  uses: actions/github-script@v7
 4  with:
 5    script: |
 6      const feature = '${{ env.FEATURE }}';
 7      const score = parseInt('${{ steps.audit.outputs.score }}') || 0;
 8      const minScore = parseInt('${{ env.MIN_AUDIT_SCORE }}');
 9
10      // Update PR labels
11      const labels = [];
12      if (score >= 95) labels.push('spec-excellent');
13      else if (score >= 90) labels.push('spec-pass');
14      else if (score >= 85) labels.push('spec-warning');
15      else labels.push('spec-fail');
16
17      await github.rest.issues.addLabels({
18        issue_number: context.issue.number,
19        owner: context.repo.owner,
20        repo: context.repo.repo,
21        labels
22      });
23
24      // Update PR title with score badge
25      const pr = await github.rest.pulls.get({
26        pull_number: context.issue.number,
27        owner: context.repo.owner,
28        repo: context.repo.repo,
29      });
30
31      const icon = score >= minScore ? '✅' : '⚠️';
32      const titleWithScore = pr.data.title.includes('[spec:')
33        ? pr.data.title.replace(/\[spec:[^\]]*\]/, `[spec:${score}]`)
34        : `${pr.data.title} [spec:${score}]`;
35
36      await github.rest.pulls.update({
37        pull_number: context.issue.number,
38        owner: context.repo.owner,
39        repo: context.repo.repo,
40        title: titleWithScore
41      });

Dengan dekorasi ini, status compliance sebuah PR bisa dibaca sekilas dari daftar PR tanpa perlu membuka detail — mempercepat triase reviewer saat antrean PR menumpuk.


19.9 Caching untuk Spec Kit

Menginstal Spec Kit dan menjalankan audit dari nol di setiap run itu boros waktu. Konfigurasi cache berikut menyimpan artefak npx dan hasil audit, dengan key yang di-invalidate hanya ketika file spec berubah.

yaml
 1- name: Cache Spec Kit
 2  uses: actions/cache@v4
 3  with:
 4    path: ~/.npm/_npx
 5    key: spec-kit-${{ runner.os }}-${{ hashFiles('.speckit-version') }}
 6
 7- name: Cache spec audit results
 8  uses: actions/cache@v4
 9  with:
10    path: .specify/audit/.cache
11    key: audit-${{ runner.os }}-${{ hashFiles('.specify/**/*.md') }}
12    restore-keys: |
13      audit-${{ runner.os }}-

Karena cache key di-hash dari isi .specify/**/*.md, commit yang tidak menyentuh spec akan langsung hit cache — memangkas durasi run sekaligus menekan panggilan API yang berulang untuk konten yang sama.


19.10 Handling Secrets dan Security

ANTHROPIC_API_KEY adalah kredensial sensitif yang tidak boleh bocor ke log. Langkah berikut memvalidasi ketersediaan key tanpa pernah mencetak nilainya, dan menunjukkan cara mengikat job ke environment tertentu.

yaml
 1# Jangan expose API key di logs
 2- name: Validate API key available
 3  run: |
 4    if [ -z "$ANTHROPIC_API_KEY" ]; then
 5      echo "❌ ANTHROPIC_API_KEY not set"
 6      echo "Add secret: Settings → Secrets → Actions → ANTHROPIC_API_KEY"
 7      exit 1
 8    fi
 9    echo "✅ API key configured (${#ANTHROPIC_API_KEY} chars)"
10  env:
11    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
12
13# Rotasi key: gunakan repo environment jika perlu per-environment key
14environment: staging  # atau production

Perhatikan trik ${#ANTHROPIC_API_KEY} yang hanya mencetak panjang string, bukan isinya — cukup untuk memastikan secret ter-load tanpa membocorkannya ke log CI yang bisa dibaca banyak orang.


19.11 Notifikasi Slack setelah CI

Kegagalan spec CI di main — yang berarti sudah lolos merge — adalah sinyal serius yang butuh perhatian segera. Step berikut mengirim notifikasi Slack hanya ketika CI gagal di branch main.

yaml
 1- name: Notify Slack on spec failure
 2  if: failure() && github.ref == 'refs/heads/main'
 3  uses: slackapi/slack-github-action@v1.27.0
 4  with:
 5    channel-id: 'dev-alerts'
 6    slack-message: |
 7      ❌ Spec CI Failed on main!
 8      PR: ${{ github.event.pull_request.title }}
 9      Author: ${{ github.actor }}
10      Score: ${{ steps.audit.outputs.score }}/100
11      Link: ${{ github.event.pull_request.html_url }}
12  env:
13    SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Kondisi failure() && github.ref == 'refs/heads/main' menjaga agar channel tidak dibanjiri notifikasi dari kegagalan PR biasa — hanya insiden yang benar-benar kritis yang memicu alert.


19.12 Complete .github Structure untuk Spec Kit Project

Agar semua workflow di atas terorganisir, kita perlu struktur direktori .github/ yang jelas. Pohon berikut menunjukkan pembagian antara workflow utama, workflow terjadwal, dan template.

text
 1.github/
 2├── workflows/
 3│   ├── spec-ci.yml                 ← Main CI pipeline (artikel ini)
 4│   ├── spec-audit-weekly.yml       ← Scheduled weekly audit
 5│   ├── spec-badge.yml              ← Update README badge
 6│   ├── spec-pr-cleanup.yml         ← Close stale spec PRs
 7│   └── reusable-spec-check.yml     ← Reusable workflow
 8├── pull_request_template.md         ← Template dengan spec reference
 9├── ISSUE_TEMPLATE/
10│   └── spec-deviation.md           ← Template untuk report spec issues
11└── CODEOWNERS                      ← Review rules untuk spec files

Struktur di atas memisahkan kepedulian dengan rapi: satu workflow per tanggung jawab, sehingga mudah dinonaktifkan atau di-debug secara independen. Salah satu file kunci di sana adalah CODEOWNERS, yang menegakkan siapa wajib me-review perubahan spec.

text
1# CODEOWNERS
2# .specify/constitution.md butuh approval tech lead
3.specify/constitution.md    @tech-lead @architecture-board
4
5# Feature specs butuh approval senior dev
6.specify/features/          @senior-devs
7
8# Event contracts butuh approval dari semua affected team leads
9.specify/contracts/         @order-team-lead @notification-team-lead

Dengan CODEOWNERS, perubahan pada constitution atau event contract otomatis membutuhkan reviewer yang tepat — governance yang ditegakkan tooling, bukan bergantung pada ingatan orang.


19.13 Emergency Override: Skip Spec Check

Kadang ada hotfix urgent yang tidak bisa menunggu proses spec lengkap. Mekanisme bypass berikut mengizinkan skip, tapi hanya jika ada penanda eksplisit di PR body dan approval dari tech lead.

yaml
 1# Di .github/workflows/spec-ci.yml
 2- name: Check for emergency bypass
 3  id: bypass-check
 4  run: |
 5    PR_BODY="${{ github.event.pull_request.body }}"
 6
 7    if echo "$PR_BODY" | grep -q "\[emergency-bypass\]"; then
 8      # Cek apakah ada approval dari tech lead
 9      APPROVALS=$(gh pr view ${{ github.event.pull_request.number }} \
10        --json reviews --jq '.reviews[] | select(.state=="APPROVED") | .author.login' | \
11        grep -c "tech-lead\|architecture-board" || echo "0")
12
13      if [ "$APPROVALS" -gt "0" ]; then
14        echo "bypass=true" >> $GITHUB_OUTPUT
15        echo "⚠️ Emergency bypass approved by tech lead"
16      else
17        echo "bypass=false" >> $GITHUB_OUTPUT
18        echo "❌ Emergency bypass requires tech lead approval"
19        exit 1
20      fi
21    else
22      echo "bypass=false" >> $GITHUB_OUTPUT
23    fi

Kunci desain ini adalah bypass yang tetap terkontrol: bukan tombol darurat bebas, melainkan pintu yang hanya terbuka dengan approval eksplisit — sehingga jalur cepat tidak berubah menjadi celah yang disalahgunakan.


19.14 Tracking CI Metrics

Untuk memahami tren kualitas spec dari waktu ke waktu, kirim skor audit sebagai metrics ke observability platform. Step berikut mengirim skor, feature, dan status job ke Datadog di akhir setiap run.

yaml
 1# Kirim metrics ke Datadog setelah setiap CI run
 2- name: Send CI metrics
 3  if: always()
 4  run: |
 5    SCORE="${{ steps.audit.outputs.score || '0' }}"
 6    FEATURE="${{ env.FEATURE }}"
 7    STATUS="${{ job.status }}"
 8
 9    # Datadog metrics via API
10    curl -X POST "https://api.datadoghq.com/api/v1/series" \
11      -H "DD-API-KEY: ${{ secrets.DATADOG_API_KEY }}" \
12      -H "Content-Type: application/json" \
13      -d "{
14        \"series\": [{
15          \"metric\": \"spec_kit.ci.audit_score\",
16          \"points\": [[$(date +%s), $SCORE]],
17          \"tags\": [\"feature:$FEATURE\", \"branch:${{ github.ref }}\", \"status:$STATUS\"]
18        }]
19      }"

Dengan skor audit yang mengalir ke dashboard, kamu bisa melihat apakah kualitas spec membaik seiring adopsi tim atau justru menurun — data yang menjadi bahan retrospektif berbasis fakta, bukan opini.


19.15 Troubleshooting CI Issues

Menjalankan specify di runner CI kerap memunculkan masalah yang tidak muncul di lokal. Berikut empat kendala paling umum beserta solusinya, dimulai dari command yang tidak ditemukan setelah instalasi global.

yaml
1# Problem 1: "specify: command not found" di CI
2- name: Install Spec Kit
3  run: |
4    npm install -g @github/spec-kit
5    # Verify
6    specify --version
7    echo "$(npm root -g)/.bin" >> $GITHUB_PATH

Masalah kedua adalah timeout API yang membuat job gagal padahal specnya baik-baik saja. Solusinya adalah membungkus audit dengan mekanisme retry berbatas waktu.

yaml
1# Problem 2: API timeout di CI
2- name: Run audit with retry
3  uses: nick-fields/retry@v3
4  with:
5    timeout_minutes: 10
6    max_attempts: 2
7    command: specify audit --feature $FEATURE --timeout 300

Ketiga, saat banyak PR di-audit bersamaan kamu bisa terkena rate limit dari Anthropic. Loop retry dengan exponential backoff berikut memberi jeda yang makin panjang di tiap percobaan.

yaml
 1# Problem 3: Rate limit dari Anthropic
 2- name: Audit with rate limit handling
 3  env:
 4    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
 5  run: |
 6    # Retry dengan exponential backoff
 7    for i in 1 2 3; do
 8      specify audit --feature "$FEATURE" && break
 9      echo "Attempt $i failed, waiting $((i*30))s..."
10      sleep $((i*30))
11    done

Terakhir, skor audit yang berbeda antara lokal dan CI biasanya disebabkan versi Spec Kit yang tidak sinkron. Perintah berikut membantu menyamakan versi antara kedua lingkungan.

bash
1# Problem 4: Audit score berbeda antara local dan CI
2# Pastikan versi yang sama
3specify --version  # local
4# vs
5echo "npm install -g @github/spec-kit@$(specify --version | cut -d' ' -f2)"
6# Pin versi di CI

Benang merah keempat masalah ini sama: perlakukan CI runner sebagai lingkungan yang berbeda dari laptop kamu — pin versi, tambahkan retry, dan verifikasi PATH secara eksplisit agar hasilnya reproducible.


19.16 Cost Estimation untuk CI

Sebelum meng-enforce audit di seluruh tim, wajar jika ada kekhawatiran soal biaya. Estimasi berikut membedah biaya bulanan untuk tim 10 developer beserta perbandingannya dengan nilai yang diselamatkan.

text
 1Estimasi biaya Spec Kit di CI per bulan
 2(asumsi: 10 developer, 4 PRs per dev per sprint, 2 sprints per bulan)
 3
 4Total PRs per bulan: 10 × 4 × 2 = 80 PRs
 5
 6Per PR:
 7- Stage 2 (LLM audit): ~$0.05-0.10
 8- Weekly audit (shared): ~$0.50 per minggu
 9
10Monthly estimate:
11- PR audits: 80 × $0.075 avg = $6.00
12- Weekly audits: 4 × $0.50 = $2.00
13- Total: ~$8/bulan
14
15Value dari 80 PRs yang di-audit:
16- Assumed 20% PRs punya spec issue (16 PRs)
17- Cost per post-deploy fix: 4 jam × $80 = $320
18- Value if 50% caught: 8 × $320 = $2,560
19- ROI: $2,560 saved / $8 cost = 320x

Angka ROI 320x ini menjelaskan mengapa cost bukan alasan valid untuk menunda adopsi: biaya audit hampir tak berarti dibanding biaya memperbaiki satu bug spec yang lolos ke production.


19.17 Integration dengan Status Checks

Agar audit tidak sekadar informatif tapi benar-benar mengikat, jadikan ketiga stage sebagai required status check di branch protection. Konfigurasi berikut menegaskan check mana yang wajib hijau sebelum merge diizinkan.

yaml
1# Tambahkan spec audit sebagai required status check di GitHub
2# Settings → Branches → Branch protection rules → Require status checks
3
4required_status_checks:
5  - "Stage 1: Spec File Check"
6  - "Stage 2: LLM Spec Audit"
7  - "Stage 3: Quality Gate"
8
9# Tanpa ketiga status checks ini pass, PR tidak bisa di-merge

Tanpa required status check, developer yang sedang terburu-buru akan tergoda melewati audit — dan enforcement di level branch protection inilah yang mengubah SDD dari “anjuran” menjadi “standar yang tidak bisa dinegosiasikan”.


19.18 PR Template untuk Spec-Driven Project

Melengkapi enforcement otomatis, PR template membantu developer menyertakan konteks spec secara konsisten. Template berikut menyediakan kolom spec reference, hasil audit, dan checklist kesiapan merge.

markdown
 1<!-- .github/pull_request_template.md -->
 2
 3## Summary
 4[Deskripsi singkat fitur atau perubahan]
 5
 6## Spec Reference
 7- **Spec**: `.specify/features/[feature]/spec.md` (v[x.x])
 8- **Plan**: `.specify/features/[feature]/plan.md`
 9- **Spec approved by**: @[reviewer] on [date]
10
11## Spec Audit
12Jalankan sebelum PR: `specify audit --feature [feature]`
13- Audit score: [score]/100
14- ACs: [n]/[total] implemented
15- EC handling: [status]
16
17## Type of Change
18- [ ] New feature (spec required)
19- [ ] Bug fix (hotfix — spec optional)
20- [ ] Refactoring (spec optional)
21- [ ] Documentation
22- [ ] Other: [describe]
23
24## Checklist
25- [ ] All ACs implemented
26- [ ] Tests cover all ACs
27- [ ] `specify audit --score` ≥ 90
28- [ ] `go test -race` passes
29- [ ] Coverage ≥ 85%
30- [ ] `golangci-lint` clean
31- [ ] CLAUDE.md updated (if new patterns)
32- [ ] `.specify/` committed with feature
33
34## Emergency Bypass (jika hotfix urgent)
35- [ ] Add `[emergency-bypass]` ke PR body
36- [ ] Get tech lead approval sebelum merge

Template ini menyelaraskan ekspektasi antara author dan reviewer sejak PR pertama dibuka — reviewer tahu persis di mana mencari spec, dan author diingatkan checklist yang sama yang di-enforce oleh CI.


19.19 Tips & Gotchas

💡 Tip 1: Gunakan required status checks

Jika spec audit tidak di-require, developer akan skip. Enforce via branch protection.

💡 Tip 2: Fast check dulu, LLM check kemudian

Stage 1 (fast) menangkap masalah obvious dalam 30 detik. Ini menghemat LLM cost — jika Stage 1 fail, LLM tidak perlu dipanggil.

💡 Tip 3: Cache spec audit results

Audit yang sama di commit yang sama tidak perlu dijalankan ulang. Cache berdasarkan hash dari .specify/ folder.

💡 Tip 4: Alert ke Slack untuk failure di main

Spec CI failure di main (setelah merge) adalah signal serius — perlu notifikasi immediate.

⚠️ Gotcha 1: API rate limit di paralel CI

Jika banyak PR di-audit bersamaan, bisa kena rate limit Anthropic. Untuk mengantre alih-alih membatalkan job yang sedang berjalan, gunakan concurrency group seperti berikut.

yaml
1concurrency:
2  group: spec-audit-${{ github.actor }}
3  cancel-in-progress: false  # Jangan cancel, queue saja

Dengan cancel-in-progress: false, audit yang datang bersamaan akan diantre satu per satu — mencegah lonjakan panggilan API yang memicu rate limit tanpa kehilangan hasil audit apa pun.

⚠️ Gotcha 2: ANTHROPIC_API_KEY harus di secret, bukan env

Jangan hardcode API key di workflow file. Gunakan ${{ secrets.ANTHROPIC_API_KEY }} selalu.

⚠️ Gotcha 3: CI context berbeda dengan local

specify audit di CI mungkin punya hasil berbeda dari local jika CLAUDE.md tidak identik. Pastikan CLAUDE.md di-commit.

⚠️ Gotcha 4: Audit di monorepo bisa lambat

specify audit --all untuk 10+ features bisa 30+ menit. Gunakan path-based filtering untuk hanya audit yang berubah.


19.20 Ringkasan

Spec Kit + GitHub Actions memberikan three-stage pipeline yang robust:

Stage 1 (Spec Check, ~2 menit): Static validation tanpa LLM — verifikasi spec file exist, format valid, section lengkap. Gratis.

Stage 2 (Spec Audit, ~5 menit): LLM-powered AC compliance check — verifikasi setiap acceptance criteria ter-implementasikan. ~$0.05-0.10 per PR.

Stage 3 (Quality Gate, ~10 menit): Standard Go quality checks — race condition test, coverage, lint.

Total runtime: ~17 menit. Total cost per PR: ~$0.10. ROI: 320x dari mencegah post-deploy fixes.

Required status checks memastikan spec audit tidak bisa di-bypass — enforcement yang konsisten tanpa friction manual.

Di artikel terakhir Topik 2, kita lihat bagaimana semua output Spec Kit — spec, plan, tasks, contracts — menjadi kontrak formal dalam AI pipeline yang lebih advanced, membuka jalan ke Topik 3-10.

Artikel Terkait

💬 Komentar