AI Code Review sebagai Merge Gate CI: Apa yang Boleh Block

Foto oleh User:ArnoldReinhold via Wikimedia Commons (CC BY-SA 3.0)
Hanya untuk sekumpulan finding yang sempit. Sebuah class boleh memblokir kalau ia objektif, murah diverifikasi penulisnya cuma dari komentarnya, dan terukur jarang salah, yang pada praktiknya mencakup hal seperti secret di dalam diff, route baru tanpa authorization check, dan migration yang menghapus column yang masih dibaca release yang sedang jalan. Sisanya, termasuk penamaan dan struktur, sebaiknya cuma berkomentar tanpa membuat check jadi merah.
Shadow mode berarti menjalankan reviewer pada pull request yang sudah merged, tanpa memposting apa pun dan tanpa memberi tahu siapa pun. Diff-nya nyata dan hasil akhirnya sudah diketahui, jadi Anda bisa menghitung berapa finding per class yang akan disetujui manusia sebelum gate diizinkan memblokir apa pun. Simpan harness-nya setelah itu dan jalankan lagi tiap bulan, karena versi model dan codebase sama-sama bergerak.
Kumpulkan finding dari shadow mode, buang label class dan hasil merge-nya, acak barisnya, lalu minta manusia menjawab satu pertanyaan per baris: apakah Anda akan meminta perubahan di sini. Dari situ Anda mendapat precision per class, dan penilaian buta itu penting karena penilai yang tahu pull request-nya jalan tanpa insiden akan menandai hampir semuanya sebagai noise. Di bawah sekitar dua puluh finding yang dinilai dalam satu class, laporkan sample tidak cukup, bukan sebuah angka.
Biasanya karena bot itu diizinkan memblokir finding yang butuh selera. Saran penyederhanaan dan penamaan adalah output dengan volume tertinggi dari sebagian besar AI reviewer sekaligus yang paling tidak bisa dipaksakan, jadi memblokir berdasarkan itu mengajari semua orang bahwa keputusan bot bisa dinegosiasikan. Begitu komentarnya di-collapse karena kebiasaan, finding yang benar-benar berguna di dalamnya juga tidak terbaca, dan itulah kenapa gate yang di-mute lebih buruk daripada tidak ada gate.
Bisa, dan justru inilah kegagalan yang harus Anda uji. Referensi status checks GitHub menyatakan bahwa job yang skipped melaporkan statusnya sebagai Success dan tidak akan mencegah pull request di-merge walaupun ia required check. Kondisi apa pun pada job itu, misalnya filter paths atau pengecualian fork, bisa mengubah gate menjadi centang hijau, jadi buka satu pull request yang sengaja memicu kondisi itu dan pastikan merge-nya masih tertahan.

