README Source History Refs Compare Notes Search .system/research/SMH-RESEARCH-RSCH0005-crate-ecosystem-research/index.md Raw Rendered preview
id: SMH-RESEARCH-RSCH0005
type: research
title: "Crate Ecosystem Research: smith"
Crate Ecosystem Research: smith
Date: 2026-05-21
Status: Research — informs Cargo.toml decisions and architecture
This document records crate evaluations, alternatives considered, and dependency tradeoffs for smith.
1. Async Runtime: tokio
Decision: tokio (full feature set)
Why:
De facto standard for Rust async
tokio::sync::mpsc for agent-harness-TUI event channels
tokio::task::spawn_blocking for crossterm polling
tokio::time::sleep for tick-based TUI refresh
tokio::signal for graceful shutdown
Why not async-std:
Less ecosystem support, fewer integrations
tokio is already used by reqwest (HTTP client)
Why not smol:
Smaller ecosystem, less mature
Good for embedded but overkill for smith's needs
Version: 1.43+ (for tokio::sync::watch improvements)
2. HTTP Client: reqwest
Decision: reqwest
Why:
Built on hyper (HTTP/2 support)
Async-native with tokio integration
SSE (Server-Sent Events) support via reqwest::bytes_stream() and a small parser
Built-in JSON serialization via serde
Proxy support (needed for secret proxy)
Why not hyper directly:
reqwest is a higher-level wrapper; smith doesn't need raw HTTP control
reqwest handles connection pooling, retries, timeouts
Why not surf:
Part of async-std ecosystem; mismatch with tokio
Version: 0.12+ (for http 1.0 compatibility)
SSE strategy: Hand-rolled SSE parsing over reqwest streaming is the v1 path (P9). reqwest-eventsource or eventsource-stream remain viable v2 alternatives if parser complexity grows.
3. Serialization: serde + ciborium
Decision: serde for JSON, ciborium for binary
serde (JSON):
Config files, provider API payloads, tool arguments
serde_json for JSON, serde_lua (future) for Lua tables
ciborium (CBOR):
Session persistence, trace logs
Compact, binary-safe, serde-native
Proven by P3b/P6/P7/P11 prototypes with complex enums and trace/session roundtrips
Why not MessagePack:
CBOR is an IETF standard (RFC 8949)
Better forward/backward compatibility guarantees
Used by WebAuthn, COSE — well-vetted
Why not bincode:
bincode is Rust-specific; CBOR is language-agnostic
CBOR has better schema evolution support
Why not protobuf:
Overkill for smith's simple structs
Requires code generation step
CBOR with serde derives is zero-friction
Why not mini-cbor:
P3 found derive friction for complex enums used by smith session/trace data.
ciborium works directly with existing serde derives, avoiding duplicate serialization annotations.
Versions:
serde = "1", serde_json = "1"
ciborium = "0.2"
4. TUI Framework: ratatui + crossterm
Decision: ratatui with crossterm backend
ratatui:
Widget framework with layout system
TestBackend for snapshot testing
17 built-in widgets matching pi's component set
Active development (0.29+), large community
crossterm:
Cross-platform terminal control (Unix + Windows)
Event polling (keyboard, mouse, resize)
Raw mode, alternate screen, cursor control
Kitty keyboard protocol support
Why not tui-rs (deprecated):
ratatui is the actively maintained fork
tui-rs is unmaintained
Why not termion:
Unix-only; no Windows support
Less featureful than crossterm
Why not console:
console is simpler but lacks TUI framework
Would require building widget system from scratch
Versions:
ratatui = "0.29"
crossterm = "0.28"
5. Lua Runtime: mlua (LuaJIT)
Decision: mlua with LuaJIT feature
Why mlua:
Safe Rust bindings to Lua/LuaJIT
mlua::Lua manages Lua state lifetime
mlua::Function for calling Lua from Rust
mlua::RegistryKey for storing Lua references across calls
serde integration for Lua table ↔ Rust struct conversion
Lua coroutine support for plugin workflows
Why LuaJIT:
JIT compilation → native code (fast)
FFI for C libraries (future extensibility)
Smaller memory footprint than standard Lua
mlua feature flags: features = ["luajit", "vendored", "serialize"]
Why not rlua:
rlua is unmaintained; mlua is the successor
mlua has better async and serde support
Why not wasmtime (WASM runtime):
WASM is future option (tier 2 plugins)
LuaJIT is simpler for v1, better performance for plugin startup
WASM has sandboxing advantages but higher complexity
Why not Rhai:
Rhai is Rust-native but slower than LuaJIT
Smaller ecosystem, fewer libraries
Lua has better plugin developer familiarity
Version: mlua = { version = "0.10", features = ["luajit", "vendored", "serialize"] }
Android/Termux note: Vendored LuaJIT on aarch64-linux-android needs compiler-rt builtins linked for __clear_cache (P2d): -lclang_rt.builtins-aarch64-android.
6. CLI Parser: clap
Decision: clap v4 with derive feature
Why:
Derive macros for declarative CLI definition
Subcommands, flags, positional args, env var fallback
Shell completion generation (bash, zsh, fish)
Help text generation with formatting
Validation (e.g., value_parser = clap::value_parser!(u64))
Why not structopt:
structopt is merged into clap v3+; no longer separate
Why not argh:
argh is smaller but less featureful
clap's ecosystem and documentation are superior
Why not gumdrop:
Version: clap = { version = "4", features = ["derive", "env", "cargo"] }
7. Error Handling: thiserror + anyhow
Decision: thiserror for library crates, anyhow for CLI/binary
thiserror (smith, smith-core, smith-ai, smith-harness, smith-tui):
Structured error enums with #[derive(Error)]
#[from] for automatic conversion
#[error("message")] for Display formatting
Perfect for library APIs
anyhow (smith-cli):
Ergonomic error handling in application code
.context("...") for adding context
Result<T> type alias for simplicity
Why not eyre:
eyre has color-eyre for pretty backtraces
But smith is a TUI app — backtrace display is TUI-controlled, not terminal
thiserror + anyhow is simpler
Versions:
thiserror = "2"
anyhow = "1"
8. Logging: tracing + tracing-subscriber
Decision: tracing ecosystem
Why tracing over log:
Structured logging with spans (track request lifecycle)
Async-aware (spans follow tasks across threads)
tracing::info!, tracing::debug! macros
Compatible with log crate via tracing-log
tracing-subscriber:
Formatter: fmt::layer() for pretty/colored output
Filter: EnvFilter for RUST_LOG=smith=debug
Layer composition: file + stdout simultaneously
Why not env_logger:
env_logger is simpler but not async-aware
tracing's span system is essential for tracking agent loop turns
Why not slog:
slog is powerful but complex
tracing is the modern standard
Versions:
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
9. Configuration: toml + serde
Decision: Lua for user config, TOML for internal/metadata
User config (~/.smith/config.lua):
Lua tables — users already write Lua for plugins
Single language for config and plugins
Dynamic: config can call functions, use conditionals
Internal metadata (Cargo.toml, providers.json):
TOML for Cargo workspace
JSON for provider registry (fetched from pi.dev + catwalk)
Why not YAML:
YAML has too many footguns ( Norway problem, type coercion)
TOML is simpler and unambiguous
Why not JSON for user config:
JSON doesn't support comments
Lua is more expressive (functions, conditionals)
Why not KDL:
KDL is interesting but niche; no Lua integration
Versions:
toml = "0.8" (for Cargo.toml parsing if needed)
10. XDG Directories: dirs
Decision: dirs
Why:
Cross-platform XDG directory resolution
dirs::config_dir() → ~/.config/smith/ (Linux), ~/Library/Application Support/smith/ (macOS), %APPDATA%\smith\ (Windows)
dirs::cache_dir() for bytecode cache
dirs::data_dir() for session storage
Why not directories:
directories is the higher-level crate; dirs is lower-level
dirs is sufficient for smith's needs
Version: dirs = "5"
11. UUID Generation: uuid
Decision: uuid with v7 feature
Why:
UUIDv7 is time-ordered (database-friendly, sortable)
uuid::Uuid::now_v7() generates time-ordered UUIDs
Used for EntryId, SessionId
Why not ulid:
ULID is also time-ordered but less standard
UUIDv7 is an IETF standard (RFC 9562)
Version: uuid = { version = "1", features = ["v7"] }
12. Process Spawning: tokio::process
Decision: tokio::process::Command
Why:
Async-aware process spawning
Command::spawn() returns Child with async I/O
Used by bash tool for async command execution
Timeout support via tokio::time::timeout
Why not std::process:
std::process is blocking; would block async runtime
tokio::process integrates with tokio's I/O system
Built into tokio — no extra dependency.
13. Regular Expressions: regex
Decision: regex
Why:
Fast, safe regex engine (no catastrophic backtracking)
Used by grep tool, provider response parsing
regex::Regex is Send + Sync
Why not fancy-regex:
fancy-regex supports backreferences but is slower
smith doesn't need backreferences
Why not ripgrep's regex engine:
ripgrep's regex-automata is lower-level
regex crate is the standard, well-documented
Version: regex = "1"
14. Terminal Image Protocols
Decision: kitty graphics protocol primary, sixel fallback
Kitty graphics:
Modern, well-specified, supports PNG transmission
Placement with z-index, cropping, animation
Supported by kitty, ghostty, wezterm
Sixel:
Older standard, widely supported
Lower quality (256 colors), no alpha
Fallback for terminals without kitty support
No iTerm2 inline images:
iTerm2 protocol is macOS-specific
Kitty protocol is more widely supported
Implementation: Custom escape sequence generation (no crate needed — simple string formatting).
15. Compression: zstd
Decision: zstd
Why:
Fast compression/decompression
Dictionary support for small payloads
Used for trace log compression (per-entry)
Better ratio than gzip, faster than lz4 for medium payloads
Why not gzip:
gzip is slower, worse ratio
Why not lz4:
lz4 is faster but worse ratio
zstd is the sweet spot for smith's trace entries
Version: zstd = "0.13"
16. Cryptographic Hashing: sha2
Decision: sha2
Why:
SHA-256 for provider payload hashing (privacy)
SHA-256 for file hash snapshots (deterministic replay)
sha2::Sha256 from RustCrypto ecosystem
Why not blake3:
blake3 is faster but less standard
SHA-256 is universally recognized
Version: sha2 = "0.10"
17. Streaming: futures + tokio-stream
Decision: tokio-stream
Why:
tokio_stream::iter() for mock streams
tokio_stream::wrappers::ReceiverStream for channel-backed streams
StreamExt for collect(), timeout(), fold()
Built on futures-core — no extra dependency beyond tokio.
18. JSON Schema: jsonschema
Decision: jsonschema (future consideration)
Why not yet:
Tool definition validation uses JSON Schema
But mlua + manual validation is sufficient for v1
Consider jsonschema crate for v2 if validation complexity grows
Version: jsonschema = "0.28" (workspace dep, deferred)
19. Terminal Size Detection
Decision: crossterm
Built into crossterm:
crossterm::terminal::size() → (cols, rows)
Resize events via crossterm::event::Event::Resize
No extra dependency needed
20. Signal Handling
Decision: tokio::signal
Why:
tokio::signal::ctrl_c() for SIGINT
tokio::signal::unix::signal() for SIGTERM (Unix)
Async-aware — doesn't block runtime
Integrated with graceful shutdown flow
Why not signal-hook:
signal-hook is good but tokio::signal is simpler when already using tokio
Built into tokio — no extra dependency.
21. Built-in Tool Search: ignore + ripgrep crates
Decision: ignore for find, grep crates for grep
Why ignore:
Same directory walking and .gitignore handling foundation used by ripgrep/fd.
Handles gitignore negation, directory-only rules, hidden files, symlinks, glob overrides, max depth, and parallel traversal.
Reimplementing this is non-product code with many cross-platform edge cases.
Why grep + grep-regex + grep-searcher:
Ripgrep is exposed as a library ecosystem.
grep-searcher handles efficient line search, binary detection, context, and memory mapping.
grep-regex provides the regex matcher backend.
grep provides the Sink abstraction used to stream structured matches into smith tool output.
Why not shell out to rg/fd:
Library use gives structured results, timeout control, cancellation, and no text parsing.
External commands remain available through the bash tool but are not the tool implementation substrate.
Versions:
ignore = "0.4"
grep = "0.4", grep-regex = "0.1", grep-searcher = "0.1"
22. TUI Feature Helpers: similar, syntastica, fuzzy-matcher
Decision: focused crates for diff, syntax, and fuzzy filtering
similar:
Provides line/word diffs, hunks, unified diff output, and similarity ratio.
Used by replay compare mode, VCS/time-travel diff views, and tool result rendering.
Reimplementing Myers/patience diff plus hunk formatting is complex and not smith-specific.
syntastica + syntastica-parsers:
Tree-sitter-based syntax highlighting with runtime-c2rust, avoiding C runtime links.
P16 verified ANSI terminal output, HTML output, custom themes, multiple languages, and Processor reuse on Android/Termux.
Syntax highlighting is a TUI primitive; user-facing views remain Lua plugins.
fuzzy-matcher:
Zero-dependency fuzzy scoring with match indices.
Fits SelectList/timeline filtering: score candidates and highlight matched characters.
nucleo is stronger for editor-scale fuzzy matching but overkill for smith v1.
Versions:
similar = "3"
syntastica = { version = "0.6", default-features = false, features = ["runtime-c2rust"] }
syntastica-parsers = "0.6"
fuzzy-matcher = "0.3"
23. CLI Completion: clap_complete
Decision: clap_complete
Why:
Generates shell completions from the same clap command definitions used by smith-cli.
Keeps bash/zsh/fish completions in sync without hand-maintained scripts.
Thin API, but maintenance savings are high and the crate is part of the clap ecosystem.
Version: clap_complete = "4"
24. VCS Primitives: jj CLI first, targeted gix when structured plugin queries land
Decision: expose VCS as primitives; implement features as Lua plugins
Architecture principle:
Rust core exposes primitives (smith.vcs.*, smith.shortcut.*, smith.tui.*).
Built-in features are Lua plugins using the same SDK as user plugins.
No feature gets a privileged Rust UI path unless it is a reusable primitive.
jj role:
Operation log, undo/redo, restore, time-travel inspection, and interdiff are jj strengths.
jj state can be relocated by symlinking .jj to XDG state and fixing git_target to an absolute path.
P17 validates Lua plugins over smith.vcs.* primitives for time-travel, commands, and VCS tools.
gix role:
Use targeted gix features for structured plugin query APIs where CLI parsing is fragile: status, revision resolution, blame/annotate, blob diffs, changed files.
Do not expose gix itself to Lua; expose smith-owned data contracts.
Avoid the full umbrella/default feature set unless a plugin API needs it.
Version: gix = { version = "0.83", default-features = false, features = ["blame", "blob-diff", "revision"] }
25. Summary: Dependency Count
Category
Crates
Direct Deps
Async
tokio, tokio-stream
2
HTTP
reqwest
1
Serialization
serde, serde_json, ciborium
3
TUI
ratatui, crossterm
2
Lua
mlua
1
CLI
clap, clap_complete
2
Errors
thiserror, anyhow
2
Logging
tracing, tracing-subscriber
2
Tool search
ignore, grep, grep-regex, grep-searcher
4
TUI helpers
similar, syntastica, syntastica-parsers, fuzzy-matcher
4
VCS primitives
gix (targeted features)
1
System
dirs, uuid, regex, zstd, sha2
5
Total production
~28
Dev/test
insta, proptest, assert_cmd, assert_fs, expectrl, criterion, cargo-nextest, cargo-tarpaulin, cargo-mutants
9 (tools)
26. Rust Version Policy
Current policy: Latest stable Rust, edition 2024.
Why rolling stable:
Smith is pre-1.0 and not yet bound by downstream library MSRV contracts.
Current crate choices move quickly; pinning an old compiler blocks maintained dependencies.
Edition 2024 stays fixed in manifests; compiler channel follows stable.
Release policy:
Avoid setting rust-version in workspace manifests unless release policy requires it.
rust-toolchain.toml uses channel = "stable".
If smith later needs long-term support, define a formal MSRV window then.
27. Cargo Workspace Structure
# Cargo.toml (workspace root)
[workspace]
members = ["smith", "smith-core", "smith-ai", "smith-tui", "smith-harness", "smith-cli"]
resolver = "3"
[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT OR Apache-2.0"
[workspace.dependencies]
tokio = { version = "1.43", features = ["full"] }
reqwest = { version = "0.12", features = ["json", "stream"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
ciborium = "0.2"
ratatui = "0.29"
crossterm = "0.28"
mlua = { version = "0.10", features = ["luajit", "vendored", "serialize"] }
clap = { version = "4", features = ["derive", "env"] }
clap_complete = "4"
ignore = "0.4"
grep = "0.4"
grep-regex = "0.1"
grep-searcher = "0.1"
similar = "3"
syntastica = { version = "0.6", default-features = false, features = ["runtime-c2rust"] }
syntastica-parsers = "0.6"
fuzzy-matcher = "0.3"
gix = { version = "0.83", default-features = false, features = ["blame", "blob-diff", "revision"] }
thiserror = "2"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
dirs = "5"
uuid = { version = "1", features = ["v7"] }
regex = "1"
zstd = "0.13"
sha2 = "0.10"
insta = "1"
proptest = "1.6"
assert_cmd = "2"
assert_fs = "1"
criterion = { version = "0.5", features = ["html_reports"] }
28. Rejected Crates (Documented for Future Reference)
Crate
Why Rejected
Context
async-std
Ecosystem mismatch with tokio
Async runtime
smol
Too small, less mature
Async runtime
surf
async-std ecosystem
HTTP client
tui-rs
Unmaintained, superseded by ratatui
TUI framework
termion
Unix-only
Terminal control
rlua
Unmaintained, superseded by mlua
Lua bindings
wasmtime
Complexity, v2 consideration
Plugin runtime
Rhai
Slower, smaller ecosystem
Plugin runtime
structopt
Merged into clap
CLI parser
argh
Less featureful
CLI parser
eyre
Unnecessary complexity
Error handling
env_logger
Not async-aware
Logging
slog
Too complex
Logging
ulid
Less standard than UUIDv7
ID generation
blake3
Less standard than SHA-256
Hashing
fancy-regex
Slower, backreferences not needed
Regex
jsonschema
Deferred to v2
Validation
mini-cbor
Derive friction for complex enums; ciborium uses existing serde derives cleanly
CBOR serialization
color-eyre
Not selected for current dependency set
Error display
indicatif
Not selected for current dependency set
Progress bars
gix (full umbrella/default)
Deferred; use targeted features only behind smith.vcs.* structured query primitives
VCS integration
viuer
Deferred until multi-protocol image rendering is required; kitty-first custom rendering is enough for v1
Terminal images
walkdir
Subsumed by ignore, which adds gitignore/glob/hidden-file semantics
File traversal
glob
Subsumed by ignore/globset behavior in the find tool path
Pattern matching
syntect
Oniguruma C dependency and regex-based highlighting; P16 proves syntastica with runtime-c2rust is Android-safe
Syntax highlighting
notify
Defer to v2; manual/plugin-triggered reload is enough for v1
File watching
dashmap
Premature concurrency optimization; locks or actor ownership are clearer for smith's current maps
Concurrent maps
nucleo
Strong editor-scale fuzzy engine but overkill; fuzzy-matcher fits v1 SelectList filtering
Fuzzy matching
---
id: SMH-RESEARCH-RSCH0005
type: research
title: "Crate Ecosystem Research: smith"
---
# Crate Ecosystem Research: smith
**Date:** 2026-05-21
**Status:** Research — informs Cargo.toml decisions and architecture
This document records crate evaluations, alternatives considered, and dependency tradeoffs for smith.
---
## 1. Async Runtime: tokio
### Decision: tokio (full feature set)
**Why:**
- De facto standard for Rust async
- `tokio::sync::mpsc` for agent-harness-TUI event channels
- `tokio::task::spawn_blocking` for crossterm polling
- `tokio::time::sleep` for tick-based TUI refresh
- `tokio::signal` for graceful shutdown
**Why not async-std:**
- Less ecosystem support, fewer integrations
- tokio is already used by reqwest (HTTP client)
**Why not smol:**
- Smaller ecosystem, less mature
- Good for embedded but overkill for smith's needs
**Version:** 1.43+ (for `tokio::sync::watch` improvements)
---
## 2. HTTP Client: reqwest
### Decision: reqwest
**Why:**
- Built on hyper (HTTP/2 support)
- Async-native with tokio integration
- SSE (Server-Sent Events) support via `reqwest::bytes_stream()` and a small parser
- Built-in JSON serialization via serde
- Proxy support (needed for secret proxy)
**Why not hyper directly:**
- reqwest is a higher-level wrapper; smith doesn't need raw HTTP control
- reqwest handles connection pooling, retries, timeouts
**Why not surf:**
- Part of async-std ecosystem; mismatch with tokio
**Version:** 0.12+ (for http 1.0 compatibility)
**SSE strategy:** Hand-rolled SSE parsing over `reqwest` streaming is the v1 path (P9). `reqwest-eventsource` or `eventsource-stream` remain viable v2 alternatives if parser complexity grows.
---
## 3. Serialization: serde + ciborium
### Decision: serde for JSON, ciborium for binary
**serde (JSON):**
- Config files, provider API payloads, tool arguments
- `serde_json` for JSON, `serde_lua` (future) for Lua tables
**ciborium (CBOR):**
- Session persistence, trace logs
- Compact, binary-safe, serde-native
- Proven by P3b/P6/P7/P11 prototypes with complex enums and trace/session roundtrips
**Why not MessagePack:**
- CBOR is an IETF standard (RFC 8949)
- Better forward/backward compatibility guarantees
- Used by WebAuthn, COSE — well-vetted
**Why not bincode:**
- bincode is Rust-specific; CBOR is language-agnostic
- CBOR has better schema evolution support
**Why not protobuf:**
- Overkill for smith's simple structs
- Requires code generation step
- CBOR with serde derives is zero-friction
**Why not mini-cbor:**
- P3 found derive friction for complex enums used by smith session/trace data.
- ciborium works directly with existing serde derives, avoiding duplicate serialization annotations.
**Versions:**
- `serde = "1"` , `serde_json = "1"`
- `ciborium = "0.2"`
---
## 4. TUI Framework: ratatui + crossterm
### Decision: ratatui with crossterm backend
**ratatui:**
- Widget framework with layout system
- `TestBackend` for snapshot testing
- 17 built-in widgets matching pi's component set
- Active development (0.29+), large community
**crossterm:**
- Cross-platform terminal control (Unix + Windows)
- Event polling (keyboard, mouse, resize)
- Raw mode, alternate screen, cursor control
- Kitty keyboard protocol support
**Why not tui-rs (deprecated):**
- ratatui is the actively maintained fork
- tui-rs is unmaintained
**Why not termion:**
- Unix-only; no Windows support
- Less featureful than crossterm
**Why not console:**
- console is simpler but lacks TUI framework
- Would require building widget system from scratch
**Versions:**
- `ratatui = "0.29"`
- `crossterm = "0.28"`
---
## 5. Lua Runtime: mlua (LuaJIT)
### Decision: mlua with LuaJIT feature
**Why mlua:**
- Safe Rust bindings to Lua/LuaJIT
- `mlua::Lua` manages Lua state lifetime
- `mlua::Function` for calling Lua from Rust
- `mlua::RegistryKey` for storing Lua references across calls
- `serde` integration for Lua table ↔ Rust struct conversion
- Lua coroutine support for plugin workflows
**Why LuaJIT:**
- JIT compilation → native code (fast)
- FFI for C libraries (future extensibility)
- Smaller memory footprint than standard Lua
- `mlua` feature flags: `features = ["luajit", "vendored", "serialize"]`
**Why not rlua:**
- rlua is unmaintained; mlua is the successor
- mlua has better async and serde support
**Why not wasmtime (WASM runtime):**
- WASM is future option (tier 2 plugins)
- LuaJIT is simpler for v1, better performance for plugin startup
- WASM has sandboxing advantages but higher complexity
**Why not Rhai:**
- Rhai is Rust-native but slower than LuaJIT
- Smaller ecosystem, fewer libraries
- Lua has better plugin developer familiarity
**Version:** `mlua = { version = "0.10", features = ["luajit", "vendored", "serialize"] }`
**Android/Termux note:** Vendored LuaJIT on `aarch64-linux-android` needs compiler-rt builtins linked for `__clear_cache` (P2d): `-lclang_rt.builtins-aarch64-android` .
---
## 6. CLI Parser: clap
### Decision: clap v4 with derive feature
**Why:**
- Derive macros for declarative CLI definition
- Subcommands, flags, positional args, env var fallback
- Shell completion generation (bash, zsh, fish)
- Help text generation with formatting
- Validation (e.g., `value_parser = clap::value_parser!(u64)` )
**Why not structopt:**
- structopt is merged into clap v3+; no longer separate
**Why not argh:**
- argh is smaller but less featureful
- clap's ecosystem and documentation are superior
**Why not gumdrop:**
- gumdrop is unmaintained
**Version:** `clap = { version = "4", features = ["derive", "env", "cargo"] }`
---
## 7. Error Handling: thiserror + anyhow
### Decision: thiserror for library crates, anyhow for CLI/binary
**thiserror (smith, smith-core, smith-ai, smith-harness, smith-tui):**
- Structured error enums with `#[derive(Error)]`
- `#[from]` for automatic conversion
- `#[error("message")]` for Display formatting
- Perfect for library APIs
**anyhow (smith-cli):**
- Ergonomic error handling in application code
- `.context("...")` for adding context
- `Result<T>` type alias for simplicity
**Why not eyre:**
- eyre has color-eyre for pretty backtraces
- But smith is a TUI app — backtrace display is TUI-controlled, not terminal
- thiserror + anyhow is simpler
**Versions:**
- `thiserror = "2"`
- `anyhow = "1"`
---
## 8. Logging: tracing + tracing-subscriber
### Decision: tracing ecosystem
**Why tracing over log:**
- Structured logging with spans (track request lifecycle)
- Async-aware (spans follow tasks across threads)
- `tracing::info!` , `tracing::debug!` macros
- Compatible with `log` crate via `tracing-log`
**tracing-subscriber:**
- Formatter: `fmt::layer()` for pretty/colored output
- Filter: `EnvFilter` for `RUST_LOG=smith=debug`
- Layer composition: file + stdout simultaneously
**Why not env_logger:**
- env_logger is simpler but not async-aware
- tracing's span system is essential for tracking agent loop turns
**Why not slog:**
- slog is powerful but complex
- tracing is the modern standard
**Versions:**
- `tracing = "0.1"`
- `tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }`
---
## 9. Configuration: toml + serde
### Decision: Lua for user config, TOML for internal/metadata
**User config ( `~/.smith/config.lua` ):**
- Lua tables — users already write Lua for plugins
- Single language for config and plugins
- Dynamic: config can call functions, use conditionals
**Internal metadata ( `Cargo.toml` , `providers.json` ):**
- TOML for Cargo workspace
- JSON for provider registry (fetched from pi.dev + catwalk)
**Why not YAML:**
- YAML has too many footguns ( Norway problem, type coercion)
- TOML is simpler and unambiguous
**Why not JSON for user config:**
- JSON doesn't support comments
- Lua is more expressive (functions, conditionals)
**Why not KDL:**
- KDL is interesting but niche; no Lua integration
**Versions:**
- `toml = "0.8"` (for Cargo.toml parsing if needed)
---
## 10. XDG Directories: dirs
### Decision: dirs
**Why:**
- Cross-platform XDG directory resolution
- `dirs::config_dir()` → `~/.config/smith/` (Linux), `~/Library/Application Support/smith/` (macOS), `%APPDATA%\smith\` (Windows)
- `dirs::cache_dir()` for bytecode cache
- `dirs::data_dir()` for session storage
**Why not directories:**
- `directories` is the higher-level crate; `dirs` is lower-level
- `dirs` is sufficient for smith's needs
**Version:** `dirs = "5"`
---
## 11. UUID Generation: uuid
### Decision: uuid with v7 feature
**Why:**
- UUIDv7 is time-ordered (database-friendly, sortable)
- `uuid::Uuid::now_v7()` generates time-ordered UUIDs
- Used for `EntryId` , `SessionId`
**Why not ulid:**
- ULID is also time-ordered but less standard
- UUIDv7 is an IETF standard (RFC 9562)
**Version:** `uuid = { version = "1", features = ["v7"] }`
---
## 12. Process Spawning: tokio::process
### Decision: tokio::process::Command
**Why:**
- Async-aware process spawning
- `Command::spawn()` returns `Child` with async I/O
- Used by bash tool for async command execution
- Timeout support via `tokio::time::timeout`
**Why not std::process:**
- std::process is blocking; would block async runtime
- tokio::process integrates with tokio's I/O system
**Built into tokio** — no extra dependency.
---
## 13. Regular Expressions: regex
### Decision: regex
**Why:**
- Fast, safe regex engine (no catastrophic backtracking)
- Used by grep tool, provider response parsing
- `regex::Regex` is `Send + Sync`
**Why not fancy-regex:**
- fancy-regex supports backreferences but is slower
- smith doesn't need backreferences
**Why not ripgrep's regex engine:**
- ripgrep's regex-automata is lower-level
- `regex` crate is the standard, well-documented
**Version:** `regex = "1"`
---
## 14. Terminal Image Protocols
### Decision: kitty graphics protocol primary, sixel fallback
**Kitty graphics:**
- Modern, well-specified, supports PNG transmission
- Placement with z-index, cropping, animation
- Supported by kitty, ghostty, wezterm
**Sixel:**
- Older standard, widely supported
- Lower quality (256 colors), no alpha
- Fallback for terminals without kitty support
**No iTerm2 inline images:**
- iTerm2 protocol is macOS-specific
- Kitty protocol is more widely supported
**Implementation:** Custom escape sequence generation (no crate needed — simple string formatting).
---
## 15. Compression: zstd
### Decision: zstd
**Why:**
- Fast compression/decompression
- Dictionary support for small payloads
- Used for trace log compression (per-entry)
- Better ratio than gzip, faster than lz4 for medium payloads
**Why not gzip:**
- gzip is slower, worse ratio
**Why not lz4:**
- lz4 is faster but worse ratio
- zstd is the sweet spot for smith's trace entries
**Version:** `zstd = "0.13"`
---
## 16. Cryptographic Hashing: sha2
### Decision: sha2
**Why:**
- SHA-256 for provider payload hashing (privacy)
- SHA-256 for file hash snapshots (deterministic replay)
- `sha2::Sha256` from RustCrypto ecosystem
**Why not blake3:**
- blake3 is faster but less standard
- SHA-256 is universally recognized
**Version:** `sha2 = "0.10"`
---
## 17. Streaming: futures + tokio-stream
### Decision: tokio-stream
**Why:**
- `tokio_stream::iter()` for mock streams
- `tokio_stream::wrappers::ReceiverStream` for channel-backed streams
- `StreamExt` for `collect()` , `timeout()` , `fold()`
**Built on futures-core** — no extra dependency beyond tokio.
---
## 18. JSON Schema: jsonschema
### Decision: jsonschema (future consideration)
**Why not yet:**
- Tool definition validation uses JSON Schema
- But mlua + manual validation is sufficient for v1
- Consider `jsonschema` crate for v2 if validation complexity grows
**Version:** `jsonschema = "0.28"` (workspace dep, deferred)
---
## 19. Terminal Size Detection
### Decision: crossterm
**Built into crossterm:**
- `crossterm::terminal::size()` → `(cols, rows)`
- `Resize` events via `crossterm::event::Event::Resize`
- No extra dependency needed
---
## 20. Signal Handling
### Decision: tokio::signal
**Why:**
- `tokio::signal::ctrl_c()` for SIGINT
- `tokio::signal::unix::signal()` for SIGTERM (Unix)
- Async-aware — doesn't block runtime
- Integrated with graceful shutdown flow
**Why not signal-hook:**
- signal-hook is good but tokio::signal is simpler when already using tokio
**Built into tokio** — no extra dependency.
---
## 21. Built-in Tool Search: ignore + ripgrep crates
### Decision: ignore for find, grep crates for grep
**Why `ignore` :**
- Same directory walking and `.gitignore` handling foundation used by ripgrep/fd.
- Handles gitignore negation, directory-only rules, hidden files, symlinks, glob overrides, max depth, and parallel traversal.
- Reimplementing this is non-product code with many cross-platform edge cases.
**Why `grep` + `grep-regex` + `grep-searcher` :**
- Ripgrep is exposed as a library ecosystem.
- `grep-searcher` handles efficient line search, binary detection, context, and memory mapping.
- `grep-regex` provides the regex matcher backend.
- `grep` provides the `Sink` abstraction used to stream structured matches into smith tool output.
**Why not shell out to `rg` / `fd` :**
- Library use gives structured results, timeout control, cancellation, and no text parsing.
- External commands remain available through the bash tool but are not the tool implementation substrate.
**Versions:**
- `ignore = "0.4"`
- `grep = "0.4"` , `grep-regex = "0.1"` , `grep-searcher = "0.1"`
---
## 22. TUI Feature Helpers: similar, syntastica, fuzzy-matcher
### Decision: focused crates for diff, syntax, and fuzzy filtering
**similar:**
- Provides line/word diffs, hunks, unified diff output, and similarity ratio.
- Used by replay compare mode, VCS/time-travel diff views, and tool result rendering.
- Reimplementing Myers/patience diff plus hunk formatting is complex and not smith-specific.
**syntastica + syntastica-parsers:**
- Tree-sitter-based syntax highlighting with `runtime-c2rust` , avoiding C runtime links.
- P16 verified ANSI terminal output, HTML output, custom themes, multiple languages, and Processor reuse on Android/Termux.
- Syntax highlighting is a TUI primitive; user-facing views remain Lua plugins.
**fuzzy-matcher:**
- Zero-dependency fuzzy scoring with match indices.
- Fits `SelectList` /timeline filtering: score candidates and highlight matched characters.
- `nucleo` is stronger for editor-scale fuzzy matching but overkill for smith v1.
**Versions:**
- `similar = "3"`
- `syntastica = { version = "0.6", default-features = false, features = ["runtime-c2rust"] }`
- `syntastica-parsers = "0.6"`
- `fuzzy-matcher = "0.3"`
---
## 23. CLI Completion: clap_complete
### Decision: clap_complete
**Why:**
- Generates shell completions from the same `clap` command definitions used by `smith-cli` .
- Keeps bash/zsh/fish completions in sync without hand-maintained scripts.
- Thin API, but maintenance savings are high and the crate is part of the clap ecosystem.
**Version:** `clap_complete = "4"`
---
## 24. VCS Primitives: jj CLI first, targeted gix when structured plugin queries land
### Decision: expose VCS as primitives; implement features as Lua plugins
**Architecture principle:**
- Rust core exposes primitives ( `smith.vcs.*` , `smith.shortcut.*` , `smith.tui.*` ).
- Built-in features are Lua plugins using the same SDK as user plugins.
- No feature gets a privileged Rust UI path unless it is a reusable primitive.
**jj role:**
- Operation log, undo/redo, restore, time-travel inspection, and interdiff are jj strengths.
- jj state can be relocated by symlinking `.jj` to XDG state and fixing `git_target` to an absolute path.
- P17 validates Lua plugins over `smith.vcs.*` primitives for time-travel, commands, and VCS tools.
**gix role:**
- Use targeted gix features for structured plugin query APIs where CLI parsing is fragile: status, revision resolution, blame/annotate, blob diffs, changed files.
- Do not expose gix itself to Lua; expose smith-owned data contracts.
- Avoid the full umbrella/default feature set unless a plugin API needs it.
**Version:** `gix = { version = "0.83", default-features = false, features = ["blame", "blob-diff", "revision"] }`
---
## 25. Summary: Dependency Count
| Category | Crates | Direct Deps |
|----------|--------|-------------|
| Async | tokio, tokio-stream | 2 |
| HTTP | reqwest | 1 |
| Serialization | serde, serde_json, ciborium | 3 |
| TUI | ratatui, crossterm | 2 |
| Lua | mlua | 1 |
| CLI | clap, clap_complete | 2 |
| Errors | thiserror, anyhow | 2 |
| Logging | tracing, tracing-subscriber | 2 |
| Tool search | ignore, grep, grep-regex, grep-searcher | 4 |
| TUI helpers | similar, syntastica, syntastica-parsers, fuzzy-matcher | 4 |
| VCS primitives | gix (targeted features) | 1 |
| System | dirs, uuid, regex, zstd, sha2 | 5 |
| **Total production** | | **~28** |
| **Dev/test** | insta, proptest, assert_cmd, assert_fs, expectrl, criterion, cargo-nextest, cargo-tarpaulin, cargo-mutants | 9 (tools) |
---
## 26. Rust Version Policy
**Current policy:** Latest stable Rust, edition 2024.
**Why rolling stable:**
- Smith is pre-1.0 and not yet bound by downstream library MSRV contracts.
- Current crate choices move quickly; pinning an old compiler blocks maintained dependencies.
- Edition 2024 stays fixed in manifests; compiler channel follows stable.
**Release policy:**
- Avoid setting `rust-version` in workspace manifests unless release policy requires it.
- `rust-toolchain.toml` uses `channel = "stable"` .
- If smith later needs long-term support, define a formal MSRV window then.
---
## 27. Cargo Workspace Structure
``` toml
# Cargo.toml (workspace root)
[workspace]
members = ["smith", "smith-core", "smith-ai", "smith-tui", "smith-harness", "smith-cli"]
resolver = "3"
[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT OR Apache-2.0"
[workspace.dependencies]
tokio = { version = "1.43", features = ["full"] }
reqwest = { version = "0.12", features = ["json", "stream"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
ciborium = "0.2"
ratatui = "0.29"
crossterm = "0.28"
mlua = { version = "0.10", features = ["luajit", "vendored", "serialize"] }
clap = { version = "4", features = ["derive", "env"] }
clap_complete = "4"
ignore = "0.4"
grep = "0.4"
grep-regex = "0.1"
grep-searcher = "0.1"
similar = "3"
syntastica = { version = "0.6", default-features = false, features = ["runtime-c2rust"] }
syntastica-parsers = "0.6"
fuzzy-matcher = "0.3"
gix = { version = "0.83", default-features = false, features = ["blame", "blob-diff", "revision"] }
thiserror = "2"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
dirs = "5"
uuid = { version = "1", features = ["v7"] }
regex = "1"
zstd = "0.13"
sha2 = "0.10"
insta = "1"
proptest = "1.6"
assert_cmd = "2"
assert_fs = "1"
criterion = { version = "0.5", features = ["html_reports"] }
```
---
## 28. Rejected Crates (Documented for Future Reference)
| Crate | Why Rejected | Context |
|-------|-------------|---------|
| async-std | Ecosystem mismatch with tokio | Async runtime |
| smol | Too small, less mature | Async runtime |
| surf | async-std ecosystem | HTTP client |
| tui-rs | Unmaintained, superseded by ratatui | TUI framework |
| termion | Unix-only | Terminal control |
| rlua | Unmaintained, superseded by mlua | Lua bindings |
| wasmtime | Complexity, v2 consideration | Plugin runtime |
| Rhai | Slower, smaller ecosystem | Plugin runtime |
| structopt | Merged into clap | CLI parser |
| argh | Less featureful | CLI parser |
| eyre | Unnecessary complexity | Error handling |
| env_logger | Not async-aware | Logging |
| slog | Too complex | Logging |
| ulid | Less standard than UUIDv7 | ID generation |
| blake3 | Less standard than SHA-256 | Hashing |
| fancy-regex | Slower, backreferences not needed | Regex |
| jsonschema | Deferred to v2 | Validation |
| mini-cbor | Derive friction for complex enums; ciborium uses existing serde derives cleanly | CBOR serialization |
| color-eyre | Not selected for current dependency set | Error display |
| indicatif | Not selected for current dependency set | Progress bars |
| gix (full umbrella/default) | Deferred; use targeted features only behind `smith.vcs.*` structured query primitives | VCS integration |
| viuer | Deferred until multi-protocol image rendering is required; kitty-first custom rendering is enough for v1 | Terminal images |
| walkdir | Subsumed by `ignore` , which adds gitignore/glob/hidden-file semantics | File traversal |
| glob | Subsumed by `ignore` / `globset` behavior in the find tool path | Pattern matching |
| syntect | Oniguruma C dependency and regex-based highlighting; P16 proves syntastica with `runtime-c2rust` is Android-safe | Syntax highlighting |
| notify | Defer to v2; manual/plugin-triggered reload is enough for v1 | File watching |
| dashmap | Premature concurrency optimization; locks or actor ownership are clearer for smith's current maps | Concurrent maps |
| nucleo | Strong editor-scale fuzzy engine but overkill; `fuzzy-matcher` fits v1 SelectList filtering | Fuzzy matching |