hanoman 0.9.28 → 0.9.29
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.
- package/README.md +5 -2
- package/dist/build-info.json +3 -3
- package/dist/cli.js +337 -90
- package/dist/server.js +2677 -1389
- package/docs/agent-integration.md +100 -6
- package/package.json +1 -1
- package/prisma/migrations/20261014130000_cron_report/migration.sql +19 -0
- package/prisma/schema.prisma +25 -0
- package/scripts/vps/provision.sh +26 -3
- package/web/assets/{DalangHanomanScreen-C3UMPHFg.js → DalangHanomanScreen-BMrikKnt.js} +1 -1
- package/web/assets/{DocPreviewModal-Bp5stb4M.js → DocPreviewModal-5j31WUwI.js} +1 -1
- package/web/assets/{DocsWorkspace-C9yFxK4D.js → DocsWorkspace-GkTrniF3.js} +1 -1
- package/web/assets/{IdeScreen-CRFRAP6E.js → IdeScreen-CAQfv1_e.js} +1 -1
- package/web/assets/LeadScreen-Da5-1FLe.js +1 -0
- package/web/assets/{ReviewScreen-CMHOlmgX.js → ReviewScreen-B8KHzQpd.js} +1 -1
- package/web/assets/SchedulerScreen-CBd4O5kw.js +1 -0
- package/web/assets/SettingsScreen-BxQ3CH2D.js +62 -0
- package/web/assets/{TeamScreen-D4tA34Mj.js → TeamScreen-P6vz88Pp.js} +1 -1
- package/web/assets/TerminalScreen-B_cagBZd.js +2 -0
- package/web/assets/{TriageScreen-B8ZCexaX.js → TriageScreen-IYkxyhdm.js} +1 -1
- package/web/assets/{VpsScreen-BCrLfZh7.js → VpsScreen-DFGT5S8Y.js} +2 -2
- package/web/assets/fields-CmVwp6QK.js +1 -0
- package/web/assets/{index-BqAP-VDq.css → index-0T3s-I82.css} +1 -1
- package/web/assets/index-C-iSWHn3.js +1012 -0
- package/web/assets/{live-DuBdI0MZ.js → live-lSo-u7M6.js} +1 -1
- package/web/assets/{sched-lead-shared-QqbtjoVv.js → sched-lead-shared-BTz6aILx.js} +1 -1
- package/web/assets/{scheduler-CQt-VP9-.css → scheduler-D64RlGvZ.css} +1 -1
- package/web/assets/settings-rebase-CPawFVpp.js +1 -0
- package/web/index.html +2 -2
- package/web/assets/LeadScreen-DfVUawoK.js +0 -1
- package/web/assets/SchedulerScreen-CK5QvX5c.js +0 -1
- package/web/assets/SettingsScreen-D7P5JgjU.js +0 -62
- package/web/assets/TerminalScreen-CKySL63X.js +0 -2
- package/web/assets/index-BRR3JL_o.js +0 -1012
- package/web/assets/settings-rebase-Cbh-JGJ-.js +0 -1
|
@@ -18,7 +18,7 @@ Naskah ini punya **satu sumber** dan tiga cara membacanya — isinya byte yang s
|
|
|
18
18
|
## 0. Apa itu hanoman, dan bagaimana ia bekerja
|
|
19
19
|
|
|
20
20
|
hanoman adalah **orchestrator + dashboard** untuk pengembangan yang digerakkan dokumentasi. Ia tidak
|
|
21
|
-
menulis kode sendiri; ia **menjalankan agen** (Claude Code atau
|
|
21
|
+
menulis kode sendiri; ia **menjalankan agen** (Claude Code, Codex CLI atau Antigravity CLI) sebagai sesi interaktif,
|
|
22
22
|
lalu memantau semuanya dalam satu tempat.
|
|
23
23
|
|
|
24
24
|
Model kerjanya tiga tingkat, dan penting kamu pegang sebelum memanggil endpoint apa pun:
|
|
@@ -114,7 +114,7 @@ membuat token; endpoint katalognya (`GET /api/agent-tokens/capabilities`) kini t
|
|
|
114
114
|
| `docs` | `/api/prds*`, `/api/projects/:id/{docs,prds}*` | dokumen SoT project & PRD |
|
|
115
115
|
| `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 |
|
|
116
116
|
| `vps` | `/api/vps*` | kelola VPS, audit, harden, konsol — **high-risk (remote exec)** |
|
|
117
|
-
| `settings` | `/api/settings*`, `/api/config*`, `/api/scheduler*` kecuali `/api/scheduler/crons*` | setelan instance & config runtime |
|
|
117
|
+
| `settings` | `/api/settings*`, `/api/config*`, `/api/scheduler*` kecuali `/api/scheduler/crons*` dan `/api/scheduler/reports*` | setelan instance & config runtime |
|
|
118
118
|
| `support` | `/api/tickets*`, `/api/github-issues*`, `/api/projects/:id/github*` | tiket Help Center & issue GitHub (triase) |
|
|
119
119
|
| `notifications` | `/api/notifications*` | notifikasi |
|
|
120
120
|
| `lead` | `/api/lead*` | minta putusan ke hanoman-lead & baca jejaknya — **`lead:write` bisa menggerakkan sesi** (ADR-0091) |
|
|
@@ -131,7 +131,7 @@ membuat token; endpoint katalognya (`GET /api/agent-tokens/capabilities`) kini t
|
|
|
131
131
|
| `team` | `/api/tasks*`, `/api/members*` | papan **Tim**: kartu kerja MANUSIA & direktori anggota (ADR-0157). `status` kartu milik manusia — ia **bukan** `stage` backlog. `POST /api/tasks/:id/escalate` melahirkan backlog item dan tetap `team:write` (cermin `POST /api/tickets/:id/accept`) |
|
|
132
132
|
| `qa` | `/api/projects/:id/qa/**`, `/api/qa/**` | laporan QA, test case, temuan, lampiran, ekspor/impor. Method `DELETE` menuntut `qa:delete` (akses `danger`, ADR-0185), bukan `qa:write` |
|
|
133
133
|
| `decisions` | `/api/terminal/sessions/:id/dialog*` (capability `sessions:decide`, akses `danger`) | **gerbang manusia-yang-memutuskan** (ADR-0190, ADR-0192): baca, jawab, dan rebut dialog `AskUserQuestion` sebuah sesi. **Tak diimplikasikan `sessions:write`.** Beri HANYA ke token yang tidak dipakai agen di dalam sesi hanoman — lihat §13 |
|
|
134
|
-
| `scheduler` | `/api/scheduler/crons*` | cronjob per project — **cron = sesi agen berizin penuh yang ditunda** (ADR-0191). Membuat, mengubah prompt/agen/model/effort/jadwal, mengaktifkan, dan menjalankan cron menuntut **`scheduler:write` DAN `sessions:spawn`**; server menjawab 403 `need: sessions:spawn` bila yang kedua tak dicentang |
|
|
134
|
+
| `scheduler` | `/api/scheduler/crons*`, `/api/scheduler/reports*` (baca-saja, ADR-0222) | cronjob per project — **cron = sesi agen berizin penuh yang ditunda** (ADR-0191). Membuat, mengubah prompt/agen/model/effort/jadwal, mengaktifkan, dan menjalankan cron menuntut **`scheduler:write` DAN `sessions:spawn`**; server menjawab 403 `need: sessions:spawn` bila yang kedua tak dicentang |
|
|
135
135
|
| `tokens` | `/api/agent-tokens*` | cetak/ubah/cabut **agent token**. Dulu cookie-only (ADR-0065); ADR-0196 membukanya dengan **pagar tak-eskalasi-diri** (§5b). `tokens:write` **bukan** izin menaikkan hak sendiri |
|
|
136
136
|
| `devices` | `/api/device-tokens*` | **device token** (identitas mesin untuk sync & relay) atas nama akun admin yang menerbitkan agent token ini (ADR-0197). Plaintext sekali-tampil |
|
|
137
137
|
| `access` | `/api/auth/users*`, `POST /api/auth/change-password` | daftar akun (`access:read`); **`access:admin`** (danger) membuat akun admin, menghapus akun, dan menyetel ulang password akun lain — **setara akses penuh instance** |
|
|
@@ -143,6 +143,17 @@ membuat token; endpoint katalognya (`GET /api/agent-tokens/capabilities`) kini t
|
|
|
143
143
|
| `sync` | `GET /api/sync/{conflicts,pending}`, `POST /api/sync/now`, `POST /api/sync/conflicts/:entity/:recordId/resolve` | **hanya tiga permukaan UI sync** (ADR-0199): antrean konflik & hapus-tertunda (`sync:read`), pemicu sync manual (`sync:write`), memutuskan konflik (`sync:resolve`, akses `danger` — **membuang data satu sisi tanpa jalan pulang**, tak diimplikasikan `sync:write`). Kanal mesin-ke-mesin (`/api/sync/{pull,bootstrap,push,logs,attachments,qa-attachments,ws,relay}`) tetap bukan milik agen — lihat §5 |
|
|
144
144
|
| `instance` | `POST /api/update/apply`, `POST /api/restart` | satu capability `instance:admin` (akses `danger`, ADR-0200): memasang versi hanoman terbaru dari registry npm dan me-**restart** proses server. Hanya sah bila instance punya supervisor (`canApply` di `GET /api/update`); wajib `{ "confirm": true }` dan balasan 202 dikirim **sebelum** proses keluar |
|
|
145
145
|
|
|
146
|
+
SPEC-1442 / ADR-0221: tool Custom Agent existing diperluas additive, tanpa endpoint/tool baru.
|
|
147
|
+
`hanoman_agent_create` dan `hanoman_agent_update` menerima runtime `antigravity`; model child
|
|
148
|
+
hanya tier `inherit|flash|pro`, bukan slug parent dan bukan effort independen. Mode `write` dan
|
|
149
|
+
capability `agents:write` tetap. `hanoman_agents_list` menerima filter `runtime` opsional untuk
|
|
150
|
+
menampilkan availability terhadap parent itu, dengan mode `read`/`agents:read` tetap. Profil
|
|
151
|
+
warisan yang tidak kompatibel tetap terbaca dengan `availabilityReason`; auditor read-only,
|
|
152
|
+
isolated-worktree dan allowlist tool eksplisit unavailable pada Antigravity. `maxTurns`/timeout
|
|
153
|
+
bersifat instruksional (`executionLimits:"instructional"`), bukan jaminan native. Runtime tetap
|
|
154
|
+
penyaring persona, bukan pemilih proses anak. Keputusan MCP ini memenuhi ADR-0202 untuk perluasan
|
|
155
|
+
kontrak existing; tak ada pengecualian auth baru.
|
|
156
|
+
|
|
146
157
|
## 4. Aturan gate & kode status
|
|
147
158
|
|
|
148
159
|
Gate `onRequest` yang sama menegakkan semuanya:
|
|
@@ -306,6 +317,14 @@ Tool MCP-nya (semua tulis bermode `danger`, hanya tampil dengan `--danger`):
|
|
|
306
317
|
| `GET /api/scheduler/queue` | `settings:read` | antrean scheduler (`?status=queued\|launched\|done\|failed`). `GET /api/scheduler/state` **tak lagi** memuat `queue` — ia memuat `queueCounts`. |
|
|
307
318
|
| `POST /api/lead/decisions` | `lead:write` | minta putusan — baca **§8** dan **§11** dulu. |
|
|
308
319
|
|
|
320
|
+
Settings memakai tool existing `hanoman_settings_get`/`hanoman_settings_set` dan capability
|
|
321
|
+
`settings:read|write` untuk runtime Antigravity (SPEC-1442/ADR-0221). Panggil `_get` tepat sebelum
|
|
322
|
+
`_set`, kirim objek terbaru dengan perubahan yang diperlukan. Default parent berada di
|
|
323
|
+
`antigravity.model/effort`; sel `orchestration.<flow>.antigravity` menerima tier native
|
|
324
|
+
`inherit|flash|pro` atau model `null`, dengan effort `null`. Slug parent/effort independen pada
|
|
325
|
+
sel baru/diubah ditolak 400. Sel legacy yang tidak berubah tetap terbaca untuk diagnosis;
|
|
326
|
+
blok runtime ketiga yang tidak dikirim client lama dipertahankan beserta ownership-nya.
|
|
327
|
+
|
|
309
328
|
## 7. `POST /api/specs` — bentuk payload per `source`
|
|
310
329
|
|
|
311
330
|
`source` dan bentuk `payload` **saling mengikat**. Salah pasang → **400**
|
|
@@ -497,9 +516,10 @@ capability yang sama** — bukan jalur otorisasi baru — jadi seluruh aturan §
|
|
|
497
516
|
|
|
498
517
|
Prasyarat: `npm i -g hanoman` di mesin tempat klien AI-nya jalan.
|
|
499
518
|
|
|
500
|
-
**Claude Code / Claude Desktop / Cursor / Copilot** (`~/.claude.json`,
|
|
519
|
+
**Claude Code / Claude Desktop / Cursor / Copilot / Antigravity** (`~/.claude.json`,
|
|
501
520
|
`claude_desktop_config.json`, `~/.cursor/mcp.json`, `.vscode/mcp.json` — Cursor & Copilot memakai
|
|
502
|
-
kunci `"servers"` alih-alih `"mcpServers"
|
|
521
|
+
kunci `"servers"` alih-alih `"mcpServers"`; Antigravity memakai global
|
|
522
|
+
`~/.gemini/config/mcp_config.json`):
|
|
503
523
|
|
|
504
524
|
```json
|
|
505
525
|
{
|
|
@@ -525,7 +545,25 @@ args = ["mcp"]
|
|
|
525
545
|
env = { HANOMAN_HOST = "https://hanoman.example", HANOMAN_AGENT_TOKEN = "hnm_agt_…" }
|
|
526
546
|
```
|
|
527
547
|
|
|
528
|
-
|
|
548
|
+
**Antigravity — pemasangan global oleh operator (SPEC-1442/ADR-0221):** gunakan blok JSON
|
|
549
|
+
`mcpServers.hanoman` di atas. Pilih token instance ini lewat alur **Akses AI Agent** existing,
|
|
550
|
+
lalu ganti placeholder secara lokal. Untuk konfigurasi yang sudah ada, gabungkan **hanya** entri
|
|
551
|
+
`mcpServers.hanoman`; pertahankan semua server lain dan field root yang tidak dikenal. Jangan
|
|
552
|
+
mengganti seluruh berkas dengan contoh. Jika JSON rusak, bentuk root/`mcpServers` tidak berupa
|
|
553
|
+
objek, atau entri `hanoman` operator berbeda sudah ada, hentikan pemasangan dan lakukan
|
|
554
|
+
rekonsiliasi manual. Simpan berkas global dengan izin `0600`. Konfigurasi workspace
|
|
555
|
+
`.agents/mcp_config.json` tetap utuh. Dashboard hanya menyediakan panduan/snippet; tidak ada
|
|
556
|
+
writer konfigurasi lokal, endpoint baru, penerbitan token otomatis, atau penambahan capability.
|
|
557
|
+
|
|
558
|
+
Pilihan tingkat tetap sama: `args: ["mcp"]` untuk default, `["mcp", "--read-only"]` untuk
|
|
559
|
+
baca-saja, atau `["mcp", "--danger"]` untuk menampilkan tool berbahaya; jangan gabungkan dua
|
|
560
|
+
flag itu. Tingkat tampilan bukan otorisasi tambahan dan token tetap tunduk pada capability
|
|
561
|
+
existing. Buka sesi TUI Antigravity baru, periksa `/mcp` atau daftar tools native, lalu panggil
|
|
562
|
+
`hanoman_about`. Receipt cukup mencatat nama tool/status hasil; jangan menyalin token, host,
|
|
563
|
+
atau config credential mentah ke log/dokumen. Keberhasilan snippet/fixture sendiri belum
|
|
564
|
+
membuktikan koneksi native.
|
|
565
|
+
|
|
566
|
+
Panduan siap salin untuk klien di atas, berikut tabel tool → capability, ada di dashboard:
|
|
529
567
|
**Settings → Akses AI Agent → MCP server**.
|
|
530
568
|
|
|
531
569
|
### Tool
|
|
@@ -545,6 +583,7 @@ Panduan siap salin untuk keempat klien, berikut tabel tool → capability, ada d
|
|
|
545
583
|
| `hanoman_scheduler_queue_cancel`, `hanoman_scheduler_queue_requeue` | tulis | `settings:write`; requeue baris `failed` juga menuntut `sessions:spawn` (ADR-0204, 403 bila tak ada) |
|
|
546
584
|
| `hanoman_lead_ask` | tulis | `lead:write` |
|
|
547
585
|
| `hanoman_health`, `hanoman_models`, `hanoman_codex_version`, `hanoman_methods_status` | baca | — (`GLOBAL_READ`, ADR-0187) |
|
|
586
|
+
| `hanoman_antigravity_version` | baca | — (`GLOBAL_READ`, ADR-0221) |
|
|
548
587
|
| `hanoman_overview_get` | baca | — (`GLOBAL_READ`, ADR-0220) |
|
|
549
588
|
| `hanoman_activity_list` | baca | `activity:read` |
|
|
550
589
|
| `hanoman_activity_purge` | **berbahaya** | `activity:write` |
|
|
@@ -587,6 +626,11 @@ praktik: **jangan pasang token ber-`sessions:decide` di konfigurasi yang dibaca
|
|
|
587
626
|
(mis. `~/.claude.json` tingkat-user diwarisi setiap sesi). Pakai token terpisah, di klien MCP yang
|
|
588
627
|
dijalankan manusia di luar sesi.
|
|
589
628
|
|
|
629
|
+
**Laporan run cron** (ADR-0222): pesan akhir agen tiap run cron disimpan sebagai dokumen markdown, satu
|
|
630
|
+
per run, tersync antar mesin (retensi 90/cron). Tool baca `hanoman_scheduler_reports` (daftar, tanpa
|
|
631
|
+
isi; saring `cron`/`project`) dan `hanoman_scheduler_report_get` (`report` = id atau `latest`; isi di
|
|
632
|
+
`body`), keduanya `scheduler:read`. Laporan terakhir sebuah cron: `hanoman_scheduler_report_get {cron:"<id>"}`.
|
|
633
|
+
|
|
590
634
|
**Cronjob** (`/api/scheduler/crons*`, ADR-0191 membalik ADR-0112) kini punya tool
|
|
591
635
|
`hanoman_scheduler_crons_list|cron_runs` (baca, `scheduler:read`) dan
|
|
592
636
|
`hanoman_scheduler_cron_create|update|delete|run` (`danger`, `scheduler:write`). Karena sebuah cron
|
|
@@ -659,3 +703,53 @@ Memori project dan changelog dihapus (ADR-0182). Tool `hanoman_memory_*` dan
|
|
|
659
703
|
`/api/projects/:id/changelog*` tidak terdaftar. Token tidak lagi memiliki
|
|
660
704
|
capability `memory:*` atau allowlist project khusus memori. Fungsi context/memory
|
|
661
705
|
kanal Telegram tetap tersedia melalui domain `telegram`.
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
SPEC-1442 memperluas response `hanoman_methods_status` additive: untuk Antigravity,
|
|
709
|
+
`available:false`, `availabilityReason`, `ready:false`, `install:[]` sampai installer upstream
|
|
710
|
+
terverifikasi. Tool tetap baca `GLOBAL_READ`. Terminal install Antigravity ditolak 400
|
|
711
|
+
`{error:string}` sebelum spawn lewat route existing; tidak menambah installer endpoint/tool.
|
|
712
|
+
|
|
713
|
+
SPEC-1442 memperluas skill library existing: `loadedBy` dapat memuat `antigravity`;
|
|
714
|
+
source user `.gemini/antigravity-cli` menunjuk skill native global. Source tersebut hanya
|
|
715
|
+
diterima untuk `layer:user`, bukan project; source asing/traversal tetap 400. Keputusan MCP
|
|
716
|
+
ADR-0202: `hanoman_skills_list`/`hanoman_skill_read` tetap `skills:read`, sedangkan
|
|
717
|
+
`hanoman_skill_create`/`hanoman_skill_fork` memperluas enum source melalui tool existing,
|
|
718
|
+
mode `write` dan capability `skills:write` tetap sama. Menulis berkas native lewat key
|
|
719
|
+
existing tetap memakai `hanoman_skill_write`/baseHash. Tidak ada endpoint atau capability baru.
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
SPEC-1442 Task 7 memperluas kontrak phase override transient menjadi
|
|
723
|
+
`{model?:string|null,effort?:string|null}`. Runtime efektif memakai `body.agent` atau Settings
|
|
724
|
+
bila tidak dikirim. Antigravity menerima tier `inherit|flash|pro`/model null dan effort null;
|
|
725
|
+
slug parent atau effort independen ditolak 400 sebelum perubahan worktree/backlog. Sel Settings
|
|
726
|
+
mengikuti kontrak tier yang sama. Keputusan MCP ADR-0202: tools Settings existing tetap memakai
|
|
727
|
+
blok penuh; deskripsi `hanoman_settings_set` dan `hanoman_session_phases` menjelaskan kontrak
|
|
728
|
+
native. Tidak ada endpoint baru di task ini. Terminal/events WebSocket tetap streaming exception;
|
|
729
|
+
relay native `invoke_subagent`/`send_message` adalah tool runtime, bukan route REST tambahan.
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
SPEC-1442 Task 8: `hanoman_session_create` menerima runtime `antigravity` dan phaseOverrides
|
|
733
|
+
nullable/berbatas untuk backlog; tier child inherit|flash|pro, effort null. Launch tetap mode danger
|
|
734
|
+
dengan capability `sessions:spawn`; cron existing juga menerima runtime additive. `hanoman_models`
|
|
735
|
+
filter Antigravity mengembalikan model dan provider runtime itu. Sandbox/hardening menolak launch
|
|
736
|
+
409 `antigravity-sandbox-unsupported`, goal native 409 `antigravity-goal-unsupported`, trust/config
|
|
737
|
+
gagal 500 `antigravity-config-failed` teredaksi. Tidak ada fallback host/runtime. Override invalid
|
|
738
|
+
400 `{error:string}` mengikuti runtime efektif sebelum approval. Frame fase Antigravity memberi
|
|
739
|
+
`evidence:unknown` dan attempts/effort/durasi/token null; ini bukan bukti nol penggunaan atau gagal
|
|
740
|
+
delegasi. Completion tetap berdasarkan marker lengkap dan checked plan. Sesi kerja tetap
|
|
741
|
+
interaktif; hanya advisor internal lead memakai JSON one-shot.
|
|
742
|
+
|
|
743
|
+
### Versi Antigravity (SPEC-1442 / ADR-0221)
|
|
744
|
+
|
|
745
|
+
`hanoman_antigravity_version` membungkus `GET /api/antigravity/version`: cookie admin atau
|
|
746
|
+
agent token sah tanpa capability tambahan memperoleh 200 `{version,error}`. Version semver
|
|
747
|
+
atau null dengan alasan aman untuk biner hilang, timeout, output cacat/berlebih, dan sandbox
|
|
748
|
+
yang belum didukung; tidak mengungkap stdout/stderr mentah, path home, atau token. Properti
|
|
749
|
+
mesin ini read-only: method tulis dan path lain di bawah `/antigravity` tetap cookie-only.
|
|
750
|
+
|
|
751
|
+
Cache lima menit keyed biner efektif DB→env→`agy` dan boundary eksekusi; perubahan biner/
|
|
752
|
+
boundary langsung membuang cache dan hasil in-flight lama. Probe hanya `--version`, maksimum
|
|
753
|
+
10 detik dan 16 KiB per stream; sandbox aktif/hardening tidak mem-probe host. Kegagalan
|
|
754
|
+
observasi tidak memblokir sesi host. Tool tidak memasang CLI atau mengubah konfigurasi dan
|
|
755
|
+
endpoint/tool versi Codex tidak berubah.
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- ADR-0222 · laporan run cron (tersync). Ditulis tangan, ADITIF murni: satu tabel baru.
|
|
2
|
+
CREATE TABLE "CronReport" (
|
|
3
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
4
|
+
"projectId" TEXT NOT NULL,
|
|
5
|
+
"cronId" TEXT NOT NULL,
|
|
6
|
+
"cronName" TEXT NOT NULL,
|
|
7
|
+
"runDate" TEXT NOT NULL,
|
|
8
|
+
"dueAt" DATETIME NOT NULL,
|
|
9
|
+
"sessionId" TEXT,
|
|
10
|
+
"body" TEXT NOT NULL,
|
|
11
|
+
"bytes" INTEGER NOT NULL DEFAULT 0,
|
|
12
|
+
"version" INTEGER NOT NULL DEFAULT 0,
|
|
13
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
14
|
+
"updatedAt" DATETIME NOT NULL,
|
|
15
|
+
CONSTRAINT "CronReport_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX "CronReport_cronId_dueAt_idx" ON "CronReport"("cronId", "dueAt");
|
|
19
|
+
CREATE INDEX "CronReport_projectId_dueAt_idx" ON "CronReport"("projectId", "dueAt");
|
package/prisma/schema.prisma
CHANGED
|
@@ -35,6 +35,7 @@ model Project {
|
|
|
35
35
|
clientAccess ClientProjectAccess[] // SPEC-617 · ADR-0110 · akun klien yang boleh melihat project ini
|
|
36
36
|
portalChats PortalChatSession[] // SPEC-854 · ADR-0129 · percakapan portal klien project ini
|
|
37
37
|
tasks Task[] // SPEC-945 · ADR-0150 · kartu kerja manusia di project ini
|
|
38
|
+
cronReports CronReport[] // ADR-0222 · laporan run cron project ini
|
|
38
39
|
qaReports QaReport[] // Workspace QA · laporan QA project ini
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -1041,6 +1042,30 @@ model LogCursor {
|
|
|
1041
1042
|
@@id([deviceId, lane])
|
|
1042
1043
|
}
|
|
1043
1044
|
|
|
1045
|
+
// ADR-0222 · laporan hasil SATU run cron: pesan akhir agen (hook `Stop`) sebagai markdown. TERSYNC
|
|
1046
|
+
// (entitas `cronReport`). `id` = `SchedulerCronRun.id` supaya tulis ulang idempoten dan dua mesin tak
|
|
1047
|
+
// bisa menerbitkan laporan ganda untuk run yang sama. `cronId` soft-link TANPA FK: SchedulerCron
|
|
1048
|
+
// LOCAL-only, jadi cron milik mesin lain tak ada di DB ini sementara laporannya tetap harus terbaca.
|
|
1049
|
+
// Nama berkas `.md` diturunkan (`cronReportFilename`), tak disimpan.
|
|
1050
|
+
model CronReport {
|
|
1051
|
+
id String @id
|
|
1052
|
+
projectId String
|
|
1053
|
+
cronId String
|
|
1054
|
+
cronName String
|
|
1055
|
+
runDate String // YYYY-MM-DD, zona lokal mesin penjalan, dari dueAt
|
|
1056
|
+
dueAt DateTime
|
|
1057
|
+
sessionId String?
|
|
1058
|
+
body String
|
|
1059
|
+
bytes Int @default(0)
|
|
1060
|
+
version Int @default(0)
|
|
1061
|
+
createdAt DateTime @default(now())
|
|
1062
|
+
updatedAt DateTime @updatedAt
|
|
1063
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1064
|
+
|
|
1065
|
+
@@index([cronId, dueAt])
|
|
1066
|
+
@@index([projectId, dueAt])
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1044
1069
|
// Workspace QA · bagian 1 · laporan QA per project (design 2026-10-01).
|
|
1045
1070
|
//
|
|
1046
1071
|
// LOCAL-only di bagian ini, tetapi `version` sudah ada pada tiga entitas utama supaya bagian 3
|
package/scripts/vps/provision.sh
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# NOL asumsi tentang SSH: skrip ini dijalankan lewat `ssh … bash -s` DAN secara lokal oleh
|
|
12
12
|
# `hanoman provision`. Jangan pernah membaca $SSH_*, /dev/tty, atau berasumsi ada tty.
|
|
13
13
|
# NOL rahasia: tak pernah membaca, menulis, atau meminta kredensial agen.
|
|
14
|
+
unset GEMINI_API_KEY GOOGLE_GEMINI_BASE_URL
|
|
14
15
|
set -uo pipefail # sengaja TANPA -e: satu komponen gagal tak boleh membunuh laporan sisanya
|
|
15
16
|
|
|
16
17
|
MODE="${MODE:-probe}"
|
|
@@ -59,9 +60,21 @@ probe_agent_cli() {
|
|
|
59
60
|
else comp "$id" absent ""; fi
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
probe_antigravity() {
|
|
64
|
+
if [ "$PROFILE" = production ]; then comp antigravity absent sandbox-unsupported; return; fi
|
|
65
|
+
local bin="${HANOMAN_ANTIGRAVITY_BIN:-agy}" v
|
|
66
|
+
if ! have "$bin"; then comp antigravity absent ""; return; fi
|
|
67
|
+
if ! v="$(timeout -s KILL 10 "$bin" --version 2>/dev/null | head -c 16385)"; then
|
|
68
|
+
comp antigravity partial "version-unavailable auth-manual"; return
|
|
69
|
+
fi
|
|
70
|
+
if [ "${#v}" -le 16384 ] && [[ "$v" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
|
|
71
|
+
comp antigravity partial "auth-manual $v"
|
|
72
|
+
else comp antigravity partial "version-unavailable auth-manual"; fi
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
do_probe() {
|
|
63
76
|
probe_base; probe_node; probe_hanoman; probe_caddy; probe_podman; probe_agent_image
|
|
64
|
-
probe_agent_cli claude; probe_agent_cli codex; probe_gh
|
|
77
|
+
probe_agent_cli claude; probe_agent_cli codex; probe_antigravity; probe_gh
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
# ---------- apply ----------
|
|
@@ -80,7 +93,7 @@ deps_of() {
|
|
|
80
93
|
podman) echo "base" ;;
|
|
81
94
|
agent-image) echo "podman" ;;
|
|
82
95
|
claude|codex) if [ "$PROFILE" = production ]; then echo "agent-image"; else echo "node"; fi ;;
|
|
83
|
-
gh)
|
|
96
|
+
antigravity|gh) echo "base" ;;
|
|
84
97
|
*) echo "" ;;
|
|
85
98
|
esac
|
|
86
99
|
}
|
|
@@ -105,6 +118,11 @@ apply_gh() { pkg_install gh; }
|
|
|
105
118
|
apply_claude() { npm i -g @anthropic-ai/claude-code >/dev/null 2>&1; }
|
|
106
119
|
apply_codex() { npm i -g @openai/codex >/dev/null 2>&1; }
|
|
107
120
|
|
|
121
|
+
# SPEC-1442: official host installer; never run it inside or as a replacement for Podman.
|
|
122
|
+
apply_antigravity() {
|
|
123
|
+
curl -fsSL https://antigravity.google/cli/install.sh 2>/dev/null | bash >/dev/null 2>&1
|
|
124
|
+
}
|
|
125
|
+
|
|
108
126
|
apply_podman() {
|
|
109
127
|
pkg_install podman || return 1
|
|
110
128
|
id -u "$HANOMAN_USER" >/dev/null 2>&1 || useradd --system --create-home --home-dir "$HANOMAN_DATA" "$HANOMAN_USER"
|
|
@@ -215,6 +233,7 @@ probe_one() {
|
|
|
215
233
|
base) probe_base ;; node) probe_node ;; hanoman) probe_hanoman ;; caddy) probe_caddy ;;
|
|
216
234
|
podman) probe_podman ;; agent-image) probe_agent_image ;; gh) probe_gh ;;
|
|
217
235
|
claude|codex) probe_agent_cli "$1" ;;
|
|
236
|
+
antigravity) probe_antigravity ;;
|
|
218
237
|
esac | awk '{print $3}'
|
|
219
238
|
}
|
|
220
239
|
|
|
@@ -224,13 +243,16 @@ already_present() {
|
|
|
224
243
|
local id="$1" st="$2"
|
|
225
244
|
[ "$st" = ok ] && return 0
|
|
226
245
|
if [ "$st" = partial ]; then
|
|
227
|
-
case "$id" in claude|codex|gh) return 0 ;; esac
|
|
246
|
+
case "$id" in claude|codex|antigravity|gh) return 0 ;; esac
|
|
228
247
|
fi
|
|
229
248
|
return 1
|
|
230
249
|
}
|
|
231
250
|
|
|
232
251
|
apply_one() {
|
|
233
252
|
local id="$1"
|
|
253
|
+
if [ "$id" = antigravity ] && [ "$PROFILE" != lab ]; then
|
|
254
|
+
step "$id" fail "sandbox-unsupported; Antigravity hanya host/lab"; mark_failed "$id"; return
|
|
255
|
+
fi
|
|
234
256
|
|
|
235
257
|
for d in $(deps_of "$id"); do
|
|
236
258
|
if has_failed "$d"; then step "$id" skip "blocked-by $d"; mark_failed "$id"; return; fi
|
|
@@ -251,6 +273,7 @@ apply_one() {
|
|
|
251
273
|
base) apply_base || rc=$? ;; node) apply_node || rc=$? ;; hanoman) apply_hanoman || rc=$? ;;
|
|
252
274
|
caddy) apply_caddy || rc=$? ;; podman) apply_podman || rc=$? ;;
|
|
253
275
|
agent-image) apply_agent_image || rc=$? ;; claude) apply_claude || rc=$? ;;
|
|
276
|
+
antigravity) apply_antigravity || rc=$? ;;
|
|
254
277
|
codex) apply_codex || rc=$? ;; gh) apply_gh || rc=$? ;;
|
|
255
278
|
*) step "$id" fail "komponen tak dikenal"; mark_failed "$id"; return ;;
|
|
256
279
|
esac
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{j as e,u as be,I as C,l as I,S as se,F as te}from"./index-
|
|
1
|
+
import{j as e,u as be,I as C,l as I,S as se,F as te}from"./index-C-iSWHn3.js";import{R as d}from"./react-DfGhg7ZW.js";import"./markdown-CWEY34jk.js";import"./xterm-3VOAfa_q.js";function ye({height:s,color:a,rod:l}){return e.jsxs("svg",{viewBox:"0 0 64 128",width:s*64/128,height:s,"aria-hidden":"true",focusable:"false",children:[e.jsx("path",{d:"M31.2 74 L30.2 127 L33.8 127 L32.8 74 Z",fill:l}),e.jsx("path",{d:"M51 79 L50.2 122 L53 122 L52.4 79 Z",fill:l}),e.jsx("path",{d:`M32 73
|
|
2
2
|
C 24 76, 19 88, 17.5 103 C 17 106, 20 107, 24 106.4
|
|
3
3
|
C 28 106, 30 104, 32 104 C 34 104, 36 106, 40 106.4 C 44 107, 47 106, 46.5 103
|
|
4
4
|
C 45 88, 40 76, 32 73 Z`,fill:a}),e.jsx("path",{d:"M32 74 C 29.5 66, 29.5 60, 31 53 L 37 53 C 38.5 60, 37.5 67, 34.5 74 Z",fill:a}),e.jsx("path",{d:`M31 53
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e,M as r,
|
|
1
|
+
import{j as e,M as r,aP as d,aR as a,m as x}from"./index-C-iSWHn3.js";import"./react-DfGhg7ZW.js";function f({path:i,text:o,eyebrow:n,download:t,onClose:s}){const l=i.slice(i.lastIndexOf("/")+1)||i;return e.jsx(r,{open:!0,title:l,eyebrow:n??i,icon:"book-open",onClose:s,width:980,fillHeight:!0,children:e.jsxs("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[t&&e.jsx("div",{style:{display:"flex",justifyContent:"flex-end",paddingBottom:6,borderBottom:"1px solid var(--border-hair)",marginBottom:8},children:e.jsx(d,{href:t})}),e.jsx("div",{"data-testid":"doc-preview-scroll",style:{flex:"1 1 0",minHeight:0,overflow:"auto",padding:"0 4px 8px"},children:o?e.jsx(a,{text:o,name:i}):e.jsx(x,{kind:"empty",icon:"file-text",title:"Berkas kosong",hint:i})})]})})}export{f as D};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{q as Ne,d as De,h as S,j as e,m as w,I as N,U as Se,Y as Ce,B as O,f as Ke,n as q,O as Le,aO as Me,aQ as te,D as $e,aT as ze,i as ie}from"./index-BRR3JL_o.js";import{R as o}from"./react-DfGhg7ZW.js";import"./markdown-CWEY34jk.js";import"./xterm-3VOAfa_q.js";function de(n){let t={...n,kids:n.kids.map(de)};for(;!t.cat&&t.kids.length===1;){const i=t.kids[0];t={...i,label:t.label+"/"+i.label}}return t}function re(n){const t={path:"",kids:[]};for(const i of n){if(i.cat==="."){t.kids.push({path:".",label:".",cat:i,kids:[]});continue}let l=t;for(const u of i.cat.split("/")){const k=l.path?l.path+"/"+u:u;let m=l.kids.find(s=>s.path===k);m||(m={path:k,label:u,kids:[]},l.kids.push(m)),l=m}l.cat=i}return t.kids.map(de)}function le(n){const t=n.find(i=>i.scored&&i.linked)??n.find(i=>i.scored)??n[0];return t&&t.files[0]?`${t.cat}/${t.files[0]}`:""}function oe(n){return/(^|\/)adr(s)?(\/|$)/i.test(n)?"ADR":/(^|\/)prd(s)?(\/|$)/i.test(n)?"PRD":/(^|\/)spec(s|ification)?(\/|$)/i.test(n)?"Spec":"Lainnya"}const Fe=["PRD","Spec","ADR","Lainnya"],ue=n=>`dir:${n}`,Pe=(n,t)=>!!t&&t.startsWith(n.path+"/");function he({node:n,depth:t,ctx:i}){var h,p,y;const l=Pe(n,i.selected),[u,k]=o.useState(l);o.useEffect(()=>{l&&k(!0)},[l]);const m=((h=n.cat)==null?void 0:h.scored)??!0,s=((p=n.cat)==null?void 0:p.linked)??!0,v=ue(n.path),b=()=>k(d=>!d),c=d=>{d.key==="Enter"||d.key===" "?(d.preventDefault(),b()):d.key==="ArrowRight"&&!u?(d.preventDefault(),k(!0)):d.key==="ArrowLeft"&&u&&(d.preventDefault(),k(!1))};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{role:"treeitem","aria-level":t+1,"aria-expanded":u,tabIndex:i.tabbable===v?0:-1,className:"hn-docs-node hn-docs-node-dir",style:{paddingLeft:6+t*12},onClick:b,onKeyDown:c,onFocus:()=>i.setFocusKey(v),children:[e.jsx(N,{name:u?"chevron-down":"chevron-right",size:14,color:"var(--text-subtle)"}),e.jsx(N,{name:"folder",size:15,color:m?s?"var(--brass-500)":"var(--clay-500)":"var(--text-subtle)"}),e.jsxs("span",{className:"hn-docs-dir-label",children:[n.label,"/"]}),n.cat&&m&&(n.cat.linked?e.jsx(N,{name:"link",size:13,color:"var(--leaf-600)"}):e.jsx(N,{name:"unlink",size:13,color:"var(--clay-500)"}))]}),u&&e.jsxs("div",{role:"group",className:"hn-docs-group",children:[n.kids.map(d=>e.jsx(he,{node:d,depth:t+1,ctx:i},d.path)),(y=n.cat)==null?void 0:y.files.map(d=>{const f=n.path+"/"+d,j=f===i.selected;return e.jsxs("div",{role:"treeitem","aria-level":t+2,"aria-selected":j,"aria-current":j?"true":void 0,tabIndex:i.tabbable===f?0:-1,className:`hn-docs-node hn-docs-node-file${j?" is-selected":""}`,style:{paddingLeft:18+t*12},onClick:()=>i.onSelect(f),onFocus:()=>i.setFocusKey(f),onKeyDown:g=>{(g.key==="Enter"||g.key===" ")&&(g.preventDefault(),i.onSelect(f))},children:[e.jsx(N,{name:"file-text",size:13,color:j?"var(--brass-700)":"var(--text-subtle)"}),e.jsx("span",{className:`hn-docs-file-label${!m||s?"":" is-unlinked"}`,children:d})]},d)})]})]})}function ce({label:n,nodes:t,selected:i,onSelect:l}){const[u,k]=o.useState(null),m=t[0]?ue(t[0].path):"",v={selected:i,onSelect:l,tabbable:u??(i||m),setFocusKey:c=>k(c)},b=c=>{if(c.key!=="ArrowDown"&&c.key!=="ArrowUp")return;const h=Array.from(c.currentTarget.querySelectorAll('[role="treeitem"]')),p=h.indexOf(document.activeElement);if(p<0)return;c.preventDefault();const y=h[Math.min(h.length-1,Math.max(0,p+(c.key==="ArrowDown"?1:-1)))];y==null||y.focus()};return e.jsx("div",{role:"tree","aria-label":n,className:"hn-docs-tree",onKeyDown:b,children:t.map(c=>e.jsx(he,{node:c,depth:0,ctx:v},c.path))})}function Ie({projectId:n,projectName:t,docStatus:i}){const[l,u]=o.useState([]),[k,m]=o.useState(0),[s,v]=Ne(ze("docs",n),"selected","",$e),[b,c]=o.useState({}),[h,p]=o.useState("preview"),[y,d]=o.useState("tree"),[f,j]=o.useState(""),[g,U]=o.useState(!1),[L,W]=o.useState(!1),[G,C]=o.useState(""),[$,Q]=o.useState(""),[D,z]=o.useState("all"),{confirm:J,dialog:me}=De(),[F,K]=o.useState("loading"),[pe,ke]=o.useState(0);o.useEffect(()=>{let a=!0;return K("loading"),S.getDocs(n).then(r=>{if(!a)return;const x=r.tree;u(x),m(r.coverage),c({}),p("preview"),K("ready"),C(""),Q(""),z("all"),v(le(x))}).catch(()=>{a&&(u([]),v(""),K("error"))}),()=>{a=!1}},[n,pe]),o.useEffect(()=>{if(!s||s in b)return;let a=!0;return S.getDoc(n,s).then(r=>{a&&c(x=>({...x,[s]:r.content}))}).catch(()=>{a&&c(r=>({...r,[s]:null}))}),()=>{a=!1}},[s,n,b]);const P=!!s&&!(s in b),A=s?b[s]===null:!1,fe=()=>c(a=>{const r={...a};return delete r[s],r}),V=a=>D==="all"||oe(a.cat)===D,xe=re(l.filter(a=>a.scored&&V(a))),Y=re(l.filter(a=>!a.scored&&V(a))),B=b[s]??"",be=s.includes("/")?s.slice(0,s.lastIndexOf("/")):".",T=l.find(a=>a.cat===be),_=s,E=h==="edit"&&f!==B,I=o.useMemo(()=>l.flatMap(a=>a.files.map(r=>({key:`${a.cat}/${r}`,scored:a.scored,linked:a.linked,kind:oe(a.cat)}))),[l]),R=$.trim().toLowerCase(),X=R?I.filter(a=>(D==="all"||a.kind===D)&&a.key.toLowerCase().includes(R)):[],ve=o.useMemo(()=>{const a={PRD:0,Spec:0,ADR:0,Lainnya:0};for(const r of I)a[r.kind]+=1;return a},[I]),ye=i==="broken"?"broken":i==="drift"?"drift":"ok";function Z(a){if(!E){p("preview"),a();return}J({title:"Buang perubahan?",eyebrow:s,message:"Isi dokumen yang kamu ubah belum disimpan.",impact:["Perubahan di editor hilang dan tak bisa dikembalikan dari dashboard."],confirmLabel:"Buang perubahan",cancelLabel:"Lanjut mengedit",tone:"danger",icon:"trash-2"}).then(r=>{r&&(p("preview"),a())})}function ge(){C(""),j(B),p("edit")}const ee=()=>Z(()=>{});async function ae(){if(!(L||!s)){W(!0),C("");try{await S.putDoc(n,s,f),c(a=>({...a,[s]:f})),p("preview")}catch(a){C(ie("menyimpan dokumen",a))}finally{W(!1)}}}function H(a){Z(()=>{v(a),p("preview"),d("viewer")})}async function se(){const a=await S.getDocs(n),r=a.tree;u(r),m(a.coverage),r.some(x=>x.files.some(M=>`${x.cat}/${M}`===s))||v(le(r))}async function ne(){if(!g){U(!0);try{await se()}catch{K("error")}finally{U(!1)}}}async function je(){if(!s)return;const a=s;let r=!1;try{r=await J({title:`Hapus ${a}?`,eyebrow:"Source of Truth",message:"Dokumen ini adalah Source of Truth project. Berkas aslinya di disk ikut dihapus.",impact:["Berkas di disk dihapus dan tak bisa dikembalikan dari dashboard.","Kategori indeks dihitung ulang setelah penghapusan."],confirmLabel:"Hapus dokumen",tone:"danger",icon:"trash-2",run:()=>S.deleteDoc(n,a)})}catch(x){C(ie("menghapus dokumen",x));return}if(r){c(x=>{const M={...x};return delete M[a],M});try{await se()}catch{K("error")}}}const we=F==="loading"?e.jsx(w,{kind:"loading",compact:!0,title:"Memuat indeks…"}):F==="error"?e.jsx(w,{kind:"error",compact:!0,title:"Gagal memuat indeks dokumen",hint:t,action:()=>ke(a=>a+1)}):l.length===0?e.jsx(w,{kind:"empty",compact:!0,icon:"folder-open",title:"Belum ada dokumen",hint:"Belum ada Markdown di repo ini.",action:ne,actionLabel:"Muat ulang",actionIcon:"radar"}):R?X.length===0?e.jsx(w,{kind:"empty",compact:!0,icon:"search",title:"Tak ada berkas cocok",hint:`Tak ada yang cocok dengan “${$.trim()}”.`}):e.jsx("div",{role:"list","aria-label":"Hasil pencarian berkas",className:"hn-docs-results",children:X.map(a=>e.jsxs("button",{type:"button","aria-current":a.key===s?"true":void 0,onClick:()=>H(a.key),className:`hn-docs-result${a.key===s?" is-selected":""}`,children:[e.jsx(N,{name:"file-text",size:13,color:"var(--text-subtle)"}),e.jsx("span",{className:"hn-prj-mono",children:a.key}),e.jsx("span",{className:"hn-docs-result-kind",children:a.kind})]},a.key))}):e.jsxs(e.Fragment,{children:[e.jsx(ce,{label:"Berkas dokumen",nodes:xe,selected:s,onSelect:H}),Y.length>0&&e.jsxs("div",{className:"hn-docs-unscored",children:[e.jsx("div",{className:"hn-eyebrow hn-docs-unscored-label",children:"Lainnya (tidak dinilai)"}),e.jsx(ce,{label:"Dokumen tidak dinilai",nodes:Y,selected:s,onSelect:H})]})]});return e.jsxs(e.Fragment,{children:[e.jsx(Se,{ariaLabel:"Panel Dokumen",active:y,onActiveChange:a=>d(a),masterWidth:288,className:"hn-workspace-panels",panels:[{id:"tree",label:"Indeks",className:"hn-panel-flex",content:e.jsxs("aside",{"aria-label":"Indeks dokumen",className:"hn-docs-index",children:[e.jsxs("div",{className:"hn-docs-index-head",children:[e.jsxs("span",{className:"hn-eyebrow",children:["dokumen · ",t]}),e.jsxs("div",{className:"hn-docs-index-actions",children:[e.jsx(Ce,{status:ye,size:"sm"}),e.jsx(O,{size:"sm",variant:"ghost",leftIcon:g?"loader":"radar",onClick:ne,disabled:g,children:g?"Memuat…":"Muat ulang"})]})]}),F==="ready"&&l.length>0&&e.jsxs("div",{className:"hn-docs-index-tools",children:[e.jsx(Ke,{size:"sm","aria-label":"Cari berkas dokumen",leftIcon:"search",value:$,placeholder:"mis. product/prd",onChange:a=>Q(a.target.value),className:"hn-prj-full"}),e.jsxs("div",{role:"group","aria-label":"Kategori dokumen",className:"hn-prj-chips",children:[e.jsx("button",{type:"button",className:"hn-prj-chip hn-touch-target","aria-pressed":D==="all",onClick:()=>z("all"),children:"Semua"}),Fe.map(a=>e.jsxs("button",{type:"button",className:"hn-prj-chip hn-touch-target","aria-pressed":D===a,onClick:()=>z(a),children:[a,e.jsx("span",{className:"hn-prj-chip-count",children:ve[a]})]},a))]}),e.jsxs("span",{className:"hn-docs-cov",title:"Persentase kategori yang terindeks",children:["Cakupan ",Math.round(k),"%"]})]}),e.jsx("div",{className:"hn-docs-tree-scroll",children:we})]})},{id:"viewer",label:"Dokumen",className:"hn-panel-flex",content:e.jsxs("section",{"aria-label":"Isi dokumen",className:"hn-docs-viewer",style:{display:"flex",flexDirection:"column",minHeight:0},children:[e.jsxs("div",{className:"hn-docs-viewer-head",children:[e.jsx(N,{name:"file-text",size:15,color:"var(--text-muted)"}),e.jsx("span",{className:"hn-prj-mono hn-docs-path",children:_}),T&&T.scored&&(T.linked?e.jsx(q,{tone:"ok",size:"sm",icon:"link",children:"terindeks"}):e.jsx(q,{tone:"err",size:"sm",icon:"unlink",children:"tak tertaut"})),e.jsx("span",{className:"hn-grow"}),e.jsxs("div",{role:"group","aria-label":"Mode dokumen",className:"hn-prj-seg",children:[e.jsx("button",{type:"button","aria-pressed":h==="preview",className:"hn-touch-target",onClick:()=>{h==="edit"&&ee()},children:"Baca"}),e.jsx("button",{type:"button","aria-pressed":h==="edit",className:"hn-touch-target",onClick:()=>{h!=="edit"&&ge()},disabled:!s||P||A,children:"Edit"})]}),h==="preview"&&e.jsx(Le,{label:"Aksi untuk dokumen",icon:"ellipsis",items:[{key:"delete",label:"Hapus dokumen…",icon:"trash-2",tone:"danger",disabled:!s,onSelect:()=>{je()}}],children:e.jsx(Me,{href:a=>S.docDownloadUrl(n,s,a),disabled:!s||P||A})})]}),h==="edit"&&e.jsxs("div",{role:"region","aria-label":"Simpan perubahan",className:"hn-docs-savebar",children:[E?e.jsx(q,{tone:"warn",size:"sm",children:"belum disimpan"}):e.jsx("span",{className:"hn-prj-hint",children:"Tak ada perubahan"}),e.jsx("span",{className:"hn-grow"}),e.jsx(O,{size:"sm",variant:"ghost",onClick:ee,disabled:L,children:"Batal"}),e.jsx(O,{size:"sm",leftIcon:"check",onClick:()=>void ae(),loading:L,disabled:L||!E,children:"Simpan"})]}),G&&e.jsx("div",{role:"alert",className:"hn-docs-error",children:G}),h==="preview"?e.jsx("div",{"data-testid":"doc-preview-scroll",className:"hn-docs-body",style:{flex:"1 1 auto",minHeight:0,overflow:"auto"},children:s?P?e.jsx(w,{kind:"loading",title:"Memuat dokumen…",hint:s}):A?e.jsx(w,{kind:"error",title:"Gagal memuat dokumen",hint:s,action:fe}):e.jsx("div",{className:"hn-docs-read",children:e.jsx(te,{text:B,name:s})}):e.jsx(w,{kind:"empty",icon:"file-text",title:"Tidak ada dokumen dipilih",hint:"Pilih file dari indeks di kiri."})}):e.jsxs("div",{className:"hn-docs-edit",children:[e.jsxs("div",{className:"hn-docs-edit-pane",children:[e.jsx("div",{className:"hn-docs-edit-label",children:e.jsx("span",{className:"hn-eyebrow",children:"Markdown"})}),e.jsx("textarea",{"aria-label":`Isi ${_}`,value:f,spellCheck:!1,onChange:a=>j(a.target.value),onKeyDown:a=>{(a.metaKey||a.ctrlKey)&&a.key.toLowerCase()==="s"&&(a.preventDefault(),ae())},placeholder:`# Judul dokumen
|
|
1
|
+
import{q as Ne,d as De,h as S,j as e,m as w,I as N,U as Se,Y as Ce,B as O,f as Ke,n as U,O as Le,aP as Me,aR as te,D as Pe,aU as $e,i as ie}from"./index-C-iSWHn3.js";import{R as o}from"./react-DfGhg7ZW.js";import"./markdown-CWEY34jk.js";import"./xterm-3VOAfa_q.js";function de(n){let t={...n,kids:n.kids.map(de)};for(;!t.cat&&t.kids.length===1;){const i=t.kids[0];t={...i,label:t.label+"/"+i.label}}return t}function re(n){const t={path:"",kids:[]};for(const i of n){if(i.cat==="."){t.kids.push({path:".",label:".",cat:i,kids:[]});continue}let l=t;for(const u of i.cat.split("/")){const k=l.path?l.path+"/"+u:u;let m=l.kids.find(s=>s.path===k);m||(m={path:k,label:u,kids:[]},l.kids.push(m)),l=m}l.cat=i}return t.kids.map(de)}function le(n){const t=n.find(i=>i.scored&&i.linked)??n.find(i=>i.scored)??n[0];return t&&t.files[0]?`${t.cat}/${t.files[0]}`:""}function oe(n){return/(^|\/)adr(s)?(\/|$)/i.test(n)?"ADR":/(^|\/)prd(s)?(\/|$)/i.test(n)?"PRD":/(^|\/)spec(s|ification)?(\/|$)/i.test(n)?"Spec":"Lainnya"}const ze=["PRD","Spec","ADR","Lainnya"],ue=n=>`dir:${n}`,Fe=(n,t)=>!!t&&t.startsWith(n.path+"/");function he({node:n,depth:t,ctx:i}){var h,p,y;const l=Fe(n,i.selected),[u,k]=o.useState(l);o.useEffect(()=>{l&&k(!0)},[l]);const m=((h=n.cat)==null?void 0:h.scored)??!0,s=((p=n.cat)==null?void 0:p.linked)??!0,v=ue(n.path),b=()=>k(d=>!d),c=d=>{d.key==="Enter"||d.key===" "?(d.preventDefault(),b()):d.key==="ArrowRight"&&!u?(d.preventDefault(),k(!0)):d.key==="ArrowLeft"&&u&&(d.preventDefault(),k(!1))};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{role:"treeitem","aria-level":t+1,"aria-expanded":u,tabIndex:i.tabbable===v?0:-1,className:"hn-docs-node hn-docs-node-dir",style:{paddingLeft:6+t*12},onClick:b,onKeyDown:c,onFocus:()=>i.setFocusKey(v),children:[e.jsx(N,{name:u?"chevron-down":"chevron-right",size:14,color:"var(--text-subtle)"}),e.jsx(N,{name:"folder",size:15,color:m?s?"var(--brass-500)":"var(--clay-500)":"var(--text-subtle)"}),e.jsxs("span",{className:"hn-docs-dir-label",children:[n.label,"/"]}),n.cat&&m&&(n.cat.linked?e.jsx(N,{name:"link",size:13,color:"var(--leaf-600)"}):e.jsx(N,{name:"unlink",size:13,color:"var(--clay-500)"}))]}),u&&e.jsxs("div",{role:"group",className:"hn-docs-group",children:[n.kids.map(d=>e.jsx(he,{node:d,depth:t+1,ctx:i},d.path)),(y=n.cat)==null?void 0:y.files.map(d=>{const f=n.path+"/"+d,j=f===i.selected;return e.jsxs("div",{role:"treeitem","aria-level":t+2,"aria-selected":j,"aria-current":j?"true":void 0,tabIndex:i.tabbable===f?0:-1,className:`hn-docs-node hn-docs-node-file${j?" is-selected":""}`,style:{paddingLeft:18+t*12},onClick:()=>i.onSelect(f),onFocus:()=>i.setFocusKey(f),onKeyDown:g=>{(g.key==="Enter"||g.key===" ")&&(g.preventDefault(),i.onSelect(f))},children:[e.jsx(N,{name:"file-text",size:13,color:j?"var(--brass-700)":"var(--text-subtle)"}),e.jsx("span",{className:`hn-docs-file-label${!m||s?"":" is-unlinked"}`,children:d})]},d)})]})]})}function ce({label:n,nodes:t,selected:i,onSelect:l}){const[u,k]=o.useState(null),m=t[0]?ue(t[0].path):"",v={selected:i,onSelect:l,tabbable:u??(i||m),setFocusKey:c=>k(c)},b=c=>{if(c.key!=="ArrowDown"&&c.key!=="ArrowUp")return;const h=Array.from(c.currentTarget.querySelectorAll('[role="treeitem"]')),p=h.indexOf(document.activeElement);if(p<0)return;c.preventDefault();const y=h[Math.min(h.length-1,Math.max(0,p+(c.key==="ArrowDown"?1:-1)))];y==null||y.focus()};return e.jsx("div",{role:"tree","aria-label":n,className:"hn-docs-tree",onKeyDown:b,children:t.map(c=>e.jsx(he,{node:c,depth:0,ctx:v},c.path))})}function Ie({projectId:n,projectName:t,docStatus:i}){const[l,u]=o.useState([]),[k,m]=o.useState(0),[s,v]=Ne($e("docs",n),"selected","",Pe),[b,c]=o.useState({}),[h,p]=o.useState("preview"),[y,d]=o.useState("tree"),[f,j]=o.useState(""),[g,q]=o.useState(!1),[L,W]=o.useState(!1),[G,C]=o.useState(""),[P,J]=o.useState(""),[D,$]=o.useState("all"),{confirm:Q,dialog:me}=De(),[z,K]=o.useState("loading"),[pe,ke]=o.useState(0);o.useEffect(()=>{let a=!0;return K("loading"),S.getDocs(n).then(r=>{if(!a)return;const x=r.tree;u(x),m(r.coverage),c({}),p("preview"),K("ready"),C(""),J(""),$("all"),v(le(x))}).catch(()=>{a&&(u([]),v(""),K("error"))}),()=>{a=!1}},[n,pe]),o.useEffect(()=>{if(!s||s in b)return;let a=!0;return S.getDoc(n,s).then(r=>{a&&c(x=>({...x,[s]:r.content}))}).catch(()=>{a&&c(r=>({...r,[s]:null}))}),()=>{a=!1}},[s,n,b]);const F=!!s&&!(s in b),A=s?b[s]===null:!1,fe=()=>c(a=>{const r={...a};return delete r[s],r}),V=a=>D==="all"||oe(a.cat)===D,xe=re(l.filter(a=>a.scored&&V(a))),Y=re(l.filter(a=>!a.scored&&V(a))),B=b[s]??"",be=s.includes("/")?s.slice(0,s.lastIndexOf("/")):".",T=l.find(a=>a.cat===be),_=s,E=h==="edit"&&f!==B,I=o.useMemo(()=>l.flatMap(a=>a.files.map(r=>({key:`${a.cat}/${r}`,scored:a.scored,linked:a.linked,kind:oe(a.cat)}))),[l]),R=P.trim().toLowerCase(),X=R?I.filter(a=>(D==="all"||a.kind===D)&&a.key.toLowerCase().includes(R)):[],ve=o.useMemo(()=>{const a={PRD:0,Spec:0,ADR:0,Lainnya:0};for(const r of I)a[r.kind]+=1;return a},[I]),ye=i==="broken"?"broken":i==="drift"?"drift":"ok";function Z(a){if(!E){p("preview"),a();return}Q({title:"Buang perubahan?",eyebrow:s,message:"Isi dokumen yang kamu ubah belum disimpan.",impact:["Perubahan di editor hilang dan tak bisa dikembalikan dari dashboard."],confirmLabel:"Buang perubahan",cancelLabel:"Lanjut mengedit",tone:"danger",icon:"trash-2"}).then(r=>{r&&(p("preview"),a())})}function ge(){C(""),j(B),p("edit")}const ee=()=>Z(()=>{});async function ae(){if(!(L||!s)){W(!0),C("");try{await S.putDoc(n,s,f),c(a=>({...a,[s]:f})),p("preview")}catch(a){C(ie("menyimpan dokumen",a))}finally{W(!1)}}}function H(a){Z(()=>{v(a),p("preview"),d("viewer")})}async function se(){const a=await S.getDocs(n),r=a.tree;u(r),m(a.coverage),r.some(x=>x.files.some(M=>`${x.cat}/${M}`===s))||v(le(r))}async function ne(){if(!g){q(!0);try{await se()}catch{K("error")}finally{q(!1)}}}async function je(){if(!s)return;const a=s;let r=!1;try{r=await Q({title:`Hapus ${a}?`,eyebrow:"Source of Truth",message:"Dokumen ini adalah Source of Truth project. Berkas aslinya di disk ikut dihapus.",impact:["Berkas di disk dihapus dan tak bisa dikembalikan dari dashboard.","Kategori indeks dihitung ulang setelah penghapusan."],confirmLabel:"Hapus dokumen",tone:"danger",icon:"trash-2",run:()=>S.deleteDoc(n,a)})}catch(x){C(ie("menghapus dokumen",x));return}if(r){c(x=>{const M={...x};return delete M[a],M});try{await se()}catch{K("error")}}}const we=z==="loading"?e.jsx(w,{kind:"loading",compact:!0,title:"Memuat indeks…"}):z==="error"?e.jsx(w,{kind:"error",compact:!0,title:"Gagal memuat indeks dokumen",hint:t,action:()=>ke(a=>a+1)}):l.length===0?e.jsx(w,{kind:"empty",compact:!0,icon:"folder-open",title:"Belum ada dokumen",hint:"Belum ada Markdown di repo ini.",action:ne,actionLabel:"Muat ulang",actionIcon:"radar"}):R?X.length===0?e.jsx(w,{kind:"empty",compact:!0,icon:"search",title:"Tak ada berkas cocok",hint:`Tak ada yang cocok dengan “${P.trim()}”.`}):e.jsx("div",{role:"list","aria-label":"Hasil pencarian berkas",className:"hn-docs-results",children:X.map(a=>e.jsxs("button",{type:"button","aria-current":a.key===s?"true":void 0,onClick:()=>H(a.key),className:`hn-docs-result${a.key===s?" is-selected":""}`,children:[e.jsx(N,{name:"file-text",size:13,color:"var(--text-subtle)"}),e.jsx("span",{className:"hn-prj-mono",children:a.key}),e.jsx("span",{className:"hn-docs-result-kind",children:a.kind})]},a.key))}):e.jsxs(e.Fragment,{children:[e.jsx(ce,{label:"Berkas dokumen",nodes:xe,selected:s,onSelect:H}),Y.length>0&&e.jsxs("div",{className:"hn-docs-unscored",children:[e.jsx("div",{className:"hn-eyebrow hn-docs-unscored-label",children:"Lainnya (tidak dinilai)"}),e.jsx(ce,{label:"Dokumen tidak dinilai",nodes:Y,selected:s,onSelect:H})]})]});return e.jsxs(e.Fragment,{children:[e.jsx(Se,{ariaLabel:"Panel Dokumen",active:y,onActiveChange:a=>d(a),masterWidth:288,className:"hn-workspace-panels",panels:[{id:"tree",label:"Indeks",className:"hn-panel-flex",content:e.jsxs("aside",{"aria-label":"Indeks dokumen",className:"hn-docs-index",children:[e.jsxs("div",{className:"hn-docs-index-head",children:[e.jsxs("span",{className:"hn-eyebrow",children:["dokumen · ",t]}),e.jsxs("div",{className:"hn-docs-index-actions",children:[e.jsx(Ce,{status:ye,size:"sm"}),e.jsx(O,{size:"sm",variant:"ghost",leftIcon:g?"loader":"radar",onClick:ne,disabled:g,children:g?"Memuat…":"Muat ulang"})]})]}),z==="ready"&&l.length>0&&e.jsxs("div",{className:"hn-docs-index-tools",children:[e.jsx(Ke,{size:"sm","aria-label":"Cari berkas dokumen",leftIcon:"search",value:P,placeholder:"mis. product/prd",onChange:a=>J(a.target.value),className:"hn-prj-full"}),e.jsxs("div",{role:"group","aria-label":"Kategori dokumen",className:"hn-prj-chips",children:[e.jsx("button",{type:"button",className:"hn-prj-chip hn-touch-target","aria-pressed":D==="all",onClick:()=>$("all"),children:"Semua"}),ze.map(a=>e.jsxs("button",{type:"button",className:"hn-prj-chip hn-touch-target","aria-pressed":D===a,onClick:()=>$(a),children:[a,e.jsx("span",{className:"hn-prj-chip-count",children:ve[a]})]},a))]}),e.jsxs("span",{className:"hn-docs-cov",title:"Persentase kategori yang terindeks",children:["Cakupan ",Math.round(k),"%"]})]}),e.jsx("div",{className:"hn-docs-tree-scroll",children:we})]})},{id:"viewer",label:"Dokumen",className:"hn-panel-flex",content:e.jsxs("section",{"aria-label":"Isi dokumen",className:"hn-docs-viewer",style:{display:"flex",flexDirection:"column",minHeight:0},children:[e.jsxs("div",{className:"hn-docs-viewer-head",children:[e.jsx(N,{name:"file-text",size:15,color:"var(--text-muted)"}),e.jsx("span",{className:"hn-prj-mono hn-docs-path",children:_}),T&&T.scored&&(T.linked?e.jsx(U,{tone:"ok",size:"sm",icon:"link",children:"terindeks"}):e.jsx(U,{tone:"err",size:"sm",icon:"unlink",children:"tak tertaut"})),e.jsx("span",{className:"hn-grow"}),e.jsxs("div",{role:"group","aria-label":"Mode dokumen",className:"hn-prj-seg",children:[e.jsx("button",{type:"button","aria-pressed":h==="preview",className:"hn-touch-target",onClick:()=>{h==="edit"&&ee()},children:"Baca"}),e.jsx("button",{type:"button","aria-pressed":h==="edit",className:"hn-touch-target",onClick:()=>{h!=="edit"&&ge()},disabled:!s||F||A,children:"Edit"})]}),h==="preview"&&e.jsx(Le,{label:"Aksi untuk dokumen",icon:"ellipsis",items:[{key:"delete",label:"Hapus dokumen…",icon:"trash-2",tone:"danger",disabled:!s,onSelect:()=>{je()}}],children:e.jsx(Me,{href:a=>S.docDownloadUrl(n,s,a),disabled:!s||F||A})})]}),h==="edit"&&e.jsxs("div",{role:"region","aria-label":"Simpan perubahan",className:"hn-docs-savebar",children:[E?e.jsx(U,{tone:"warn",size:"sm",children:"belum disimpan"}):e.jsx("span",{className:"hn-prj-hint",children:"Tak ada perubahan"}),e.jsx("span",{className:"hn-grow"}),e.jsx(O,{size:"sm",variant:"ghost",onClick:ee,disabled:L,children:"Batal"}),e.jsx(O,{size:"sm",leftIcon:"check",onClick:()=>void ae(),loading:L,disabled:L||!E,children:"Simpan"})]}),G&&e.jsx("div",{role:"alert",className:"hn-docs-error",children:G}),h==="preview"?e.jsx("div",{"data-testid":"doc-preview-scroll",className:"hn-docs-body",style:{flex:"1 1 auto",minHeight:0,overflow:"auto"},children:s?F?e.jsx(w,{kind:"loading",title:"Memuat dokumen…",hint:s}):A?e.jsx(w,{kind:"error",title:"Gagal memuat dokumen",hint:s,action:fe}):e.jsx("div",{className:"hn-docs-read",children:e.jsx(te,{text:B,name:s})}):e.jsx(w,{kind:"empty",icon:"file-text",title:"Tidak ada dokumen dipilih",hint:"Pilih file dari indeks di kiri."})}):e.jsxs("div",{className:"hn-docs-edit",children:[e.jsxs("div",{className:"hn-docs-edit-pane",children:[e.jsx("div",{className:"hn-docs-edit-label",children:e.jsx("span",{className:"hn-eyebrow",children:"Markdown"})}),e.jsx("textarea",{"aria-label":`Isi ${_}`,value:f,spellCheck:!1,onChange:a=>j(a.target.value),onKeyDown:a=>{(a.metaKey||a.ctrlKey)&&a.key.toLowerCase()==="s"&&(a.preventDefault(),ae())},placeholder:`# Judul dokumen
|
|
2
2
|
|
|
3
3
|
Paragraf pembuka…`,className:"hn-docs-textarea"})]}),e.jsxs("div",{className:"hn-docs-edit-pane",children:[e.jsx("div",{className:"hn-docs-edit-label",children:e.jsx("span",{className:"hn-eyebrow",children:"Pratinjau langsung"})}),e.jsx("div",{className:"hn-docs-preview",children:e.jsx(te,{text:f,name:s})})]})]})]})}]}),me]})}export{Ie as DocsWorkspace,re as buildTree,oe as docKind,le as firstDoc};
|