---
name: hexagonal-vertical-slice-migration
description: Migrasikan/refactor struktur folder aplikasi Agentic AI atau backend Python (FastAPI, Google ADK, LangGraph, dsb) yang SUDAH ADA menjadi arsitektur hybrid Hexagonal (Ports & Adapters) dengan Vertical Slice di dalam domain layer. Gunakan skill ini saat user minta "refactor ke hexagonal", "pisahkan domain dari infra", "buat arsitektur clean/hexagonal", "migrate struktur folder ke ports and adapters", atau saat vibe-coding diminta merapikan arsitektur project existing tanpa mengubah behavior/performa aplikasi. WAJIB jalan secara incremental dan verifikasi test tetap hijau di tiap step — jangan restrukturisasi big-bang sekaligus.
---

# Migrasi ke Hexagonal + Vertical Slice Hybrid

Tujuan: mengubah struktur folder aplikasi existing menjadi hexagonal (domain terisolasi dari vendor/framework) dengan vertical slice di dalam domain (per-agent/per-feature folder), **tanpa mengubah behavior atau menurunkan performa**. Ini adalah refactor struktural, bukan rewrite — logic yang sudah bekerja dipindah, bukan ditulis ulang dari nol.

## Prinsip Non-Negosiabel

1. **Test hijau sebelum dan sesudah tiap step.** Kalau belum ada test, buat smoke test/characterization test dulu sebelum migrasi mulai (capture behavior existing sebagai baseline).
2. **Satu langkah kecil per commit.** Jangan pindahkan semua file sekaligus. Pindahkan satu modul/agent, jalankan test, baru lanjut.
3. **Tidak menambah abstraksi yang tidak perlu.** Buat `port` (interface) HANYA untuk 3 kategori yang memang akan berganti-ganti: LLM provider, tool/external integration (termasuk MCP), dan storage/vector store. Jangan bikin port untuk logic internal yang stabil — itu over-engineering yang menambah indirection tanpa manfaat.
4. **Tidak ada DI container berat.** Pakai `typing.Protocol` atau ABC ringan + factory function sederhana di startup. Jangan pakai library reflection-based (dependency-injector dkk) — itu yang bikin lambat, bukan pattern-nya.
5. **Async tetap propagate natural.** Semua method port yang I/O-bound harus `async def`. Jangan bungkus sync-to-async atau sebaliknya di tengah proses migrasi.

## Target Struktur Akhir

```
src/
├── domain/                        # ZERO import dari framework/vendor (no fastapi, no google.adk, no langchain)
│   ├── agents/                    # vertical slice per agent/feature
│   │   ├── <nama_agent>/
│   │   │   ├── agent.py           # pure logic, terima port lewat constructor
│   │   │   ├── schemas.py         # Pydantic models untuk input/output agent ini
│   │   │   └── prompts.py         # template prompt (string), no vendor SDK call
│   │   └── <nama_agent_lain>/
│   └── ports/                     # abstract interface — kontrak, bukan implementasi
│       ├── llm_provider.py        # Protocol: async def generate(...), async def stream(...)
│       ├── tool_executor.py       # Protocol: async def call_tool(name, args)
│       └── vector_store.py        # Protocol: async def query(...), async def upsert(...)
├── application/                   # use case / orchestration antar-agent
│   └── use_cases/
│       └── <nama_use_case>.py     # merangkai beberapa agent domain sesuai orchestration pattern
├── infrastructure/                 # ADAPTER — implementasi konkret dari port, boleh import vendor SDK
│   ├── llm/
│   │   ├── gemini_adapter.py
│   │   └── <provider_lain>_adapter.py
│   ├── tools/
│   │   └── <nama_mcp_atau_api>_adapter.py
│   ├── persistence/
│   │   └── <db_atau_vectorstore>_adapter.py
│   └── observability/
│       └── <langfuse_atau_phoenix>_adapter.py
├── interfaces/                     # entrypoint — HTTP, webhook, CLI
│   ├── api/
│   │   └── routes.py               # FastAPI route, panggil use_case, tidak ada business logic di sini
│   └── webhooks/
└── config/
    └── container.py                # factory sederhana: bikin adapter, inject ke use case, sekali di startup
tests/
├── unit/                            # test domain, mock semua port
├── integration/                     # test adapter terhadap vendor asli/sandbox
└── eval/                            # RAGAS/DeepEval kalau ada
```

---

## Langkah Migrasi (jalankan berurutan, verifikasi tiap step)

### Step 0 — Audit & Baseline

1. Scan struktur project saat ini (`view` direktori root, cari entrypoint, agent definitions, tool call, config).
2. Identifikasi 3 kategori vendor-coupling yang perlu jadi port: pemanggilan LLM (Gemini/OpenAI/Claude SDK), tool/external call (MCP, REST API, DB), dan storage/vector store.
3. Kalau belum ada test yang cover flow utama, buat characterization test dulu: rekam input/output beberapa skenario nyata (termasuk edge case) sebagai baseline "ground truth" sebelum refactor apa pun.
4. Catat baseline performa (latency p50/p95, cost per request) kalau observability sudah ada — dipakai untuk verifikasi "tidak ada regresi performa" di akhir.

### Step 1 — Buat Skeleton Folder Kosong

Buat struktur folder target (domain, application, infrastructure, interfaces, config) **tanpa memindahkan kode apa pun dulu**. Commit terpisah, supaya history migrasi jelas.

