Skip to main content

oy/
lib.rs

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