Imported from iggbudi/smart-marketing-agent-rfm (
AGENTS.md). Install upstream withnpx skills add iggbudi/smart-marketing-agent-rfm. Copyright stays with the author.
AGENTS.md — Panduan Coding Agent (Smart Marketing Agent / RFM)
Kontrak kerja untuk semua agen (AI/human) yang mengerjakan repo smartrfm.my.id. Panduan ini adalah peta akurat codebase: arsitektur, konvensi wajib, cara menambah fitur, dan checklist sebelum refactor area tertentu. Semua di sini merujuk kondisi nyata repo setelah
RENCANA_PERBAIKAN.mdtuntas (4 fase selesai,composer testhijau).
1. Ringkasan Arsitektur
Plain PHP 7.4+ tanpa framework. Pola halaman prosedural: require config
→ requireAuth() → ambil business → handler POST (+CSRF) → query PDO →
render HTML + includes/sidebar.php. Tidak ada router/front controller;
URL = file .php langsung di docroot.
Peta file
| Area | File | Catatan |
|---|---|---|
| Halaman UMKM owner | dashboard.php, customers.php, transactions.php, analysis.php, upload.php, ai-content.php, profile.php |
requireAuth(['umkm_owner']) |
| Halaman admin (super_admin) | admin/*.php |
requireAuth(['super_admin']) |
| Auth & global helpers | config/auth.php |
AuthManager + auth(), requireAuth(), requireAuthJson(), csrf_*(), getCurrentUser() |
| Kredensial env | config/env.php |
env($key, $default) — prioritas: env var > .env > default |
| DB / OpenAI | config/database.php, config/openai.php (gitignored) |
Template yang di-commit: *.example.php |
| API | api/*.php |
requireAuthJson() → JSON + status HTTP benar |
| Logika terpusat (cross-cutting) | includes/pagination.php, includes/sidebar.php (+ includes/mobile-topbar.php, includes/bottom-nav.php, assets/mobile.js, assets/table-cards.js utk shell & komponen UI mobile) |
Satu-satunya sumber sidebar & shell mobile (admin pakai wrapper) |
Slice per fitur (PSR-4 App\) |
src/Customers, src/Transactions, src/Dashboard, src/Rfm, src/Import, src/Upload, src/Export, src/Ai, src/Business |
Tiap fitur punya class repository/service (query + aturan bisnis TIDAK inline di halaman); halaman/API tipis memanggil class ini |
Logika murni (PSR-4 App\) |
src/Rfm.php |
Single source of truth skor/segmen RFM; SQL di src/Rfm/RfmService.php dibangun dari sini |
| Test | tests/*.php |
PHPUnit 9.6; bootstrap arahkan ke DB test |
| SQL | database_schema.sql, database_update.sql, database_indexes.sql |
Migrasi manual (tidak ada migrasi otomatis) |
Tabel DB (11)
businesses, users, user_sessions, activity_logs, customers,
transactions, rfm_analysis, ai_generated_content, upload_history,
api_usage_logs, system_settings.
Relasi kunci: businesses.user_id → users.id; customers/transactions/ rfm_analysis.business_id → businesses.id. Semua query data bisnis WAJIB
di-scope business_id = ? milik user session (via auth()->getUserBusiness()).
2. Konvensi Kode (WAJIB)
- PDO prepared statements untuk semua query input dinamis.
Gotcha pagination:
LIMIT ? OFFSET ?gagal (PDO meng-quote sebagai string). Pakai inline(int)cast:LIMIT " . (int)$perPage . " OFFSET " . (int)$offset. - Output data user selalu
htmlspecialchars()(anti-XSS). - CSRF: semua form POST menyertakan
csrf_field(); handler POST dipanggilrequireCsrf()(fail-fast 403). Jangan pernah menambah form POST tanpa ini. - API (
/api/*):requireAuthJson(['role'])→ JSON + 401 (belum login) / 403 (role salah), bukan redirect HTML. - Pemilik data: tolak akses lintas-bisnis (selalu
business_iddari session, bukan input user). - File rahasia (
config/database.php,config/openai.php,.env,.env.*.local) tidak pernah di-commit; hanya commit*.example.phpdan.env.example. - File
debug_*,check_*,fix_*,test*,generate_*tidak boleh ada di web root (tidak bisa diakses via URL). - Jangan duplikasi logika yang sudah terpusat: sidebar, pagination, slice
per fitur (
src/<Fitur>/). Kalau menambah fitur serupa, perpanjang class yang ada. - Header keamanan & session cookie sudah diset di
config/auth.php— jangan hapus/double-set. Detail:docs/SECURITY.md. - Autoload: halaman/API yang memakai class
App\*wajib memuatvendor/autoload.phpsendiri di awal (__DIR__ . '/vendor/autoload.php'docroot /dirname(__DIR__) . '/vendor/autoload.php'diapi/). Web request tidak memuat autoload otomatis. JANGAN pindahkan keconfig/database.php— itu mengubah perilakuclass_exists()diapi/export-*.php(CSV → XLSX). Untuk fitur AI, halaman juga wajibrequire config/openai.php(definisi\OpenAIClient).
3. Environment & Kredensial
- Semua kredensial lewat
env()(config/env.php): prioritas env var asli > file.env> default. - Variabel:
DB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORD,OPENAI_API_KEY,OPENAI_MODEL,OPENAI_BASE_URL. - Template:
.env.example(commit); isi lokal:.env(gitignored). - Saat mengubah nama/tambah var env baru: update
.env.example+ bagian "Konfigurasi Kredensial" diREADME.md.
4. Alur Kerja Setiap Perubahan (WAJIB)
- Pahami — baca file terkait + pola di atas; cek
RENCANA_PERBAIKAN.mdbila menyentuh item yang belum selesai (tersisa: CSP & kunci IP/UA — didefer). - Kerjakan — ikuti konvensi §2; satu unit kerja = satu commit.
- Test lokal (§5).
- Update docs — centang checkbox di
RENCANA_PERBAIKAN.mdbila relevan; updateREADME.md/docs/*/*_SUMMARY.mduntuk perubahan behaviour/setup. - Commit (§6) → push
git push origin main.
Aturan keras:
- Satu commit = satu unit kerja; JANGAN campur banyak perubahan.
- JANGAN commit rahasia (§2.6).
- JANGAN menghapus/merefactor di luar lingkup pekerjaan berjalan.
5. Local Testing (wajib sebelum commit)
# 1) Lint semua PHP
find . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n1 php -l
# 2) Unit test (PHPUnit 9.6) — DB test: smart_marketing_rfm_test
composer test
# Catatan: saat jalan sebagai root, composer butuh: COMPOSER_ALLOW_SUPERUSER=1
# 3) Cek dependency & security advisory
composer validate --no-check-publish
composer audit # harus 0 CVE (saat ini phpspreadsheet 1.30.6)
# 4) Test fungsional (bila DB live aksesibel, MariaDB 10.11 lokal)
# - RfmService::recalculate() harus cocok dgn App\Rfm::segmentFromScores()
# - render halaman via wrapper CLI dengan session valid (pola tests/ + /tmp)
# 5) E2E mobile via Playwright (bila server live aksesibel)
# App HANYA tersedia di https://smartrfm.my.id (vhost nginx + Let's Encrypt;
# http://localhost adalah nginx default /var/www/html — BUKAN app ini).
# Flow: emulasi mobile (Pixel 5, 393x851, hasTouch) -> login -> cek tombol
# .mobile-menu-toggle TERLIHAT & sidebar off-canvas bisa dibuka (class .show,
# box x>=0) -> klik link menu -> navigasi sukses. Pakai ignoreHTTPSErrors:true
# (sertifikat tidak diverifikasi curl lokal).
# Kredensial demo (DB live): budi@batiksemarang.com / password123 (umkm_owner),
# admin@smartmarketing.local / password123 (super_admin).
# /login.php punya rate limit (rb_login burst=5 nodelay) — jangan hammer;
# skrip e2e ad-hoc ditulis di /tmp (pola: /tmp/pw-e2e/check-mobile-menu.js,
# `npm i playwright` di sana) — JANGAN commit skrip ad-hoc ke repo.
Aturan test DB: tests/bootstrap.php set DB_NAME=smart_marketing_rfm_test
sebelum config dimuat — jangan pernah jalankan test ke DB produksi.
Jika schema berubah, refresh test DB:
mysql -u root -e "DROP DATABASE IF EXISTS smart_marketing_rfm_test; CREATE DATABASE smart_marketing_rfm_test CHARACTER SET utf8mb4;"
for f in database_schema.sql database_update.sql database_indexes.sql; do sed '/^USE /d' "$f" | mysql -u root smart_marketing_rfm_test; done
6. Konvensi Commit
Untuk pekerjaan di luar plan, pakai prefix konvensional:
| Prefix | Contoh |
|---|---|
feat: |
feat: tambah halaman rekap bulanan untuk UMKM |
fix: |
fix(export): total transaksi salah saat qty > 1 |
refactor: |
refactor(rfm): pindah threshold ke src/Rfm.php |
test: |
test: tambah kasus segmentasi At Risk |
docs: |
docs: perbarui SECURITY.md |
security: |
security: bump phpspreadsheet |
chore: |
chore: tambah index transaksi |
Bila masih mengerjakan sisa item RENCANA_PERBAIKAN.md, lanjutkan prefix
faseN: / fix(area):. Git user lokal sudah diset (iggbudi).
7. Menambah Fitur — Checklist Arsitektur
7.1 Halaman baru (UMKM owner)
- Salin pola dari
customers.php:require database.php + auth.php→requireAuth(['umkm_owner'])→auth()->getUserBusiness()(bailout bila null). - Handler POST (bila ada form):
requireCsrf()di awal. - Query: prepared statements + scope
business_id. - HTML:
htmlspecialcharssemua output; pakaiincludes/sidebar.php+assets/user-styles.css; bootstrap 5 CDN; baris baruindex=$offset + $index + 1bila paginated. - Tambah menu di
includes/sidebar.php(SATU sumber — jangan edit wrapper admin). - Test:
php -l, render CLI (session valid),composer testbila logika di-extract.
7.2 Endpoint API baru
requireAuthJson(['umkm_owner'])baris paling awal (sebelum query).- Semua respon JSON + status benar: 401 belum login, 403 role/ownership, 500 error internal. Jangan pernah redirect/HTML.
- Scope
business_iddari session. Log aktivitas bila relevan (auth()->logActivity()). - Jangan letakkan API key di file (pakai
env('OPENAI_API_KEY')dst).
7.3 Fitur data baru (tabel/migrasi)
- Buat file migrasi
database_xxx.sql(konvensi: schema/update/indexes). SertakanUSE smart_marketing_rfm;di baris awal dan catat cara apply ke DB lain (stripUSEviased). - Terapkan ke DB live + test DB, lalu refresh test DB (lihat §5).
- Index komposit untuk query filter/order umum (contoh:
idx_trans_biz_cust_date (business_id, customer_id, transaction_date)). - Update
README.mdbagian Database & (bila ada)docs/SECURITY.md.
7.4 Form baru
- Wajib:
csrf_field()di form +requireCsrf()di handler POST. Gagal = 403. - Validasi server-side (jangan andalkan HTML5
requiredsaja).
7.5 Fitur export/import baru
- Export: perpanjang
src/Export/CustomersExporter.php/TransactionsExporter.php— tambahheaders(),formatRow(),writeCsv(),buildSpreadsheet(); jangan tulis logika PhpSpreadsheet inline di API. Tambah kasus ditests/ExportTest.php(BOM, header, baris, round-trip XLSX). - Import: perpanjang
src/Import/SpreadsheetImporter.php(import()+ method privatereadCsv/mapColumns/cell/normalizeDate/normalizeAmount/upsertCustomer/logStart/logFinish); header fleksibel ID/EN; upsert perbusiness_id;beginTransaction/commit/rollBack; laporan sukses/gagal per baris. - Validasi upload: ekstensi + MIME
finfo_file(), ≤ 5MB, rename acak, simpan distorage/uploads/(terproteksi, lihat SECURITY.md §6).
7.6 Fitur AI (OpenAI)
api/generate-content.php+ai-content.php; logika terpusat disrc/Ai/ContentGenerator.php(generate(segment, ?\OpenAIClient), fallback dummy, persist,recent()).- key dari
env('OPENAI_API_KEY'); halaman/API yang memakainya wajibrequire config/openai.php(definisi\OpenAIClient) +require vendor/autoload.php. - Jangan commit key;
config/openai.example.phpadalah template. - Hitung/pantau token via
api_usage_logsbila relevan.
8. Refactor — Checklist per Area (WAJIB dicek sebelum mulai)
RFM (src/Rfm/RfmService.php, src/Rfm.php, analysis.php)
src/Rfm.phpadalah single source of truth (skor + segmentasi); SQL disrc/Rfm/RfmService.phpDIBANGUN dariApp\Rfm::*Sql(). Ubah logika di SATU tempat, dua-duanya (PHP & SQL) otomatis sinkron.- Setelah refactor, jalankan
composer test(RfmTest 125 kombinasi skor) dan test fungsional:(new App\Rfm\RfmService($db))->recalculate(1)→ cocokkanrfm_segmentvsApp\Rfm::segmentFromScores(). analysis.php& dashboard membaca tabelrfm_analysislangsung (skor sudah dipersist) — jangan pindahkan komputasi ke page-load.- JANGAN jalankan DELETE+INSERT massal di setiap page-load; hanya via tombol "Hitung Ulang RFM" (POST+CSRF) atau first-run saat data kosong.
Auth / session (config/auth.php)
- Jaga:
session_regenerate_id(true)saat login (guardheaders_sent()utk test CLI), cookie flags,hasRequiredRole()dipakairequireAuth&requireAuthJson. Updatetests/AuthManagerTest.phpbila logika berubah (login/session expiry/role check terhadap DB test). - Jangan ubah
AuthManagertanpa update test yang menyangkutnya.
Export (src/Export/*, api/export-*.php)
- Pertahankan: BOM UTF-8 di CSV, header kolom, format tanggal
d/m/Y, fallback'-', totalamount*qty.tests/ExportTest.phpmengunci format ini. - API export wajib tetap
requireAuthJson+ scope bisnis; jangan pindahkan query data bisnis keluar dari scope.
Pagination (includes/pagination.php, customers.php, transactions.php)
- Pertahankan query string
q/page;LIMIT/OFFSETinline(int)cast (bukan placeholder — lihat §2.1). - Statistik kartu = query agregat penuh (bukan dari array halaman aktif).
DB / query (database_*.sql, semua halaman)
- Prepared statements; jangan pecahkan index
idx_trans_biz_cust_date. - Pindah/rename tabel? Update semua query +
database_*.sql+ README.
Config / env (config/env.php, config/database*.php, config/openai*.php)
- Ubah kredensial →
config/database.php/openai.php(gitignored) +.envlokal; contoh di-commit =*.example.php+.env.example. - Tambah var env → update
.env.example+ README §Konfigurasi.
Upload (upload.php, api/upload-excel.php, src/Import/SpreadsheetImporter.php, src/Upload/UploadValidator.php)
- Jangan longgarkan validasi (MIME
finfo, 5MB, rename acak, folder terproteksi). - Error handling: jangan
echodetail koneksi/query ke output —error_log+ exception netral.
Rename/pindah file
- Cek referensi:
grep -rn "nama_file" --include="*.php" --include="*.md" .(sidebar, includes, docs,*_SUMMARY.md). Update semuanya dalam 1 commit.
9. Aturan Keamanan (ringkas)
- Prepared statements +
htmlspecialchars+ CSRF + API JSON auth (401/403) — semua wajib, jangan diregress. - Kredensial hanya via env/
.env; rotasi bila pernah bocor (prosedur:docs/SECURITY.md§7). composer auditharus tetap 0 advisory — jangan turunkan versi phpspreadsheet ke versi yang punya CVE (min. 1.30.6).- Perubahan perilaku keamanan → update
docs/SECURITY.md.
10. Status Plan & Sisa Item (konteks)
RENCANA_PERBAIKAN.md selesai (Fase 1–4, DoD semua centang). Sisa 2 item
sengaja didefer dan tercatat:
- CSP dasar (butuh refactor inline
<script>/CDN bertahap) — RENCANA 2.4 - Kunci
user_sessionske IP/UA ringan (risiko user IP dinamis) — RENCANA 2.2
Opsional di masa depan: purge blobs PDF lama dari history git
(git filter-repo, commit 3802c5c ke atas).