Skip to main content

trusty_common/
lib.rs

1//! Shared utility surface for trusty-* projects.
2//!
3//! Why: Port auto-detect, data-directory resolution, tracing init, NO_COLOR
4//! handling, and the OpenRouter chat-completions client appeared in both
5//! trusty-memory and trusty-search with subtle divergence. Centralising keeps
6//! them aligned and gives future trusty-* binaries a one-import surface.
7//!
8//! What: pure utility functions — no global state. Each subsystem is a free
9//! function or a small helper struct.
10//!
11//! Test: `cargo test -p trusty-common` covers port walking, data-dir creation,
12//! and the OpenRouter request shape (without hitting the network).
13//!
14//! # Test isolation: `TRUSTY_DATA_DIR_OVERRIDE`
15//!
16//! macOS's [`dirs::data_dir()`] resolves the application-support directory via
17//! `NSFileManager`, a native Cocoa API that completely ignores the `HOME` and
18//! `XDG_DATA_HOME` environment variables. This makes it impossible to redirect
19//! data-directory access in tests using ordinary env-var tricks, because the
20//! kernel query bypasses the environment entirely.
21//!
22//! To work around this, [`resolve_data_dir`] checks the
23//! [`DATA_DIR_OVERRIDE_ENV`] (`TRUSTY_DATA_DIR_OVERRIDE`) environment variable
24//! before consulting `dirs::data_dir()`. When set, the variable's value is used
25//! as the base directory verbatim, and `dirs::data_dir()` is never called.
26//!
27//! **This escape hatch is intended for testing only.** Do not set it in
28//! production deployments; rely on the OS-standard data directory instead.
29
30pub mod chat;
31pub mod claude_config;
32pub mod project_discovery;
33
34/// Shared graceful-shutdown signal helper for trusty-* daemons (issue #534).
35///
36/// Why: trusty-search, trusty-memory, and trusty-analyze all need the same
37/// SIGTERM + SIGINT shutdown future to pass to axum's `with_graceful_shutdown`.
38/// Centralising it here eliminates three-way duplication and guarantees every
39/// daemon responds identically to `launchctl bootout`.
40/// What: exposes [`shutdown_signal`] — an async fn that resolves on SIGTERM
41/// (unix) or SIGINT/Ctrl-C (all platforms), whichever fires first.
42/// Test: `cargo test -p trusty-common -- shutdown`.
43pub mod shutdown;
44pub use shutdown::shutdown_signal;
45
46/// Bounded in-memory ring buffer of recent tracing log lines.
47///
48/// Why: trusty-* daemons expose a `/logs/tail` endpoint so operators can read
49/// recent logs over HTTP without file I/O or a daemon restart. The buffer and
50/// its `tracing_subscriber::Layer` live here so every daemon shares one impl.
51/// What: `LogBuffer` (thread-safe capped `VecDeque<String>`) plus
52/// `LogBufferLayer` (the tracing layer that feeds it).
53/// Test: `cargo test -p trusty-common log_buffer` covers capacity eviction,
54/// tail semantics, and layer capture.
55pub mod log_buffer;
56
57/// Process RSS / CPU sampling and data-directory sizing for daemon health.
58///
59/// Why: every trusty-* daemon's `/health` endpoint reports its own resident
60/// memory, CPU usage, and on-disk footprint; the sampling logic is identical
61/// across them so it lives here once.
62/// What: `SysMetrics` (per-process RSS + CPU sampler) and `dir_size_bytes`
63/// (recursive directory byte count).
64/// Test: `cargo test -p trusty-common sys_metrics`.
65pub mod sys_metrics;
66
67/// macOS LaunchAgent generation and lifecycle management. macOS-only —
68/// the module compiles to nothing on every other platform.
69#[cfg(target_os = "macos")]
70pub mod launchd;
71
72#[cfg(feature = "axum-server")]
73pub mod server;
74
75/// Shared JSON-RPC 2.0 / MCP primitives (formerly the `trusty-mcp-core` crate).
76///
77/// Why: Centralises `Request`/`Response`/`JsonRpcError` envelopes, the
78/// `initialize` response builder, an async stdio dispatch loop, and the
79/// OpenRPC `rpc.discover` helpers so every MCP server in the workspace
80/// imports the same types.
81/// What: Gated behind the `mcp` feature; pulls in no extra dependencies
82/// beyond `serde` / `tokio`, both of which are already required.
83/// Test: `cargo test -p trusty-common --features mcp` runs the module's
84/// own unit tests (envelope round-trips, stdio loop dispatch, OpenRPC
85/// builder shape).
86#[cfg(feature = "mcp")]
87pub mod mcp;
88
89/// General-purpose JSON-RPC client + transports (formerly the library half
90/// of the `trusty-rpc` crate).
91///
92/// Why: Both `trpc` (the CLI) and any future library consumer want one
93/// place that owns the JSON-RPC envelope construction, stdio-subprocess
94/// transport, HTTP transport, and pretty-printers.
95/// What: Gated behind the `rpc` feature; requires `uuid` for request id
96/// generation. The HTTP transport reuses the workspace `reqwest`.
97/// Test: `cargo test -p trusty-common --features rpc` runs the module's
98/// own unit tests (envelope extraction, pretty-print smoke tests).
99#[cfg(feature = "rpc")]
100pub mod rpc;
101
102/// Shared text-embedding abstraction (formerly the `trusty-embedder` crate).
103///
104/// Why: trusty-memory and trusty-search both ship near-identical `Embedder`
105/// traits and `FastEmbedder` implementations; centralising the surface here
106/// keeps them aligned and lets future consumers pick up embedding for free
107/// without a separate published crate.
108/// What: Gated behind the `embedder` feature. Exposes the `Embedder` trait,
109/// `FastEmbedder` (fastembed-rs, all-MiniLM-L6-v2, 384-d) with LRU caching
110/// and ORT warmup, and (under `embedder-test-support`) the `MockEmbedder`
111/// test double.
112/// Test: `cargo test -p trusty-common --features embedder,embedder-test-support`
113/// covers the mock embedder and ONNX-backed `#[ignore]`d integration tests.
114#[cfg(feature = "embedder")]
115pub mod embedder;
116
117/// Unified RPC client surface for the `trusty-embedderd` standalone process.
118///
119/// Why: absorbs both the former `trusty-embedder-client` HTTP crate (PR #163)
120/// and the former `embed_client` UDS module (PR #157) into a single unified
121/// module. Reduces workspace crate count and provides one trait (`EmbedderClient`)
122/// with three concrete implementations (InProcess, HTTP remote, UDS remote) so
123/// call sites are identical regardless of transport. The `embed-client` feature
124/// and `embed_client` module are retired by issue #164; use `embedder-client`
125/// and `trusty_common::embedder_client::UdsEmbedderClient` instead.
126/// What: Gated behind the `embedder-client` feature. Exposes the
127/// `EmbedderClient` trait, `InProcessEmbedderClient`, `RemoteEmbedderClient`
128/// (HTTP), `UdsEmbedderClient` (UDS), `EmbedRequest` / `EmbedResponse` wire
129/// types, and `EmbedderError`. The UDS impl uses `tokio::net::UnixStream`
130/// with newline-framed JSON-RPC 2.0 — no additional dependencies.
131/// Test: `cargo test -p trusty-common --features embedder-client` covers
132/// error-display, JSON round-trip, URL assembly, UDS wire types, and empty-
133/// batch short-circuits. ONNX-backed tests are in
134/// `trusty-embedderd/tests/bit_identical.rs` (`#[ignore]`).
135#[cfg(feature = "embedder-client")]
136pub mod embedder_client;
137
138/// Zero-dependency BM25 lexical index + code-aware tokenizer (issue #156).
139///
140/// Why: trusty-memory, trusty-search, and the per-palace
141/// `trusty-bm25-daemon` subprocess all want one shared BM25 implementation
142/// so the tokenizer's camelCase / PascalCase / alpha↔digit splits stay
143/// consistent across the workspace. Originally ported from open-mpm; now
144/// the single source of truth lives here.
145/// What: Gated behind the `bm25` feature. Adds no new dependencies — pure
146/// `std` + `tracing` (already required).
147/// Test: `cargo test -p trusty-common --features bm25`.
148#[cfg(feature = "bm25")]
149pub mod bm25;
150
151/// Reusable schema-migration kernel (issue #179).
152///
153/// Why: trusty-search, trusty-memory, and other long-lived stores have grown
154/// ad-hoc schema-migration loops that drift apart. Centralising the
155/// `SchemaVersion` newtype, the `Migration<S>` trait, and a `MigrationRunner`
156/// that applies pending steps in order (writing a stamp after each) collapses
157/// those into one shared kernel. The `file_stamp` helper covers the common
158/// "JSON sidecar in the store's data dir" stamp format; redb-stamp users get
159/// a documented recipe instead of a heavyweight dep.
160/// What: gated behind the `migrations` feature flag. Adds no new
161/// dependencies — pure `serde` + `serde_json` + `anyhow` + `tracing` which
162/// the crate already requires.
163/// Test: `cargo test -p trusty-common --features migrations` covers the
164/// runner ordering, crash resumption, write-stamp failure propagation, and
165/// the file-stamp round-trip / atomic-write behaviour.
166#[cfg(feature = "migrations")]
167pub mod migrations;
168
169/// UDS JSON-RPC client for the per-palace `trusty-bm25-daemon` subprocess
170/// (issue #156).
171///
172/// Why: trusty-memory needs a lexical-search lane without holding an
173/// in-process BM25 index. `Bm25Client` delegates to the per-palace daemon
174/// over `$TMPDIR/trusty-bm25-<palace>.sock`, matching the design of
175/// `EmbedClient` and `trusty-embed-daemon` (PR #157).
176/// What: Gated behind the `bm25-client` feature. Pure user of existing
177/// `tokio` / `serde_json` / `anyhow` workspace deps — adds no new
178/// dependencies.
179/// Test: `cargo test -p trusty-common --features bm25-client` covers
180/// request shape and path defaults; end-to-end coverage lives in
181/// `trusty-bm25-daemon/tests/`.
182#[cfg(feature = "bm25-client")]
183pub mod bm25_client;
184
185/// Symbol-graph engine (formerly the `trusty-symgraph` crate).
186///
187/// Why: All trusty-* tools that touch source code (open-mpm, trusty-search,
188/// trusty-analyze) want the same `EntityType` / `RawEntity` / `EdgeKind`
189/// data shapes and (for orchestrators) the same tree-sitter pipeline. Living
190/// here lets the workspace ship one tree-sitter `links =` slot instead of
191/// juggling two crates that both claim it.
192/// What: Gated behind two features. `symgraph` exposes only the contracts
193/// surface (`EntityType`, `RawEntity`, `EdgeKind`, `fact_hash_str`, tables)
194/// — no tree-sitter, no `links` conflict. `symgraph-parser` additionally
195/// pulls in tree-sitter and the full parse → registry → emit stack.
196/// `symgraph-server` enables the HTTP server frontend.
197/// Test: `cargo test -p trusty-common --features symgraph` exercises the
198/// contracts surface; `cargo test -p trusty-symgraph` covers the parser
199/// path through the thin re-export shim.
200#[cfg(feature = "symgraph")]
201pub mod symgraph;
202
203/// Memory Palace storage engine (formerly the `trusty-memory-core` crate).
204///
205/// Why: Centralises the Memory Palace data model (`Palace` / `Wing` /
206/// `Room` / `Drawer`), storage backends (usearch vector index + SQLite
207/// knowledge graph + chat-session log + payload store), retrieval handle,
208/// and the dream / decay / analytics / git-history surfaces so every
209/// trusty-* binary that talks to a palace reuses the same types. Absorbed
210/// into `trusty-common` (issue #5 phase 2d) so we ship one fewer published
211/// crate.
212/// What: Gated behind the `memory-core` feature because it pulls in heavy
213/// storage deps (`usearch`, `rusqlite`, `r2d2`, `git2`, `kuzu`). Enables
214/// the embedder surface automatically (memory-core → embedder).
215/// Test: `cargo test -p trusty-common --features memory-core` exercises
216/// the full surface.
217#[cfg(feature = "memory-core")]
218pub mod memory_core;
219
220/// Unified ticketing MCP server (formerly the `trusty-tickets` crate).
221///
222/// Why: Claude Code and the rest of the trusty-* suite need a single MCP
223/// surface that can talk to GitHub Issues, JIRA, and Linear without the
224/// caller needing to know which backend is configured. Absorbing into
225/// `trusty-common` reduces the workspace crate count and co-locates the
226/// HTTP client surface with the other protocol helpers.
227/// What: Gated behind the `tickets` feature. Exposes `tickets::api::*`
228/// (config, models, Backend trait, three concrete backends), `tickets::server`
229/// (MCP dispatch loop + `run_stdio`), and `tickets::tools` (the tool-list
230/// schema). Requires the `mcp` feature for the stdio loop.
231/// Test: `cargo test -p trusty-common --features tickets` runs the module's
232/// own unit tests (dispatch, tool-list counts, config parsing, serde
233/// round-trips). Live backend tests require env-var credentials.
234#[cfg(feature = "tickets")]
235pub mod tickets;
236
237/// Declarative CLI help system with "did you mean?" suggestions (issue #216).
238///
239/// Why: every standalone trusty-* binary used to render its `--help` and
240/// unknown-subcommand error output independently, so the formats drifted
241/// apart over time. Centralising the help model into one YAML schema, one
242/// canonical renderer, and one Jaro-Winkler suggester keeps the six binaries
243/// (search, memory, analyze, mpm-cli, tga, open-mpm) speaking with a single
244/// user-facing voice.
245/// What: gated behind the `cli-help` feature. Pulls in `serde_yaml`, `strsim`,
246/// and `indexmap`. Exposes `HelpConfig` / `CommandDef` / `FlagDef` / `Example`
247/// + `load_help` / `render_help` / `suggest`.
248/// Test: `cargo test -p trusty-common --features cli-help`.
249#[cfg(feature = "cli-help")]
250pub mod help;
251
252/// Unified monitor TUI for the trusty-search and trusty-memory daemons
253/// (formerly the `trusty-monitor-tui` crate).
254///
255/// Why: operators run both daemons and want one terminal surface that shows
256/// the health of both at a glance. Living here behind the `monitor-tui`
257/// feature flag matches the workspace's "one fewer published crate" direction
258/// (issue #31 companion) and keeps the dashboard logic unit-testable.
259/// What: gated behind the `monitor-tui` feature, which pulls in `ratatui` and
260/// `crossterm`. Exposes `monitor::run` (the entry point the `trusty-monitor`
261/// binary calls) plus the pure `dashboard` / `search_client` / `memory_client`
262/// submodules.
263/// Test: `cargo test -p trusty-common --features monitor-tui` covers the
264/// rendering, layout, and HTTP-client pieces.
265#[cfg(feature = "monitor-tui")]
266pub mod monitor;
267
268// epic #1104: stdio MCP client + console metrics contract (feature-gated).
269#[cfg(feature = "console-metrics")]
270pub mod console_metrics;
271#[cfg(feature = "stdio-mcp-client")]
272pub mod stdio_mcp_client;
273
274/// Throttled crates.io update-notification helper.
275///
276/// Why: User-facing CLIs should nudge operators when a newer release is
277/// available without adding perceptible latency. A shared implementation
278/// keeps the throttle, cache, opt-out, and User-Agent logic consistent across
279/// every consumer in the workspace.
280/// What: Gated behind the `update-check` feature. Exposes
281/// [`update::check_throttled`] (the main entry — reads a per-crate JSON cache
282/// under the OS cache dir, queries crates.io at most once per 24 h),
283/// [`update::check_crates_io`] (the raw network call), [`update::notice`]
284/// (formatted upgrade message), and [`update::UpdateInfo`] (the result type).
285/// All failures degrade to `None` — the check is best-effort and will not
286/// panic or stall a CLI.
287/// Opt-out: set `TRUSTY_NO_UPDATE_CHECK` or `CI` to any non-empty value.
288/// Test: `cargo test -p trusty-common --features update-check`.
289#[cfg(feature = "update-check")]
290pub mod update;
291
292/// Error-capture layer for the trusty-* consent-gated bug-reporting system
293/// (bug-reporting Phase 1, issue #479).
294///
295/// Why: Every trusty-* daemon encounters runtime errors that developers need
296///      to see but that must be captured locally and only filed to GitHub after
297///      explicit user consent. A shared capture layer in `trusty-common` means
298///      all daemons gain error capture without per-binary changes.
299/// What: Gated behind the `bug-capture` feature. Exposes:
300///      - [`error_capture::CapturedError`] — structured error record.
301///      - [`error_capture::ErrorStore`] — ring buffer + JSONL store.
302///      - [`error_capture::BugCaptureLayer`] — the tracing Layer.
303///      - [`error_capture::bug_capture_layer`] — convenience constructor.
304///      - [`error_capture::TRUSTY_NO_BUG_CAPTURE_ENV`] — opt-out env name.
305///      Additive: does not alter stderr logging. Opt-out via
306///      `TRUSTY_NO_BUG_CAPTURE=1`. New dep: `sha2` (already workspace-optional).
307/// Test: `cargo test -p trusty-common --features bug-capture`.
308#[cfg(feature = "bug-capture")]
309pub mod error_capture;
310
311/// The `~/.trusty-tools/<crate>/config.yaml` cross-crate config convention (#1220).
312///
313/// Why: every trusty-* crate had its own config location/format; #1220
314/// standardises one convention so an operator always knows where a crate's
315/// configuration lives. Centralising the path resolution and typed YAML
316/// load/save here means each crate adopts it by calling two functions.
317/// What: Gated behind the `crate-config` feature. Exposes
318/// [`crate_config::crate_config_path`], [`crate_config::load`],
319/// [`crate_config::load_or_default`], and [`crate_config::save`].
320/// Test: `cargo test -p trusty-common --features crate-config -- crate_config::tests`.
321#[cfg(feature = "crate-config")]
322pub mod crate_config;
323
324// ─── Focused submodules (split from lib.rs in issue #1108) ────────────────
325
326/// TCP port auto-walking helper.
327///
328/// Why: Running multiple daemon instances shouldn't produce noisy failures
329/// when a port is already occupied.
330/// What: Exposes [`bind_with_auto_port`] which walks forward to the next free
331/// port within `max_attempts`.
332/// Test: `cargo test -p trusty-common -- port::tests`.
333pub mod port;
334
335/// Shared GitHub `owner/repo` path derivation (issue #1220).
336///
337/// Why: trusty-mpm's managed-session workspace root
338/// (`~/trusty-mpm-projects/<owner>/<repo>/…`) and trusty-memory's palace-ID
339/// derivation both need the canonical `owner/repo` identity of a project's git
340/// origin remote. Centralising the parsing here keeps the two crates in lockstep.
341/// What: Exposes [`github_path::GithubPath`], [`github_path::parse_github_path`]
342/// (pure URL parse), and [`github_path::derive_github_path`] (reads
343/// `remote.origin.url`).
344/// Test: `cargo test -p trusty-common -- github_path::tests`.
345pub mod github_path;
346
347/// Data-directory resolution and filesystem utilities.
348///
349/// Why: All trusty-* tools share the same per-app data-directory resolution
350/// logic including the macOS `NSFileManager` bypass needed for test isolation.
351/// What: Exposes [`data_dir::resolve_data_dir`], [`data_dir::sanitize_data_root`],
352/// [`data_dir::DATA_DIR_OVERRIDE_ENV`], and [`data_dir::is_dir`].
353/// Test: `cargo test -p trusty-common -- data_dir::tests`.
354pub mod data_dir;
355
356/// Shared CLI daemon-guard helper (probe + spinner + spawn).
357///
358/// Why: trusty-search, trusty-memory, and trusty-analyze each had an identical
359/// probe-spawn-poll-spinner loop in their `commands/daemon_guard.rs` files.
360/// Centralising it here (issue #985) removes the divergence risk and gives
361/// the three crates a single tested implementation to delegate to.
362/// What: Exposes [`daemon_guard::DaemonGuardConfig`],
363/// [`daemon_guard::probe_once`], [`daemon_guard::spin_until_ready`], and
364/// [`daemon_guard::spawn_current_exe`].
365/// Test: `cargo test -p trusty-common -- daemon_guard::tests`.
366pub mod daemon_guard;
367
368/// Daemon HTTP-address file helpers.
369///
370/// Why: Both trusty-search and trusty-memory persist their bound `host:port`
371/// to disk for discovery by CLI and MCP clients. Centralising keeps them in sync.
372/// What: Exposes [`daemon_addr::write_daemon_addr`], [`daemon_addr::read_daemon_addr`],
373/// and [`daemon_addr::check_already_running`].
374/// Test: `cargo test -p trusty-common -- daemon_addr::tests`.
375pub mod daemon_addr;
376
377/// HTTP health-probe helper.
378///
379/// Why: Every daemon uses the same tight-timeout `/health` probe to detect
380/// whether a prior instance is still running.
381/// What: Exposes [`health_probe::probe_health`].
382/// Test: covered via daemon_addr integration tests.
383pub mod health_probe;
384
385/// Global tracing subscriber initialisation helpers.
386///
387/// Why: Every trusty-* binary needs the same verbosity ladder, `RUST_LOG`
388/// override, and (for daemons) the log-buffer + bug-capture layer composition.
389/// What: Exposes [`tracing_init::init_tracing`],
390/// [`tracing_init::init_tracing_with_buffer`],
391/// [`tracing_init::init_tracing_with_buffer_and_capture`] (feature-gated),
392/// and [`tracing_init::maybe_disable_color`].
393/// Test: side-effecting global — covered by downstream integration tests.
394pub mod tracing_init;
395
396/// Deprecated single-shot OpenRouter helpers.
397///
398/// Why: Backward-compatible wrapper for the pre-streaming OpenRouter API.
399/// New code should use `chat::OpenRouterProvider::chat_stream` instead.
400/// What: Exposes [`openrouter_legacy::ChatMessage`],
401/// [`openrouter_legacy::openrouter_chat`] (deprecated), and
402/// [`openrouter_legacy::openrouter_chat_stream`] (deprecated).
403/// Test: `chat_message_round_trips`, `openrouter_chat_rejects_empty_key`.
404pub mod openrouter_legacy;
405
406// ─── Re-exports preserving the pre-split public API ───────────────────────
407
408pub use chat::{
409    BedrockProvider, ChatEvent, ChatProvider, DEFAULT_BEDROCK_MODEL, LocalModelConfig,
410    OllamaProvider, OpenRouterProvider, ToolCall, ToolDef, auto_detect_local_provider,
411};
412
413// Port
414pub use port::bind_with_auto_port;
415
416// Data directory
417pub use data_dir::{DATA_DIR_OVERRIDE_ENV, is_dir, resolve_data_dir, sanitize_data_root};
418
419// Daemon address
420pub use daemon_addr::{check_already_running, read_daemon_addr, write_daemon_addr};
421
422// Health probe
423pub use health_probe::probe_health;
424
425// Tracing init
426#[cfg(feature = "bug-capture")]
427pub use tracing_init::init_tracing_with_buffer_and_capture;
428pub use tracing_init::{init_tracing, init_tracing_with_buffer, maybe_disable_color};
429
430// OpenRouter legacy (deprecated but must remain reachable)
431#[allow(deprecated)]
432pub use openrouter_legacy::{ChatMessage, openrouter_chat, openrouter_chat_stream};