# Stockbiter — Dokumentasi Teknis

> Asisten AI pasar saham Indonesia (IDX) dengan persona **"Buva Cuantika"** — chatbot bergaya Jaksel (lo-gue) yang menjawab pertanyaan seputar saham IDX dengan data live dari Stockbit, dilengkapi watchlist + investment thesis, trading journal, MCP server, dan admin usage dashboard.

---

## Daftar Isi

1. [Overview](#1-overview)
2. [Tech Stack](#2-tech-stack)
3. [Arsitektur](#3-arsitektur)
4. [Struktur Folder](#4-struktur-folder)
5. [Database & Models](#5-database--models)
6. [Fitur Utama](#6-fitur-utama)
7. [Desain Visual (Design System)](#7-desain-visual-design-system)
8. [Integrasi Eksternal](#8-integrasi-eksternal)
9. [Routes & Controllers](#9-routes--controllers)
10. [Testing](#10-testing)
11. [Deployment](#11-deployment)

---

## 1. Overview

Stockbiter adalah aplikasi web yang menjawab pertanyaan seputar pasar saham Indonesia (IDX/BEI) melalui chat AI streaming. Persona asistennya, **Buva Cuantika**, menjawab dalam Bahasa Indonesia gaya Jaksel dan **wajib** mengambil angka dari tool calls (data live Stockbit) — tidak boleh mengarang angka.

Fitur pendukung:

- **Watchlist** saham pribadi per user, lengkap dengan **Thesis** (hipotesis investasi + status validitas) dan **Journal Log** (catatan trading).
- **MCP server** (`/mcp/stockbit`) yang mengekspos 39 tool data pasar untuk klien MCP eksternal.
- **Admin dashboard** untuk memantau pemakaian token & biaya chat.
- **Shareholders browser** untuk melihat data pemegang saham emiten.

---

## 2. Tech Stack

### Backend (PHP)

| Komponen | Versi | Peran |
|---|---|---|
| Laravel | 13 | Framework utama |
| PHP | 8.4 | Runtime |
| Fortify | v1 | Auth backend (login, register, 2FA, email verification) |
| Socialite | v5 | Login Google OAuth |
| Laravel MCP | v0.6.7 | MCP server (`routes/ai.php`) |
| Sanctum | v4 | Token auth untuk `/api/user` & MCP |
| Inertia Laravel | v3 | Bridge ke frontend SPA |
| Wayfinder | v0 | Generator route TypeScript |
| Resend | v1 | Email transactional |
| Pest | v4 | Testing framework |

### Frontend (TypeScript)

| Komponen | Peran |
|---|---|
| Svelte 5 + Inertia Svelte v3 | SPA rendering, `resources/js/pages` |
| Vercel AI SDK v7 (`ai`, `@ai-sdk/svelte`) | Chat UI streaming (`useChat`) |
| Tailwind CSS v4 + shadcn-style (bits-ui) | Styling & komponen UI (~28 primitives di `components/ui/`) |
| marked + DOMPurify | Render markdown chat dengan sanitasi |
| Lucide | Ikon |
| Bun / npm + Vite 8 | Tooling & bundler |

### Chat Server (Node/Bun terpisah)

- Express server di `server/` (`prod-server.ts`, `chat-server.ts`, `tools.ts`) yang menjalankan `streamText()` dari Vercel AI SDK.
- Provider AI dapat dipilih via env `AI_PROVIDER` (`deepseek` | `groq` | `openai`), default `groq` dengan model `llama-3.3-70b-versatile`.
- Membaca session Laravel langsung dari PostgreSQL via `pg` untuk autentikasi user.

---

## 3. Arsitektur

### Alur Chat (request streaming)

```
┌─────────────────┐   POST /api/chat    ┌──────────────────┐   cURL SSE proxy   ┌──────────────────────┐
│ Svelte chat UI  │ ──────────────────► │ ChatProxyController│ ─────────────────► │ Node chat server     │
│ (@ai-sdk/svelte)│    (throttle 20/m)  │ (Laravel)          │                    │ (stockbiter-chat:3001)│
└─────────────────┘                     └──────────────────┘                    └──────────┬───────────┘
                                                                                           │ streamText()
                                                        ┌──────────────────────────────────┤
                                                        │ tool call (POST /api/tools/execute)│
                                                        ▼                                  ▼
                                              ┌──────────────────┐              ┌────────────────────┐
                                              │ ToolController    │              │ Provider AI        │
                                              │ TOOL_MAP → MCP    │              │ (groq/deepseek/    │
                                              │ Tool → StockbitSv │              │ openai)            │
                                              └────────┬─────────┘              └────────────────────┘
                                                       ▼
                                              ┌──────────────────────┐
                                              │ Stockbit APIs         │
                                              │ exodus / carina       │
                                              └──────────────────────┘
```

Langkah lengkap:

1. Svelte page (`resources/js/pages/chat/Index.svelte`) POST ke Laravel `/api/chat`.
2. `ChatProxyController` memvalidasi request, cek kepemilikan conversation, dan cek **kuota token harian** (`ChatUsage`, batas via `DAILY_TOKEN_LIMIT`, default 3 juta token/hari per sistem, admin bebas kuota) dengan batas hari mengikuti zona WIB.
3. Laravel **stream SSE via cURL** ke service internal Node `http://stockbiter-chat:3001/internal/chat`, meneruskan messages, conversation id, user id, session cookie, dan timezone.
4. Node `chat-server.ts` mengotentikasi user dengan membaca session Laravel dari PostgreSQL, lalu menjalankan AI SDK `streamText()` dengan registry tool (`server/tools.ts`) dan system prompt persona Buva Cuantika (aturan ketat: tanpa angka halusinasi, aturan pasar IDX, caveat data broker T-1).
5. Setiap tool call AI di-POST balik ke Laravel `/api/tools/execute` (`ToolController` → TOOL_MAP → MCP Tool class → `StockbitService` → Stockbit API). Respons dinormalisasi (mis. shares → lots) sebelum kembali ke model.
6. Pemakaian token & biaya dicatat ke tabel `chat_usage`; metadata conversation di DB, sedangkan isi pesan disimpan sebagai **file JSONL** di local disk: `storage/app/chat-conversations/{id}.jsonl`.

### Alur MCP (klien eksternal)

```
Klien MCP ──(Sanctum token)──► /mcp/stockbit ──► StockbitServer (39 tools) ──► StockbitService ──► Stockbit API
```

### Topologi Docker

```
            proxy-net (external)
                 │
   ┌─────────────┼──────────────┐
   │             │              │
stockbiter-web  stockbiter-app  stockbiter-chat
(nginx:80)      (PHP Laravel)   (Bun, port 3001, internal)
   │             │
   └──── db-net (external, PostgreSQL) ────┘
```

---

## 4. Struktur Folder

| Path | Peran |
|---|---|
| `app/Mcp/Servers/StockbitServer.php` | Registrasi 39 MCP tool |
| `app/Mcp/Tools/` | 39 tool class (mis. `SymbolInfoTool`, `PortfolioTool`, `RealizedPnlTool`, `TradeBookGroupTool`) |
| `app/Http/Controllers/` | `ChatProxyController` (SSE proxy + kuota), `ChatController` (CRUD conversation), `ToolController` (TOOL_MAP eksekusi tool), `WatchlistController`, `ThesisController`, `JournalLogController`, `ShareholderController`, `StockController`, `GoogleAuthController`, `ChatUsageController`, `LegalController`, `Api/` (TokenController, SyncController), `Settings/` |
| `app/Services/` | `StockbitService` (klien API Stockbit, ~783 baris; token exodus & carina disimpan sebagai file lokal `stockbit_token` / `stockbit_carina`), `StockPriceService`, `SyncForeignFlowService`, `AdimologyService` (kalkulator tick/fraksi harga IDX), `FinancialModelingPrepService` + `FinancialModelingPrep/` (klien FMP dengan DTO) |
| `app/Console/Commands/` | `stock:sync`, `stock:sync-foreign-flow` (jadwal harian 17:30 WIB), `stock:token`, `fmp:sync-exchange`, `app:import-shareholders` |
| `app/Models/` | 11 model Eloquent (lihat §5) |
| `server/` | Chat server Bun: `chat-server.ts` (streamText + system prompt Buva), `tools.ts` (registry tool yang POST balik ke Laravel), `prod-server.ts`, `chat-vite-plugin.ts` (intersepsi `/api/chat` saat dev) |
| `resources/js/` | Frontend Inertia Svelte (pages, components, layouts) |
| `stockbit-api/` | 31 dokumen markdown referensi endpoint Stockbit (dokumentasi, bukan kode) |
| `docs/superpowers/` | Spec & plan fitur (design docs) |
| `routes/` | `web.php`, `api.php`, `chat.php`, `ai.php` (MCP), `settings.php`, `console.php` |
| `tests/` | Pest feature tests |

---

## 5. Database & Models

Database utama: **PostgreSQL** (driver `pgsql` di produksi; `sqlite` untuk dev/testing default).

| Model | Tabel | Relasi & Catatan |
|---|---|---|
| `User` | `users` | `hasMany` Watchlist & ChatConversation; kolom `google_id` (Socialite); cek admin via `ADMIN_USER_IDS` env |
| `Stock` | `stocks` | `hasMany` StockMetric, `latestMetric()` (latestOfMany); flag `is_fca`, `is_predict` |
| `StockMetric` | `stock_metrics` | belongsTo Stock; metrik kaya termasuk JSON `trade_book_by_time` / `trade_book_by_price` |
| `DailyPrice` | `daily_prices` | OHLC harian per stock |
| `ForeignFlow` | `foreign_flows` | belongsTo Stock; disinkronkan harian |
| `Shareholder` | `shareholders` | belongsTo Stock; diimpor via command |
| `Watchlist` | `watchlists` | belongsTo User; `hasMany` Thesis & JournalLog; unique (user, ticker); scope `withValidTheses` |
| `Thesis` | `theses` | belongsTo Watchlist; `validity_status` (VALID/INVALID), `price_at_thesis`; method `invalidate()`, `revalidate()`, `updateStatus()` |
| `JournalLog` | `journal_logs` | belongsTo Watchlist; `log_date` datetime |
| `ChatConversation` | `chat_conversations` | PK UUID, belongsTo User; **pesan disimpan sebagai JSONL di disk**, bukan di DB |
| `ChatUsage` | `chat_usage` | belongsTo User; total token + biaya untuk kuota & monitoring |

---

## 6. Fitur Utama

### 6.1 Chat AI — Buva Cuantika

- UI chat streaming dengan riwayat percakapan (sidebar, mobile sheet nav), markdown tersanitasi (DOMPurify), dan suggested prompts.
- Streaming SSE melalui proxy Laravel ke Node chat server (lihat §3).
- Kuota harian token per sistem (`DAILY_TOKEN_LIMIT`, default 3 juta) berbasis tabel `chat_usage` dengan batas hari WIB; admin dikecualikan.
- System prompt persona memuat aturan ketat anti-halusinasi angka, aturan bursa IDX, dan caveat data broker T-1.

### 6.2 Tool Eksekusi & MCP

- `ToolController` memetakan nama tool → MCP Tool class via `TOOL_MAP`, endpoint `POST /api/tools/execute` (dipakai baik oleh chat server maupun klien MCP).
- 39 MCP tool teregistrasi di `StockbitServer`, dikelompokkan:
  - **8 tool user data** (token Carina, `carina.stockbit.com`): `stockbit_portfolio`, `stockbit_user_orders`, `stockbit_order_history`, `stockbit_realized_pnl`, `stockbit_performance_cumulative_return`, `stockbit_performance_equity_return`, `stockbit_performance_equity`, `stockbit_performance_trade` — holdings, balance, unrealized/realized P&L, win rate, dll.
  - **~31 tool data publik** (`exodus.stockbit.com`): symbol info/chart/key stats, trade book, broker summary/distribution, market mover, top stock, foreign flow, corp actions (dividen, split, right issue, tender offer), orderbook, bid-offer pattern, dll.
- 3 MCP resources juga tersedia di `app/Mcp/Resources/`.

### 6.3 Watchlist + Thesis + Journal

Fitur riset pribadi per user: daftar saham yang dipantau, hipotesis investasi (**Thesis**), dan diary keputusan trading (**Journal Log**). Semua digabung jadi satu **feed timeline** ala sosial media dengan gaya terminal UI.

#### 6.3.1 Watchlist (entitas induk)

- Model `App\Models\Watchlist` — 1 user punya banyak watchlist item, **unique per ticker** (index unique `user_id + stock_ticker`).
- Ticker di-uppercase otomatis dan **harus terdaftar di tabel `stocks`** (validasi `Rule::exists`) — jadi gak bisa nambah sembarang kode.
- **Status posisi**: `WATCHING` (cuma pantau), `POSITION_OPEN` (posisi jalan), `POSITION_CLOSED` (sudah exit).
- Halaman `/watchlists` nampilin harga terakhir tiap ticker dari tabel `daily_prices` (harga hari ini, `latest_price`).
- Ada juga mode **"Semua"** (`allMode`): gabungin feed dari semua watchlist jadi satu timeline.
- Otorisasi via `WatchlistPolicy` — user cuma bisa lihat/ubah/hapus miliknya sendiri.

#### 6.3.2 Thesis (hipotesis investasi)

- Model `App\Models\Thesis`, belongsTo Watchlist.
- **Tipe**: `REASON` (alasan fundamental/teknikal) atau `STORY` (narasi bandar/momentum).
- **Conviction level awal**: skala 1–10 (`initial_conviction`).
- **Harga saat thesis dibuat** (`price_at_thesis`, decimal 4 digit, opsional) — jadi acuan evaluasi apakah hipotesis jalan atau tidak.
- **Siklus status validitas** (via `POST /theses/{thesis}/status`):
  - `OPEN` — baru dibuat, belum dievaluasi.
  - `VALID` — hipotesis terbukti (✓, label hijau).
  - `INVALID` — terbantahkan (✕, label merah); model menyimpan `invalidated_at` + `invalidated_reason` via method `invalidate()`.
  - `OUTDATED` — tidak relevan lagi (↻).
  - Setiap perubahan status mencatat `validity_status_updated_at` + `validity_status_reason` (evaluasi via `EvalModal`); method `revalidate()` kembali ke VALID dan reset data invalidasi.
- Edit & hapus thesis via `TesisModal`.

#### 6.3.3 Journal Log (diary trading)

- Model `App\Models\JournalLog`, belongsTo Watchlist.
- Isi per entri: `log_date` (datetime — kalau cuma tanggal, diisi jam sekarang biar urutan antar-log dalam satu hari benar), `price_at_log`, `conviction_level` (1–10), **keputusan** `BUY | SELL | HOLD | SKIP`, dan `notes` (max 5000 char).
- **Auto-fill harga**: kalau `price_at_log` gak diisi, diisi otomatis dari `StockPriceService::getLatestClosingPrice()` (harga penutupan terakhir ticker).
- Entry dicatat melalui `LogModal`.

#### 6.3.4 UI Feed & Komponen

Halaman `resources/js/pages/watchlist/Index.svelte` + komponen di `resources/js/components/watchlist/`:

| Komponen | Peran |
|---|---|
| `StockTabs` | Tab per watchlist item (ticker) dengan underline style; juga mode "Semua" |
| `FeedTypeTabs` | Filter feed: `Semua` / `Tesis` / `Log Trade` |
| `ComposeArea` | Area compose untuk menambah entri (menu add) |
| `TesisModal` | Form tambah/edit thesis (tipe, konten, conviction, harga) |
| `LogModal` | Form tambah/edit journal log (tanggal, keputusan, conviction, catatan) |
| `EvalModal` | Evaluasi thesis: ubah status validitas + alasan |
| `WatchlistPanel` | Panel dengan **sorting**: `created_at` / `conviction` (rata-rata conviction thesis) / `alphabet` — pilihan tersimpan di localStorage |

- Feed menggabungkan thesis + journal log jadi timeline tunggal, **diurutkan dari terbaru** (`_ts`), tiap item punya kind (`tesis`/`log`) dan ticker asal; item bisa di-expand/collapse.
- Badge status validitas tampil inline di feed (klik badge → buka `EvalModal`).
- Indikator waktu relatif ("baru saja", `5m`, `3j`, dst.).

#### 6.3.5 Endpoint

| Method | URI | Controller | Keterangan |
|---|---|---|---|
| GET | `/watchlists` | `WatchlistController@index` | List + thesis + journal (eager loaded, terurut) + harga terakhir |
| POST | `/watchlists` | `WatchlistController@store` | Tambah ticker (valid: exists di stocks, unik per user) |
| DELETE | `/watchlists/{watchlist}` | `WatchlistController@destroy` | Hapus watchlist (beserta thesis & log, cascade) |
| POST | `/watchlists/{watchlist}/theses` | `ThesisController@store` | Tambah thesis |
| PATCH | `/theses/{thesis}` | `ThesisController@update` | Edit thesis |
| DELETE | `/theses/{thesis}` | `ThesisController@destroy` | Hapus thesis |
| POST | `/theses/{thesis}/status` | `ThesisController@status` | Ubah status validitas + alasan |
| POST | `/watchlists/{watchlist}/journal-logs` | `JournalLogController@store` | Tambah journal log |
| PATCH | `/journal-logs/{journalLog}` | `JournalLogController@update` | Edit journal log |
| DELETE | `/journal-logs/{journalLog}` | `JournalLogController@destroy` | Hapus journal log |

> Catatan: ada juga konsep watchlist global di `StockController` (`is_predict` flag pada tabel `stocks`, halaman `stocks/Index`) — daftar saham hasil prediksi/sync, terpisah dari watchlist pribadi per user di atas.

### 6.4 Fitur Lain

- **Shareholders browser** (`/shareholders`, tanpa auth) — data diimpor via `app:import-shareholders`.
- **Admin panel** (`/admin`, `/admin/chat-usage`) — middleware `EnsureUserIsAdmin` + `ADMIN_USER_IDS`; pagination untuk recent requests & users; tracking cost.
- **Settings** — profil, keamanan (password/2FA via Fortify), appearance.
- **Auth** — login Google (Socialite) + Fortify (login/register/2FA/verifikasi email).
- **Legal pages** — privacy policy & terms of service.

### 6.5 Sinkronisasi Data Terjadwal

- `stock:sync` — sinkronisasi metrik saham.
- `stock:sync-foreign-flow` — sinkronisasi asing harian (jadwal 17:30 WIB) via `SyncForeignFlowService`.
- `fmp:sync-exchange` — daftar emiten dari Financial Modeling Prep.
- `app:import-shareholders` — impor data pemegang saham.

---

## 7. Desain Visual (Design System)

Aplikasi memakai **bahasa desain terminal/quant** ("Analyst Console") — monokrom dengan aksen neon, label gaya komentar kode, dan radius tajam. Semua warna dikendalikan token CSS (shadcn-style) di `resources/css/app.css`.

### Warna Utama

| Token | Light (`:root`) | Dark (`.dark`) |
|---|---|---|
| `--primary` | `#0d9488` (teal) | `#00ffd1` (neon cyan) |
| `--primary-foreground` | `#f8fafc` | `#0a0a0f` |
| `--ring` / `--sidebar-primary` | `#0d9488` | `#00ffd1` |
| `--background` | `#f8fafc` | `#0a0a0f` |
| `--card` | `#ffffff` | `#111118` |
| `--border` | `#e2e8f0` | `#1e1e2e` |
| `--destructive` | `#ef4444` | `#ef4444` |
| `--radius` | `0.25rem` (tajam) | `0.25rem` |

### Aksen Neon (token khusus)

| Token | Nilai | Kegunaan umum |
|---|---|---|
| `--color-neon-cyan` | `#00ffd1` | Aksen utama dark mode / chart |
| `--color-neon-purple` | `#a855f7` | Aksen sekunder |
| `--color-neon-green` | `#22c55e` | Naik / gain |
| `--color-neon-red` | `#ef4444` | Turun / loss / destructive |
| `--color-neon-yellow` | `#facc15` | Peringatan (WARN) |
| `--color-neon-orange` | `#fb923c` | Aksen lainnya |

Warna chart: light `chart-1..5` = teal, hijau, ungu, kuning tua, oranye; dark = neon cyan, hijau, ungu, kuning, oranye.

### Tipografi

| Font stack | Token | Peran |
|---|---|---|
| Plus Jakarta Sans | `--font-sans` | Teks umum & konten markdown |
| JetBrains Mono (→ IBM Plex Mono, Fira Code) | `--font-mono` | Label, data, prompt terminal |
| Fraunces | `--font-serif` | Heading markdown respons AI (`.markdown-body h1-h4`) |

### Konvensi Terminal UI

- Label seksi memakai prefix `//` gaya komentar kode: `// PERCAKAPAN`, `// buva` — `font-mono text-xs uppercase tracking-widest` dengan `//` berwarna `text-primary`.
- Prinsip **"sans body, mono data"**: chrome UI (sidebar, header, label, prompt) mono, isi pesan markdown sans agar mudah dibaca.
- Tab underline, active item dengan border kiri `border-primary` (edge neon), prompt line `BUVA:~$`.
- Komponen dasar: ~28 primitives shadcn-style (bits-ui) di `resources/js/components/ui/`, semua via token semantik (`bg-card`, `bg-muted`, `text-muted-foreground`, dst.) — tidak ada warna hardcode.
- Dark mode via class `.dark` (custom variant `@custom-variant dark`); scrollbar custom tipis (`custom-scrollbar`).
- Aksesibilitas: `prefers-reduced-motion` menonaktifkan animasi/transisi.

---

## 8. Integrasi Eksternal

| Integrasi | Detail |
|---|---|
| **Stockbit (publik)** | `exodus.stockbit.com` — data pasar: symbol, chart, trade book, broker, market mover, foreign flow, corp actions. Referensi lengkap 31 endpoint di folder `stockbit-api/`. |
| **Stockbit Carina** | `carina.stockbit.com` — data user terautentikasi: portfolio, orders, realized PnL, performance. Token disimpan lokal (`stockbit_token`, `stockbit_carina`), di-set via `stock:token` atau `POST /api/update-token`. |
| **Provider AI** | Vercel AI SDK v7 di chat server; provider via `AI_PROVIDER`: `deepseek` / `groq` (default, `llama-3.3-70b-versatile`) / `openai`; kredensial `AI_API_KEY`, opsional `AI_BASE_URL`, model via `AI_MODEL`. |
| **Financial Modeling Prep** | `FMP_API_KEY` — daftar emiten IDX (via `fmp:sync-exchange`). |
| **Resend** | Email transactional (`RESEND_API_KEY`). |
| **Google OAuth** | Socialite untuk login sosial. |

---

## 9. Routes & Controllers

### routes/web.php

| Method | URI | Controller | Keterangan |
|---|---|---|---|
| GET | `/` | closure | Redirect ke `/chat` (auth) atau `/login` |
| GET | `/privacy-policy`, `/terms-of-service` | `LegalController` | Halaman legal, publik |
| GET | `/auth/google/redirect`, `/auth/google/callback` | `GoogleAuthController` | OAuth Google |
| GET | `/shareholders` | `ShareholderController@index` | Browser pemegang saham |
| GET | `/chat` | closure (Inertia `chat/Index`) | Halaman chat utama (auth+verified) |
| POST | `/api/chat` | `ChatProxyController` | Proxy streaming chat, throttle 20/menit |
| CRUD | `/watchlists`, `/watchlists/{w}/theses`, `/theses/{t}/status`, `/watchlists/{w}/journal-logs` | Watchlist/Thesis/JournalLog | Watchlist + thesis + journal (auth+verified) |
| GET | `/admin`, `/admin/chat-usage` | closure, `ChatUsageController` | Admin panel (middleware `admin`) |
| * | `routes/settings.php` | Settings controllers | Profil, keamanan, appearance |

### routes/chat.php (prefix `/api`, auth)

| Method | URI | Controller |
|---|---|---|
| GET/POST | `/api/conversations` | `ChatController@index|store` |
| GET/DELETE | `/api/conversations/{conversation}` | `ChatController@show|destroy` |
| GET/POST | `/api/conversations/{conversation}/messages` | `ChatController@messages|appendMessage` |
| POST | `/api/tools/execute` | `ToolController@execute` |

### routes/api.php

| Method | URI | Middleware | Keterangan |
|---|---|---|---|
| GET | `/api/user` | sanctum | Data user terautentikasi |
| GET/POST | `/api/update-token` | `BasicAuthEnv` | Simpan token Stockbit/Carina ke storage |
| GET | `/api/sync-stocks` | `BasicAuthEnv` | Trigger sinkronisasi saham |

### routes/ai.php

| URI | Keterangan |
|---|---|
| `/mcp/stockbit` | MCP server (Laravel MCP, auth Sanctum), `StockbitServer` |

---

## 10. Testing

- Framework: **Pest v4** dengan `RefreshDatabase` pada feature tests.
- Konvensi: `php artisan make:test --pest {name}`, jalankan `php artisan test --compact`.
- Coverage (`tests/Feature/`):
  - Chat: `ChatControllerTest`, `ChatConversationTest`, `ToolControllerTest`
  - Watchlist: `WatchlistControllerTest`, `JournalLogControllerTest`
  - Data: `ShareholderControllerTest`, `StockControllerTest`, `DashboardTest`, `DatabaseIsolationTest`
  - API: `Api/SyncControllerTest`, `Api/TokenControllerTest`
  - Auth: 7 test Fortify (`tests/Feature/Auth/`)
  - Commands/Services/Settings: `Commands/`, `Services/FinancialModelingPrepService`, `Settings/`
- CI: `composer ci:check` menjalankan lint (Pint), format check (Prettier), types check (svelte-check), dan test.

---

## 11. Deployment

### Docker Compose

Tiga service (lihat `docker-compose.yml`):

| Service | Image/Build | Peran |
|---|---|---|
| `stockbiter-app` | `Dockerfile` (target builder) | PHP Laravel, volume-mount repo, jaringan `proxy-net` + `db-net` |
| `stockbiter-web` | `nginx:alpine` | Web server (config: `docker-config/nginx.conf`), expose 80 |
| `stockbiter-chat` | `Dockerfile.chat` (Bun Alpine) | Chat server `bun run server/prod-server.ts` di port 3001, internal-only, jaringan `proxy-net` + `db-net` |

Koneksi DB: PostgreSQL eksternal via env `DB_HOST`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD` (jaringan `db-net`).

### Environment Variables Kunci

| Variabel | Keterangan |
|---|---|
| `APP_KEY` | Key Laravel (juga dipakai chat server) |
| `DB_*` | Koneksi PostgreSQL (dipakai app, chat server membaca session) |
| `SESSION_DRIVER=database` | Wajib — chat server membaca session dari DB |
| `AI_PROVIDER` / `AI_MODEL` / `AI_API_KEY` / `AI_BASE_URL` | Provider AI chat server |
| `DAILY_TOKEN_LIMIT` | Kuota token harian chat (default `3000000`) |
| `ADMIN_USER_IDS` | Daftar user ID admin (dipisah koma) |
| `FMP_API_KEY` | Financial Modeling Prep |
| `RESEND_API_KEY` | Email |
| Google OAuth creds | Untuk Socialite login |

### Perintah Operasional

```bash
# Dev lokal (all-in-one: server, queue, vite)
composer dev

# Chat server standalone (dev)
bun run chat:dev        # tsx watch server/prod-server.ts

# Build frontend
bun run build           # atau: npm run build

# Sinkronisasi data
php artisan stock:sync
php artisan stock:sync-foreign-flow   # jadwal harian 17:30 WIB

# Set token Stockbit/Carina
php artisan stock:token

# Test & lint
php artisan test --compact
composer lint           # Pint
vendor/bin/pint --dirty --format agent
```

---

*Dokumen ini dihasilkan dari eksplorasi kodebase per September 2026. Referensi endpoint Stockbit lengkap: folder `stockbit-api/`. Spec & plan fitur: `docs/superpowers/`.*
