hanoman 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,429 @@
1
+ # hanoman — dokumentasi AI Agent
2
+
3
+ **Halaman ini ditulis untuk kamu, agen.** Kalau kamu diberi tautan ini dan satu **agent token**,
4
+ tak ada lagi yang perlu dijelaskan manusia: semua yang kamu butuhkan ada di bawah.
5
+
6
+ Naskah ini punya **satu sumber** dan tiga cara membacanya — isinya byte yang sama:
7
+
8
+ | Cara | Alamat |
9
+ |---|---|
10
+ | **markdown mentah** (paling berguna untukmu) | `GET $HANOMAN_HOST/api/agent-integration.md` — **publik, tanpa auth** |
11
+ | repo GitHub | [`docs/agent-integration.md`](https://github.com/denameidina/hanoman/blob/main/docs/agent-integration.md) |
12
+ | dashboard | Settings → **Dokumentasi AI Agent** |
13
+
14
+ > SPEC-257/265/489 · ADR-0065 (agent token & capability) · ADR-0099 (MCP server).
15
+
16
+ ---
17
+
18
+ ## 0. Apa itu hanoman, dan bagaimana ia bekerja
19
+
20
+ hanoman adalah **orchestrator + dashboard** untuk pengembangan yang digerakkan dokumentasi. Ia tidak
21
+ menulis kode sendiri; ia **menjalankan agen** (Claude Code atau Codex CLI) sebagai sesi interaktif,
22
+ lalu memantau semuanya dalam satu tempat.
23
+
24
+ Model kerjanya tiga tingkat, dan penting kamu pegang sebelum memanggil endpoint apa pun:
25
+
26
+ ```
27
+ backlog item (Spec) → sesi agen (tmux) → git worktree terisolasi
28
+ "SPEC-489" satu sesi per item <repo>/.worktrees/spec-489
29
+ ```
30
+
31
+ - **Backlog item** (`Spec`, id `SPEC-nnn`) adalah unit kerja: sebuah brief, temuan QA, laporan
32
+ audit, tiket Help Center, atau satu goal. Ia milik sebuah **project**.
33
+ - **Satu backlog = satu sesi.** Menekan Start dua kali bukan melahirkan sesi kedua — ia menyambung
34
+ ke sesi yang sudah ada.
35
+ - **Sesi hidup di worktree-nya sendiri**, bercabang dari branch basis, dan mendorong hasilnya ke
36
+ `hanoman/<id>`. Isolasi worktree itulah satu-satunya batas keamanan eksekusi di hanoman —
37
+ tak ada guardrail perintah.
38
+ - **Fase bukan proses, melainkan giliran** di dalam satu sesi: `Brainstorm → Objective → Spec →
39
+ Plan → Execute` (bervariasi per jenis kerja). Kemajuannya dibaca dari berkas fase, bukan dari
40
+ status proses.
41
+ - **Dokumentasi project (`internal/docs/**`) adalah Source of Truth.** Sebelum mengusulkan apa pun,
42
+ baca dokumen project-nya — itu sikap yang diharapkan dari agen di sini.
43
+
44
+ Dashboard React hanyalah **satu klien** dari REST API di bawah `/api`. Kamu memakai API yang persis
45
+ sama; yang berbeda hanya jalur auth-nya: **`Authorization: Bearer`**, bukan cookie sesi.
46
+
47
+ ## 1. Nyalakan akses & buat token (manusia, sekali)
48
+
49
+ Langkah ini **bukan** milikmu — mintalah ke manusia bila belum dilakukan. Di dashboard hanoman:
50
+ **Settings → Akses AI Agent**.
51
+
52
+ 1. **Aktifkan "Akses AI Agent"** (master switch). Selagi mati, *semua* agent token dibalas **401**
53
+ apa pun capability-nya.
54
+ 2. **Buat token:** beri nama (mis. `agent-ci`), centang **capability** yang dibutuhkan (baca/tulis
55
+ per domain), klik **Buat token**.
56
+ 3. **Salin token plaintext sekarang** — bentuknya `hnm_agt_<hex>` dan **hanya ditampilkan sekali**
57
+ (di server hanya `sha256` yang tersimpan). Simpan di rahasia agen (mis. env
58
+ `HANOMAN_AGENT_TOKEN`).
59
+
60
+ Cabut/nonaktifkan token atau matikan master switch kapan saja → efek **instan**.
61
+
62
+ ## 2. Base URL & autentikasi
63
+
64
+ ```bash
65
+ export HANOMAN_HOST="https://hanoman.example" # TANPA "/" di ekor
66
+ export HANOMAN_AGENT_TOKEN="hnm_agt_…" # dari langkah §1
67
+ ```
68
+
69
+ - **Seluruh path berawalan `/api`.** `$HANOMAN_HOST/api/specs`, bukan `$HANOMAN_HOST/specs`.
70
+ - **`HANOMAN_HOST` tanpa garis miring di ekor** — path di dokumen ini selalu dimulai dengan `/`,
71
+ jadi ekor ganda menghasilkan `//api/...` yang tak dikenal router.
72
+ - Sertakan token di **tiap** request:
73
+
74
+ ```
75
+ Authorization: Bearer hnm_agt_xxxxxxxxxxxx
76
+ ```
77
+
78
+ - Untuk **WebSocket** (terminal PTY, event stream) yang tak bisa memasang header dari browser,
79
+ kirim sebagai query: `?agent_token=hnm_agt_...`.
80
+ - **Token diterbitkan per-instance.** Token dari instance lain selalu 401 di sini.
81
+
82
+ ```bash
83
+ curl -s "$HANOMAN_HOST/api/specs" -H "Authorization: Bearer $HANOMAN_AGENT_TOKEN"
84
+ ```
85
+
86
+ **Probe host lebih dulu.** `GET /api/health` bersifat **publik** (tanpa auth), begitu pula halaman
87
+ ini (`GET /api/agent-integration.md`). Keduanya memisahkan tiga sebab yang tampak identik sebagai
88
+ "401 telanjang": host salah · master switch mati · token dicabut. Kalau `/api/health` menjawab 200,
89
+ host-mu benar dan masalahnya ada pada token atau master switch.
90
+
91
+ ## 3. Capability
92
+
93
+ Capability berformat `"<domain>:<access>"`, `access ∈ {read, write}`, dan **write meng-implikasikan
94
+ read** pada domain yang sama. Ada **12 domain × 2 = 24 capability**. Katalog resmi (dengan label &
95
+ deskripsi) tampil di panel **Settings → Akses AI Agent** saat manusia membuat token; endpoint
96
+ katalognya (`GET /api/agent-tokens/capabilities`) bersifat **cookie-only** (lihat §5) — kamu tak
97
+ perlu mengambilnya, cukup rujuk tabel di bawah:
98
+
99
+ | Domain | Cakupan endpoint | Catatan |
100
+ |---|---|---|
101
+ | `projects` | `/api/projects*` | project, branch, binding, Help Center |
102
+ | `backlog` | `/api/specs*` | spec/backlog, dokumen, review diff, integrate |
103
+ | `sessions` | `/api/terminal*` (+ WS terminal) | jalankan sesi agen/shell, kirim input — **high-risk (RCE)** |
104
+ | `docs` | `/api/prds*`, `/api/projects/:id/{docs,prds}*` | dokumen SoT project & PRD |
105
+ | `ide` | `/api/projects/:id/{tree,file,file-diff,working-status,graph,commit,git,status,stashes,remotes,compare,archive,pr-url}*` | tree/file working tree, operasi git |
106
+ | `vps` | `/api/vps*` | kelola VPS, audit, harden, konsol — **high-risk (remote exec)** |
107
+ | `settings` | `/api/settings*`, `/api/config*`, `/api/scheduler*` | setelan instance & config runtime |
108
+ | `support` | `/api/tickets*`, `/api/github-issues*`, `/api/projects/:id/github*` | tiket Help Center & issue GitHub (triase) |
109
+ | `notifications` | `/api/notifications*` | notifikasi |
110
+ | `lead` | `/api/lead*` | minta putusan ke hanoman-lead & baca jejaknya — **`lead:write` bisa menggerakkan sesi** (ADR-0091) |
111
+ | `agents` | `/api/custom-agents*` | katalog custom agent global & per project — **`agents:write` mengubah apa yang dilihat SETIAP sesi baru** (ADR-0094) |
112
+ | `telegram` | `/api/telegram*` kecuali sub-path kredensial | context/memory/reply/audit kanal operator Telegram (ADR-0096) |
113
+
114
+ Aturan pemetaan **deterministik** (`server/src/services/agent-capabilities.ts`): `GET`/`HEAD` →
115
+ `:read`, metode lain → `:write`. Itu berlaku untuk domain `lead` juga — **`POST /api/lead/decisions`
116
+ menuntut `lead:write`**, dan `lead:read` tak pernah cukup: meminta putusan melahirkan baris jejak
117
+ permanen dan keputusannya bisa menggerakkan sesi. Sub-path `/api/projects/:id/{docs,prds}` dihitung
118
+ domain **`docs`**; sub-path IDE/git di atas dihitung domain **`ide`**; WebSocket terminal butuh
119
+ **`sessions:write`**.
120
+
121
+ ## 4. Aturan gate & kode status
122
+
123
+ Gate `onRequest` yang sama menegakkan semuanya:
124
+
125
+ | Situasi | Balasan |
126
+ |---|---|
127
+ | Master switch mati, atau token invalid/nonaktif/dicabut | **401** `{ error: "unauthorized" }` |
128
+ | Token valid tapi capability kurang | **403** `{ error: "capability required", need: "<domain>:<access>" }` |
129
+ | Route cookie-only (§5) diakses agen | **403** `{ error: "cookie session required" }` |
130
+ | Capability cukup | request diproses seperti biasa |
131
+
132
+ Field **`need`** pada 403 memberi tahu capability persis yang harus ditambahkan ke token. Baca 403
133
+ seperti itu bukan sebagai "gagal" melainkan sebagai **instruksi**: sampaikan `need` ke manusia dan
134
+ minta capability itu ditambahkan di Settings.
135
+
136
+ ## 5. Yang tak bisa didelegasikan (cookie-only)
137
+
138
+ Untuk mencegah privilege-escalation, endpoint berikut **hanya** untuk sesi cookie manusia — agent
139
+ token selalu **403**, apa pun capability-nya, dan tak ada capability yang bisa membukanya:
140
+
141
+ - `/api/auth/*` — kelola user & password
142
+ - `/api/agent-tokens*` — agen tak boleh mencetak/menaikkan token sendiri
143
+ - `/api/device-tokens*`, `/api/sync*` — identitas mesin & sync hub
144
+ - `/api/webhooks*` — memegang secret penandatanganan **dan** menentukan ke mana data workspace
145
+ mengalir keluar (ADR-0100)
146
+ - `/api/telegram/settings`, `/api/telegram/test`, `/api/telegram/credentials` — permukaan
147
+ **kredensial** (bot token & agent token), beda dari sisa `/api/telegram*` (ADR-0097)
148
+ - `POST /api/update/apply` dan tulis lain di bawah prefix status (`/api/limits`, `/api/update`,
149
+ `/api/events`, `/api/fs`, `/api/health`) — **baca**-nya terbuka untuk token mana pun, **tulis**-nya
150
+ cookie-only
151
+
152
+ Route yang tak dikenal peta juga **default cookie-only** (aman): endpoint baru tak pernah terbuka
153
+ karena kelalaian. Endpoint `/api/help*` (Help Center publik) punya otorisasi sendiri (kunci tiket)
154
+ dan tak memakai agent token.
155
+
156
+ ## 6. Endpoint yang paling sering dipakai
157
+
158
+ | Method & path | Capability | Catatan |
159
+ |---|---|---|
160
+ | `GET /api/health` | — (publik) | probe host. Tanpa auth. |
161
+ | `GET /api/agent-integration.md` | — (publik) | halaman ini, markdown mentah. |
162
+ | `GET /api/projects` | `projects:read` | daftar project. `id` di sini yang dipakai `POST /api/specs`. |
163
+ | `GET /api/projects/:id` | `projects:read` | detail satu project. |
164
+ | `GET /api/specs` | `backlog:read` | backlog. Filter: `project`, `source`, `q`, `stage`, `priority`, `startable=true`, `dateField=created\|started` + `from`/`to` (`YYYY-MM-DD`, inklusif), `page`, `limit`. |
165
+ | `POST /api/specs` | `backlog:write` | buat backlog item — bentuk payload di §7. |
166
+ | `PATCH /api/specs/:id` | `backlog:write` | ubah item; konten hanya selagi belum dimulai. |
167
+ | `GET /api/specs/:id/docs` | `backlog:read` | dokumen yang ditulis sesi item itu. |
168
+ | `GET /api/specs/:id/review` | `backlog:read` | diff hasil kerja sesi. |
169
+ | `GET /api/projects/:id/docs` | `docs:read` | index Source of Truth project. |
170
+ | `GET /api/projects/:id/docs/<path>` | `docs:read` | isi satu dokumen. |
171
+ | `GET /api/terminal/sessions` | `sessions:read` | sesi yang sedang hidup. |
172
+ | `GET /api/notifications` | `notifications:read` | notifikasi. |
173
+ | `GET /api/tickets` | `support:read` | tiket Help Center. |
174
+ | `GET /api/lead/decisions` | `lead:read` | jejak keputusan hanoman-lead. |
175
+ | `POST /api/lead/decisions` | `lead:write` | minta putusan — baca **§8** dan **§11** dulu. |
176
+
177
+ ## 7. `POST /api/specs` — bentuk payload per `source`
178
+
179
+ `source` dan bentuk `payload` **saling mengikat**. Salah pasang → **400**
180
+ `"bentuk payload tak cocok dengan source"`. Union saja tak menjaganya (objek non-strict), jadi
181
+ server menegakkannya di boundary.
182
+
183
+ | `source` | Bentuk `payload` | Field |
184
+ |---|---|---|
185
+ | `brief` | brief | `context`, `outcome`, `constraints`, `priority` |
186
+ | `audit` | brief | idem — audit-only: hasilnya dokumen temuan, tanpa Execute |
187
+ | `help` | brief | idem — item yang lahir dari tiket Help Center |
188
+ | `qa` | qa | `severity` (`critical`\|`major`\|`minor`), `steps`, `expected`, `actual`, `env` |
189
+ | `goal` | goal | `goal` (wajib), `done`, `constraints`, `priority` |
190
+
191
+ Body lengkap: `project` (slug project), `source`, `title`, `priority`
192
+ (`tinggi`\|`sedang`\|`rendah`), `payload`; opsional `branchFrom` (branch basis — harus benar-benar
193
+ ada di repo project) dan `dependsOn` (array id backlog yang harus selesai & ter-merge lebih dulu).
194
+
195
+ Yang **tak** kamu kirim karena diturunkan server: `objective` (dari `outcome`/`context` untuk brief,
196
+ `actual`/`steps` untuk qa, `goal` untuk goal) dan — khusus `qa` — `priority`, yang diturunkan dari
197
+ `severity`.
198
+
199
+ ```json
200
+ {
201
+ "project": "hanoman",
202
+ "source": "qa",
203
+ "title": "Tombol Lanjutkan diam saat pane mati",
204
+ "priority": "tinggi",
205
+ "payload": {
206
+ "severity": "major",
207
+ "steps": "Buka Terminal → tunggu sesi keluar → klik Lanjutkan",
208
+ "expected": "Sesi dilanjutkan dari fase terakhir",
209
+ "actual": "Tak terjadi apa-apa",
210
+ "env": "hanoman 0.1.13, macOS"
211
+ }
212
+ }
213
+ ```
214
+
215
+ Balasannya **201** dengan seluruh baris `Spec`, termasuk `id` (`SPEC-nnn`) yang diterbitkan server.
216
+
217
+ ## 8. Tindakan berbahaya — wajib konfirmasi manusia
218
+
219
+ Tiga permukaan ini **wajib** kamu konfirmasikan ke manusia lebih dulu, walaupun token-mu sudah punya
220
+ capability-nya. Capability menjawab "boleh?", bukan "sebaiknya?".
221
+
222
+ | Tindakan | Kenapa |
223
+ |---|---|
224
+ | `POST /api/terminal/sessions` | melahirkan proses agen `--dangerously-skip-permissions` di sebuah worktree — **RCE efektif**. Batas satu-satunya adalah isolasi git worktree (ADR-0037). |
225
+ | `POST`/`PUT`/`DELETE` di bawah `/api/vps` | **remote exec** di server produksi. |
226
+ | `POST /api/lead/decisions` | putusannya bisa **menggerakkan sesi** (integrate ke main, menghentikan sesi) dan selalu melahirkan baris jejak permanen (ADR-0091/0098). |
227
+
228
+ Perlakukan `POST /api/specs/:id/integrate`, `DELETE /api/specs/:id`, dan perubahan `stage` dengan
229
+ disiplin yang sama: ketiganya mengubah sejarah git atau membuang pekerjaan.
230
+
231
+ **Preseden yang mengikat:** MCP server resmi (`hanoman mcp`, §13) sengaja **tak punya tool** untuk
232
+ satu pun dari yang di atas — batasnya ada di katalog tool, bukan di token. Token yang punya
233
+ `sessions:write` sekalipun tak akan menemukan tool untuk memakainya. Lewat REST kamu *bisa*
234
+ memanggilnya; jangan lakukan tanpa manusia.
235
+
236
+ ## 9. Jebakan yang sudah diketahui
237
+
238
+ | Jebakan | Yang benar |
239
+ |---|---|
240
+ | `startable` hanya bereaksi pada string **`"true"`**; nilai lain (`false`, `1`, `yes`) diabaikan **senyap** dan kamu menerima daftar penuh yang terlihat sah | kirim `?startable=true`, atau jangan kirim sama sekali |
241
+ | `q` mencari di `id`, `title`, dan `objective` saja — ia **tak menyentuh `payload`** | untuk mencari isi brief/QA, ambil itemnya lalu baca `payload` sendiri |
242
+ | `id` dan `stage` yang kamu sertakan di `POST /api/specs` **dibuang diam-diam** — tak ada galat | `id` diterbitkan server (`SPEC-nnn` berikutnya), `stage` selalu mulai `brainstorming`. Untuk mengubah stage pakai `PATCH /api/specs/:id`, dan ia hanya boleh **mundur** (ADR-0027) |
243
+ | **`GET /api/specs/:id` tidak ada** | `GET /api/specs?q=SPEC-489` lalu cocokkan `id` **persis** — `q` itu substring, jadi ia bisa mengembalikan lebih dari satu |
244
+ | daftar mengembalikan amplop `{ items, total, page, pageSize }` | jangan perlakukan responsnya sebagai array |
245
+ | tanpa `limit`, daftar mengembalikan **seluruh** item dalam satu halaman | kirim `limit` untuk backlog besar |
246
+ | `PATCH /api/specs/:id` menolak edit konten begitu item pernah dimulai | ubah `title`/`payload` hanya selagi item belum punya sesi |
247
+ | `branchFrom` yang tak ada di repo project → **400**, bukan diterima lalu gagal di tengah sesi | ambil kandidatnya dari `GET /api/projects/:id/branches` |
248
+ | **401 telanjang** tak memisahkan "host salah" dari "token salah" dari "master switch mati" | probe `GET /api/health` sekali: 200 = host benar → masalahnya token atau master switch |
249
+ | **403** bukan kegagalan permanen | bacalah field `need`, sampaikan ke manusia, minta capability itu ditambahkan |
250
+
251
+ ## 10. Contoh alur end-to-end
252
+
253
+ Bisa disalin apa adanya.
254
+
255
+ ```bash
256
+ export HANOMAN_HOST="https://hanoman.example" # tanpa "/" di ekor
257
+ export HANOMAN_AGENT_TOKEN="hnm_agt_…" # dari Settings → Akses AI Agent
258
+ auth=(-H "Authorization: Bearer $HANOMAN_AGENT_TOKEN")
259
+
260
+ # 0. Host benar? (publik, tanpa auth — memisahkan "host salah" dari "token salah")
261
+ curl -fsS "$HANOMAN_HOST/api/health"
262
+
263
+ # 0b. Baca halaman ini sendiri (publik, markdown mentah)
264
+ curl -fsS "$HANOMAN_HOST/api/agent-integration.md"
265
+
266
+ # 1. Project apa saja yang ada? (projects:read) — `id` di sini yang dipakai langkah berikutnya
267
+ curl -fsS "${auth[@]}" "$HANOMAN_HOST/api/projects"
268
+
269
+ # 2. Backlog yang belum selesai di satu project (backlog:read)
270
+ curl -fsS "${auth[@]}" "$HANOMAN_HOST/api/specs?project=hanoman&startable=true&limit=20"
271
+
272
+ # 3. Sudah ada item tentang "webhook"? (q = substring atas id+title+objective, BUKAN payload)
273
+ curl -fsS "${auth[@]}" "$HANOMAN_HOST/api/specs?project=hanoman&q=webhook"
274
+
275
+ # 4. Filekan temuan sebagai backlog item (backlog:write)
276
+ curl -fsS -X POST "$HANOMAN_HOST/api/specs" "${auth[@]}" \
277
+ -H "Content-Type: application/json" \
278
+ -d '{
279
+ "project": "hanoman",
280
+ "source": "qa",
281
+ "title": "Preview docs menggulir ke samping",
282
+ "priority": "sedang",
283
+ "payload": { "severity": "minor", "steps": "Buka Docs → pilih .md panjang",
284
+ "expected": "Teks membungkus", "actual": "Muncul scrollbar horizontal",
285
+ "env": "hanoman 0.1.13, Chrome" }
286
+ }'
287
+ # → 201 { "id": "SPEC-490", ... } ← id datang dari server; jangan pernah dikirim
288
+
289
+ # 5. Ambil satu item (tak ada GET /api/specs/:id — pakai q lalu cocokkan persis)
290
+ curl -fsS "${auth[@]}" "$HANOMAN_HOST/api/specs?q=SPEC-490" \
291
+ | python3 -c 'import json,sys; print([s for s in json.load(sys.stdin)["items"] if s["id"]=="SPEC-490"])'
292
+
293
+ # 6. Baca Source of Truth project sebelum mengusulkan apa pun (docs:read)
294
+ curl -fsS "${auth[@]}" "$HANOMAN_HOST/api/projects/hanoman/docs"
295
+
296
+ # 7. 403? Bacanya bukan "gagal" — bacanya "tambahkan capability ini ke token":
297
+ # { "error": "capability required", "need": "backlog:write" }
298
+ ```
299
+
300
+ **Yang TIDAK kamu lakukan tanpa manusia:** menjalankan backlog itu
301
+ (`POST /api/terminal/sessions`) — lihat §8.
302
+
303
+ ## 11. Minta putusan ke hanoman-lead
304
+
305
+ Agen yang menemui persimpangan tak selalu harus berhenti menunggu manusia — bila project-nya
306
+ meng-opt-in **hanoman-lead**, ia boleh **meminta putusan**:
307
+
308
+ ```bash
309
+ curl -s -X POST "$HANOMAN_HOST/api/lead/decisions" \
310
+ -H "Authorization: Bearer $HANOMAN_AGENT_TOKEN" \
311
+ -H "Content-Type: application/json" \
312
+ -d '{
313
+ "projectId": "hanoman",
314
+ "specId": "SPEC-409",
315
+ "question": "Tambah kolom baru di Spec, atau turunkan dari updatedAt?",
316
+ "options": ["kolom baru", "turunkan dari updatedAt"],
317
+ "context": "Filter rentang tanggal butuh waktu item dibuat."
318
+ }'
319
+ # 201 { id, decision, reason, refs: ["ADR-0090", "internal/docs/..."], confidence: "tinggi", action: "none" }
320
+ ```
321
+
322
+ Jawabannya **terbaca mesin**, bukan prosa bebas, dan `refs` hanya memuat rujukan yang benar-benar
323
+ ada di repo — jadi kamu bisa memverifikasi sendiri dasar keputusannya. `confidence: "ragu"` berarti
324
+ lead tetap memutuskan tapi memilih opsi yang paling mudah dibatalkan, dan operator sudah
325
+ dinotifikasi.
326
+
327
+ Kode balasan yang perlu ditangani:
328
+
329
+ | Kode | Artinya |
330
+ |---|---|
331
+ | **409** | lead tak aktif / project belum opt-in → **kembali ke perilaku lama**: berhenti & tunggu manusia |
332
+ | **503** + `Retry-After` | lead sedang penuh (batas konkurensi) → **boleh diulang** sesudah jeda; ini bukan kegagalan lead |
333
+ | **504** | lead tak berhasil memutuskan dalam batas waktu; kegagalannya sudah tercatat & dinotifikasi |
334
+ | **403** `{ need: "lead:write" }` | token cuma punya `lead:read` |
335
+
336
+ Ingat §8: permintaan putusan **bisa menggerakkan sesi**. Konfirmasikan ke manusia dulu.
337
+
338
+ ## 12. Keamanan
339
+
340
+ - Token = rahasia. Simpan di env/secret manager, **jangan commit**, dan **jangan pernah** berikan
341
+ lewat argumen baris perintah — ARGV terbaca proses lain di mesin yang sama. Bocor → **Cabut** di
342
+ Settings (efek instan).
343
+ - Beri capability **seminimal** mungkin. `sessions:write` (spawn agen
344
+ `--dangerously-skip-permissions`) dan `vps:write` (remote exec) adalah RCE efektif — batas
345
+ eksekusi sesungguhnya tetap **isolasi git worktree** (ADR-0037), tapi tetap tandai high-risk.
346
+ - `lastUsedAt` per token = jejak audit ringan. Matikan master switch untuk kill-switch seluruh
347
+ workspace.
348
+ - Halaman ini sendiri **tak pernah memuat token nyata** — hanya format/placeholder. Kalau kamu
349
+ melihat sesuatu yang menyerupai token asli di sini, itu bug; laporkan.
350
+
351
+ ## 13. MCP server (ADR-0099)
352
+
353
+ Agen yang berbicara **MCP** tak perlu menulis pembungkus sendiri. `hanoman mcp` adalah MCP server
354
+ **stdio** yang membungkus permukaan REST di atas sebagai **17 tool**. Ia memakai **agent token dan
355
+ capability yang sama** — bukan jalur otorisasi baru — jadi seluruh aturan §3–§5 berlaku apa adanya.
356
+
357
+ Prasyarat: `npm i -g hanoman` di mesin tempat klien AI-nya jalan.
358
+
359
+ **Claude Code / Claude Desktop / Cursor / Copilot** (`~/.claude.json`,
360
+ `claude_desktop_config.json`, `~/.cursor/mcp.json`, `.vscode/mcp.json` — Cursor & Copilot memakai
361
+ kunci `"servers"` alih-alih `"mcpServers"`):
362
+
363
+ ```json
364
+ {
365
+ "mcpServers": {
366
+ "hanoman": {
367
+ "command": "hanoman",
368
+ "args": ["mcp"],
369
+ "env": {
370
+ "HANOMAN_HOST": "https://hanoman.example",
371
+ "HANOMAN_AGENT_TOKEN": "hnm_agt_…"
372
+ }
373
+ }
374
+ }
375
+ }
376
+ ```
377
+
378
+ **Codex** (`~/.codex/config.toml`):
379
+
380
+ ```toml
381
+ [mcp_servers.hanoman]
382
+ command = "hanoman"
383
+ args = ["mcp"]
384
+ env = { HANOMAN_HOST = "https://hanoman.example", HANOMAN_AGENT_TOKEN = "hnm_agt_…" }
385
+ ```
386
+
387
+ Panduan siap salin untuk keempat klien, berikut tabel tool → capability, ada di dashboard:
388
+ **Settings → Akses AI Agent → MCP server**.
389
+
390
+ ### Tool
391
+
392
+ | Tool | Mode | Capability |
393
+ |---|---|---|
394
+ | `hanoman_about` | baca | — (tak memanggil `/api` selain `/health`) |
395
+ | `hanoman_projects_list`, `hanoman_project_get` | baca | `projects:read` |
396
+ | `hanoman_backlog_search`, `hanoman_backlog_get`, `hanoman_backlog_docs_list`, `hanoman_backlog_doc_read` | baca | `backlog:read` |
397
+ | `hanoman_sessions_list` | baca | `sessions:read` |
398
+ | `hanoman_notifications_list` | baca | `notifications:read` |
399
+ | `hanoman_tickets_list`, `hanoman_ticket_get`, `hanoman_github_issues_list` | baca | `support:read` |
400
+ | `hanoman_lead_decisions_list` | baca | `lead:read` |
401
+ | `hanoman_backlog_create`, `hanoman_backlog_update` | tulis | `backlog:write` |
402
+ | `hanoman_notifications_mark_read` | tulis | `notifications:write` |
403
+ | `hanoman_lead_ask` | tulis | `lead:write` |
404
+
405
+ ### Yang sengaja TIDAK tersedia lewat MCP
406
+
407
+ Membuat sesi terminal (`POST /api/terminal/sessions` — menjalankan agen di worktree, RCE efektif)
408
+ dan seluruh `/api/vps*` (remote exec) **tidak ikut**, begitu pula merge/rebase (`integrate`),
409
+ penghapusan backlog, dan perubahan `stage`. Batasan ini ada di katalog toolnya, bukan di token:
410
+ token yang punya `sessions:write` sekalipun tak akan menemukan tool untuk memakainya. Lihat §8.
411
+
412
+ ### Opsi
413
+
414
+ | Variabel / flag | Arti |
415
+ |---|---|
416
+ | `HANOMAN_HOST` / `--host <url>` | **Wajib.** Instance yang dituju. Agent token diterbitkan per-instance — token dari instance lain selalu 401 di sini, dan MCP server menjelaskannya, bukan meneruskan 401 telanjang. |
417
+ | `HANOMAN_AGENT_TOKEN` | **Wajib.** Hanya dari env atau `~/.hanoman/agent-token` — **tak pernah** dari argumen baris perintah (ARGV terbaca proses lain di mesin yang sama). |
418
+ | `HANOMAN_MCP_READ_ONLY=1` / `--read-only` | Menyembunyikan seluruh tool tulis dari `tools/list`. |
419
+ | `HANOMAN_MCP_MAX_BYTES` / `--max-bytes <n>` | Plafon ukuran balasan tool. Default 24576. Balasan yang dipotong ditandai `truncated: true` + `shown`/`total`. |
420
+
421
+ Skema tool berversi (`MCP_TOOL_SCHEMA_VERSION`, saat ini **1**) dan aditif dalam satu versi:
422
+ menambah tool tak mematahkan klien lama.
423
+
424
+ ---
425
+
426
+ *Doc-of-record fitur: [ADR-0065](../internal/docs/adr/0065-ai-agent-capability-agent-token.md) dan,
427
+ untuk permukaan MCP, [ADR-0099](../internal/docs/adr/0099-mcp-server-hanoman.md). Kontrak API penuh:
428
+ [`internal/docs/architecture/api-contract.md`](../internal/docs/architecture/api-contract.md) —
429
+ permukaan REST-nya identik dengan yang dipakai dashboard.*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanoman",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Orchestrator + dashboard workflow docs-driven untuk sesi Claude Code / Codex",
5
5
  "type": "module",
6
6
  "repository": {
@@ -22,6 +22,7 @@
22
22
  "dist",
23
23
  "web",
24
24
  "prisma",
25
+ "docs",
25
26
  "README.md",
26
27
  "LICENSE"
27
28
  ],
@@ -0,0 +1,8 @@
1
+ -- SPEC-486 · ADR-0103 · kebijakan auto-merge saat sesi selesai.
2
+ --
3
+ -- ADITIF & nullable tanpa default → `ADD COLUMN` polos (pola migration SPEC-447). Yang dilarang
4
+ -- SQLite adalah `ADD COLUMN … DEFAULT <non-konstan>`, bukan `ADD COLUMN` itu sendiri. Baris lama
5
+ -- tetap NULL, dan NULL berarti "tanpa auto-merge" di Project / "warisi project" di Spec —
6
+ -- jadi tak ada backfill, dan project lama tak berubah perilaku satu langkah git pun.
7
+ ALTER TABLE "Project" ADD COLUMN "autoMerge" JSONB;
8
+ ALTER TABLE "Spec" ADD COLUMN "autoMerge" JSONB;
@@ -0,0 +1,8 @@
1
+ -- SPEC-484 · ADR-0101 · penyaring mesin sesi per definisi agen.
2
+ --
3
+ -- NULLABLE TANPA DEFAULT, dan itu yang membuat migration ini satu baris: setiap baris lama tetap
4
+ -- NULL = "ikut sesi induk" = perilaku ADR-0094 apa adanya, jadi tak ada backfill dan tak ada satu
5
+ -- pun roster yang berubah saat rilis ini mendarat. Larangan SQLite atas
6
+ -- `ADD COLUMN … DEFAULT CURRENT_TIMESTAMP` (migration SPEC-408) tak berlaku di sini justru karena
7
+ -- kolomnya tanpa default — tabel tak perlu diredefinisi.
8
+ ALTER TABLE "CustomAgent" ADD COLUMN "runtime" TEXT;
@@ -0,0 +1,36 @@
1
+ -- SPEC-485 · ADR-0102 · rantai keputusan lead + pilihan jamak.
2
+ --
3
+ -- Ditulis tangan (bukan `migrate dev`): worktree tetangga membuat `migrate dev` me-reset DB saat
4
+ -- ada drift. ADITIF murni — satu tabel baru + empat kolom NULLABLE tanpa default, tak ada tabel
5
+ -- diredefinisi, tak ada baris disentuh. Larangan SQLite atas `ADD COLUMN … DEFAULT <non-konstan>`
6
+ -- (lihat migration SPEC-408) tak berlaku di sini karena tak ada default sama sekali pada ALTER.
7
+ --
8
+ -- `LeadFlow` LOCAL-only: tanpa kolom `version`, tak pernah masuk changefeed sync — barisnya
9
+ -- menunjuk sesi tmux & worktree di MESIN INI, persis alasan `LeadDecision` juga tak disync.
10
+ CREATE TABLE "LeadFlow" (
11
+ "id" TEXT NOT NULL PRIMARY KEY,
12
+ "projectId" TEXT NOT NULL,
13
+ "specId" TEXT,
14
+ "sessionId" TEXT,
15
+ "gate" TEXT NOT NULL,
16
+ "status" TEXT NOT NULL DEFAULT 'menunggu',
17
+ "title" TEXT NOT NULL,
18
+ "steps" INTEGER NOT NULL DEFAULT 0,
19
+ "closeReason" TEXT,
20
+ "openedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
21
+ "closedAt" DATETIME,
22
+ "expiresAt" DATETIME NOT NULL,
23
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
24
+ "updatedAt" DATETIME NOT NULL
25
+ );
26
+ CREATE INDEX "LeadFlow_projectId_createdAt_idx" ON "LeadFlow"("projectId", "createdAt");
27
+ CREATE INDEX "LeadFlow_status_idx" ON "LeadFlow"("status");
28
+
29
+ -- `choices` menggantikan pasangan skalar `choice`/`choiceIndex` sebagai bentuk yang berlaku; yang
30
+ -- lama DIPERTAHANKAN dan diisi dari `choices[0]` supaya pembaca lama (dan baris pra-migrasi) tetap
31
+ -- terbaca tanpa backfill apa pun.
32
+ ALTER TABLE "LeadDecision" ADD COLUMN "flowId" TEXT;
33
+ ALTER TABLE "LeadDecision" ADD COLUMN "step" INTEGER;
34
+ ALTER TABLE "LeadDecision" ADD COLUMN "choices" JSONB;
35
+ ALTER TABLE "LeadDecision" ADD COLUMN "select" JSONB;
36
+ CREATE INDEX "LeadDecision_flowId_idx" ON "LeadDecision"("flowId");
@@ -21,6 +21,9 @@ model Project {
21
21
  helpEnabled Boolean @default(false) // SPEC-253 · opt-in Help Center publik
22
22
  schedulerOptIn Boolean @default(false) // SPEC-294 · ADR-0072 · opt-in scheduler otonom (LOCAL — tak masuk FIELDS sync)
23
23
  leadOptIn Boolean @default(false) // SPEC-409 · ADR-0091 · opt-in hanoman-lead (LOCAL — cermin schedulerOptIn)
24
+ // SPEC-486 · ADR-0103 · kebijakan auto-merge saat backlog item selesai (LOCAL — tak masuk
25
+ // FIELDS sync, cermin repoDir/schedulerOptIn). null = tanpa auto-merge (default, nol backfill).
26
+ autoMerge Json?
24
27
  specs Spec[]
25
28
  tickets Ticket[]
26
29
  customAgents CustomAgent[] // SPEC-450 · ADR-0094 · custom agent milik project ini
@@ -54,6 +57,9 @@ model Spec {
54
57
  // dan kolom ikut FIELDS.spec sync apa adanya. Integritas ditegakkan di boundary route +
55
58
  // pembersihan saat spec dihapus, bukan FK.
56
59
  dependsOn Json?
60
+ // SPEC-486 · ADR-0103 · override kebijakan auto-merge untuk item ini. null = warisi project.
61
+ // `Json?` dengan alasan yang sama seperti dependsOn: blok dibaca utuh, tak pernah difilter.
62
+ autoMerge Json?
57
63
  updatedAt DateTime @updatedAt
58
64
  project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
59
65
  }
@@ -82,6 +88,7 @@ model CustomAgent {
82
88
  tools Json?
83
89
  model String?
84
90
  mentions Json?
91
+ runtime String? // SPEC-484 · ADR-0101 · penyaring mesin sesi; null = ikut sesi induk
85
92
  enabled Boolean @default(true)
86
93
  version Int @default(0) // SPEC-213 · version-stamp sync (ADR-0045)
87
94
  createdAt DateTime @default(now())
@@ -371,6 +378,33 @@ model SchedulerQueueItem {
371
378
  @@index([status])
372
379
  }
373
380
 
381
+ // SPEC-485 · ADR-0102 · satu RANTAI keputusan lead: beberapa pertanyaan berurutan yang satu urusan,
382
+ // dari pertanyaan pertama sampai submit akhir. Sebelum model ini "alur" hanya ada sebagai kebetulan
383
+ // (baris jejak yang berdekatan waktunya), jadi tak ada tempat untuk menegakkan "pertanyaan lanjutan
384
+ // hanya boleh masuk ke alur yang masih aktif" dan tak ada yang bisa ditanya "sudah di-submit belum".
385
+ //
386
+ // LOCAL-ONLY (tanpa `version`, tak pernah disync) dan TANPA FK — cermin LeadDecision di bawah:
387
+ // id project/spec/sesi boleh lenyap tanpa menyeret jejaknya.
388
+ model LeadFlow {
389
+ id String @id @default(cuid())
390
+ projectId String
391
+ specId String?
392
+ sessionId String?
393
+ gate String // pintu yang membuka rantai (contract|detected|pulse)
394
+ status String @default("menunggu") // menunggu | sebagian | selesai | dibatalkan
395
+ title String // pertanyaan pertama, terpangkas — ini yang dibaca operator
396
+ steps Int @default(0)
397
+ closeReason String? // tunggal | submit | operator | kedaluwarsa
398
+ openedAt DateTime @default(now())
399
+ closedAt DateTime?
400
+ expiresAt DateTime // pagar anti-menggantung; penyapunya menumpang tick lead
401
+ createdAt DateTime @default(now())
402
+ updatedAt DateTime @updatedAt
403
+
404
+ @@index([projectId, createdAt])
405
+ @@index([status])
406
+ }
407
+
374
408
  // SPEC-409 · ADR-0091 · jejak keputusan hanoman-lead. Data BARU yang harus bertahan (bukan nilai
375
409
  // turunan seperti coverage/PRD, ADR-0011/0018): pertanyaan yang ditanyakan sesi yang sudah mati,
376
410
  // dan alasan yang dipakai lead, tak bisa dihitung ulang dari sumber mana pun.
@@ -402,12 +436,20 @@ model LeadDecision {
402
436
  choiceIndex Int? // 1-basis, sepasang dengan `choice`
403
437
  options Json? // string[] — daftar opsi yang DIKIRIM peminta, supaya jejak bisa diaudit
404
438
  missing Json? // string[] — apa yang kurang bila lead menyatakan konteksnya tak cukup
439
+ // SPEC-485 · ADR-0102 · aditif & nullable: baris lama sah apa adanya. `choices` adalah bentuk
440
+ // penyimpanan yang BERLAKU; `choice`/`choiceIndex` di atas tinggal turunan `choices[0]`, dan
441
+ // baris pra-migrasi diturunkan balik jadi `choices` saat dibaca (kompatibilitas mundur).
442
+ flowId String? // rantai tempat langkah ini duduk
443
+ step Int? // urutan langkah dalam rantai, 1-basis
444
+ choices Json? // {index,option}[] — SELALU daftar
445
+ select Json? // {mode,min,max} sebagaimana dikirim peminta
405
446
  createdAt DateTime @default(now())
406
447
  updatedAt DateTime @updatedAt
407
448
 
408
449
  @@index([projectId, createdAt])
409
450
  @@index([specId])
410
451
  @@index([sessionId])
452
+ @@index([flowId])
411
453
  }
412
454
 
413
455
  // SPEC-215 · ADR-0049 · LOCAL-ONLY: override config runtime (env → DB). Tak pernah disync.