review_enabled (default TRUE) selama ini no-op: tampil di TUI settings
overlay ("Review: true"), bisa di-toggle via command, TAPI spawn_background_review
tidak pernah dipanggil & Origin::Reviewer tidak pernah dikonstruksi. User
melihat "Review: true" padahal auto-review setelah edit tak pernah jalan.
Sekarang feature yang sudah dibangun penuh (subagent/auto/engine.rs: git diff
-> LLM review -> auto-fix HIGH/MEDIUM) di-wire:
- daemon/handler.rs: trigger setelah run_turn bila review_enabled; capture
flag+creds SEBELUM api_key/provider_cfg di-move ke LlmClient.
- tui/turn.rs: sama, gated by state.settings.flags.review_enabled.
- ws/lib.rs: channel minimal tanpa settings -> review nyala tiap prompt
(konsisten dgn default ON).
Aman: review fire-and-forget (tokio::spawn), get_git_diff skip bila no-change,
no-op bila bukan git repo (auto/engine). Creds dipakai = creds ter-resolve yg
sama dgn komposisi turn.
Verifikasi: check/clippy/fmt/test workspace hijau (0 error/warning/fail).
Sebelumnya SemanticSearch/ListSymbols/RebuildIndex menahan SYMBOL_INDEX
Mutex selama full `rebuild` (walk seluruh workspace, bisa detikan) +
selama search. Di main loop yang menjalankan read-only tools paralel,
semantic_search/list_symbols lain jadi BLOCK selama rebuild.
Refactor:
- Global berubah Mutex<Option<SymbolIndex>> -> OnceLock<Mutex<HashMap<
workspace, SymbolIndex>>> — index per-workspace, jadi pencarian workspace B
tidak mungkin bocor simbol stale dari A (workspace-awareness kini struktural,
bukan hanya via needs_rebuild).
- ensure_symbol_index(workspace, force): rebuild dijalankan DI LUAR lock
(mutex hanya dicek/insert/lookup singkat), lalu hasilnya di-swap-in di bawah
short lock. Search/list/rebuild-report tak lagi memblock thread lain selama
walk I/O. Per-workspace key menghilangkan race lintas-workspace dari skema
swap tunggal.
- Test +1 (test_ensure_symbol_index_per_workspace_isolation): verifikasi dua
workspace punya index independen, rebuild A tidak menimpa B.
Verifikasi: check/clippy -D warnings/fmt clean; test infra 64 (0 gagal).
SymbolIndex global sudah melacak workspace_path tapi SemanticSearch dan
ListSymbols Cuma rebuild saat index kosong (is_empty). Akibat: setelah
mengindeks workspace A, mencari di workspace B diam-diam mengembalikan
simbol STALE dari A — menyesatkan coding agent (referensikan simbol yang
tidak ada di repo aktif).
Fix:
- Tambah SymbolIndex::needs_rebuild(workspace) — true bila index kosong
ATAU workspace diminta beda dari yang ter-cache.
- Pakai di 2 call site (SemanticSearch::run, ListSymbols::run) menggantikan
is_empty(), jadi pindah workspace otomatis trigger rebuild.
- test: +1 (test_needs_rebuild_workspace_aware — verifikasi flip workspace
memicu rebuild bolak-balik A -> B -> A).
Catatan (bukan bug, dilaporkan): mutex SYMBOL_INDEX masih dipegang selama
full rebuild di run() — bottleneck saat semantic_search dipanggil paralel;
perbaikan butuh restrukturisasi double-checked rebuild, tak diubah di sini.
Verifikasi: check/clippy -D warnings/fmt clean; test infra 63 (0 gagal).
synth_consensus selama ini Cuma concatenate output node lalu dilabeli
"Consensus" — tidak ada sintesis. Kini:
- Resolve kredensial LLM (provider/model/base_url/api_key) dari Store,
sumber yang sama dgn execute_cycle.
- Kirim prompt sintesis ke model: minta distilasi node outputs jadi satu
laporan konsensus berisi AGREEMENTS / CONFLICTS / KEY FINDINGS /
RECOMMENDATION.
- Graceful fallback ke summary concatenation bila panggilan LLM gagal /
output kosong, supaya sintesis konsensus tidak pernah merusak siklus
hive-mind (konsisten dgn filosofi isolated-errors utk node).
- Batasi output per-node (MAX_NODE_OUTPUT_CHARS=4000, char-safe via
truncate_chars) agar prompt tetap bounded.
- test: +2 (truncation char-safe pada output besar multi-byte; concat
summary memuat semua node id).
Verifikasi: cargo check/clippy -D warnings/fmt clean; test infra 62 (0
gagal). Disk root sudah di-cargo clean (free 43.7GB, turun 98% -> 63%).
Hasil audit round 4 (workflow/hive_mind + memory + semantic_search).
- fix(memory): recall.search selama ini TIDAK pernah dipakai — tool
mengiklankan keyword search di skema tapi run() cuma list semua nama.
Kini search benar-benar memfilter (cocok di name/description/content,
case-insensitive), + output 'No memories match' bila kosong.
- fix(memory): ToolCtxBuilder tidak punya setter memory_dir dan tak ada
call-site yang mengisinya — remember/recall/forget memakai PathBuf kosong
dan menulis memory ke CWD (bukan lokasi persisten). Tambah setter
memory_dir + worktrees_dir, dan helper resolve_memory_dir() yang fallback
ke Store::new().memory_dir bila ctx.memory_dir kosong; dipakai di ketiga
tool memory.
- refactor(workflow): hapus LlmClient dummy di WorkflowRun (dibuat dengan
API key kosong + model default + base_url default lalu tak pernah dipakai
— execute_workflow menerimanya sebagai _llm_client). Kini execute_workflow
tak ambil parameter tak terpakai; LLM asli tetap lewat execute_primitive
yang resolve kredensial dengan benar.
- test: +2 (recall search memfilter; resolve_memory_dir fallback/eksplisit).
Catatan audit yang dilaporkan (belum difix): synth_consensus hanya
menggabungkan output (label Consensus menyesatkan, bukan sintesis LLM), dan
semantic_search memegang Mutex index global saat full rebuild (bottleneck
saat paralel) + index tidak workspace-aware.
PENTING (infra): disk root 100% saat kerja. Saya bebaskan ~4.6G dari /tmp +
cache aman (sekai*, verify-z, bun/npm cache). target/debug di repo = 38G —
rampah, perlu cargo clean + rebuild (jangan dibiarkan).
Hasil audit alur AI agent round 3 (fokus correctness & latent crash).
- fix(subagent): engine.rs sebelumnya mengeksekusi tool lalu push
ChatMessage::tool hasil TANPA mendahuluinya dengan pesan assistant yang
mendeklarasikan tool_calls → history malformed ([..., tool, tool,
assistant(text)]). Kontrak OpenAI/Anthropic mensyaratkan pesan assistant
(berisi tool_calls) sebelum hasil tool. Kini push response_msg
(assistant + tool_calls + content) sebelum eksekusi, dan hapus push
assistant content-only di akhir (agar tidak duplikat). Loop utama sudah
benar; subagent kini selaras.
- fix(utils): &content[..1500] / &content[..1000] di build_rich_context
dan &diff[..5000] di auto/engine.rs bisa panic saat indeks byte jatuh di
tengah karakter multi-byte UTF-8 (emoji/CJK/panah). Tambah helper
truncate_chars() yang memotong per karakter (char-safe) dan pakai di
3 titik tersebut.
- test: +4 unit test truncate_chars (ASCII, potong, multibyte no-panic,
emoji).
Catatan audit: subagent/auto (auto-review) & build_rich_context adalah dead
code (spawn_background_review & build_rich_context tidak pernah dipanggil).
Auto-review jangan diaktifkan asal (parser format teks rapuh + tanpa
verifikasi pasca-fix) — dilaporkan, bukan dicolokkan.
Lanjutan audit alur AI agent (round 2), mengisi celah yang tersisa dari
perpbaikan paralel tool di loop utama (74b1ad4) agar lebih mirip Claude Code.
- feat(subagent): eksekusi batch tool read-only paralel di subagent engine
(engine.rs). Tool::run sinkron, jadi pakai scoped OS thread (bounded
window 8); hasil dipertahankan dalam urutan panggilan asli. Batch dengan
tool mutating jatuh balik ke jalur sequential aman.
- feat(agent): auto-load AGENTS.md/CLAUDE.md/.cursorrules ke system prompt
tiap turn (seperti Claude Code load AGENTS.md saat startup). Fungsi
main_agent_prompt_with_project_context menempel blok PROJECT CONTEXT;
dibaca dari workspace root pertama & dibatasi 12k char.
- feat(prompt): arahan VERIFY AFTER EDIT — setelah edit/write, agent wajib
jalankan cargo check/clippy/test (atau lint/test sesuai stack) via bash
sebelum mengakhiri turn; perbaiki error yang terlihat, jangan klaim
'compiles/works' tanpa hasil nyata.
- feat(infra): build_rich_context kini membaca AGENTS.md & CLAUDE.md juga
(untuk explore_codebase/scout).
- test: +3 subagent engine (order paralel, kecepatan konkuren, fallback
mutating), +2 domain prompt (konteks proyek & fallback kosong).
Sebelumnya loop utama mengeksekusi semua tool call satu-per-satu
(sequential for loop). Seperti Claude Code, tool read-only yang
independen dalam satu pesan assistant (read/grep/glob/semantic_search
dsb.) kini dijalankan konkuren dengan bounded parallelism (max 8),
mengurangi latensi per turn secara signifikan untuk beban coding.
- feat(registry): tool_is_parallel_safe() — whitelist tool read-only
yang aman dijalankan paralel; tool mutating/shell tetap sequential
- feat(executor): is_parallel_safe() delegasi ke registry; ToolExecutor
trait Default=false (konservatif)
- fix(application): execute_tool_calls_in_parallel() — join_all +
semaphore bounded 8, hasil dikumpulkan dalam URUTAN panggilan asli
(kontrak OpenAI/Anthropic tool-result ordering)
- loop utama: batch paralel hanya jika SEMUA tool parallel-safe; jika
ada satu tool mutating, jatuh balik ke jalur sequential aman
- test: +2 registry test, +2 application test (konkurensi & urutan,
fallback batch mutating)
Model terbaru di 9router adalah claude-opus-5. Update semua jalur
model default Opus:
- fix(app_config_repo): fallback default_model custom_model.unwrap_or
-> claude-opus-5; model_roles list claude-opus-5
- fix(app_config): router provider default_model -> claude-opus-5
- fix(settings test): assertion claude-opus-5
- fix(data): ~/.local/share/zesdex/settings.json model -> claude-opus-5
TUI turn.rs & daemon handler.rs ambil model langsung dari
settings.model (tersimpan 'deepseek-v4-flash-free' di
~/.local/share/zesdex/settings.json) padahal provider sudah 'claude'.
- feat(domain): resolve_effective_model() — saat provider claude, model
diambil dari app_config provider claude (default_model=claude-opus-4-8
hasil deteksi ~/.claude/settings.json), menang atas settings.model basi.
Provider non-claude tetap hormati settings.model user.
- fix(tui): turn.rs pakai resolve_effective_model (bukan settings.model)
- fix(daemon): handler.rs run_turn + compaction pakai resolve_effective_model
- fix(data): ~/.local/share/zesdex/settings.json model deepseek -> claude-opus-4-8
- test: 3 unit test resolve_effective_model
Perbaiki provider claude agar selalu refresh dari settings.json dan
menjadi default (claude-opus-4-8) setiap startup:
- fix(app_config_repo): ganti or_insert -> insert untuk provider claude —
base_url/key dari ~/.claude/settings.json selalu di-refresh, tidak
tertutup snapshot lama app_config.json.
- fix(app_config_repo): hapus kondisi default_provider == default — saat
settings.json terdeteksi, default_provider='claude' dan
default_model='claude-opus-4-8' SELALU di-set (sebelumnya skip kalau
user pernah ganti provider).
- fix(subagent/provider): resolve_subagent_provider fallback ke
app_config.default_provider/default_model kalau settings.provider/model
kosong — subagent ikut pakai Opus.
- test: 4 unit test (parse settings.json, refresh stale provider, custom
model, env fallback). Verified live: settings.json terbaca (9router URL
+ key).
Ganti explore phase MANDATORY (3 subagent tiap turn, boros) dengan
tool explore_codebase yang DIPUTUSKAN agent sendiri (lazy, token-aware):
- hapus ExploreService trait + with_explore + Phase 0 dari turn loop
- ExploreServiceImpl kini jadi tool 'explore_codebase' (1 context-scout
subagent, read-only, cap output 4k chars)
- system prompt: instruksi TOKEN BUDGET (jawab langsung utk query simple,
panggil explore_codebase sekali utk task kompleks)
Loop utama kini adaptif & self-healing:
- max_tokens adaptif (800/1600/4096 by request length) — bukan selalu 4096
- temperature 0.2 saat tool-calling, 0.7 utk final answer
- ErrorTracker: deteksi tool error berulang → inject recovery note,
stop setelah 8 error total (bukan 50 iterasi sia-sia)
- auto-compact history > 60k chars sebelum LLM call
- tool output di-truncate ke 12k chars sebelum masuk konteks
Tambah 8 unit test (truncation, adaptive tokens, error tracker).
- fix(view): token streaming kini benar-benar tampil — sebelumnya cache
display_lines tidak pernah di-rebuild saat pesan terakhir berubah
(msg_count == cached_count), jadi teks AI streaming tidak pernah muncul
sampai pesan baru/resize
- perf(view): streaming kini hanya re-render pesan TERAKHIR (splice di
batas cached_last_start) → O(konten baru) per token, bukan O(seluruh
history); guard cached_last_len mencegah re-render pada frame spinner
tanpa token baru
- perf(run): skip chrono::Utc::now() + drain toasts saat tidak ada toast
- perf(misc): drain_expired_toasts tidak lagi clone seluruh daftar toast
- perf(view): render_toasts fast-path saat toasts kosong
- Tambah users_lock (Mutex) di ApiState untuk serialisasi read-modify-write
users.json pada endpoint register; lock hanya dipegang selama operasi
file sinkron (tidak pernah lintas .await, menjaga future tetap Send)
- Hash password dihitung sebelum lock sehingga request concurrent tidak
saling blokir selama hashing Argon2
Security fixes hasil audit:
- fix(auth): refresh token kini memakai claim typ=refresh; access token
tidak bisa dipakai sebagai refresh token (sebelumnya bisa — eskalasi
masa berlaku 1 jam -> 7 hari)
- fix(api): layer JWT hanya melindungi route /sessions dan /chat;
/auth/login, /auth/register, /auth/refresh, /health kini publik
(sebelumnya semua route 401-lock, API tidak bisa dipakai sama sekali)
- fix(ws): endpoint /ws kini memverifikasi token ZESDEX_WS_TOKEN via
query param jika env diset (mencegah pemakaian LLM proxy terbuka)
- feat(api): rate limiting login/register/refresh (20 request / 10 menit
per client IP) memakai RateLimiter yang tadinya dead code
- test(jwt): tambah unit test token type access vs refresh + expired
openssl-sys vendors an OpenSSL source that runs ./Configure via perl during
the build phase. The Nix sandbox had no perl on PATH, causing the gatekeeper
build of the zesdex binary to fail ('Command failed ... openssl-build ...
Configure'). Adding perl to nativeBuildInputs makes the vendored build
resolve. This unblocks the GHA-only deploy workflow.
The gateway crate's package name is zesdex-gateway (not zesdex-backend);
the stale -p flag caused to fail in CI with
'package ID specification zesdex-backend did not match any packages'.
This unblocks the GHA-only deploy workflow.
Adds the canonical Nix CI/CD deploy workflow (install Nix, build .#default,
copy to VPS via SSH, update profile, systemctl restart) previously missing
from this repo. Enforces push-to-GitHub + GHA deploy only (no direct deploy).
- Use type alias ExtDispatch for the HashMap type (fixes type_complexity)
- Use .insert() instead of HashMap::from([...]) to avoid fn ptr trivial casts
- Remove all as fn(...) casts to avoid clippy::trivial_casts