Luigit
repositories / smith

smith

There are many coding harnesses - but this one is fast

owned by admin

.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:

  • 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

# 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 |