Foto oleh User:ArnoldReinhold via Wikimedia Commons (CC BY-SA 3.0)
Ringkasan Utama
Sebuah AI code review merge gate sebaiknya hanya memblokir finding yang objektif, murah diverifikasi, dan jarang salah: secret di dalam diff, route baru tanpa authorization check, atau migration yang menghapus column yang masih dibaca release yang sedang jalan. Semua yang butuh selera tetap advisory sampai shadow mode mengukurnya.
Reviewer itu dipasang pada hari Kamis. Pertengahan pekan berikutnya ia sudah mengomentari setiap pull request yang terbuka di repository, termasuk satu perubahan teks dua baris. Komentarnya tidak salah, sebenarnya: nama variabel, helper yang seharusnya bisa jadi map, komentar yang sudah tidak cocok dengan baris di bawahnya. Dan di bagian bawah salah satu review itu, jauh melewati titik di mana orang masih membaca, ada catatan bahwa sebuah migration di dalam diff menghapus column yang masih dibaca release yang sedang ter-deploy.
Itulah kegagalan yang layak ditulis: bukan reviewer yang melewatkan sesuatu, tapi yang cukup sering benar untuk dipasang dan cukup berisik untuk di-mute. Memasangnya ke CI butuh satu sore. Memutuskan apa yang boleh dia blokir, dan membuktikan keputusan itu dengan angka sebelum dia memblokir apa pun, itu pekerjaan sesungguhnya, dan itu bagian yang saya salah lakukan lebih dulu. Berikut pembagian yang sekarang saya pakai, pengukuran yang membuat sebuah class berhak memblokir, dan aturan yang mencabut hak itu kembali.
Adopsi dan kepercayaan bergerak ke arah yang berbeda, dan sebuah merge gate harus dirancang untuk jarak itu. Stack Overflow Developer Survey 2025 mencatat 84% responden sedang memakai atau berencana memakai AI tools dalam proses development mereka, naik dari 76% tahun sebelumnya, dan 51% developer profesional memakai AI tools setiap hari. Survei yang sama mencatat lebih banyak developer yang justru tidak mempercayai akurasi AI tools, 46%, dibanding yang mempercayainya, 33%, dan hanya 3% yang sangat mempercayai outputnya. Pemakaian naik; kepercayaan pada akurasi jatuh ke titik terendah.
Itulah alasan sebuah gate butuh bukti, bukan antusiasme. Tim yang meragukan akurasi sebuah tool tidak akan berdebat dengannya secara terbuka; mereka cuma akan hafal di mana tombol collapse-nya. Minggu pertama orang membaca komentarnya, minggu kedua mereka melewatinya dan langsung ke diff, minggu ketiga ada yang bertanya apakah check itu masih jalan atau tidak, dan pertanyaan itu adalah ukuran paling jujur dari gate yang di-mute: tidak ada yang tahu jawabannya. Gate yang di-mute lebih buruk daripada tidak ada gate, karena tim sekarang percaya ada sesuatu yang memeriksa, dan reviewer manusia membaca lebih longgar kalau merasa mesin sudah melihat lebih dulu.
Sebuah class finding boleh memblokir merge hanya kalau ketiga sifat ini terpenuhi sekaligus. Dua dari tiga berarti komentar advisory, dan itu bukan hadiah hiburan.
Tiga class lolos standar itu, dan ketiganya membosankan. Migration yang menghapus atau mengganti nama column yang masih dibaca release yang sedang ter-deploy, bisa diputuskan dengan grep pada tag yang ter-deploy untuk nama column itu, dan cukup mahal di production untuk pantas dapat tanda merah. Route HTTP baru tanpa authorization check, risiko yang ditaruh OWASP Top 10:2021 di peringkat pertama, muncul pada 94% aplikasi yang diuji untuk salah satu bentuk Broken Access Control, dan aturan pencegahannya cuma satu kalimat yang bisa dijalankan mesin: kecuali untuk resource publik, tolak secara default. Dan credential aktif di baris yang ditambahkan, yang sebenarnya hampir bukan finding AI sama sekali.
Separuh aturan yang lain lebih mudah. Penamaan, tata letak file, apakah sebuah function bisa lebih sederhana, apakah sebuah abstraction datang terlalu cepat: inilah finding yang paling lancar diproduksi AI reviewer dan paling tidak punya dasar untuk dipaksakan. Reviewer manusia menyelesaikan ini dengan kosakata, bukan dengan tooling. Panduan engineering practices Google menyuruh reviewer memberi awalan Nit untuk komentar kecil, artinya secara teknis sebaiknya dikerjakan tetapi dampaknya tidak besar, memakai Optional atau Consider untuk ide yang tidak wajib, dan FYI untuk hal yang memang tidak diharapkan dikerjakan pada perubahan ini. Label itu menahan penulis dari membaca setiap komentar sebagai keharusan. AI reviewer yang dirilis tanpa kosakata itu adalah reviewer yang setiap ucapannya terbaca sebagai perintah.
| Class finding | Apa yang memutuskan | Gate |
|---|---|---|
| Credential aktif di baris yang ditambahkan | Pattern match pada rentang diff, tanpa model | Block |
| Route baru tanpa authorization check | Diff route dicek silang dengan daftar guard | Block |
| Migration menghapus column yang masih dibaca | Grep tag yang ter-deploy untuk nama column | Block |
| Cabang logika baru tanpa test yang menutupinya | Delta coverage pada baris yang berubah, lalu penilaian | Advisory |
| Penamaan, struktur, bisa lebih sederhana | Tidak ada. Ini preferensi dengan argumen yang bagus | Never |
Baris terakhir itulah yang membuat saya rugi. Saran penyederhanaan adalah output dengan volume tertinggi dari reviewer, sering kali masuk akal, dan membiarkannya memblokir selama satu sprint saja sudah cukup mengajari semua orang untuk mengabaikan bot itu selamanya. Itu bentuk kesalahannya: sebuah gate di-mute oleh class yang paling terdengar bisa dibela, bukan oleh class yang paling jelas salah.

