feat: remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide

This commit is contained in:
asepharyana
2026-07-20 14:47:55 +07:00
parent 44c3dd1239
commit 66ac4dbf02
23 changed files with 775 additions and 13170 deletions
+105 -52
View File
@@ -1,65 +1,118 @@
# Architecture Overview
# Arsitektur Sistem Zesdex
## System Layout
## Gambaran Umum
Zesdex is an autonomous AI coding agent with a TUI — an LLM client wrapped in a tool-use harness with 37 built-in tools.
Zesdex adalah autonomous AI coding agent berbasis TUI, dibangun dengan **Rust** menggunakan clean architecture berlapis. LLM client dibungkus dalam tool-use harness dengan 37+ built-in tools — file ops, git, shell, LSP, MCP, subagent orchestration, dan lainnya.
## Struktur Workspace
```
┌─────────────────────────────────────────────────────────────┐
│ Process Mode │
Single-Process ─── Daemon (background) ─── Attach (client) │
└──────────────────────────┬──────────────────────────────────┘
│ IPC (Unix domain socket)
┌─────────────────────────────────────────────────────────────┐
src/main.rs │
┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ Controller │──▶│ Runtime │──▶│ View │ │
│ │ (input.rs) │ │ (actions.rs) │ │ (chat,status,…)│ │
└──────────────┘ └─────┬───────┘ └────────────────┘ │
│ │
┌───────▼────────┐ │
│ │ Harness │ │
│ │ (tool dispatch)│ │
│ └───────┬────────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌────────────┐ ┌───────────────┐ │
│ │ Tools │ │ Subagents │ │ Workflow │ │
│ │ (37x) │ │ (auto/gen) │ │ Engine │ │
│ └─────────┘ └────────────┘ │ (hive_mind) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
zesdex/
├── apps/
├── domain/ # Layer 1: Pure entities, traits, value objects
│ ├── application/ # Layer 2: Use-case services
├── infrastructure/ # Layer 3: Semua I/O (LLM, DB, tools, MCP, LSP)
│ ├── interfaces/
│ │ ├── tui/ # Ratatui terminal UI
│ ├── api/ # Axum REST API
│ ├── daemon/ # Unix socket daemon
│ ├── ws/ # WebSocket server
├── grpc/ # gRPC server
└── web/ # Web frontend
├── gateway/ # CLI entry point & dispatcher
└── bootstrap/ # Initial data seeder
```
## Dependency Graph Antar Layer
```
gateway/bootstrap
interfaces/* (tui, api, daemon, ws, grpc, web)
infrastructure ─── implements ──▶ domain ports
application ─── depends on ──▶ domain traits
domain (zero framework deps: serde, chrono, uuid only)
```
> **Aturan**: layer bawah tidak boleh tahu tentang layer atas. `domain` tidak import apapun dari `infrastructure` atau `interfaces`.
## Process Modes
| Mode | Description |
|------|-------------|
| **Single-process** | TUI + agent run in the same process. Simplest mode. |
| **Daemon** | `--daemon` flag. Agent processes state in background; clients attach to render. |
| **Attach** | `--attach <id>` flag. Connect to existing daemon with IPC. |
| Mode | Flag | Keterangan |
|------|------|------------|
| **TUI** | *(default)* | TUI + agent loop dalam satu proses |
| **Daemon** | `--daemon` | Agent berjalan di background via IPC socket |
| **Attach** | `--attach <id>` | TUI terhubung ke daemon yang sedang berjalan |
| **REST API** | `--api` | HTTP server (default port 8080) |
| **WebSocket** | `--ws` | WS server (default port 8081) |
| **gRPC** | `--grpc` | gRPC server (default port 50051) |
| **Web** | `--web` | Static web frontend (default port 3000) |
In daemon mode, the daemon runs the full agent loop; clients are stateless renderers that sync via Unix domain sockets with diff-based state synchronization.
## Alur Data (Single-Process Mode)
## Data Flow
```
Keyboard/Event
controller/input.rs: handle_key()
│ returns Vec<Action>
action.rs: apply_action(&mut AppStateRest)
│ state dimutasi in-place
├──▶ turn.rs: spawn_agent_turn() ──▶ background thread
│ │
│ ├─ LLM call (blocking reqwest)
│ ├─ tool execution (Tool trait)
│ └─ push TurnEvent ke queue
run.rs: run_loop_inner()
│ drain TurnEvent setiap tick
│ skip render jika dirty=false
view/: draw frame ke terminal (ratatui)
```
1. **Input**`controller/input.rs` handles key events and autocomplete
2. **Dispatch**`app/runtime/actions/mod.rs` applies actions to state (`AppStateRest`)
3. **LLM Stream**`app/runtime/stream/mod.rs` parses SSE chunks into typed events
4. **Tool Execution**`app/harness.rs` gates and runs tool calls via the `Tool` trait
5. **Rendering**`view/` modules read `AppStateRest` and render via ratatui
## File Kunci
## Key Files
| File | Peran |
|------|-------|
| `apps/gateway/src/main.rs` | CLI entry point, parse args, dispatch ke mode |
| `apps/interfaces/tui/src/state.rs` | `AppStateRest` — single source of truth state TUI |
| `apps/interfaces/tui/src/action.rs` | `apply_action()` — satu-satunya tempat state dimutasi |
| `apps/interfaces/tui/src/run.rs` | Event loop: render → poll → handle → tick |
| `apps/interfaces/tui/src/turn.rs` | Spawn agent turn di background thread |
| `apps/interfaces/tui/src/view/mod.rs` | Top-level render pipeline + `pre_render` hook |
| `apps/infrastructure/src/llm/` | LLM client (streaming + non-streaming) |
| `apps/infrastructure/src/tools/` | 37 tool implementations |
| `apps/domain/src/core/` | Entity inti: `ChatMessage`, `Role`, `Store`, `Tool` trait |
| File | Purpose |
|------|---------|
| `src/main.rs` | Entry point, process mode dispatch, TUI init |
| `src/app/state/rest.rs` | Single source-of-truth state struct |
| `src/app/runtime/actions/mod.rs` | State reducer (`apply_action`) |
| `src/app/runtime/stream/mod.rs` | SSE stream parser |
| `src/app/harness.rs` | Tool harness with safety gating |
| `src/app/workflow/hive_mind.rs` | Multi-agent orchestration |
| `src/tool/mod.rs` | Tool trait + registry (37 tools) |
| `src/view/mod.rs` | TUI render pipeline |
## IPC Protocol (Daemon Mode)
```
┌──────────┐ Unix domain socket ┌──────────┐
│ Client │ ◄──────────────────► │ Daemon │
│ (TUI) │ [4-byte len][JSON] │ (agent) │
└──────────┘ └──────────┘
Client ──Action──▶ Daemon (apply_action → state mutasi)
Daemon ──StatePayload──▶ Client (render snapshot)
```
## Lints & Kualitas Kode
Semua workspace crate menerapkan lint ketat di `Cargo.toml`:
- `unused`, `dead_code`, `unreachable_code`**deny**
- `unused_imports`, `unused_variables`, `unused_mut`**deny**
- `clippy::all` + `clippy::pedantic`**warn**
## Release Profile
`opt-level=3`, `lto="fat"`, `codegen-units=1`, `panic="abort"`, `strip="symbols"`
+108 -47
View File
@@ -1,68 +1,129 @@
# Backend Architecture
# Backend & Infrastructure
## Provider Layer
Semua implementasi I/O ada di `apps/infrastructure/src/`. Layer ini mengimplementasikan port/trait yang didefinisikan di `apps/domain/`.
The provider abstraction in `dto/provider/` and `service/provider.rs` wraps LLM API calls:
## LLM Client (`infrastructure/src/llm/`)
- **Configuration**: `model/app_config.rs` loads Anthropic/OpenAI-compatible endpoint settings
- **Authentication**: `service/oauth/` handles OAuth 2.0 with PKCE flow and token management
- **Requests**: `dto/provider/request.rs` builds provider-agnostic request structs
- **Responses**: `dto/provider/response.rs` parses streaming and non-streaming responses
- **Token tracking**: `dto/provider/usage.rs` tracks token consumption
Wrapper di atas provider OpenAI-compatible:
## IPC (Inter-Process Communication)
- **`provider/`** — `LlmClient`: HTTP client dengan `reqwest::blocking` (sync) untuk agent turn, dan async streaming untuk preview
- **Request/Response** — `ChatMessage`, `ChatCompletionRequest`, `ChatCompletionResponse` dengan support tool calls
- **Streaming** — SSE event parser untuk streaming response
- **Usage tracking** — `tokens_in`, `tokens_out`, `last_tokens_in`, `last_tokens_out` per panggilan
- **Provider defaults** — DeepSeek v4 flash free via OpenCode AI proxy (default)
The daemon-client protocol in `src/ipc/`:
- **Transport**: Unix domain sockets
- **Framing**: Length-prefixed frames with `serde_json` serialization (`ipc/frame.rs`)
- **State Sync**: Full state push from daemon after each action (`ipc/snapshot.rs`); diff-based updates for efficiency (`ipc/diff.rs`)
- **Protocol**: `ipc/protocol.rs` defines message types (Action, StateSnapshot, etc.)
Flow:
```
Client ──Action──▶ Daemon ──apply_action()──▶ State mutated
└──StatePayload──▶ Client (render)
```rust
// Contoh penggunaan di turn.rs
let result = client.chat_with_tools_non_streaming(
&mut messages,
Some(tool_defs),
Some(4096), // max_tokens
Some(0.7), // temperature
None, // abort flag
);
```
## Workflow Engine
## Tool System (`infrastructure/src/tools/`)
Located in `src/app/workflow/`:
37 tool yang mengimplementasikan trait `Tool` dari domain:
- **Script DSL** (`engine.rs`): Executes the workflow script language (agent/parallel/pipeline/phase). Supports subagent spawning with schema-validated output, concurrency limiting, and budget tracking.
- **Hive Mind** (`hive_mind.rs`): Core Intelligence spawns a CognitiveCyclePlan — ordered cycles of parallel processing nodes. Each node has a directive and access tier (`read`/`write`/`full`). Node outputs merge into a shared collective state in real time. Final consensus synthesis completes the convergence.
- **Docs** (`docs.rs`): Deterministic (not LLM) convergence writer — records every node's output + final consensus to `docs/runs/`.
```rust
pub trait Tool: Send + Sync {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn parameters(&self) -> Value; // JSON Schema
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String>;
}
```
## MCP (Model Context Protocol)
### Kategori Tool
`src/app/mcp/manager.rs` manages MCP client connections:
| Kategori | Tools |
|----------|-------|
| **File System** | `read`, `write`, `edit`, `delete`, `dir_list`, `dir_cache_update` |
| **Shell** | `bash`, `bash_interactive`, `bash_kill`, `bash_output` |
| **Git** | `git_operator`, `git_cred`, `git_worktree` |
| **Search** | `search`, `grep`, `glob`, `semantic_search` |
| **LSP** | `lsp_connect`, `lsp_hover`, `lsp_completion`, `lsp_definition`, `lsp_references`, `lsp_diagnostics`, `lsp_disconnect` |
| **Memory** | `remember`, `recall`, `forget` |
| **Workflow** | `spawn_agents`, `spawn_pipeline`, `plan`, `sequential_think`, `hive_mind` |
| **Utility** | `todo_write`, `todo_finish`, `pong`, `cd` |
| **Background** | `bash_bg_cancel`, `bash_bg_status`, `bash_bg_list` |
- Uses the `rmcp` crate for the MCP protocol
- Supports stdio-based transport (child process) and streamable HTTP
- Tool discovery via `list_tools()` and dynamic tool registration
`ToolCtx` berisi:
- `session_dir: PathBuf` — direktori sesi aktif
- `workspaces: Vec<PathBuf>` — root workspace yang dibuka
## LSP Integration
## Background Shell Jobs (`infrastructure/src/bgbash/`)
`src/app/lsp/` provides Language Server Protocol support:
Manajemen proses shell jangka panjang:
- **Spawn** dengan Unix process groups (untuk kill seluruh tree)
- **Output buffering** — collect stdout/stderr secara async
- **Cancel/status/list** — kontrol via tool calls
- **Progress monitoring** — track state: `Running`, `Completed`, `Failed`
- **Auto-provisioner** (`provisioner.rs`): Detects and starts LSP servers for Rust, TypeScript, Python, Go, and other languages
- **Client** (`client.rs`): JSON-RPC-based LSP client with typed notifications
- **Tools** (`tool/lsp/mod.rs`): 7 LSP tools (connect, hover, completion, definition, references, diagnostics, disconnect)
## MCP Manager (`infrastructure/src/mcp/`)
## Background Bash
Integrasi **Model Context Protocol**:
- Menggunakan crate `rmcp` (v2.2)
- Transport: **stdio** (child process) dan **streamable HTTP**
- `list_tools()` → tool discovery otomatis → registrasi ke tool harness
- Persistent connection management
`src/app/bgbash/` manages long-running shell jobs:
## LSP Integration (`infrastructure/src/lsp/`)
- **Control** (`control.rs`): Job lifecycle management (spawn, signal, terminate) using Unix process groups
- **Job** (`job.rs`): Individual job state tracking with output buffering and progress monitoring
Integrasi **Language Server Protocol**:
- **Auto-provisioner** — deteksi bahasa dari file extension, start LSP server yang sesuai
- Mendukung: `rust-analyzer`, `typescript-language-server`, `pyright`, `gopls`, dan lainnya
- **JSON-RPC client** — typed notifications + request/response
- 7 tools LSP yang diekspose ke LLM
## Review System
## Persistence (`infrastructure/src/persistence/`)
`src/app/subagent/auto.rs` spawns background reviews:
### SQLite Message Log
- Quick review after every edit
- Background test generation
- Architecture review
- Security review
- All retry once on failure, escalate to blocking error if retry also fails
Session database dengan `rusqlite` (bundled):
- Per-session isolation
- Table: `messages`, `sessions`
- CRUD, query/filter, blob storage
### Settings Repository
`JsonSettingsRepository` — simpan/load `Settings` dari `settings.json`:
- `provider`: nama provider LLM
- `model`: model ID
- `max_tokens`: override context window (default: 256k jika tidak diset)
- `temperature`, `concise_output`, dll
### Memory Files
File-based memory di `~/.local/share/zesdex/memories/`:
- Setiap memory = satu `.md` dengan frontmatter YAML
- Fields: `name`, `description`, `type` (`user`/`feedback`/`project`/`reference`)
- Index di `MEMORY.md`
## Session Management (`infrastructure/src/session/`)
- Setiap sesi memiliki UUID, direktori sendiri di `sessions/<uuid>/`
- `.lock` file untuk cegah concurrent access
- `session.json` — metadata (waktu mulai, workspace, model yang dipakai)
## Utils
| Util | Fungsi |
|------|--------|
| `utils::write_osc52` | Tulis teks ke clipboard via OSC52 escape sequence |
| `Toast` / `ToastKind` | Notifikasi sementara (Success/Warning/Error/Info/Lesson) |
| `TurnEvent` | Event dari background agent ke TUI (queue-based) |
| `DirCache` | Cache async listing direktori untuk `@mention` autocomplete |
| `MentionIndex` | Index file workspace untuk fuzzy autocomplete |
| `SessionRuntime` | Runtime state: messages history, usage stats, session start time |
## OAuth 2.0
Flow PKCE untuk provider LLM:
1. Generate code verifier + challenge
2. Open browser ke authorization URL
3. Start localhost HTTP server untuk tangkap redirect
4. Exchange code → access + refresh token
5. Simpan token di settings
+114 -65
View File
@@ -1,89 +1,138 @@
# Data Architecture
# Data & Persistence
## State Model
## State Runtime (TUI)
The single source of truth is `AppStateRest` (`src/app/state/rest.rs`):
State TUI yang berjalan di memori adalah `AppStateRest` (`apps/interfaces/tui/src/state.rs`).
```
AppStateRest
├── session: SessionRuntime (hive_mind state, convergence flag)
├── runtime: RuntimeState (mode, provider status)
├── chat: ChatState (messages, scroll)
├── input: InputState (text, cursor, autocomplete)
├── settings: Settings (provider, model, temperature, concise_output)
├── config: AppConfig (endpoints, credentials)
├── scroll: ScrollState (per-panel offset)
├── diff: DiffState (edit review)
├── tools: Vec with outputs
├── statusline, sidebar, etc.
└── toasts: pending notifications
### TranscriptCache
```rust
pub struct TranscriptCache {
pub messages: VecDeque<ChatMessageDisplay>, // O(1) eviction
pub max_lines: usize, // default: 200
pub dirty: bool, // perlu rebuild cache?
}
```
**Mutation rules** (per CLAUDE.md):
- Mutated in-place from exactly two locations: `actions/mod.rs` (apply_action) and `controller/input.rs` (key handlers)
- Read-only from every other module
- No generic update function — direct field mutation only
Saat `dirty=true`, `pre_render_chat()` rebuild `display_lines_cache` (render markdown semua pesan) sebelum frame berikutnya.
## Persistence
### SessionRuntime
### SQLite Message Log (`src/model/msglog/`)
```rust
pub struct SessionRuntime {
pub messages: Vec<ChatMessage>, // history untuk LLM context
pub usage: UsageStats, // token counting akumulasi
pub session_start: i64, // unix ms saat sesi dimulai
pub hive_mind_converged: bool, // flag selesai hive mind
}
```
| File | Purpose |
|------|---------|
| `schema.rs` | Table definitions (messages, sessions) |
| `mod.rs` | CRUD operations |
| `query.rs` | Query helpers (search, filter) |
| `blobs.rs` | Large message blob storage |
| `summary.rs` | Conversation summary cache |
## Context Window
Schema uses `rusqlite` (bundled) with per-session isolation — each session gets its own database.
`resolve_context_window()` di `state.rs`:
1. Ambil `settings.max_tokens` jika ada dan > 0
2. Fallback ke **256.000 token** (default)
### Memory System (`src/model/memory.rs`)
Token dihitung lazily via `count_tokens()` (tiktoken `cl100k_base`, fallback `len/4`), di-cache di `AppStateRest::cached_token_count`, hanya dihitung ulang saat `token_count_dirty=true`.
File-based memory stored under `~/.claude/projects/<project>/memory/`:
## Persistence di Disk
- Each memory is one markdown file with frontmatter (name, description, type)
- Types: `user`, `feedback`, `project`, `reference`
- Memory index in MEMORY.md
- Export/import for lesson sharing
- PID-file session lock prevents concurrent access
Semua data disimpan di **platform data directory**:
- **Linux**: `~/.local/share/zesdex/`
- **macOS**: `~/Library/Application Support/zesdex/`
### Settings & Config (`src/model/`)
```
~/.local/share/zesdex/
├── settings.json # User settings (provider, model, max_tokens, dll)
├── sessions/
│ └── <uuid>/
│ ├── session.json # Metadata sesi
│ ├── messages.jsonl # Message log (append-only)
│ └── .lock # Lock file (cegah concurrent access)
├── memories/
│ └── *.md # Memory files dengan frontmatter
├── lessons/
│ └── *.md # Lesson files (output dari learning system)
└── worktrees/ # Git worktree per sesi (isolasi perubahan)
```
| File | Purpose |
|------|---------|
| `settings.rs` | Serialized user preferences (provider, model, theme) |
| `app_config.rs` | Provider endpoints, API key resolution from env |
| `session.rs` | Current session metadata |
| `conversation.rs` | In-memory conversation state |
| `editlog.rs` | Append-only JSONL edit audit trail |
### Edit Log
`src/model/editlog.rs` records every file mutation:
### settings.json
```json
{"ts": 123, "tool": "edit", "path": "src/main.rs",
"reason": "fix bug", "content_sha256": "abc123",
"bytes_delta": 15, "origin": "chat", "session_id": "sess-1"}
{
"provider": "openai",
"model": "gpt-4o",
"max_tokens": 256000,
"temperature": 0.7,
"concise_output": false
}
```
Max 5000 entries held in memory before pruning oldest.
Diload via `JsonSettingsRepository::load()`, disimpan kembali saat TUI keluar (`state.save_settings()`).
## Context Management (`src/app/runtime/context/`)
### Memory Files
| Module | Purpose |
|--------|---------|
| `tokens.rs` | Token counting via `tiktoken-rs` |
| `window.rs` | Token window resolution (fit within model context) |
| `dedup.rs` | Deduplication of repeated tool outputs |
| `squash.rs` | Compression of large JSON tool results |
| `shaping.rs` | Message dropping when context exceeds limits |
Format markdown dengan YAML frontmatter:
```markdown
---
name: prefer-early-return
type: feedback
description: Selalu gunakan early return untuk mengurangi nesting
---
## IPC Data Flow
Ketika menulis fungsi, gunakan early return/guard clauses daripada deep nesting.
```
Types: `user`, `feedback`, `project`, `reference`
### Lesson Files
Hasil dari learning system, disimpan di `lessons/`:
- Satu file per lesson
- Plain markdown, dibaca oleh overlay `Learning`
- Bisa di-accept/reject dari TUI
## SQLite (Message Log)
`rusqlite` dengan fitur `bundled` (tidak perlu install SQLite terpisah):
- Per-session database di `sessions/<uuid>/messages.db`
- Table `messages`: `id`, `session_id`, `role`, `content`, `timestamp`, `tokens`
- Table `sessions`: `id`, `metadata`, `created_at`
## IPC Protocol (Daemon Mode)
Daemon dan client berkomunikasi via **Unix domain socket**:
```
Daemon State ──diff──▶ serialize ──frame──▶ socket ──▶ Client
Client State ◀── apply_diff ◀── deserialize ◀──── socket ─┘
~/.local/share/zesdex/daemon.sock
```
Frame format:
```
[4 bytes BE: payload length][JSON payload]
```
Message types:
- `Action` — client kirim aksi ke daemon
- `StateSnapshot` — daemon kirim snapshot state ke client
- `Ping/Pong` — keepalive
## Edit History
Agent mencatat setiap mutasi file ke event log internal:
- Tool `write`/`edit`/`delete` merekam path, bytes delta, timestamp
- Digunakan oleh subagent review untuk audit trail
## TurnEvent Queue
Agent berjalan di background thread dan mengirim events ke TUI via `Arc<Mutex<VecDeque<TurnEvent>>>`:
| Event | Payload | Efek di TUI |
|-------|---------|-------------|
| `AssistantMessage(msg)` | `ChatMessage` | Push ke transcript |
| `ToolResult { output, .. }` | String | Push sebagai tool message |
| `Usage { tokens_in, tokens_out }` | u64, u64 | Update `usage` stats |
| `Error(msg)` | String | Toast error |
| `Compacted(msgs)` | `Vec<ChatMessage>` | Update `session_runtime.messages` |
| `SystemNote { kind, message }` | String | Push ke transcript |
| `Done` | — | Set `turn_in_flight_flag = false` |
+121 -80
View File
@@ -1,99 +1,140 @@
# Dependencies
## Rust Crates (30+ direct)
Semua dependency dideklarasikan di `[workspace.dependencies]` dalam `Cargo.toml` root, lalu di-*inherit* oleh setiap crate anggota.
### Core Framework
| Crate | Version | Purpose |
|-------|---------|---------|
| `ratatui` | 0.30.2 | TUI framework |
| `crossterm` | 0.29 | Terminal manipulation |
| `tokio` | 1 | Async runtime (multi-thread, macros, sync, time, net, io-util, signal) |
## Crate per Layer
### HTTP & Networking
| Crate | Version | Purpose |
|-------|---------|---------|
| `reqwest` | 0.13 | HTTP client (JSON, streaming, native-tls-vendored, form) |
### Domain (`apps/domain`)
Hanya boleh pakai dependency yang tidak membawa I/O:
| Crate | Versi | Fungsi |
|-------|-------|--------|
| `serde` | 1 | Serialisasi (derive) |
| `serde_json` | 1 | JSON |
| `chrono` | 0.4 | Tanggal/waktu |
| `uuid` | 1 | UUID v4/v5 |
| `anyhow` | 1 | Error handling |
| `thiserror` | 1 | Derive error types |
### Infrastructure (`apps/infrastructure`)
Semua I/O, LLM, DB, tools:
| Crate | Versi | Fungsi |
|-------|-------|--------|
| `tokio` | 1 | Async runtime (rt-multi-thread, macros, sync, time, net, io-util, signal) |
| `reqwest` | 0.13 | HTTP client (json, stream, blocking, native-tls-vendored, form) |
| `rusqlite` | 0.40 | SQLite (bundled — tidak perlu install sistem) |
| `rmcp` | 2.2 | MCP client (child-process, streamable HTTP) |
| `webbrowser` | 1 | Open URLs in browser |
| `tiktoken-rs` | 0.12 | Token counting (OpenAI cl100k) |
| `similar` | 3 | Diff computation |
| `syntect` | 5 | Syntax highlighting (default-fancy) |
| `ignore` | 0.4 | File walking dengan `.gitignore` support |
| `globset` | 0.4 | Glob pattern matching |
| `include_dir` | 0.7 | Embed direktori ke binary |
| `infer` | 0.19 | Deteksi tipe file dari byte signature |
| `regex` | 1 | Regular expressions |
| `nucleo-matcher` | 0.3 | Fuzzy matching (untuk `@mention` autocomplete) |
| `webbrowser` | 1 | Buka URL di browser (OAuth) |
| `url` | 2 | URL parsing |
| `percent-encoding` | 2 | URL encoding |
### HTML/Markdown
| Crate | Version | Purpose |
|-------|---------|---------|
| `dom_smoothie` | 0.18.0 | HTML DOM manipulation |
| `fast_html2md` | 0.0.62 | HTML-to-Markdown conversion |
| `scraper` | 0.27.0 | HTML parsing/selecting |
| `pulldown-cmark` | 0.13 | Markdown parsing (no default features) |
### Serialization
| Crate | Version | Purpose |
|-------|---------|---------|
| `serde` | 1 | Serialization framework |
| `serde_json` | 1 | JSON serialization |
| `serde_yaml_ng` | 0.10 | YAML serialization |
### Storage & Files
| Crate | Version | Purpose |
|-------|---------|---------|
| `rusqlite` | 0.40 | SQLite (bundled) |
| `ignore` | 0.4 | `.gitignore`-aware file walking |
| `globset` | 0.4 | Glob pattern matching |
| `include_dir` | 0.7 | Embed directory contents in binary |
| `infer` | 0.19 | File type detection |
| `dirs` | 6 | Standard OS directories |
### Text & Search
| Crate | Version | Purpose |
|-------|---------|---------|
| `regex` | 1 | Regular expressions |
| `nucleo-matcher` | 0.3 | Fuzzy matching (for @mention autocomplete) |
| `similar` | 3 | Diff computation |
| `syntect` | 5 | Syntax highlighting |
| `tiktoken-rs` | 0.12 | OpenAI token counting |
### Cryptography & Encoding
| Crate | Version | Purpose |
|-------|---------|---------|
| `base64` | 0.22 | Base64 encoding |
| `sha2` | 0.11 | SHA-256 hashing |
| `hex` | 0.4 | Hex encoding |
| `uuid` | 1 | UUID generation (v4, v5) |
| `libc` | 0.2 | Raw C FFI bindings |
### Error Handling & Logging
| Crate | Version | Purpose |
|-------|---------|---------|
| `anyhow` | 1 | Error handling |
| `tracing` | 0.1 | Structured logging |
| `tracing-subscriber` | 0.3 | Log subscriber with env-filter |
| `chrono` | 0.4 | Date/time with serde |
### Other
| Crate | Version | Purpose |
|-------|---------|---------|
| `fast_html2md` | 0.0.62 | HTMLMarkdown |
| `scraper` | 0.27.0 | HTML parsing + CSS selector |
| `pulldown-cmark` | 0.13 | Markdown parsing |
| `lsp-types` | 0.97 | LSP protocol types |
| `futures-util` | 0.3 | Async stream combinators |
| `libc` | 0.2 | Raw C FFI (Unix process groups) |
## External Services
### TUI (`apps/interfaces/tui`)
| Service | Purpose |
|---------|---------|
| **Anthropic API** | Primary LLM provider |
| **OpenAI API** | Alternative LLM provider (including OAuth) |
| Crate | Versi | Fungsi |
|-------|-------|--------|
| `ratatui` | 0.30.2 | TUI framework |
| `crossterm` | 0.29 | Terminal manipulation (raw mode, events, mouse) |
| `base64` | 0.22 | Base64 (OSC52 clipboard) |
| `sha2` | 0.11 | SHA-256 |
| `hex` | 0.4 | Hex encoding |
| `dirs` | 6 | Platform data directory |
### API (`apps/interfaces/api`)
| Crate | Versi | Fungsi |
|-------|-------|--------|
| `axum` | 0.8 | HTTP server framework (macros) |
| `tower` | 0.5 | Middleware layer |
| `tower-http` | 0.6 | CORS, body limit |
| `argon2` | 0.5 | Password hashing |
| `jsonwebtoken` | 9 | JWT (HS256) |
| `clap` | 4 | CLI argument parsing (derive) |
| `rand_core` | 0.6 | Secure random (getrandom) |
### Serialization (semua layer)
| Crate | Versi | Fungsi |
|-------|-------|--------|
| `serde` | 1 | Framework serialisasi |
| `serde_json` | 1 | JSON |
| `serde_yaml_ng` | 0.10 | YAML (frontmatter memory files) |
## Layanan Eksternal
| Layanan | Fungsi |
|---------|--------|
| **LLM Provider** | OpenAI/Anthropic-compatible API (default: OpenCode AI / DeepSeek) |
| **MCP Servers** | Tool servers eksternal via stdio atau HTTP |
| **LSP Servers** | `rust-analyzer`, `typescript-language-server`, `pyright`, `gopls`, dll |
| **GitHub** | Release artifacts via semantic-release CI |
| **MCP Servers** | External tool servers (stdio or HTTP) |
| **LSP Servers** | Language servers (rust-analyzer, TypeScript, Pyright, gopls, etc.) |
## Build Configuration
## Build & CI
### Compiler Lints (`.cargo/config.toml`)
All unused code, dead code, and deprecation warnings promoted to errors:
`-W unused`, `-W dead_code`, `-W unreachable_code`, `-D warnings`
### Compiler Lints (`Cargo.toml` workspace)
```toml
[workspace.lints.rust]
unused = "deny"
dead_code = "deny"
unreachable_code = "deny"
unused_imports = "deny"
unused_variables = "deny"
unused_mut = "deny"
unused_must_use = "deny"
deprecated = "deny"
trivial_casts = "deny"
trivial_numeric_casts = "deny"
[workspace.lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -2 }
```
### Release Profile
`opt-level=3`, LTO="fat", `codegen-units=1`, `panic="abort"`, `strip="symbols"`, `overflow-checks=true`
```toml
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = "symbols"
overflow-checks = true
```
### CI/CD
- **CI**: cargo build + test + clippy on every push
- **Release**: semantic-release with changelog generation, Cargo.toml version bump, GitHub artifact upload
- **CI**: `cargo build` + `cargo test` + `cargo clippy --all-targets` pada setiap push
- **Release**: semantic-release — auto changelog, Cargo.toml version bump, GitHub artifact upload
- **Versioning**: `v1.17.0` (saat ini) — mengikuti semver dari commit messages
## Feature Flags Penting
| Crate | Feature | Alasan |
|-------|---------|--------|
| `rusqlite` | `bundled` | SQLite statically linked — tidak perlu install sistem |
| `reqwest` | `blocking` | Sync HTTP untuk agent turn di blocking thread |
| `reqwest` | `native-tls-vendored` | TLS tanpa dependency sistem |
| `tokio` | `rt-multi-thread` | Async runtime multi-thread |
| `pulldown-cmark` | *(no default)* | Tidak include semua fitur berat |
| `syntect` | `default-fancy` | Syntax highlighting penuh |
| `rmcp` | `transport-child-process` + `transport-streamable-http-client-reqwest` | MCP via stdio dan HTTP |
+175
View File
@@ -0,0 +1,175 @@
# Panduan Development
## Quick Start
```bash
# Build semua crate
cargo build
# Jalankan TUI (default)
cargo run
# Jalankan dengan log debug
RUST_LOG=debug cargo run
# Jalankan REST API
cargo run -- --api --api-port 8080
# Build release
cargo build --release
```
## Struktur Workspace
```
zesdex/
├── Cargo.toml # Workspace root, semua dependency terpusat di sini
├── Cargo.lock # Lock file (commit ini!)
├── apps/
│ ├── domain/ # Pure domain (tidak ada I/O)
│ ├── application/ # Use-case services
│ ├── infrastructure/ # Semua implementasi I/O
│ ├── interfaces/
│ │ ├── tui/ # TUI — fokus pengembangan utama
│ │ ├── api/ # REST API (Axum)
│ │ ├── daemon/ # Daemon mode
│ │ ├── ws/ # WebSocket
│ │ ├── grpc/ # gRPC
│ │ └── web/ # Web frontend
│ ├── gateway/ # CLI entry point
│ └── bootstrap/ # Seeder
└── docs/
└── CODEMAPS/ # Dokumentasi ini
```
## Menambah Tool Baru
1. Buat file baru di `apps/infrastructure/src/tools/<nama>.rs`
2. Implement trait `Tool`:
```rust
use zesdex_domain::core::tool_call::Tool;
use anyhow::Result;
use serde_json::Value;
pub struct MyTool;
impl Tool for MyTool {
fn name(&self) -> &'static str { "my_tool" }
fn description(&self) -> &'static str { "Deskripsi untuk LLM" }
fn parameters(&self) -> Value {
serde_json::json!({
"type": "object",
"properties": {
"param": { "type": "string", "description": "..." }
},
"required": ["param"]
})
}
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
let param = args["param"].as_str().unwrap_or("");
Ok(format!("Result: {param}"))
}
}
```
3. Daftarkan di `apps/infrastructure/src/tools/mod.rs`:
```rust
pub fn all_tools() -> Vec<Box<dyn Tool>> {
vec![
// ... tools lain ...
Box::new(my_tool::MyTool),
]
}
```
## Menambah Action TUI Baru
1. Tambah variant ke `enum Action` di `apps/interfaces/tui/src/action.rs`
2. Tangani di `apply_action()` match block yang sama
3. Emit dari `controller/input.rs::handle_key()`
```rust
// action.rs
pub enum Action {
// ... existing ...
MyNewAction { data: String },
}
// dalam apply_action:
Action::MyNewAction { data } => {
state.some_field = data;
state.mark_dirty();
}
```
## Menambah Overlay Baru
1. Buat file `apps/interfaces/tui/src/view/overlays/<nama>.rs`
2. Tambah variant ke `enum Overlay` di `state.rs`
3. Tambah entry di `overlays/mod.rs::render_overlay()`
4. Implement `pub fn render(frame, area, block, state)` di file baru
## Linting & Testing
```bash
# Cek semua warnings/errors
cargo clippy --all-targets
# Run tests
cargo test
# Test satu crate saja
cargo test -p zesdex-tui
# Check tanpa build (cepat)
cargo check --all
```
> **Penting**: Workspace ini menggunakan `deny` untuk hampir semua lint.
> Kode harus compile bersih tanpa warning apapun.
## Environment Variables
| Variable | Fungsi |
|----------|--------|
| `RUST_LOG` | Log level (`debug`, `info`, `warn`, `error`) |
| `OPENAI_API_KEY` | API key LLM (jika tidak diset via settings) |
| `ANTHROPIC_API_KEY` | API key Anthropic |
| `ZESDEX_DATA_DIR` | Override direktori data (default: platform standard) |
## Data Directory
Saat development, data disimpan di:
- **Linux**: `~/.local/share/zesdex/`
- **macOS**: `~/Library/Application Support/zesdex/`
Untuk reset bersih:
```bash
rm -rf ~/.local/share/zesdex/
```
## Konvensi Kode
- **Tidak ada `unwrap()`** di kode produksi — gunakan `?` atau `unwrap_or_default()`
- **State hanya dimutasi dari `apply_action()`** — jangan mutasi `AppStateRest` dari view
- **View functions bersifat read-only** — signature `fn draw(frame: &mut Frame, state: &AppStateRest)`
- **Cache mahal dikomputasi sekali** — gunakan flag `dirty` dan `pre_render` pattern
- **Semua string ke LLM harus deskriptif** — nama tool dan deskripsinya penting untuk LLM context
## Release
Release dilakukan via git tag semantic-release:
```bash
git commit -m "feat: tambah fitur baru" # bumps minor
git commit -m "fix: perbaiki bug" # bumps patch
git commit -m "feat!: breaking change" # bumps major
```
CI akan otomatis:
1. Bump versi di `Cargo.toml`
2. Generate `CHANGELOG.md`
3. Build release binary
4. Upload ke GitHub Releases
+151 -64
View File
@@ -1,79 +1,166 @@
# Frontend (TUI) Architecture
# TUI (Terminal User Interface)
## Render Pipeline
Dibangun di atas **ratatui** + **crossterm**. Kode ada di `apps/interfaces/tui/src/`.
The TUI is built with [ratatui](https://github.com/ratatui-org/ratatui) and [crossterm](https://github.com/crossterm-rs/crossterm).
## Struktur Source
```
Timer tick
main.rs: fn tui_loop()
├── controller/input.rs: handle_key() → action
├── app/runtime/actions/mod.rs: apply_action()
│ │
│ └── state mutates (AppStateRest)
── view/mod.rs: build TUI layout
├── view/chat.rs: Chat transcript
├── view/sidebar.rs: Usage dashboard
├── view/status.rs: Status bar
├── view/markdown.rs: Message renderer
├── view/workflow.rs: Hive-mind progress
└── view/theme.rs: Tokyo Night palette
apps/interfaces/tui/src/
├── run.rs # Event loop utama
├── state.rs # AppStateRest — single source of truth
├── action.rs # apply_action(): satu-satunya mutator state
├── turn.rs # Spawn agent turn di background thread
├── lib.rs # Re-export publik
├── controller/
│ ├── input.rs # Key handler → Vec<Action>
│ └── command.rs # Slash command parser
├── view/
── mod.rs # Layout + pre_render() + draw()
│ ├── chat.rs # Chat transcript panel (dengan display cache)
├── sidebar.rs # Sidebar: workflow, tasks, usage
│ ├── status.rs # Status bar satu baris
├── markdown.rs # Markdown → styled Span (pulldown-cmark)
├── workflow.rs # Workflow/hive-mind progress panel
│ ├── theme.rs # Tokyo Night color palette (const)
│ └── overlays/ # 16 overlay panel
└── model/ # Data model lokal TUI
```
## Overlay System
16 overlays managed by `app/mode/`:
| Overlay | File | Purpose |
|---------|------|---------|
| Chat input | `mod.rs` | Main input bar with autocomplete |
| Bash | `bash.rs` | Interactive shell panel |
| Editor | `editor.rs` | Built-in file editor |
| Effort | `effort.rs` | LLM effort selector |
| Help | `help.rs` | Keybindings help |
| Key Input | `key_input.rs` | Custom key binding |
| Learning | `learning.rs` | Lesson viewer |
| Loading | `loading.rs` | Spinner overlay |
| MCP | `mcp.rs` | MCP server management |
| Quit Confirm | `quit_confirm.rs` | Exit confirmation dialog |
| Rewind | `rewind.rs` | Message/history rewind |
| Settings | `settings.rs` | Settings panel |
| Todo | `todo.rs` | Task/TODO list |
| Workflow | (via view) | Workflow progress |
## Layout Structure
## Render Pipeline (Per Frame)
```
┌─────────────────────────────────────────────┐
│ Status Bar (view/status.rs)
├──────────────────────┬──────────────────────┤
│ │
│ Chat Transcript │ Sidebar │
(view/chat.rs) (view/sidebar.rs) │
│ scrollable, tokens, status, │
inline-log styleagent info │
│ │
├──────────────────────┴──────────────────────┤
│ Input Bar + Autocomplete dropdown │
│ (view/mod.rs) │
└─────────────────────────────────────────────┘
run_loop_inner() [50ms in-flight / 200ms idle]
├── drain expired toasts (1x, bukan 2x)
├── if dirty:
view::pre_render(&mut state) ← update cache (markdown, token count)
terminal.draw(|f| view::draw(f, &state))
state.dirty = false
└── poll events → apply_action → Action::Tick
```
## Input Handling
### Optimasi Performa
`controller/input.rs`:
| Masalah lama | Solusi saat ini |
|---|---|
| `count_tokens` (tiktoken) setiap frame | Cache `cached_token_count`, update hanya saat pesan baru |
| `render_markdown` ulang setiap frame | `display_lines_cache` di `AppStateRest`, rebuild saat `transcript_cache.dirty` |
| `Vec::remove(0)` untuk evict pesan lama | `VecDeque::pop_front()` — O(1) |
| `Mutex<bool>` untuk `turn_in_flight` | `Arc<AtomicBool>` — lock-free |
| Render terus meski idle | Skip `terminal.draw()` jika `dirty == false` |
| Poll 50ms konstan | Adaptif: 50ms saat in-flight, 200ms saat idle |
| `drain_expired_toasts` 2x per iterasi | Sekali saja di `run_loop_inner` |
- Normal mode: keystrokes go to the active overlay
- `@mention` triggers fuzzy autocomplete (via `nucleo-matcher`)
- Tab cycles autocomplete candidates
- `Ctrl+Y` copies selected text to clipboard (via OSC52 escape sequence)
- Arrow keys scroll chat, sidebar, and other scrollable panels
## State (AppStateRest)
`AppStateRest` di `state.rs` adalah satu-satunya sumber kebenaran TUI:
```
AppStateRest {
settings: Settings // provider, model, dll
app_config: AppConfig // endpoint, env vars
workspace_roots: Vec<PathBuf> // working directories
session_dir / session_id // path sesi aktif
memory_dir // direktori memory
session_runtime: Option<SessionRuntime> // history pesan, usage stats
transcript_cache: TranscriptCache // VecDeque<ChatMessageDisplay>
scroll: ScrollState // offset scroll pane chat
input: InputState // buffer, cursor, history, autocomplete
misc: MiscState // overlay aktif, toasts, flags
turn_events: Arc<Mutex<VecDeque<TurnEvent>>> // queue event dari agent
turn_in_flight_flag: Arc<AtomicBool> // apakah agent sedang jalan
abort_flag: Arc<AtomicBool> // sinyal abort oleh user
// Cache performa
display_lines_cache: Vec<Line<'static>> // hasil render markdown
cached_token_count: usize // token count terkini
token_count_dirty: bool // perlu hitung ulang?
last_render_width: u16 // lebar terminal saat render terakhir
dirty: bool // perlu render ulang?
quit: bool // keluar dari loop?
}
```
**Aturan mutasi:**
- Dimutasi hanya dari `action.rs::apply_action()` dan `run.rs` (untuk dirty/quit)
- Semua fungsi `view/*` bersifat read-only terhadap state
- `pre_render_chat()` boleh mutasi hanya field cache (`display_lines_cache`, `cached_token_count`, `token_count_dirty`)
## Input & Actions
`controller/input.rs::handle_key()``Vec<Action>``apply_action(&mut state, action)`
Semua mutasi state melewati satu titik: `apply_action`. Controller tidak tahu *bagaimana* state diubah, hanya *action apa* yang dihasilkan.
### Action Utama
| Action | Efek |
|--------|------|
| `SubmitInput(text)` | Push ke transcript, spawn agent turn |
| `Tick` | Drain `TurnEvent` queue, update state dari hasil agent |
| `ScrollUp/Down` | Ubah `scroll.offset` |
| `OpenOverlay(v)` | Set `misc.overlay = v` |
| `Resize(w, h)` | Invalidasi cache display, set `last_render_width` |
| `AbortTurn` | Store `true` ke `abort_flag` |
| `ForceQuit` | Set `quit = true` |
## Overlays (16 Panel)
| Overlay | File | Fungsi |
|---------|------|--------|
| `Help` | `overlays/help.rs` | Daftar shortcut keyboard |
| `Settings` | `overlays/settings.rs` | Panel pengaturan |
| `Bash` | `overlays/bash.rs` | Background shell jobs |
| `QuitConfirm` | `overlays/quit_confirm.rs` | Konfirmasi keluar |
| `KeyInput` | `overlays/key_input.rs` | Capture key binding |
| `Editor` | `overlays/editor.rs` | File editor inline |
| `Effort` | `overlays/effort.rs` | Pilih level reasoning LLM |
| `Mcp` | `overlays/mcp.rs` | Manajemen MCP server |
| `Todo` | `overlays/todo.rs` | Daftar TODO |
| `Rewind` | `overlays/rewind.rs` | Navigasi history pesan |
| `Learning` | `overlays/learning.rs` | Viewer lesson |
| `Usage` | `overlays/usage.rs` | Statistik token |
| `Loading` | `overlays/loading.rs` | Spinner generik |
| `ModelSelector` | `overlays/model_selector.rs` | Pilih model LLM |
| `ClearConfirm` | `overlays/clear_confirm.rs` | Konfirmasi clear chat |
## Layout Terminal
```
┌───────────────────────────────────────────────┐
│ │
│ Chat Transcript Sidebar (≥90) │
│ (view/chat.rs) ┌────────────┐ │
│ VecDeque messages │ Workflow │ │
│ + markdown cache ├────────────┤ │
│ scrollable │ Tasks │ │
│ ├────────────┤ │
│ │ Usage │ │
│ └────────────┘ │
├───────────────────────────────────────────────┤
Input Bar + Autocomplete dropdown │
├───────────────────────────────────────────────┤
│ ⚡zesdex READY │ ...center... │ tok · model │
└───────────────────────────────────────────────┘
```
Sidebar hanya tampil jika lebar terminal ≥ 90 kolom.
## Theme
`view/theme.rs` defines a Tokyo Night color palette as constants (`Theme::PRIMARY`, `Theme::ERROR`, `Theme::TEXT_MUTED`, etc.) rather than using a theme enum or hot-reloadable config. All view modules import and apply these constants directly.
`view/theme.rs` mendefinisikan palette **Tokyo Night** sebagai `const Color`:
`PRIMARY`, `BG`, `SURFACE`, `SURFACE_ELEVATED`, `BORDER`, `TEXT`, `TEXT_DIM`, `TEXT_MUTED`, `SUCCESS`, `WARNING`, `ERROR`, `INFO`, `HIGHLIGHT`, `CODE_BG`, dll.
## Markdown Rendering
`view/markdown.rs::render_markdown(text, width, dim)`:
- Parse dengan `pulldown-cmark`
- Hasilkan `Vec<Span<'static>>` dengan styling
- Support: heading, code block, diff block (warna +/-/@@), list, blockquote, table, inline code, link
- `dim=true` → semua span memakai `TEXT_DIM` + italic (untuk tool output)
- Hasil di-cache di `AppStateRest::display_lines_cache`