### Step 2 — Ekstrak Port Pertama (mulai dari LLM provider)

1. Cari semua tempat kode memanggil SDK LLM langsung (mis. `genai.GenerativeModel(...)`, `client.messages.create(...)`).
2. Definisikan `Protocol` di `domain/ports/llm_provider.py`:
```python
from typing import Protocol, AsyncIterator

class LLMProvider(Protocol):
    async def generate(self, prompt: str, **kwargs) -> str: ...
    async def stream(self, prompt: str, **kwargs) -> AsyncIterator[str]: ...
```
3. Buat adapter konkret di `infrastructure/llm/<provider>_adapter.py` yang implement Protocol tsb, isinya adalah kode SDK call yang sudah ada (dipindah, bukan ditulis ulang).
4. Ganti pemanggilan langsung SDK di kode agent menjadi menerima `LLMProvider` lewat constructor/parameter.
5. Jalankan test. Harus tetap hijau — kalau tidak, jangan lanjut, perbaiki dulu.

### Step 3 — Ekstrak Port Tool/MCP & Vector Store

Ulangi pola yang sama seperti Step 2 untuk:
- Tool executor (panggilan ke MCP Toolbox, REST API eksternal, function call executor)
- Vector store / retrieval (BigQuery, Weaviate, Qdrant, dsb)

Tiap kategori: definisikan Protocol → buat adapter → ganti pemanggilan langsung di kode lama → jalankan test → commit.

### Step 4 — Pindahkan Logic Agent ke Vertical Slice di Domain

1. Untuk tiap agent (misal `customer_support`, `billing`, `triage`), buat folder di `domain/agents/<nama_agent>/`.
2. Pindahkan pure logic agent (prompt construction, response parsing, business rule) ke `agent.py` di folder tsb. Agent HANYA boleh depend ke `domain/ports/*`, tidak boleh import adapter langsung.
3. Pindahkan Pydantic schema input/output agent ke `schemas.py` di folder yang sama.
4. Kalau ada orchestration antar-agent (Sequential/Supervisor-Router/dsb), pindahkan ke `application/use_cases/`, bukan ke dalam salah satu agent.
5. Verifikasi: `domain/` folder harus lolos check "zero vendor import" (lihat Step 6 untuk cara cek otomatis).

### Step 5 — Bersihkan Interfaces Layer

1. Pastikan `interfaces/api/routes.py` hanya berisi: parsing request → panggil use_case → format response. Tidak ada business logic atau pemanggilan LLM langsung di sini.
2. Buat `config/container.py` — factory sederhana yang instantiate semua adapter SEKALI saat startup, lalu inject ke use_case. Contoh pola:
```python
def build_container():
    llm = GeminiAdapter(api_key=settings.GEMINI_KEY)
    tools = MCPToolAdapter(toolbox_url=settings.MCP_URL)
    use_case = HandleCustomerQuery(llm=llm, tools=tools)
    return use_case
```
Jangan resolve ulang tiap request — instantiate sekali, reuse selama app lifetime (hindari overhead re-init koneksi/klien tiap request).

### Step 6 — Verifikasi Boundary (jalankan sebagai check otomatis)

Buat script sederhana atau CI step yang gagal kalau ada import vendor di `domain/`:
```bash
# contoh check sederhana — sesuaikan dengan vendor SDK yang dipakai
grep -rE "^(import|from) (fastapi|google\.adk|langchain|openai|anthropic|google\.generativeai)" src/domain/ && echo "FAIL: vendor import ditemukan di domain/" && exit 1
```
Jalankan check ini di CI supaya boundary tidak "bocor" lagi di kemudian hari.

### Step 7 — Regression Test Penuh + Perbandingan Performa

1. Jalankan seluruh test suite (unit + integration + eval kalau ada).
2. Bandingkan output characterization test Step 0 dengan output setelah migrasi — harus identik/setara.
3. Bandingkan latency & cost dengan baseline Step 0. Kalau ada regresi > 5-10%, cek apakah ada overhead dari re-instantiate adapter per request (biasanya penyebab utama) — perbaiki di `container.py`.

---

## Kesalahan Umum yang Harus Dihindari

- **Membuat port untuk semua hal.** Kalau suatu dependency tidak realistis akan diganti (misal logging internal), jangan dibungkus port — cukup dipakai langsung.
- **Big-bang rewrite.** Migrasi seluruh app dalam satu PR besar membuat regresi sulit dilacak. Selalu per-modul, per-commit.
- **Domain layer mengandung `async def` yang memanggil adapter secara langsung tanpa lewat port.** Ini bocornya boundary — domain harus selalu bicara ke interface, bukan implementasi.
- **DI container yang re-resolve dependency tiap request.** Ini penyebab paling umum "hexagonal jadi lambat" — pastikan instantiate sekali di startup.
- **Lupa update dokumentasi/README setelah migrasi.** Setelah selesai, update README dengan diagram struktur baru — ini juga jadi materi kuat untuk showcase portofolio (lihat bagaimana kamu bisa swap provider tanpa sentuh domain logic).

## Output yang Diharapkan dari Vibe Coding Agent

Setelah migrasi selesai, laporkan:
1. Daftar file yang dipindah/dibuat per step (diff ringkas)
2. Hasil check boundary (Step 6) — PASS/FAIL
3. Hasil regression test sebelum vs sesudah
4. Perbandingan latency/cost sebelum vs sesudah (kalau ada baseline)
5. Struktur folder akhir dalam bentuk tree