Pembagian itu harus hidup di pipeline, bukan di prompt. Satu job yang mengomentari segalanya lalu gagal pada sebagiannya menaruh perilaku gate di dalam output sebuah model, tempat yang tidak bisa Anda ubah tanpa redeploy dan tidak bisa diaudit sama sekali. Dua job, satu required dan satu tidak, memindahkan kebijakan itu ke YAML, tempat ia bisa direview, di-diff, dan di-revert seperti perubahan lain.
# .github/workflows/ai-review.yml
# Two jobs on purpose. Only the first is a required status check; the second
# can never turn the merge button red, whatever it thinks of your naming.
name: AI review
on: pull_request
jobs:
blocking:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # need real history to find the merge base
# A secret in a diff is a pattern match, not an opinion. Do not pay a
# model to do a job a regex does deterministically and for free.
- name: Secrets in the added lines
run: |
BASE=$(git merge-base "origin/$GITHUB_BASE_REF" HEAD)
gitleaks git --log-opts="$BASE..HEAD" --redact --exit-code 1 .
# Only the classes that earned the right to block. --fail-on=verified
# means the model proposes and a checker confirms: the reviewer must
# name the route, column or file, and the checker must find it there.
- name: Reviewer, blocking classes only
run: node scripts/ai-review.mjs
--classes=secret,missing-authz,unsafe-migration
--fail-on=verified
advisory:
runs-on: ubuntu-latest
continue-on-error: true # this job's conclusion is ignored on purpose
steps:
- uses: actions/checkout@v7
- name: Reviewer, everything else
run: node scripts/ai-review.mjs --classes=all --advisory --label=nitJob blocking menjalankan check deterministik lebih dulu, lalu reviewer yang dibatasi pada class yang sudah layak, dengan setiap finding model ditahan di belakang verifier: reviewer menyebut route, column, atau file-nya, dan sesuatu yang bukan model harus menemukannya di sana. Secret adalah kasus paling jelas untuk sama sekali tidak melibatkan model, karena gitleaks memindai rentang git log secara langsung, keluar dengan exit code 1 saat menemukan sesuatu, dan menyensor secret dari outputnya sendiri. Hanya job blocking yang masuk ke aturan branch protection; job advisory memakai continue-on-error, jadi keputusannya tercatat dan tidak pernah dipaksakan.
Kegagalan yang berbahaya pada sebuah required check bukan merah palsu, tapi hijau palsu. Referensi status checks GitHub menyatakan bahwa job yang skipped melaporkan statusnya sebagai Success, dan tidak akan mencegah pull request di-merge bahkan ketika ia adalah required check. Kondisi apa pun pada job blocking Anda, entah filter paths, guard untuk draft, atau pengecualian fork, diam-diam mengubah gate itu jadi centang hijau. Buka satu pull request yang sengaja memicu kondisi itu dan pastikan merge-nya masih tertahan.
Sebelum reviewer boleh memblokir apa pun, jalankan dia di tempat yang tidak bisa dia rusak: pada pull request yang sudah merged. Diff-nya nyata, hasil akhirnya sudah diketahui, dan tidak ada satu pun ucapannya yang sampai ke penulis kode. Dua ratus pull request yang sudah merged memberi hitungan per class yang layak diperdebatkan, dan biayanya satu laptop dan satu malam, bukan niat baik orang lain.
# Shadow mode: replay already-merged pull requests through the reviewer.
# Nothing is posted, nobody is notified. The output is a file you can count.
gh pr list --state merged --limit 200 --json number --jq '.[].number' \
> /tmp/merged-prs.txt
while read -r pr; do
gh pr diff "$pr" --patch > "/tmp/pr-$pr.patch"
# Same binary, same prompt, same config as the CI job will use. Change one
# of the three and the rate you measured stops describing what you shipped.
node scripts/ai-review.mjs --patch "/tmp/pr-$pr.patch" --json \
| jq -c --arg pr "$pr" '.findings[] | [$pr, .class, .file, .line, .message]' \
>> /tmp/shadow-findings.jsonl
done < /tmp/merged-prs.txt
# What humans flagged on the same diffs, for the other half of the picture:
# a class the reviewer never raises is not precise, it is silent.
while read -r pr; do
gh api "repos/OWNER/REPO/pulls/$pr/comments" --paginate \
--jq '.[] | [.path, .line, .user.login, .body]' \
>> /tmp/human-comments.jsonl
done < /tmp/merged-prs.txtLalu hitung. Precision per class adalah angka yang benar-benar menentukan keputusan gate: dari semua finding yang diangkat reviewer di sebuah class, berapa yang akan disetujui manusia. Recall juga penting, dan komentar review manusia pada diff yang sama adalah pengganti paling murah yang ada, karena class yang tidak pernah diangkat reviewer bukan berarti presisi, itu berarti bisu. Tahap penilaian adalah bagian yang paling sering salah, jadi lakukan secara buta.
// scripts/shadow-report.mjs
// One precision figure per finding class, with the sample size behind it.
const MIN_SAMPLE = 20; // policy, not a measurement: below this, no verdict
const PROMOTE_AT = 0.9; // policy: one wrong finding in ten is the most I
const DEMOTE_AT = 0.8; // will inflict on a gate, and one in five revokes it
const rows = readJsonl("/tmp/shadow-adjudicated.jsonl");
const byClass = new Map();
for (const r of rows) {
// r.verdict was set by a human who saw the finding WITHOUT its class label
// and WITHOUT being told the PR shipped fine. Adjudicate blind, or the
// number is theatre: knowing it merged makes every finding look like noise.
const c = byClass.get(r.class) ?? { agreed: 0, rejected: 0 };
if (r.verdict === "agreed") c.agreed++;
else c.rejected++;
byClass.set(r.class, c);
}
for (const [cls, c] of byClass) {
const n = c.agreed + c.rejected;
const precision = c.agreed / n;
const verdict =
n < MIN_SAMPLE
? "insufficient sample"
: precision >= PROMOTE_AT
? "eligible to block"
: precision < DEMOTE_AT
? "advisory, and losing ground"
: "advisory";
console.log(cls.padEnd(20), String(n).padStart(4), precision.toFixed(2), verdict);
}Buta di sini bukan formalitas. Penilai yang tahu bahwa pull request itu sudah merged dan jalan tanpa insiden akan menandai hampir semuanya sebagai noise, karena tidak adanya insiden terasa seperti bukti bahwa semuanya aman. Buang label class dan hasil akhirnya, acak barisnya, lalu ajukan satu pertanyaan per baris: apakah Anda akan meminta perubahan di sini. Angka yang keluar dari cara itu satu-satunya yang layak dimasukkan ke aturan branch protection.
Simpan aturan ini di repository sebagai kebijakan, bukan diputuskan di bawah tekanan saat sebuah release terblokir dan tiga orang menunggu. Punya saya ada lima baris.
Asimetri itu memang disengaja. Promosi punya biaya yang datang belakangan dan tersebar tipis ke semua orang, jadi ia harus lambat dan butuh bukti. Demosi mencegah tepat kegagalan yang jadi topik tulisan ini, jadi ia harus langsung dan tidak butuh apa pun. Pada praktiknya aturan demosi inilah yang membuat orang mau mempromosikan apa pun, karena ia mengubah keputusan yang terasa permanen jadi keputusan yang bisa dibatalkan.
Simpan harness shadow mode setelah gate menyala, dan jalankan lagi setiap bulan pada merge bulan sebelumnya. Versi model berubah tanpa Anda sadari dan codebase Anda berubah di bawah model itu, jadi class yang lolos standar di bulan Maret belum tentu masih lolos di bulan September. Menjalankan ulang juga cara paling murah untuk menyadari sebuah class jadi bisu, bukan jadi bersih.

