preflight

7. Arsitektur

Lima tahap plus revisi, semuanya di `lib/ai/pipeline.ts`:

Stack

Next.js 16, React 19App Router
AI SDK 7 + Zod 4structured output, provider-agnostic
PostgreSQL + Drizzlelokal dan produksi sama
Tailwind 4
mcp-handlerserver MCP

Pipeline

Lima tahap plus revisi, semuanya di lib/ai/pipeline.ts:

questions   ide                        → 5 pertanyaan yang mengubah rencana
prd         ide + jawaban              → struktur PRD
features    PRD                        → fitur + subfitur
specs       fitur                      → spec 4-bagian
tasks       fitur + spec               → task + prompt siap tempel
revise      pesan + plan saat ini      → operasi diff ber-ID

Kuota gratis adalah sumber daya langka di sini, dan tiga keputusan berikut lahir dari itu.

Batching

Bentuk naifnya satu panggilan per fitur untuk spec dan satu lagi untuk task — sekitar 17 panggilan untuk rencana 8 fitur. Dibatch 4 fitur per panggilan spec dan 3 per panggilan task, jadi ~7 panggilan.

Yang mahal itu request, bukan token. Saat kedalaman rencana dinaikkan 3× (12 → 36 task), jumlah request tidak berubah sama sekali.

PRD tidak menghasilkan Markdown

Tahap PRD mengembalikan struktur saja; prosanya dirakit di lib/ai/render.ts. Ini memangkas output token panggilan termahal kira-kira separuh, dan mengubah format dokumen jadi gratis.

Fallback antar-model

Tier gratis mengembalikan 503 di empat dari tujuh run yang diukur. Mengulang ke model yang sama tidak pernah menolong.

Tiap tier punya rantai model cadangan (lib/ai/provider.ts). Kegagalan dipindah ke model berikutnya kalau penyebabnya kapasitas, kuota, model pensiun, atau JSON yang tidak bisa divalidasi — yang terakhir itu tanda modelnya terlalu kecil untuk memegang skema, bukan skemanya yang salah. Prompt atau skema yang benar-benar rusak akan gagal sama di semua model, jadi tidak diulang.


Kontrak output ditegakkan di kode

Ini bagian yang paling menentukan kualitas.

Model gratis punya varians tinggi antar-run. Prompt yang sama menghasilkan 2,0 task per subfitur di satu run dan 1,3 di run berikutnya; judul Indonesia sekali, Inggris berikutnya. Menambal prompt tidak menempel.

Jadi kontraknya ditegakkan di lib/ai/validate.ts:

CacatAmbang
too-few-featuresminimal 5 fitur
thin-featuresminimal 3 subfitur per fitur
too-few-tasksminimal 2 task per subfitur
thin-promptsminimal 180 karakter per agent prompt
wrong-languagejudul harus sesuai bahasa keluaran

Kalau ada cacat, satu re-ask tertarget dijalankan: model diperlihatkan keluarannya sendiri plus daftar persis apa yang salah — bukan disuruh mengulang dari nol.

Dan satu detail yang penting: dua percobaan dibandingkan lewat berapa item yang masih cacat, bukan berapa jenis cacatnya.

severity(after) < severity(defects)   // benar
after.length < defects.length         // salah, dan sempat jadi bug

Versi yang salah membuang perbaikan yang membereskan 7 dari 10 prompt stub, karena jenis cacatnya tetap satu.


Persistensi

Tiap tahap commit sendiri-sendiri begitu selesai. Refresh di tengah generasi 60 detik akan melanjutkan dari plan.status, bukan mengulang.

plans         rencana, PRD (jsonb), status, slug publik
features      + spec (jsonb)
subfeatures
tasks         + agent_prompt, done
revisions     pesan, balasan, ops, snapshot untuk undo
api_keys      key user, terenkripsi
mcp_tokens    token agent, ter-hash
usage_log     tiap panggilan AI: model, tier, token, byok

usage_log diisi sejak hari pertama, termasuk panggilan yang gagal — panggilan gagal tetap menghabiskan kuota, jadi tetap masuk catatan.


Revisi sebagai diff

Model mengembalikan operasi ber-ID, bukan rencana baru. Yang tidak disebut tidak tersentuh.

Dua aturan pengaman di lib/revise.ts:

  • Operasi yang menunjuk id tidak ada dilewati, bukan ditebak. Model gratis kadang mengarang id; mengedit "yang paling mirip" akan merusak rencana tanpa jejak. Yang dilewati dilaporkan ke pengguna.
  • Menghapus fitur ikut menghapus subfitur dan task-nya. Meninggalkan yatim lebih buruk daripada tidak berubah.

Sebelum diterapkan, seluruh rencana di-snapshot ke revisions.snapshot. Undo memulihkan snapshot itu — bukan menghitung kebalikan operasinya, karena begitu cascade menghapus baris, menghitung kebalikannya jadi tebakan.

Fitur yang ditambah revisi langsung diisi: subfitur, lalu spec, lalu task. Melewati tahap spec pernah dicoba demi hemat satu panggilan, dan hasilnya prompt stub 60 karakter — lihat catatan keputusan.


Sesi dan akses

Tanpa akun. Identitasnya cookie preflight_sid, httpOnly, umur satu tahun.

Semua query rencana di-scope ke sesi. Batasannya diuji: cookie lain mendapat 404, bukan daftar kosong.

Tiga jalur akses berbagi batas yang sama:

JalurIdentitas
Aplikasi webcookie preflight_sid
MCPbearer token → sesi
Halaman publiktidak ada — memang untuk siapa saja, dan hanya memuat rencananya

Dua tampilan, satu sumber

components/PlanParts.tsx berisi bagian yang hanya merender, dan sengaja bukan client module.

  • Tampilan pemilik membungkusnya dengan interaktivitas — centang, revisi, progres generasi.
  • Halaman publik merendernya di server tanpa JavaScript sama sekali.

Hasilnya satu rencana menghasilkan ~3.900 kata di HTML, bisa diindeks sepenuhnya, dan kedua tampilan tidak bercabang seiring waktu.


MCP

app/api/mcp/[transport]/route.ts. Tiga tool, tanpa panggilan model — cuma membaca dan menulis baris yang sudah ada.

maxSubscriptions: 0 mematikan SSE, jadi tidak ada koneksi digantung per agent. Transportnya request–response biasa, sama seperti endpoint lain.

Ukurannya dijaga: definisi ketiga tool 1.594 byte (~400 token), dan ada asersi yang gagal kalau membengkak — karena definisi tool menetap di konteks agent sepanjang percakapan.


Berikutnya