refactor(docs): Revise agent guide for clarity and structure

This commit is contained in:
asepharyana
2026-09-11 18:56:28 +07:00
parent 43f2f8449d
commit e0a383c5b3
+86 -165
View File
@@ -1,179 +1,100 @@
# GMW — Agent Development Guide
# GMW — Agent Guide
> **Rule #1: No code without a spec.**
> Setiap perubahan signifikan dimulai dari spec di `docs/specs/`.
> Baca spec → verifikasi facts → implement → verify → commit.
> Gunakan `todo.md` (TODO list terdokumentasi) untuk melacak progress.
GMW (Guild Moderation Watcher) is a Discord bot + web dashboard for AI-powered moderation. A monorepo with three services: a selfbot gateway that captures messages/voice and runs LLM moderation, an Express/oRPC backend that serves the dashboard API, and a Next.js 16 SSR frontend. They communicate via Redis pub/sub (gateway→backend) and WebSocket (backend→browser).
## Project overview
GMW (Go Mod Watch) adalah Discord bot + dashboard untuk AI-powered moderation.
Monorepo berisi 3 service utama:
| Service | Path | Port | Tech |
|---|---|---|---|
| **discord-gateway** | `services/discord-gateway/` | 4016 (metrics) | discord.js-selfbot-v13, Piscina, Drizzle ORM, pino |
| **backend** | `services/backend/` | 4001 | Express, oRPC, Drizzle ORM, Redis pub/sub, Vitest |
| **frontend** | `services/frontend/` | 4017 (standalone) | Next.js 16 App Router, React 19, Tailwind v4, SWR |
## Data flow
```
Discord → discord-gateway → Redis pub/sub → backend (:4001) ←→ frontend Next.js SSR
↑ REST /api/*
└ WS /ws
```
- Gateway = event-driven (no HTTP, except Prometheus :4016/metrics)
- Backend = HTTP + WebSocket server, serves the frontend
- Frontend = SSR (RSC) + client hydration, proxied by nginx :4009
## Konvensi dokumentasi: `docs/`
Semua dokumen kerja ada di `docs/`, bukan di `.hermes/`:
```
docs/
├── README.md # Panduan workflow (spec-driven + todo)
├── spec-template.md # Template spec standar
├── todo-template.md # Template todo list
└── specs/ # Semua spec & implementation plan
├── YYYY-MM-DD_<slug>-spec.md # Spec (apa & mengapa)
└── YYYY-MM-DD_<slug>.md # Plan/fix (spec + plan dalam satu file)
```
Service-specific docs: `services/<service>/docs/specs/`.
## Workflow: Spec-Driven Development
### Checklist wajib untuk setiap perubahan signifikan
1. **Tulis spec** → `docs/specs/YYYY-MM-DD_<slug>-spec.md`
2. **Tulis `todo.md`** → daftar task konkret yang bisa diceklis
3. **Verifikasi facts** → baca kode aktual, konfirmasi referensi file:line
4. **Keputusan desain** → pilih approach, dokumentasikan alternatif yang ditolak
5. **Implementasi** → ikuti spec + todo step-by-step
6. **Verifikasi** → jalankan semua verification steps dari spec
7. **Commit** → reference spec di commit message
### Todo list (`todo.md`)
Setiap tugas berjalan WAJIB punya `todo.md`. Format:
```markdown
# Todo — <judul tugas>
## Task
- [ ] Tulis spec
- [ ] Verifikasi facts (baca kode: file:line)
- [ ] Implementasi tahap 1: ...
- [ ] Implementasi tahap 2: ...
- [ ] Verifikasi: pnpm typecheck / lint / build / test
- [ ] Commit + push
```
Aturan:
- Task harus **konkret & verifiable** — bukan "fix bug", tapi "ubah X di file Y".
- Ceklis `[x]` saat selesai, jangan menunggu batch di akhir.
- Gunakan `docs/todo-template.md` sebagai template.
- Simpan `todo.md` di `docs/` untuk tugas lintas-service, atau di
`services/<service>/docs/` untuk tugas satu service.
### Spec template
Salin `docs/spec-template.md` untuk setiap spec baru. Sections:
1. **Problem** — apa yang rusak/missing, dengan evidence
2. **Root cause** — analisis teknis (bukan symptom)
3. **Behavior target** — perilaku setelah fix, daftar verifiable
4. **Verified facts** — fakta dari pembacaan kode, citation ke file:line
5. **Keputusan desain** — pilihan + rationale + alternatif ditolak
6. **Perubahan file** — semua file yang disentuh, per service
7. **Schema/type changes** — perubahan tipe/DB
8. **Verification** — command executable + expected outcome
### Kapan perlu spec + todo.md
Perlu: fitur baru, bug fix non-trivial, refactor behavior-changing, perubahan
DB schema, perubahan API contract, perubahan arsitektur.
Tidak perlu: typo fix, dep bump, format/lint auto-fix, test-only, README update.
Baca selengkapnya di `docs/README.md`.
## Coding conventions
### General
- **TypeScript strict mode** — semua service
- **Biome** — formatting + linting (`pnpm format`, `pnpm lint`)
- **Bun** — package manager (`bun install`, `bun run`)
- **Bisa bilingual** — code comments & specs boleh Indonesia/English
### Per-service conventions
#### discord-gateway
- **Event-driven** — no HTTP server (except metrics). Listeners → Redis pub/sub.
- **Module pattern**: `src/modules/<module>/` — each module encapsulates own logic.
- **Piscina pools**: text pool (4 threads) + media pool (2 threads), each with own pg Pool.
- **Logger**: `createChildLogger('module-name')` — never raw `console`.
- **Config**: Zod-validated env in `shared/config/index.ts` — single source of truth.
- **DB**: Drizzle ORM. Migrations in `drizzle/migrations/`.
- **Invariant**: LLM is the only judge. Never reintroduce regex content classification.
- **Invariant**: Discord tokens sanitized before reaching LLM.
- Lihat `services/discord-gateway/AGENTS.md` untuk detail.
#### backend
- **Modular MVC**: `modules/<module>/` — schema → repository → service → controller → routes.
- **No cross-module repo imports** — each module owns its data.
- **Data flows up only**: Repository → Service → Controller.
- **Error hierarchy**: `AppError` subclasses with code + statusCode.
- **Config**: Zod-validated env in `shared/config/index.ts`.
- **API**: oRPC for type-safe procedures + standard Express routes.
- Lihat `services/backend/AGENTS.md` untuk detail.
#### frontend
- **SSR-first**: `page.tsx` (server component) → fetch via `src/lib/api/server.ts` → pass to `view.tsx` (client).
- **No auth**: all endpoints public.
- **Never hardcode host**: same-origin or `GMW_BACKEND_URL` only.
- **WebSocket**: `src/lib/ws/` — auto-reconnecting, typed events.
- **Local dev**: `NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_WS_URL`, `GMW_BACKEND_URL`.
- Lihat `services/frontend/AGENTS.md` untuk detail Next.js rules.
## Build & Verify
## Quick reference
```bash
# Per service (run from service root)
pnpm typecheck # TypeScript strict
pnpm lint # Biome check
pnpm build # Compile
pnpm test # Vitest (gateway & backend only)
pnpm format # Biome auto-format
# Per-service — cd into the service first
pnpm install # install deps (pnpm 11, not npm or bun)
pnpm typecheck # tsc --noEmit
pnpm lint # biome check
pnpm format # biome format --write
pnpm build # gateway/backend: tsc + fix-imports.mjs; frontend: next build
pnpm test # vitest run (gateway & backend only — frontend has no tests)
```
## Commit conventions
No monorepo-level scripts exist. Run each command from inside the service directory.
## Layout
```
<type>(<scope>): <subject>
<optional body>
Ref: docs/specs/<spec-file>.md
services/
├── discord-gateway/ Event-driven selfbot. No HTTP (except :4016 metrics).
│ ├── src/
│ │ ├── app/ Bootstrap, shutdown, retention
│ │ ├── modules/ Feature modules — each self-contained
│ │ └── shared/ Config, DB (Drizzle), logger, errors, utils
│ ├── tests/ Vitest tests (colocated, not inside src/)
│ ├── drizzle/migrations/ DB migrations
│ └── scripts/fix-imports.mjs Post-build: rewrites @/ aliases → relative .js
│
├── backend/ Express HTTP + WebSocket + oRPC server (:4001).
│ ├── src/
│ │ ├── modules/ Feature modules (schema→repo→service→controller→routes)
│ │ ├── http/ Express app setup
│ │ ├── ws/ WebSocket server + Redis bridge
│ │ ├── orpc/ oRPC router definition
│ │ └── shared/ Config, DB, errors, Redis, logger
│ └── tests/ Vitest tests
│
└── frontend/ Next.js 16 App Router, React 19, Tailwind v4 (:4017).
├── src/
│ ├── app/ Pages — route groups under (dashboard)/
│ ├── components/ UI components (primitives, shell, charts, etc.)
│ ├── hooks/ React hooks
│ └── lib/ API clients, types, utils, WebSocket, audio
└── pnpm-workspace.yaml Build-script approvals (sharp only)
```
Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `build`, `ci`
## Conventions
## Deployment
### Package manager & runtime
CI/CD: GitHub Actions → build → deploy to production server via Nix flakes.
- Gateway: `nixos-rebuild` or `systemctl restart gmw-discord-gateway`
- Backend: `nixos-rebuild` or `systemctl restart gmw-backend`
- Frontend: Next.js standalone, proxied by nginx :4009
- **pnpm** (v11), not npm or bun. Lockfiles are committed. Node ≥ 22.
- ESM throughout (`"type": "module"` in all package.json files).
## Remember
### Import style
- Spec dulu, code belakangan.
- Todo list (`todo.md`) wajib untuk tugas yang berjalan — ceklis tiap selesai.
- Verified facts harus dari pembacaan kode aktual, bukan asumsi.
- Setiap change harus verifiable — tulis command di spec.
- One spec = one focused change. Don't mix unrelated features.
Source files use `@/*` path aliases (mapped in tsconfig to `./src/*`). Relative imports **must include the `.js` extension** (e.g., `from "./embed.js"`). The `moduleResolution: "bundler"` tsconfig setting allows bare specifiers during dev, but `tsc` emits them as-is. A post-build script (`scripts/fix-imports.mjs`) rewrites both `@/` aliases and extensionless imports in `dist/` so Node ESM can resolve them at runtime.
### Error handling
Both gateway and backend define an `AppError` base class in `@/shared/errors/index` with subclasses: `ValidationError` (400), `NotFoundError` (404), `UnauthorizedError` (401), `DatabaseError` (500), `ConfigError` (500). Services throw these; callers or middleware map them to HTTP status codes.
### Logging
Use `createChildLogger('module-name')` from `@/shared/logger/index`. Never use raw `console`. It wraps pino; in development it pretty-prints via `pino-pretty`.
### Config
Environment variables are validated with Zod at startup in `shared/config/index.ts` of both gateway and backend. Do not read `process.env` directly outside config modules.
### Module boundaries
- Gateway: each feature lives in `src/modules/<name>/` with its own `index.ts` barrel. Modules register event listeners and are composed in `src/app/bootstrap.ts`.
- Backend: `modules/<name>/` follows schema → repository → service → controller → routes. Data flows up only. No cross-module repository imports.
- Frontend: `page.tsx` is a server component that fetches data via `src/lib/api/server.ts` (oRPC over HTTP, server-side only) and passes it to a `view.tsx` client component. Browser code uses `src/lib/orpc/client.ts` (oRPC over WebSocket via partysocket). Never import the server API client from a client component.
### API layer
The backend exposes an oRPC router mounted at `/trpc` (both HTTP and WebSocket). The frontend does **not** use a REST `/api/*` layer — all data goes through oRPC procedures. The server-side client (`src/lib/api/server.ts`) uses a fetch-based RPCLink; the browser client uses a WebSocket-based RPCLink backed by partysocket for auto-reconnection. Results are asserted to the frontend's local types at each call site (the backend's router type is not imported into the frontend).
### Testing
- **Gateway**: Vitest, tests in `tests/` at the service root. Config sets env vars (`DISCORD_TOKEN`, `DATABASE_URL`, etc.) so tests run without real services.
- **Backend**: Vitest, tests in `tests/` and `src/`. Includes an `e2e.test.ts` excluded from CI (needs a live backend).
- **Frontend**: No test runner configured.
- Tests are pure-function / unit-level. Mock external dependencies; do not start real DB/Redis in tests.
### Formatting
Biome, 2-space indent, spaces. Config at repo root `biome.json`. `lint` uses `--diagnostic-level=error`; `format` auto-writes.
## Pitfalls
1. **Never commit without running `fix-imports.mjs` after `tsc`** — gateway and backend builds will produce ESM that crashes at startup (`ERR_MODULE_NOT_FOUND`).
2. **Don't add `@discordjs/opus` build-from-source flags** — it ships prebuilt binaries for Node 22. Forcing source builds in CI/Nix will fail or add minutes of compile time.
3. **Frontend SSR fetches are always `cache: "no-store"`** — the server API client bypasses Next.js fetch cache. Do not add caching without understanding the live dashboard requirement.
4. **oRPC types are loosely coupled** — the frontend casts oRPC results to its own types with `as unknown`. Adding a field to the backend schema does not automatically update the frontend type. Update both sides.
5. **Gateway is a selfbot** (`discord.js-selfbot-v13`) — it uses a user token, not a bot token. It must not be deployed as a standard Discord bot.