Setelah semua kalibrasi itu, output reviewer yang benar-benar berguna ternyata tidak keren sama sekali. Ringkasan sebuah diff besar yang membuat manusia bisa memilih empat file mana yang perlu dibaca teliti. Test yang di-copy dan dibiarkan tanpa assertion apa pun. Route yang ditambahkan tanpa guard yang dimiliki semua route tetangganya. Column yang dihapus di pull request yang sama yang masih membacanya tiga file dari situ. Cepat, mekanis, dan setiap satunya bisa dilacak ke satu fakta di dalam diff.
Pendapat soal desain adalah bagian yang saya harap paling bernilai, dan justru bagian yang harus dicabut gigi-giginya. Itu bukan keterbatasan satu tool atau satu model; itu justru yang diprediksi angka kepercayaan tadi. Tim yang tidak percaya akurasi sebuah mesin tetap akan menerima keputusannya soal fakta dan menolak keputusannya soal selera, dan gate yang dibangun melawan arah itu akan di-mute sebagus apa pun modelnya nanti. Jual check yang membosankan ke dalam tim, dan reviewer itu akan bertahan melewati minggu buruk pertamanya.
Satu aturan untuk dibawa pulang: sebuah class boleh memblokir merge hanya kalau ia objektif, murah diverifikasi, dan terukur jarang salah, dan ia tetap advisory sampai ketiganya benar sekaligus. Bangun harness shadow mode sebelum gate-nya, bukan setelah perdebatannya, tulis aturan demosi selagi tidak ada yang terbakar, dan terima kenyataan bahwa karya terbaik AI reviewer yang bagus lebih mirip checklist daripada kritik.
Sumber & bacaan lanjutan