oy/lib.rs
1//! # oy
2//!
3//! `oy` adds repeatable repository audit and review workflows to
4//! [opencode](https://opencode.ai/). Its local MCP server provides deterministic repository
5//! collection, ordered chunks, target-diff input, and normalized Markdown/SARIF reports.
6//! opencode remains responsible for model execution, providers, sessions, permissions, and
7//! general coding tools.
8//!
9//! ## Start with the CLI
10//!
11//! The command-line interface is the supported automation surface:
12//!
13//! ```text
14//! oy setup --dry-run # preview generated opencode integration
15//! oy setup # install the global integration
16//! oy audit # write ISSUES.md
17//! oy review main # write REVIEW.md for git diff main
18//! oy enhance <finding-id> # remediate one reported finding
19//! ```
20//!
21//! See the [getting-started guide](https://adonm.github.io/oy-cli/getting-started.html),
22//! [workflow guide](https://adonm.github.io/oy-cli/workflows.html), and
23//! [CLI/MCP reference](https://adonm.github.io/oy-cli/reference.html) for the user-facing
24//! contract.
25//!
26//! ## Determinism boundary
27//!
28//! Input collection, ordering, limits, and report rendering are deterministic. Findings are
29//! produced by the model selected in opencode and are not deterministic. The collector also
30//! has documented exclusions; “all chunks” does not mean every byte in a repository.
31//!
32//! ## Rust API
33//!
34//! This crate exists primarily to keep the `oy` binary entrypoint small. [`run`] and
35//! [`err_line`] are public for that entrypoint and lightweight embedding, but spawning the
36//! `oy` executable is preferred for automation. Other modules and implementation details are
37//! private and may change without a semver-stable library API commitment.
38//!
39//! ```no_run
40//! # async fn example() -> anyhow::Result<()> {
41//! // Arguments exclude the executable name, just like std::env::args().skip(1).
42//! let exit_code = oy::run(vec!["doctor".into(), "--json".into()]).await?;
43//! assert_eq!(exit_code, 0);
44//! # Ok(())
45//! # }
46//! ```
47
48#![recursion_limit = "256"]
49
50mod audit;
51mod cli;
52mod mcp;
53mod opencode;
54mod review;
55mod tools;
56
57pub(crate) use cli::{config, ui};
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub(crate) enum TextDecodeError {
61 Binary,
62 NonUtf8,
63}
64
65pub(crate) fn decode_utf8(raw: Vec<u8>) -> Result<String, TextDecodeError> {
66 if raw.contains(&0) {
67 return Err(TextDecodeError::Binary);
68 }
69 String::from_utf8(raw).map_err(|_| TextDecodeError::NonUtf8)
70}
71
72/// Runs the `oy` command dispatcher with arguments that exclude the executable name.
73///
74/// Normal command and delegated opencode exit statuses are returned as `Ok(code)`. Setup,
75/// filesystem, process-launch, and protocol failures are returned as errors. This function may
76/// update opencode integration files or launch child processes depending on the arguments.
77///
78/// Prefer invoking the `oy` executable when process isolation or concurrent invocations matter;
79/// CLI output configuration is process-global.
80pub async fn run(argv: Vec<String>) -> anyhow::Result<i32> {
81 cli::app::run(argv).await
82}
83
84/// Writes a formatted diagnostic line to standard error.
85///
86/// This is primarily exposed for the binary entrypoint.
87///
88/// ```
89/// oy::err_line(format_args!("error: {}", "example"));
90/// ```
91pub fn err_line(args: std::fmt::Arguments<'_>) {
92 ui::err_line(args);
93}