Enable clippy pedantic lints and restriction lints workspace-wide

Adopts uv's clippy lint configuration: pedantic group at warn priority,
with noisy lints allowed, plus restriction lints for print/dbg/exit/use_self.
Fixes all violations across the workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-28 13:37:57 -04:00
parent 5c215033c1
commit 1dc0ebce52
No known key found for this signature in database
172 changed files with 1120 additions and 993 deletions

View file

@ -65,7 +65,53 @@ exec = "0.3"
slatedb = "0.11.2"
object_store = "0.12.5"
[workspace.lints.rust]
unsafe_code = "warn"
unreachable_pub = "warn"
[workspace.lints.clippy]
pedantic = { level = "warn", priority = -2 }
# Allowed pedantic lints
char_lit_as_u8 = "allow"
collapsible_else_if = "allow"
collapsible_if = "allow"
implicit_hasher = "allow"
map_unwrap_or = "allow"
match_same_arms = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
module_name_repetitions = "allow"
must_use_candidate = "allow"
similar_names = "allow"
struct_excessive_bools = "allow"
too_many_arguments = "allow"
too_many_lines = "allow"
used_underscore_binding = "allow"
if_not_else = "allow"
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"
cast_precision_loss = "allow"
cast_sign_loss = "allow"
doc_markdown = "allow"
items_after_statements = "allow"
needless_pass_by_value = "allow"
return_self_not_must_use = "allow"
uninlined_format_args = "allow"
unreadable_literal = "allow"
unnested_or_patterns = "allow"
# Disallowed restriction lints
print_stdout = "warn"
print_stderr = "warn"
dbg_macro = "warn"
empty_drop = "warn"
empty_structs_with_brackets = "warn"
exit = "warn"
get_unwrap = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
rest_pat_in_fully_bound_structs = "warn"
use_self = "warn"
# Project-specific lints
wildcard_imports = "warn"
absolute_paths = "warn"

View file

@ -117,6 +117,7 @@ fn is_auto_approved(level: PermissionLevel, category: &str) -> bool {
)
}
#[allow(clippy::print_stderr)]
fn build_tool_approval(
permissions: PermissionLevel,
is_interactive: bool,
@ -237,6 +238,7 @@ fn format_tool_args(args: &serde_json::Value, cwd: &str) -> String {
.join(", ")
}
#[allow(clippy::print_stdout)]
fn print_output(session: &Session, styles: &Styles) {
for turn in session.history().turns() {
if let Turn::Assistant { content, .. } = turn {
@ -247,6 +249,7 @@ fn print_output(session: &Session, styles: &Styles) {
}
}
#[allow(clippy::print_stderr)]
fn print_summary(session: &Session, styles: &Styles) {
let (mut turn_count, mut tool_call_count, mut total_tokens) = (0usize, 0usize, 0i64);
for turn in session.history().turns() {
@ -281,6 +284,7 @@ struct DebugMiddleware {
#[async_trait::async_trait]
impl Middleware for DebugMiddleware {
#[allow(clippy::print_stderr)]
async fn handle_complete(&self, request: Request, next: NextFn) -> Result<Response, SdkError> {
let s = self.styles;
eprintln!(
@ -323,6 +327,7 @@ struct VerboseMiddleware {
#[async_trait::async_trait]
impl Middleware for VerboseMiddleware {
#[allow(clippy::print_stderr)]
async fn handle_complete(&self, request: Request, next: NextFn) -> Result<Response, SdkError> {
let s = self.styles;
eprintln!(
@ -357,6 +362,7 @@ pub async fn run_with_args(
run_with_args_and_client(args, None, mcp_servers).await
}
#[allow(clippy::print_stdout, clippy::print_stderr)]
pub async fn run_with_args_and_client(
args: AgentArgs,
llm_client: Option<Client>,
@ -429,7 +435,7 @@ pub async fn run_with_args_and_client(
)));
let manager_for_callback = manager.clone();
let factory_client = client.clone();
let factory_model = model.to_string();
let factory_model = model.clone();
let factory_env = Arc::clone(&env);
let factory_hooks = config.tool_hooks.clone();
let factory: SessionFactory = Arc::new(move || {
@ -490,7 +496,9 @@ pub async fn run_with_args_and_client(
tokio::spawn(async move {
signal::ctrl_c().await.ok();
{
let mut guard = abort_reason.lock().unwrap_or_else(|e| e.into_inner());
let mut guard = abort_reason
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if guard.is_none() {
*guard = Some(AbortReason::Cancelled);
}

View file

@ -1,3 +1,5 @@
use std::fmt::Write;
use crate::agent_profile::AgentProfile;
use crate::error::AgentError;
use crate::event::EventEmitter;
@ -198,7 +200,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String {
for turn in turns {
match turn {
Turn::User { content, .. } => {
out.push_str(&format!("User: {content}\n"));
let _ = writeln!(out, "User: {content}");
}
Turn::Assistant {
content,
@ -206,7 +208,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String {
..
} => {
if !content.is_empty() {
out.push_str(&format!("Assistant: {content}\n"));
let _ = writeln!(out, "Assistant: {content}");
}
for tc in tool_calls {
let args_str = tc.arguments.to_string();
@ -218,7 +220,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String {
} else {
args_str
};
out.push_str(&format!("[Tool call: {}] {truncated}\n", tc.name));
let _ = writeln!(out, "[Tool call: {}] {truncated}", tc.name);
}
}
Turn::ToolResults { results, .. } => {
@ -232,14 +234,14 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String {
} else {
content_str
};
out.push_str(&format!("[Tool result: {}] {truncated}\n", r.tool_call_id));
let _ = writeln!(out, "[Tool result: {}] {truncated}", r.tool_call_id);
}
}
Turn::System { content, .. } => {
out.push_str(&format!("System: {content}\n"));
let _ = writeln!(out, "System: {content}");
}
Turn::Steering { content, .. } => {
out.push_str(&format!("Steering: {content}\n"));
let _ = writeln!(out, "Steering: {content}");
}
}
}

View file

@ -102,6 +102,7 @@ impl std::fmt::Debug for SessionConfig {
.field("max_command_timeout_ms", &self.max_command_timeout_ms)
.field("max_tokens", &self.max_tokens)
.field("reasoning_effort", &self.reasoning_effort)
.field("speed", &self.speed)
.field("tool_output_limits", &self.tool_output_limits)
.field("tool_line_limits", &self.tool_line_limits)
.field("enable_loop_detection", &self.enable_loop_detection)

View file

@ -1,5 +1,7 @@
use fabro_llm::types::{ToolCall, ToolResult};
use std::collections::BTreeMap;
use std::fmt::Write;
use fabro_llm::types::{ToolCall, ToolResult};
#[derive(Debug, Clone, Copy, Default)]
struct FileOps {
@ -47,7 +49,7 @@ impl FileTracker {
if ops.edited {
labels.push("edited");
}
output.push_str(&format!("- {path} ({})\n", labels.join(", ")));
let _ = writeln!(output, "- {path} ({})", labels.join(", "));
}
output
}

View file

@ -63,7 +63,7 @@ pub async fn discover_memory(
}
}
let total_bytes: usize = results.iter().map(|d| d.len()).sum();
let total_bytes: usize = results.iter().map(std::string::String::len).sum();
info!(files = results.len(), total_bytes, "Project docs loaded");
results

View file

@ -113,12 +113,11 @@ impl Session {
.await;
// Discover skills
let skill_dirs = match &self.config.skill_dirs {
Some(dirs) => dirs.clone(),
None => {
let home = dirs::home_dir().map(|p| p.to_string_lossy().to_string());
default_skill_dirs(home.as_deref(), self.config.git_root.as_deref())
}
let skill_dirs = if let Some(dirs) = &self.config.skill_dirs {
dirs.clone()
} else {
let home = dirs::home_dir().map(|p| p.to_string_lossy().to_string());
default_skill_dirs(home.as_deref(), self.config.git_root.as_deref())
};
self.skills = discover_skills(self.sandbox.as_ref(), &skill_dirs).await;
debug!(skill_count = self.skills.len(), "Skills discovered");
@ -291,15 +290,14 @@ impl Session {
}
// Get the preview URL for the port, or fall back to localhost for local sandboxes
match sandbox.get_preview_url(port).await? {
Some(url_and_headers) => Ok(url_and_headers),
None => {
info!(port, "No preview URL available, using localhost");
Ok((
format!("http://localhost:{port}"),
std::collections::HashMap::new(),
))
}
if let Some(url_and_headers) = sandbox.get_preview_url(port).await? {
Ok(url_and_headers)
} else {
info!(port, "No preview URL available, using localhost");
Ok((
format!("http://localhost:{port}"),
std::collections::HashMap::new(),
))
}
}
@ -389,7 +387,10 @@ impl Session {
}
fn set_abort_reason(&self, reason: AbortReason) {
let mut guard = self.abort_reason.lock().unwrap_or_else(|e| e.into_inner());
let mut guard = self
.abort_reason
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if guard.is_none() {
*guard = Some(reason);
}
@ -399,7 +400,7 @@ impl Session {
let reason = self
.abort_reason
.lock()
.unwrap_or_else(|e| e.into_inner())
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
.unwrap_or(AbortReason::Cancelled);
AgentError::Aborted(reason)
@ -544,7 +545,9 @@ impl Session {
tokio::spawn(async move {
time::sleep(duration).await;
{
let mut guard = reason_handle.lock().unwrap_or_else(|e| e.into_inner());
let mut guard = reason_handle
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if guard.is_none() {
*guard = Some(AbortReason::WallClockTimeout);
}
@ -777,14 +780,11 @@ impl Session {
}
}
let response = match response {
Some(response) => response,
None => {
return Err(self.emit_llm_error(SdkError::Stream {
message: "Stream ended without a Finish event (after retries)".into(),
source: None,
}));
}
let Some(response) = response else {
return Err(self.emit_llm_error(SdkError::Stream {
message: "Stream ended without a Finish event (after retries)".into(),
source: None,
}));
};
// Record assistant turn

View file

@ -225,22 +225,17 @@ pub async fn discover_skills(env: &dyn Sandbox, dirs: &[String]) -> Vec<Skill> {
std::collections::HashMap::new();
for dir in dirs {
let paths = match env.glob("*/SKILL.md", Some(dir)).await {
Ok(paths) => paths,
Err(_) => continue,
let Ok(paths) = env.glob("*/SKILL.md", Some(dir)).await else {
continue;
};
for path in paths {
let content = match env.read_file(&path, None, None).await {
Ok(c) => c,
Err(_) => continue,
let Ok(content) = env.read_file(&path, None, None).await else {
continue;
};
match parse_skill(&content) {
Ok(skill) => {
skills_by_name.insert(skill.name.clone(), skill);
}
Err(_) => continue,
if let Ok(skill) = parse_skill(&content) {
skills_by_name.insert(skill.name.clone(), skill);
}
}
}

View file

@ -238,12 +238,11 @@ async fn execute_and_emit_one_tool_with_lookup(
// Post-tool-use hooks
if let Some(hooks) = tool_hooks {
let fallback;
let content_str = match result.content.as_str() {
Some(s) => s,
None => {
fallback = result.content.to_string();
&fallback
}
let content_str = if let Some(s) = result.content.as_str() {
s
} else {
fallback = result.content.to_string();
&fallback
};
if result.is_error {
debug!(tool = %tc.name, hook_event = "post_tool_use_failure", "Calling tool hook");

View file

@ -221,7 +221,10 @@ pub fn make_shell_tool_with_config(config: &SessionConfig) -> RegisteredTool {
.min(max_timeout);
tracing::debug!(
env_var_count = ctx.tool_env.as_ref().map_or(0, |e| e.len()),
env_var_count = ctx
.tool_env
.as_ref()
.map_or(0, std::collections::HashMap::len),
"Injecting sandbox env vars into tool execution"
);
let result = ctx

View file

@ -10,7 +10,7 @@ mod system_time_iso8601 {
use serde::{self, Deserialize, Deserializer, Serializer};
use std::time::SystemTime;
pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
pub(super) fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
@ -18,7 +18,7 @@ mod system_time_iso8601 {
serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Millis, true))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
where
D: Deserializer<'de>,
{
@ -67,7 +67,7 @@ impl Turn {
/// `provider_parts`, if any.
#[must_use]
pub fn reasoning_text(&self) -> Option<&str> {
let Turn::Assistant { provider_parts, .. } = self else {
let Self::Assistant { provider_parts, .. } = self else {
return None;
};
provider_parts.iter().find_map(|p| match p {
@ -188,7 +188,7 @@ pub enum AgentEvent {
SubAgentEvent {
agent_id: String,
depth: usize,
event: Box<AgentEvent>,
event: Box<Self>,
},
McpServerReady {
server_name: String,

View file

@ -373,7 +373,7 @@ fn apply_hunks(content: &str, hunks: &[Hunk]) -> Result<String, String> {
}
// Calculate total lines consumed from original
let explicit_context_count = if has_explicit_context { 1 } else { 0 };
let explicit_context_count = usize::from(has_explicit_context);
let total_original_lines = explicit_context_count
+ hunk
.changes

View file

@ -8,8 +8,9 @@ description = "Generated Rust types from the Fabro API OpenAPI spec"
[lib]
doctest = false
[lints]
workspace = true
[lints.clippy]
# Auto-generated crate; only enforce project-specific lints
wildcard_imports = "warn"
[dependencies]
chrono = { workspace = true, features = ["serde"] }

View file

@ -1,5 +1,6 @@
//! Demo mode handlers that return static data for all API endpoints.
//! Activated per-request via the `X-Fabro-Demo: 1` header to showcase the UI without a real backend.
#![allow(clippy::default_trait_access)]
use std::sync::Arc;
@ -14,7 +15,7 @@ use crate::jwt_auth::AuthenticatedService;
use crate::server::{AppState, PaginationParams};
#[derive(serde::Deserialize)]
pub struct RetroListParams {
pub(crate) struct RetroListParams {
#[serde(rename = "page[limit]", default = "crate::server::default_page_limit")]
limit: u32,
#[serde(rename = "page[offset]", default)]
@ -41,7 +42,7 @@ fn paginated_response<T: serde::Serialize>(
// ── Runs ───────────────────────────────────────────────────────────────
pub async fn list_runs(
pub(crate) async fn list_runs(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
@ -49,7 +50,7 @@ pub async fn list_runs(
paginated_response(runs::list_items(), &pagination)
}
pub async fn start_run_stub(
pub(crate) async fn start_run_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
) -> Response {
@ -60,7 +61,7 @@ pub async fn start_run_stub(
.into_response()
}
pub async fn get_run_stages(
pub(crate) async fn get_run_stages(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -69,7 +70,7 @@ pub async fn get_run_stages(
paginated_response(runs::stages(), &pagination)
}
pub async fn get_stage_turns(
pub(crate) async fn get_stage_turns(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path((_id, _stage_id)): Path<(String, String)>,
@ -78,7 +79,7 @@ pub async fn get_stage_turns(
paginated_response(runs::turns(), &pagination)
}
pub async fn get_run_files(
pub(crate) async fn get_run_files(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -87,7 +88,7 @@ pub async fn get_run_files(
paginated_response(runs::files(), &pagination)
}
pub async fn get_run_usage(
pub(crate) async fn get_run_usage(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -95,7 +96,7 @@ pub async fn get_run_usage(
(StatusCode::OK, Json(runs::usage())).into_response()
}
pub async fn get_run_verification(
pub(crate) async fn get_run_verification(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -104,7 +105,7 @@ pub async fn get_run_verification(
paginated_response(runs::verifications(), &pagination)
}
pub async fn get_run_settings(
pub(crate) async fn get_run_settings(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -112,7 +113,7 @@ pub async fn get_run_settings(
(StatusCode::OK, Json(runs::settings())).into_response()
}
pub async fn steer_run_stub(
pub(crate) async fn steer_run_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -120,7 +121,7 @@ pub async fn steer_run_stub(
StatusCode::ACCEPTED.into_response()
}
pub async fn generate_preview_url_stub(
pub(crate) async fn generate_preview_url_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -132,7 +133,7 @@ pub async fn generate_preview_url_stub(
.into_response()
}
pub async fn get_run_status(
pub(crate) async fn get_run_status(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(id): Path<String>,
@ -153,7 +154,7 @@ pub async fn get_run_status(
}
}
pub async fn get_questions_stub(
pub(crate) async fn get_questions_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -162,7 +163,7 @@ pub async fn get_questions_stub(
paginated_response(runs::questions(), &pagination)
}
pub async fn answer_stub(
pub(crate) async fn answer_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path((_id, _qid)): Path<(String, String)>,
@ -170,7 +171,7 @@ pub async fn answer_stub(
StatusCode::NO_CONTENT.into_response()
}
pub async fn run_events_stub(
pub(crate) async fn run_events_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -178,7 +179,7 @@ pub async fn run_events_stub(
ApiError::new(StatusCode::GONE, "Event stream closed.").into_response()
}
pub async fn checkpoint_stub(
pub(crate) async fn checkpoint_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -186,7 +187,7 @@ pub async fn checkpoint_stub(
(StatusCode::OK, Json(serde_json::json!(null))).into_response()
}
pub async fn context_stub(
pub(crate) async fn context_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -194,7 +195,7 @@ pub async fn context_stub(
(StatusCode::OK, Json(serde_json::json!({}))).into_response()
}
pub async fn cancel_stub(
pub(crate) async fn cancel_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -202,7 +203,7 @@ pub async fn cancel_stub(
(StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "cancelled", "created_at": "2026-03-06T14:30:00Z"}))).into_response()
}
pub async fn pause_stub(
pub(crate) async fn pause_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -210,7 +211,7 @@ pub async fn pause_stub(
(StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "paused", "created_at": "2026-03-06T14:30:00Z"}))).into_response()
}
pub async fn unpause_stub(
pub(crate) async fn unpause_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -218,7 +219,7 @@ pub async fn unpause_stub(
(StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "running", "created_at": "2026-03-06T14:30:00Z"}))).into_response()
}
pub async fn get_run_graph(
pub(crate) async fn get_run_graph(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -229,7 +230,7 @@ pub async fn get_run_graph(
crate::server::render_dot_svg(dot_source).await
}
pub async fn get_run_retro(
pub(crate) async fn get_run_retro(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(id): Path<String>,
@ -242,7 +243,7 @@ pub async fn get_run_retro(
// ── Workflows ──────────────────────────────────────────────────────────
pub async fn list_workflows(
pub(crate) async fn list_workflows(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
@ -250,7 +251,7 @@ pub async fn list_workflows(
paginated_response(workflows::list_items(), &pagination)
}
pub async fn get_workflow(
pub(crate) async fn get_workflow(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(name): Path<String>,
@ -261,7 +262,7 @@ pub async fn get_workflow(
}
}
pub async fn list_workflow_runs(
pub(crate) async fn list_workflow_runs(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(name): Path<String>,
@ -276,7 +277,7 @@ pub async fn list_workflow_runs(
// ── Verification ──────────────────────────────────────────────────────
pub async fn list_verification_criteria(
pub(crate) async fn list_verification_criteria(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
@ -284,7 +285,7 @@ pub async fn list_verification_criteria(
paginated_response(verifications::criteria(), &pagination)
}
pub async fn get_verification_criterion(
pub(crate) async fn get_verification_criterion(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(id): Path<String>,
@ -295,7 +296,7 @@ pub async fn get_verification_criterion(
}
}
pub async fn list_verification_controls(
pub(crate) async fn list_verification_controls(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
@ -303,7 +304,7 @@ pub async fn list_verification_controls(
paginated_response(verifications::controls(), &pagination)
}
pub async fn get_verification_control(
pub(crate) async fn get_verification_control(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(id): Path<String>,
@ -317,7 +318,7 @@ pub async fn get_verification_control(
// ── Signoffs ──────────────────────────────────────────────────────────
#[derive(serde::Deserialize)]
pub struct SignoffListParams {
pub(crate) struct SignoffListParams {
#[serde(rename = "page[limit]", default = "crate::server::default_page_limit")]
limit: u32,
#[serde(rename = "page[offset]", default)]
@ -327,7 +328,7 @@ pub struct SignoffListParams {
commit_sha: Option<String>,
}
pub async fn list_signoffs(
pub(crate) async fn list_signoffs(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(params): Query<SignoffListParams>,
@ -346,7 +347,7 @@ pub async fn list_signoffs(
)
}
pub async fn get_signoff(
pub(crate) async fn get_signoff(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(id): Path<String>,
@ -357,7 +358,7 @@ pub async fn get_signoff(
}
}
pub async fn create_signoff_stub(
pub(crate) async fn create_signoff_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
) -> Response {
@ -366,7 +367,7 @@ pub async fn create_signoff_stub(
// ── Retros ─────────────────────────────────────────────────────────────
pub async fn list_retros(
pub(crate) async fn list_retros(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(params): Query<RetroListParams>,
@ -397,7 +398,7 @@ pub async fn list_retros(
// ── Sessions ───────────────────────────────────────────────────────────
pub async fn list_sessions(
pub(crate) async fn list_sessions(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
@ -405,7 +406,7 @@ pub async fn list_sessions(
paginated_response(sessions::list_items(), &pagination)
}
pub async fn create_session_stub(
pub(crate) async fn create_session_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
) -> Response {
@ -418,7 +419,7 @@ pub async fn create_session_stub(
.into_response()
}
pub async fn get_session(
pub(crate) async fn get_session(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(id): Path<String>,
@ -429,7 +430,7 @@ pub async fn get_session(
}
}
pub async fn send_message_stub(
pub(crate) async fn send_message_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -441,7 +442,7 @@ pub async fn send_message_stub(
.into_response()
}
pub async fn session_events_stub(
pub(crate) async fn session_events_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
headers: axum::http::HeaderMap,
@ -449,9 +450,8 @@ pub async fn session_events_stub(
) -> Response {
use axum::response::sse::{Event, Sse};
let session = match sessions::detail(&id) {
Some(s) => s,
None => return ApiError::not_found("Session not found.").into_response(),
let Some(session) = sessions::detail(&id) else {
return ApiError::not_found("Session not found.").into_response();
};
let last_event_id: Option<usize> = headers
@ -495,7 +495,7 @@ pub async fn session_events_stub(
// ── Insights ───────────────────────────────────────────────────────────
pub async fn list_saved_queries(
pub(crate) async fn list_saved_queries(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
@ -503,7 +503,7 @@ pub async fn list_saved_queries(
paginated_response(insights::saved_queries(), &pagination)
}
pub async fn save_query_stub(
pub(crate) async fn save_query_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
) -> Response {
@ -514,7 +514,7 @@ pub async fn save_query_stub(
.into_response()
}
pub async fn get_saved_query(
pub(crate) async fn get_saved_query(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(id): Path<String>,
@ -525,7 +525,7 @@ pub async fn get_saved_query(
}
}
pub async fn update_query_stub(
pub(crate) async fn update_query_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -537,7 +537,7 @@ pub async fn update_query_stub(
.into_response()
}
pub async fn delete_query_stub(
pub(crate) async fn delete_query_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
@ -545,7 +545,7 @@ pub async fn delete_query_stub(
StatusCode::NO_CONTENT.into_response()
}
pub async fn execute_query_stub(
pub(crate) async fn execute_query_stub(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
) -> Response {
@ -561,7 +561,7 @@ pub async fn execute_query_stub(
.into_response()
}
pub async fn list_query_history(
pub(crate) async fn list_query_history(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
@ -571,7 +571,7 @@ pub async fn list_query_history(
// ── Models ────────────────────────────────────────────────────────────
pub async fn list_models(
pub(crate) async fn list_models(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
@ -588,7 +588,7 @@ pub async fn list_models(
// ── Settings ───────────────────────────────────────────────────────────
pub async fn get_server_settings(
pub(crate) async fn get_server_settings(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
) -> Response {
@ -597,7 +597,7 @@ pub async fn get_server_settings(
// ── Usage ──────────────────────────────────────────────────────────────
pub async fn get_aggregate_usage(
pub(crate) async fn get_aggregate_usage(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
) -> Response {
@ -616,7 +616,7 @@ mod runs {
use super::ts;
use fabro_api_types::*;
pub fn list_items() -> Vec<RunListItem> {
pub(super) fn list_items() -> Vec<RunListItem> {
vec![
RunListItem {
id: "run-1".into(),
@ -1062,7 +1062,7 @@ mod runs {
]
}
pub fn stages() -> Vec<RunStage> {
pub(super) fn stages() -> Vec<RunStage> {
vec![
RunStage {
id: "detect-drift".into(),
@ -1095,7 +1095,7 @@ mod runs {
]
}
pub fn turns() -> Vec<StageTurn> {
pub(super) fn turns() -> Vec<StageTurn> {
vec![
StageTurn::SystemStageTurn(SystemStageTurn { kind: SystemStageTurnKind::System, content: "You are a drift detection agent. Compare the production and staging environments and identify any configuration or code drift.".into() }),
StageTurn::AssistantStageTurn(AssistantStageTurn { kind: AssistantStageTurnKind::Assistant, content: "I'll start by loading the environment configurations for both production and staging to compare them.".into() }),
@ -1110,14 +1110,14 @@ mod runs {
]
}
pub fn files() -> Vec<FileDiff> {
pub(super) fn files() -> Vec<FileDiff> {
vec![
FileDiff {
old_file: DiffFile { name: "src/commands/run.ts".into(), contents: "import { parseArgs } from \"node:util\";\nimport { loadConfig } from \"../config.js\";\nimport { execute } from \"../executor.js\";\n\ninterface RunOptions {\n config: string;\n dryRun: boolean;\n}\n\nexport async function run(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\", default: \"fabro.toml\" },\n \"dry-run\": { type: \"boolean\", default: false },\n },\n });\n\n const opts: RunOptions = {\n config: values.config ?? \"fabro.toml\",\n dryRun: values[\"dry-run\"] ?? false,\n };\n\n const config = await loadConfig(opts.config);\n const result = await execute(config, { dryRun: opts.dryRun });\n\n if (result.success) {\n console.log(\"Run completed successfully.\");\n } else {\n console.error(\"Run failed:\", result.error);\n process.exitCode = 1;\n }\n}\n".into() },
new_file: DiffFile { name: "src/commands/run.ts".into(), contents: "import { parseArgs } from \"node:util\";\nimport { loadConfig } from \"../config.js\";\nimport { execute } from \"../executor.js\";\nimport { createLogger, type Logger } from \"../logger.js\";\n\ninterface RunOptions {\n config: string;\n dryRun: boolean;\n verbose: boolean;\n}\n\nexport async function run(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\", default: \"fabro.toml\" },\n \"dry-run\": { type: \"boolean\", default: false },\n verbose: { type: \"boolean\", short: \"v\", default: false },\n },\n });\n\n const opts: RunOptions = {\n config: values.config ?? \"fabro.toml\",\n dryRun: values[\"dry-run\"] ?? false,\n verbose: values.verbose ?? false,\n };\n\n const logger: Logger = createLogger({ verbose: opts.verbose });\n\n const config = await loadConfig(opts.config);\n logger.debug(\"Loaded config from %s\", opts.config);\n\n const result = await execute(config, { dryRun: opts.dryRun, logger });\n logger.debug(\"Execution finished in %dms\", result.elapsed);\n\n if (result.success) {\n console.log(\"Run completed successfully.\");\n } else {\n console.error(\"Run failed:\", result.error);\n process.exitCode = 1;\n }\n}\n".into() },
},
FileDiff {
old_file: DiffFile { name: "src/logger.ts".into(), contents: "".into() },
old_file: DiffFile { name: "src/logger.ts".into(), contents: String::new() },
new_file: DiffFile { name: "src/logger.ts".into(), contents: "export interface Logger {\n info(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\ninterface LoggerOptions {\n verbose: boolean;\n}\n\nexport function createLogger({ verbose }: LoggerOptions): Logger {\n return {\n info(message, ...args) {\n console.log(message, ...args);\n },\n debug(message, ...args) {\n if (verbose) {\n console.log(\"[debug]\", message, ...args);\n }\n },\n error(message, ...args) {\n console.error(message, ...args);\n },\n };\n}\n".into() },
},
FileDiff {
@ -1127,7 +1127,7 @@ mod runs {
]
}
pub fn usage() -> RunUsage {
pub(super) fn usage() -> RunUsage {
RunUsage {
stages: vec![
UsageStage {
@ -1235,11 +1235,11 @@ mod runs {
}
}
pub fn verifications() -> Vec<fabro_api_types::RunVerification> {
pub(super) fn verifications() -> Vec<fabro_api_types::RunVerification> {
super::verifications::run_verifications()
}
pub fn questions() -> Vec<ApiQuestion> {
pub(super) fn questions() -> Vec<ApiQuestion> {
vec![
ApiQuestion {
id: "q-001".into(),
@ -1276,7 +1276,7 @@ mod runs {
]
}
pub fn settings() -> serde_json::Value {
pub(super) fn settings() -> serde_json::Value {
serde_json::to_value(fabro_config::FabroSettings {
version: Some(1),
goal: Some("Add rate limiting to auth endpoints".into()),
@ -1338,7 +1338,7 @@ mod runs {
mod usage {
use fabro_api_types::*;
pub fn aggregate() -> AggregateUsage {
pub(super) fn aggregate() -> AggregateUsage {
AggregateUsage {
totals: AggregateUsageTotals {
runs: 9,
@ -1390,7 +1390,7 @@ mod workflows {
use super::ts;
use fabro_api_types::*;
pub fn list_items() -> Vec<WorkflowListItem> {
pub(super) fn list_items() -> Vec<WorkflowListItem> {
vec![
WorkflowListItem {
name: "Fix Build".into(),
@ -1450,7 +1450,7 @@ mod workflows {
serde_json::from_value(val).unwrap()
}
pub fn detail(name: &str) -> Option<WorkflowDetail> {
pub(super) fn detail(name: &str) -> Option<WorkflowDetail> {
let items = [
WorkflowDetail {
name: "Fix Build".into(), slug: "fix_build".into(), filename: "fix_build.fabro".into(),
@ -2679,7 +2679,7 @@ mod verifications {
// ── Public API ──────────────────────────────────────────────────────
pub fn criteria() -> Vec<VerificationCriterion> {
pub(super) fn criteria() -> Vec<VerificationCriterion> {
ALL_CATEGORIES
.iter()
.map(|cat| VerificationCriterion {
@ -2703,7 +2703,7 @@ mod verifications {
.collect()
}
pub fn criterion_detail(id: &str) -> Option<VerificationCriterionDetail> {
pub(super) fn criterion_detail(id: &str) -> Option<VerificationCriterionDetail> {
ALL_CATEGORIES
.iter()
.find(|cat| slugify(cat.name) == id)
@ -2727,7 +2727,7 @@ mod verifications {
})
}
pub fn controls() -> Vec<VerificationControlListItem> {
pub(super) fn controls() -> Vec<VerificationControlListItem> {
ALL_CATEGORIES
.iter()
.flat_map(|cat| {
@ -2749,7 +2749,7 @@ mod verifications {
.collect()
}
pub fn control_detail(slug: &str) -> Option<VerificationDetailResponse> {
pub(super) fn control_detail(slug: &str) -> Option<VerificationDetailResponse> {
for cat in ALL_CATEGORIES {
for (idx, ctrl) in cat.controls.iter().enumerate() {
if ctrl.slug == slug {
@ -2802,7 +2802,7 @@ mod verifications {
None
}
pub fn run_verifications() -> Vec<RunVerification> {
pub(super) fn run_verifications() -> Vec<RunVerification> {
ALL_CATEGORIES
.iter()
.map(|cat| RunVerification {
@ -2917,7 +2917,7 @@ mod signoffs {
}
}
pub fn list_items(
pub(super) fn list_items(
control: Option<&str>,
repository: Option<&str>,
commit_sha: Option<&str>,
@ -2931,11 +2931,11 @@ mod signoffs {
.collect()
}
pub fn detail(id: &str) -> Option<Signoff> {
pub(super) fn detail(id: &str) -> Option<Signoff> {
ALL_SIGNOFFS.iter().find(|s| s.id == id).map(to_signoff)
}
pub fn stub_created() -> Signoff {
pub(super) fn stub_created() -> Signoff {
to_signoff(&ALL_SIGNOFFS[0])
}
}
@ -2969,7 +2969,7 @@ mod retros {
}
}
pub fn detail(run_id: &str) -> Option<RetroDetail> {
pub(super) fn detail(run_id: &str) -> Option<RetroDetail> {
match run_id {
"run-1" => Some(RetroDetail {
run_id: "run-1".into(),
@ -3141,7 +3141,7 @@ mod retros {
}
}
pub fn list_items() -> Vec<RetroListItem> {
pub(super) fn list_items() -> Vec<RetroListItem> {
vec![
RetroListItem {
run: RunReference {
@ -3292,7 +3292,7 @@ mod sessions {
const S7: u128 = 0x10000000_0000_4000_8000_000000000007;
const S8: u128 = 0x10000000_0000_4000_8000_000000000008;
pub fn list_items() -> Vec<SessionListItem> {
pub(super) fn list_items() -> Vec<SessionListItem> {
vec![
SessionListItem {
id: uid(S1),
@ -3361,7 +3361,7 @@ mod sessions {
]
}
pub fn detail(id: &str) -> Option<SessionDetail> {
pub(super) fn detail(id: &str) -> Option<SessionDetail> {
let parsed = id.parse::<Uuid>().ok()?;
match parsed.as_u128() {
S1 => Some(SessionDetail {
@ -3420,7 +3420,7 @@ mod insights {
use super::ts;
use fabro_api_types::*;
pub fn saved_queries() -> Vec<SavedQuery> {
pub(super) fn saved_queries() -> Vec<SavedQuery> {
vec![
SavedQuery { id: "1".into(), name: "Run duration by workflow".into(), sql: "SELECT workflow_name, AVG(duration_seconds) as avg_duration,\n COUNT(*) as run_count\nFROM runs\nGROUP BY workflow_name\nORDER BY avg_duration DESC\nLIMIT 20".into(), created_at: ts("2026-03-01T10:00:00Z"), updated_at: ts("2026-03-05T14:30:00Z") },
SavedQuery { id: "2".into(), name: "Daily failure rate".into(), sql: "SELECT date_trunc('day', created_at) as day,\n COUNT(*) FILTER (WHERE status = 'failed') as failures,\n COUNT(*) as total\nFROM runs\nGROUP BY 1\nORDER BY 1 DESC\nLIMIT 30".into(), created_at: ts("2026-03-02T09:00:00Z"), updated_at: ts("2026-03-02T09:00:00Z") },
@ -3428,7 +3428,7 @@ mod insights {
]
}
pub fn history() -> Vec<HistoryEntry> {
pub(super) fn history() -> Vec<HistoryEntry> {
vec![
HistoryEntry {
id: "h1".into(),
@ -3460,7 +3460,7 @@ mod settings {
use fabro_config::FabroSettings;
use fabro_config::server::*;
pub fn server_settings() -> serde_json::Value {
pub(super) fn server_settings() -> serde_json::Value {
serde_json::to_value(FabroSettings {
storage_dir: Some("/home/fabro/.fabro".into()),
max_concurrent_runs: Some(10),

View file

@ -17,19 +17,16 @@ type HmacSha256 = Hmac<Sha256>;
/// `signature_header` is the value of the `X-Hub-Signature-256` header,
/// expected in the form `sha256=<hex-digest>`.
pub fn verify_signature(secret: &[u8], body: &[u8], signature_header: &str) -> bool {
let hex_digest = match signature_header.strip_prefix("sha256=") {
Some(h) => h,
None => return false,
let Some(hex_digest) = signature_header.strip_prefix("sha256=") else {
return false;
};
let expected = match hex::decode(hex_digest) {
Ok(b) => b,
Err(_) => return false,
let Ok(expected) = hex::decode(hex_digest) else {
return false;
};
let mut mac = match HmacSha256::new_from_slice(secret) {
Ok(m) => m,
Err(_) => return false,
let Ok(mut mac) = HmacSha256::new_from_slice(secret) else {
return false;
};
mac.update(body);
mac.verify_slice(&expected).is_ok()
@ -50,15 +47,12 @@ async fn webhook_handler(
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
let signature = match headers
let Some(signature) = headers
.get("x-hub-signature-256")
.and_then(|v| v.to_str().ok())
{
Some(s) => s,
None => {
warn!(delivery = %delivery_id, "Webhook signature verification failed");
return StatusCode::UNAUTHORIZED;
}
else {
warn!(delivery = %delivery_id, "Webhook signature verification failed");
return StatusCode::UNAUTHORIZED;
};
if !verify_signature(&state.secret, &body, signature) {

View file

@ -226,7 +226,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedService {
.expect("AuthMode extension must be added to the router");
let strategies = match auth_mode {
AuthMode::Disabled => return Ok(AuthenticatedService),
AuthMode::Disabled => return Ok(Self),
AuthMode::Strategies(strategies) => strategies,
};
@ -246,7 +246,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedService {
} => try_jwt(parts, key, validation, allowed_usernames),
};
match result {
Ok(()) => return Ok(AuthenticatedService),
Ok(()) => return Ok(Self),
Err(e) => last_err = e,
}
}
@ -275,7 +275,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
let strategies = match auth_mode {
AuthMode::Disabled => {
return Ok(AuthenticatedUser {
return Ok(Self {
login: "demo".to_string(),
});
}
@ -297,7 +297,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
} => {
if try_jwt(parts, key, validation, allowed_usernames).is_ok() {
if let Some(login) = extract_jwt_login(parts, key, validation) {
return Ok(AuthenticatedUser { login });
return Ok(Self { login });
}
}
last_err = ApiError::unauthorized();
@ -305,7 +305,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
AuthStrategy::Mtls => {
if try_mtls(parts).is_ok() {
if let Some(login) = extract_mtls_cn(parts) {
return Ok(AuthenticatedUser { login });
return Ok(Self { login });
}
}
last_err = ApiError::unauthorized();

View file

@ -62,6 +62,7 @@ pub struct ServeArgs {
/// # Errors
///
/// Returns an error if the server fails to bind or encounters a fatal error.
#[allow(clippy::print_stderr)]
pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::Result<()> {
// Resolve dry-run mode (same pattern as run.rs)
let dry_run_mode = if args.dry_run {
@ -186,22 +187,19 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::
Some(app_id) => {
let secret = std::env::var("GITHUB_APP_WEBHOOK_SECRET").ok();
let private_key_pem = read_github_private_key();
match (secret, private_key_pem) {
(Some(secret), Some(pem)) => {
match WebhookManager::start(secret.into_bytes(), &app_id, &pem).await {
Ok(manager) => Some(manager),
Err(err) => {
error!(error = %err, "Failed to start webhook listener");
None
}
if let (Some(secret), Some(pem)) = (secret, private_key_pem) {
match WebhookManager::start(secret.into_bytes(), &app_id, &pem).await {
Ok(manager) => Some(manager),
Err(err) => {
error!(error = %err, "Failed to start webhook listener");
None
}
}
_ => {
warn!(
"Webhook config present but GITHUB_APP_WEBHOOK_SECRET or GITHUB_APP_PRIVATE_KEY not set; skipping webhook listener"
);
None
}
} else {
warn!(
"Webhook config present but GITHUB_APP_WEBHOOK_SECRET or GITHUB_APP_PRIVATE_KEY not set; skipping webhook listener"
);
None
}
}
None => None,
@ -274,8 +272,8 @@ fn resolve_model_provider(
let provider_str = cli_provider.or(config_provider);
let model = cli_model
.map(|s| s.to_string())
.or_else(|| config_model.map(|s| s.to_string()))
.map(std::string::ToString::to_string)
.or_else(|| config_model.map(std::string::ToString::to_string))
.unwrap_or_else(|| {
// Look up default model from catalog for the given provider,
// falling back to the best provider with an API key configured.
@ -292,10 +290,10 @@ fn resolve_model_provider(
Some(info) => (
info.id.clone(),
provider_str
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.or(Some(info.provider.to_string())),
),
None => (model, provider_str.map(|s| s.to_string())),
None => (model, provider_str.map(std::string::ToString::to_string)),
};
let provider_enum: Provider = provider_str

View file

@ -606,9 +606,8 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
Some(r) if r.status == RunStatus::Queued => r,
_ => return,
};
let run_dir = match managed_run.run_dir.clone() {
Some(path) => path,
None => return,
let Some(run_dir) = managed_run.run_dir.clone() else {
return;
};
let (cancel_tx, cancel_rx) = oneshot::channel::<()>();
@ -643,9 +642,8 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
let runs = state.runs.lock().expect("runs lock poisoned");
runs.get(&run_id).and_then(|r| r.cancel_token.clone())
};
let cancel_token = match cancel_token {
Some(ct) => ct,
None => return,
let Some(cancel_token) = cancel_token else {
return;
};
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
@ -835,8 +833,8 @@ pub fn spawn_scheduler(state: Arc<AppState>) {
tokio::spawn(async move {
loop {
tokio::select! {
_ = state.scheduler_notify.notified() => {},
_ = sleep(std::time::Duration::from_secs(1)) => {},
() = state.scheduler_notify.notified() => {},
() = sleep(std::time::Duration::from_secs(1)) => {},
}
// Promote as many queued runs as capacity allows
loop {
@ -862,7 +860,7 @@ pub fn spawn_scheduler(state: Arc<AppState>) {
tokio::spawn(execute_run(state_clone, id));
}
None => break,
};
}
}
}
});
@ -911,15 +909,12 @@ async fn get_questions(
let runs = state.runs.lock().expect("runs lock poisoned");
match runs.get(&id) {
Some(managed_run) => {
let interviewer = match &managed_run.interviewer {
Some(i) => i,
None => {
return (
StatusCode::OK,
Json(ListResponse::new(Vec::<ApiQuestion>::new())),
)
.into_response();
}
let Some(interviewer) = &managed_run.interviewer else {
return (
StatusCode::OK,
Json(ListResponse::new(Vec::<ApiQuestion>::new())),
)
.into_response();
};
let pending = interviewer.pending_questions();
let questions: Vec<ApiQuestion> = pending
@ -961,12 +956,9 @@ async fn submit_answer(
let runs = state.runs.lock().expect("runs lock poisoned");
match runs.get(&id) {
Some(managed_run) => {
let interviewer = match &managed_run.interviewer {
Some(i) => i,
None => {
return ApiError::new(StatusCode::CONFLICT, "Run is not yet running.")
.into_response();
}
let Some(interviewer) = &managed_run.interviewer else {
return ApiError::new(StatusCode::CONFLICT, "Run is not yet running.")
.into_response();
};
let answer = if let Some(key) = &req.selected_option_key {
let option = interviewer

View file

@ -92,9 +92,8 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool,
tokio::spawn(async move {
let (event_tx, model_id, model_provider, system_prompt, messages, generation_seq) = {
let store = store.read().expect("session store lock poisoned");
let session = match store.get(&session_id) {
Some(s) => s,
None => return,
let Some(session) = store.get(&session_id) else {
return;
};
(
session.event_tx.clone(),

View file

@ -91,8 +91,11 @@ pub async fn serve_tls(
// Extract peer certificates once per connection (not per request)
let (_, server_conn) = tls_stream.get_ref();
let peer_certs =
PeerCertificates(server_conn.peer_certificates().map(|certs| certs.to_vec()));
let peer_certs = PeerCertificates(
server_conn
.peer_certificates()
.map(<[rustls_pki_types::CertificateDer<'_>]>::to_vec),
);
let io = TokioIo::new(tls_stream);

View file

@ -501,7 +501,7 @@ pub(crate) struct WaitArgs {
}
#[derive(Args)]
pub(crate) struct WorkflowListArgs {}
pub(crate) struct WorkflowListArgs;
#[derive(Args)]
pub(crate) struct WorkflowCreateArgs {

View file

@ -1,5 +1,5 @@
#[allow(unused_imports)]
pub use fabro_config::cli::*;
pub(crate) use fabro_config::cli::*;
use std::path::Path;
@ -9,7 +9,7 @@ use fabro_config::cli::load_cli_config;
#[cfg(feature = "server")]
use tracing::debug;
pub fn load_cli_settings(path: Option<&Path>) -> anyhow::Result<FabroSettings> {
pub(crate) fn load_cli_settings(path: Option<&Path>) -> anyhow::Result<FabroSettings> {
load_cli_config(path)?.try_into()
}

View file

@ -10,7 +10,7 @@ use crate::args::AssetCpArgs;
use crate::cli_config::load_cli_settings;
use crate::shared::split_run_path;
pub fn cp_command(args: &AssetCpArgs) -> Result<()> {
pub(super) fn cp_command(args: &AssetCpArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let (run_id, asset_path) = parse_source(&args.source);

View file

@ -8,7 +8,7 @@ use crate::args::AssetListArgs;
use crate::cli_config::load_cli_settings;
use crate::shared::format_size;
pub fn list_command(args: &AssetListArgs) -> Result<()> {
pub(super) fn list_command(args: &AssetListArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let run = resolve_run(&base, &args.run_id)?;

View file

@ -5,7 +5,7 @@ use anyhow::Result;
use crate::args::{AssetCommand, AssetNamespace};
pub fn dispatch(ns: AssetNamespace) -> Result<()> {
pub(crate) fn dispatch(ns: AssetNamespace) -> Result<()> {
match ns.command {
AssetCommand::List(args) => list::list_command(&args),
AssetCommand::Cp(args) => cp::cp_command(&args),

View file

@ -6,7 +6,7 @@ use fabro_config::cli::load_cli_config;
use fabro_config::project::{ResolveSettingsInput, discover_project_config, resolve_settings};
use fabro_config::{FabroConfig, FabroSettings};
pub fn dispatch(ns: ConfigNamespace) -> anyhow::Result<()> {
pub(crate) fn dispatch(ns: ConfigNamespace) -> anyhow::Result<()> {
match ns.command {
ConfigCommand::Show(args) => show_command(&args),
}
@ -33,7 +33,7 @@ fn merged_config(workflow: Option<&Path>) -> anyhow::Result<FabroSettings> {
FabroConfig::combine(project_config, cli_config).try_into()
}
pub fn show_command(args: &ConfigShowArgs) -> anyhow::Result<()> {
pub(crate) fn show_command(args: &ConfigShowArgs) -> anyhow::Result<()> {
let config = merged_config(args.workflow.as_deref())?;
let mut yaml = serde_yaml::to_string(&config)?;
if !yaml.ends_with('\n') {

View file

@ -11,7 +11,7 @@ use fabro_config::server::{ApiAuthStrategy, AuthProvider};
use fabro_llm::client::Client as LlmClient;
use fabro_llm::types::{Message, Request};
use fabro_model::{Catalog, Provider};
pub use fabro_util::check_report::{
pub(crate) use fabro_util::check_report::{
CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus,
};
use fabro_util::terminal::Styles;
@ -194,7 +194,7 @@ fn apply_live_result(
}
}
pub fn check_config(path: Option<PathBuf>) -> CheckResult {
pub(crate) fn check_config(path: Option<PathBuf>) -> CheckResult {
match path {
Some(p) => CheckResult {
name: "Configuration".to_string(),
@ -215,7 +215,7 @@ pub fn check_config(path: Option<PathBuf>) -> CheckResult {
}
}
pub fn check_llm_providers(
pub(crate) fn check_llm_providers(
statuses: &[(Provider, bool)],
live_results: Option<&[(Provider, Result<(), String>)]>,
) -> CheckResult {
@ -252,7 +252,10 @@ pub fn check_llm_providers(
remediation: Some("Set at least one provider API key".to_string()),
}
} else if !failed_providers.is_empty() {
let names: Vec<_> = failed_providers.iter().map(|p| p.to_string()).collect();
let names: Vec<_> = failed_providers
.iter()
.map(std::string::ToString::to_string)
.collect();
CheckResult {
name: "LLM providers".to_string(),
status: CheckStatus::Warning,
@ -271,7 +274,7 @@ pub fn check_llm_providers(
}
}
pub fn check_brave_search(
pub(crate) fn check_brave_search(
api_key_set: bool,
live_result: Option<&Result<(), String>>,
) -> CheckResult {
@ -317,12 +320,12 @@ pub fn check_brave_search(
}
}
pub struct SandboxStatus {
pub(crate) struct SandboxStatus {
pub daytona_configured: bool,
pub daytona_probe: Option<Result<(), String>>,
}
pub fn check_sandbox(status: &SandboxStatus) -> CheckResult {
pub(crate) fn check_sandbox(status: &SandboxStatus) -> CheckResult {
let mut details = Vec::new();
match &status.daytona_probe {
@ -378,7 +381,7 @@ pub fn check_sandbox(status: &SandboxStatus) -> CheckResult {
}
}
pub struct GithubAppStatus {
pub(crate) struct GithubAppStatus {
pub app_id: Option<String>,
pub slug: Option<String>,
pub private_key_set: bool,
@ -416,7 +419,7 @@ impl GithubAppStatus {
}
}
pub fn check_github_app(status: &GithubAppStatus) -> CheckResult {
pub(crate) fn check_github_app(status: &GithubAppStatus) -> CheckResult {
let mut details: Vec<CheckDetail> = Vec::new();
match (&status.app_id, &status.slug) {
@ -922,7 +925,7 @@ async fn probe_url(http: &reqwest::Client, url: &str) -> Result<(), String> {
.map_err(|e| e.to_string())
}
pub async fn run_doctor(verbose: bool, live: bool) -> i32 {
pub(crate) async fn run_doctor(verbose: bool, live: bool) -> i32 {
let styles = Styles::detect_stdout();
let spinner = indicatif::ProgressBar::new_spinner();
@ -999,7 +1002,7 @@ pub async fn run_doctor(verbose: bool, live: bool) -> i32 {
Ok(pem) => Some(
fabro_github::sign_app_jwt(app_id, &pem)
.map(|_| ())
.map_err(|e| e.to_string()),
.map_err(|e| e.clone()),
),
Err(e) => Some(Err(e)),
}
@ -1177,7 +1180,7 @@ pub async fn run_doctor(verbose: bool, live: bool) -> i32 {
report.render(&styles, verbose, None, Some(term_width))
);
if report.has_errors() { 1 } else { 0 }
i32::from(report.has_errors())
}
// ---------------------------------------------------------------------------

View file

@ -8,7 +8,7 @@ use fabro_mcp::config::McpServerConfig;
use crate::args::GlobalArgs;
use crate::cli_config;
pub async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> {
pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> {
let cli_config = cli_config::load_cli_settings(None)?;
#[cfg(feature = "sleep_inhibitor")]
let _sleep_guard = crate::sleep_inhibitor::guard(cli_config.prevent_idle_sleep_enabled());
@ -63,7 +63,7 @@ pub async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> {
{
let _ = globals;
tracing::info!(mode = "standalone", "Agent session starting");
run_with_args(args, mcp_servers).await?
run_with_args(args, mcp_servers).await?;
}
Ok(())

View file

@ -17,7 +17,7 @@ use crate::shared::{print_diagnostics, read_workflow_file, relative_path};
static RANKDIR_RE: LazyLock<regex::Regex> =
LazyLock::new(|| regex::Regex::new(r"rankdir\s*=\s*\w+").unwrap());
pub fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> {
pub(crate) fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let cli_defaults = load_cli_config(None)?;
let settings = resolve_settings(ResolveSettingsInput {
@ -61,7 +61,7 @@ pub fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> {
Ok(())
}
fn apply_direction<'a>(source: &'a str, direction: Option<GraphDirection>) -> Cow<'a, str> {
fn apply_direction(source: &str, direction: Option<GraphDirection>) -> Cow<'_, str> {
match direction {
Some(dir) => {
let replacement = format!("rankdir={dir}");

View file

@ -286,9 +286,11 @@ async fn setup_github_app(
) -> Result<Vec<(String, String)>> {
// Random suffix so app names don't collide
let mut rng = rand::thread_rng();
let suffix: String = (0..6)
.map(|_| format!("{:x}", rng.gen::<u8>() % 16))
.collect();
let suffix: String = (0..6).fold(String::with_capacity(6), |mut s, _| {
use std::fmt::Write;
let _ = write!(s, "{:x}", rng.gen::<u8>() % 16);
s
});
let app_name = format!("Arc-{suffix}");
// Bind to random port
@ -441,14 +443,14 @@ async fn setup_github_app(
let cli_toml_path = arc_dir.join("cli.toml");
let existing = std::fs::read_to_string(&cli_toml_path).unwrap_or_default();
let mut doc: toml::Value = if existing.is_empty() {
toml::Value::Table(Default::default())
toml::Value::Table(toml::Table::default())
} else {
toml::from_str(&existing).context("failed to parse existing cli.toml")?
};
let table = doc.as_table_mut().context("cli.toml root is not a table")?;
let git = table
.entry("git")
.or_insert(toml::Value::Table(Default::default()));
.or_insert(toml::Value::Table(toml::Table::default()));
let git_table = git
.as_table_mut()
.context("cli.toml [git] is not a table")?;
@ -480,7 +482,7 @@ async fn setup_github_app(
Ok(env_pairs)
}
pub async fn run_install(web_url: &str) -> Result<()> {
pub(crate) async fn run_install(web_url: &str) -> Result<()> {
let s = Styles::detect_stderr();
let emoji = console::Emoji("⚒️ ", "");
@ -649,8 +651,8 @@ pub async fn run_install(web_url: &str) -> Result<()> {
let slug = {
let cli_toml_path = arc_dir.join("cli.toml");
let toml_content = std::fs::read_to_string(&cli_toml_path).unwrap_or_default();
let doc: toml::Value =
toml::from_str(&toml_content).unwrap_or(toml::Value::Table(Default::default()));
let doc: toml::Value = toml::from_str(&toml_content)
.unwrap_or(toml::Value::Table(toml::Table::default()));
doc.get("git")
.and_then(|g| g.get("slug"))
.and_then(|s| s.as_str())

View file

@ -6,7 +6,7 @@ use fabro_llm::cli::{ServerConnection, run_chat_via_server};
use crate::args::GlobalArgs;
pub async fn execute(
pub(super) async fn execute(
mut args: ChatArgs,
cli_config: &FabroSettings,
globals: &GlobalArgs,

View file

@ -6,7 +6,7 @@ use anyhow::Result;
use crate::args::{GlobalArgs, LlmCommand, LlmNamespace};
use crate::cli_config::load_cli_settings;
pub async fn dispatch(ns: LlmNamespace, globals: &GlobalArgs) -> Result<()> {
pub(crate) async fn dispatch(ns: LlmNamespace, globals: &GlobalArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
match ns.command {

View file

@ -6,7 +6,7 @@ use fabro_llm::cli::{ServerConnection, run_prompt_via_server};
use crate::args::GlobalArgs;
pub async fn execute(
pub(super) async fn execute(
mut args: PromptArgs,
cli_config: &FabroSettings,
globals: &GlobalArgs,

View file

@ -1,21 +1,21 @@
pub mod asset;
pub mod config;
pub mod doctor;
pub mod exec;
pub mod graph;
pub mod install;
pub mod llm;
pub mod model;
pub mod parse;
pub mod pr;
pub mod preflight;
pub mod provider;
pub mod repo;
pub mod run;
pub mod runs;
pub mod secret;
pub mod skill;
pub mod system;
pub mod upgrade;
pub mod validate;
pub mod workflow;
pub(crate) mod asset;
pub(crate) mod config;
pub(crate) mod doctor;
pub(crate) mod exec;
pub(crate) mod graph;
pub(crate) mod install;
pub(crate) mod llm;
pub(crate) mod model;
pub(crate) mod parse;
pub(crate) mod pr;
pub(crate) mod preflight;
pub(crate) mod provider;
pub(crate) mod repo;
pub(crate) mod run;
pub(crate) mod runs;
pub(crate) mod secret;
pub(crate) mod skill;
pub(crate) mod system;
pub(crate) mod upgrade;
pub(crate) mod validate;
pub(crate) mod workflow;

View file

@ -7,7 +7,7 @@ use crate::args::GlobalArgs;
#[cfg(feature = "server")]
use crate::cli_config;
pub async fn execute(command: Option<ModelsCommand>, globals: &GlobalArgs) -> Result<()> {
pub(crate) async fn execute(command: Option<ModelsCommand>, globals: &GlobalArgs) -> Result<()> {
let server = {
#[cfg(feature = "server")]
{

View file

@ -6,7 +6,7 @@ use fabro_graphviz::parser::parse_ast;
use crate::args::ParseArgs;
use crate::shared::read_workflow_file;
pub fn run(args: &ParseArgs) -> anyhow::Result<()> {
pub(crate) fn run(args: &ParseArgs) -> anyhow::Result<()> {
let stdout = std::io::stdout();
run_to(args, stdout.lock())
}

View file

@ -8,7 +8,7 @@ use tracing::info;
use crate::args::PrCloseArgs;
use crate::cli_config::load_cli_settings;
pub async fn close_command(
pub(super) async fn close_command(
args: PrCloseArgs,
github_app: Option<fabro_github::GitHubAppCredentials>,
) -> Result<()> {

View file

@ -15,7 +15,7 @@ use tracing::info;
use crate::args::PrCreateArgs;
use crate::cli_config::load_cli_settings;
pub async fn create_command(
pub(super) async fn create_command(
args: PrCreateArgs,
github_app: Option<fabro_github::GitHubAppCredentials>,
) -> Result<()> {

View file

@ -10,7 +10,7 @@ use tracing::info;
use crate::args::PrListArgs;
use crate::cli_config::load_cli_settings;
pub async fn list_command(
pub(super) async fn list_command(
args: PrListArgs,
github_app: Option<fabro_github::GitHubAppCredentials>,
) -> Result<()> {

View file

@ -9,7 +9,7 @@ use fabro_workflows::run_lookup::runs_base;
use crate::args::PrMergeArgs;
use crate::cli_config::load_cli_settings;
pub async fn merge_command(
pub(super) async fn merge_command(
args: PrMergeArgs,
github_app: Option<fabro_github::GitHubAppCredentials>,
) -> Result<()> {

View file

@ -15,7 +15,7 @@ use crate::args::{PrCommand, PrNamespace};
use crate::cli_config::load_cli_settings;
use crate::shared::github::build_github_app_credentials;
pub async fn dispatch(ns: PrNamespace) -> Result<()> {
pub(crate) async fn dispatch(ns: PrNamespace) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let github_app = build_github_app_credentials(cli_config.app_id());

View file

@ -9,7 +9,7 @@ use fabro_workflows::run_lookup::runs_base;
use crate::args::PrViewArgs;
use crate::cli_config::load_cli_settings;
pub async fn view_command(
pub(super) async fn view_command(
args: PrViewArgs,
github_app: Option<fabro_github::GitHubAppCredentials>,
) -> Result<()> {

View file

@ -21,7 +21,7 @@ use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate};
use crate::args::PreflightArgs;
use crate::shared::github::build_github_app_credentials;
pub async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> {
pub(crate) async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> {
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
let cli_defaults = load_cli_config(None)?;
let cli_config: FabroSettings = cli_defaults.clone().try_into()?;
@ -121,7 +121,7 @@ fn parse_sandbox_provider(settings: &FabroSettings) -> anyhow::Result<Option<San
settings
.sandbox_settings()
.and_then(|s| s.provider.as_deref())
.map(|s| s.parse::<SandboxProvider>())
.map(str::parse::<SandboxProvider>)
.transpose()
.map_err(|e| anyhow::anyhow!("Invalid sandbox provider: {e}"))
}
@ -367,8 +367,11 @@ async fn run_preflight(
let default_provider = provider.as_deref().unwrap_or("anthropic");
let llm_ok = match LlmClient::from_env().await {
Ok(c) => {
let configured: Vec<String> =
c.provider_names().iter().map(|s| s.to_string()).collect();
let configured: Vec<String> = c
.provider_names()
.iter()
.map(std::string::ToString::to_string)
.collect();
let mut model_providers = std::collections::BTreeSet::new();
for node in graph.nodes.values() {

View file

@ -6,7 +6,7 @@ use tokio::task::spawn_blocking;
use crate::args::ProviderLoginArgs;
use crate::shared::provider_auth;
pub async fn login_command(args: ProviderLoginArgs) -> Result<()> {
pub(super) async fn login_command(args: ProviderLoginArgs) -> Result<()> {
let s = Styles::detect_stderr();
let arc_dir = dirs::home_dir()
.context("could not determine home directory")?

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use crate::args::{ProviderCommand, ProviderNamespace};
pub async fn dispatch(ns: ProviderNamespace) -> Result<()> {
pub(crate) async fn dispatch(ns: ProviderNamespace) -> Result<()> {
match ns.command {
ProviderCommand::Login(args) => login::login_command(args).await,
}

View file

@ -1,6 +1,6 @@
use anyhow::{Context, Result, bail};
pub fn run_deinit() -> Result<()> {
pub(crate) fn run_deinit() -> Result<()> {
let repo_root = super::init::git_repo_root()?;
let fabro_toml = repo_root.join("fabro.toml");

View file

@ -21,7 +21,7 @@ pub(super) fn git_repo_root() -> Result<PathBuf> {
))
}
pub async fn run_init() -> Result<()> {
pub(crate) async fn run_init() -> Result<()> {
let repo_root = git_repo_root()?;
let fabro_toml = repo_root.join("fabro.toml");
@ -148,46 +148,40 @@ async fn check_github_app_installation() {
// Convert SSH URL to HTTPS and parse owner/repo
let https_url = fabro_github::ssh_url_to_https(&remote_url);
let (owner, repo) = match fabro_github::parse_github_owner_repo(&https_url) {
Ok(pair) => pair,
Err(_) => return, // Not a GitHub repo — skip silently
let Ok((owner, repo)) = fabro_github::parse_github_owner_repo(&https_url) else {
return; // Not a GitHub repo — skip silently
};
// Load CLI config to get app_id and slug
let cli_config = match load_cli_settings(None) {
Ok(c) => c,
Err(_) => return,
let Ok(cli_config) = load_cli_settings(None) else {
return;
};
let app_id = match cli_config.app_id() {
Some(id) => id.to_string(),
None => {
eprintln!(
"\n Run {} to set up the GitHub App",
console::Style::new()
.cyan()
.bold()
.apply_to("fabro install")
);
return;
}
let app_id = if let Some(id) = cli_config.app_id() {
id.to_string()
} else {
eprintln!(
"\n Run {} to set up the GitHub App",
console::Style::new()
.cyan()
.bold()
.apply_to("fabro install")
);
return;
};
let slug = cli_config.slug().map(String::from);
// Build GitHub App credentials
let creds = match build_github_app_credentials(Some(&app_id)) {
Some(c) => c,
None => {
eprintln!(
"\n Set {} to enable GitHub App integration",
console::Style::new()
.cyan()
.bold()
.apply_to("GITHUB_APP_PRIVATE_KEY")
);
return;
}
let Some(creds) = build_github_app_credentials(Some(&app_id)) else {
eprintln!(
"\n Set {} to enable GitHub App integration",
console::Style::new()
.cyan()
.bold()
.apply_to("GITHUB_APP_PRIVATE_KEY")
);
return;
};
let jwt = match fabro_github::sign_app_jwt(&creds.app_id, &creds.private_key_pem) {

View file

@ -1,11 +1,11 @@
pub mod deinit;
pub mod init;
pub(crate) mod deinit;
pub(crate) mod init;
use anyhow::Result;
use crate::args::{RepoCommand, RepoNamespace};
pub async fn dispatch(ns: RepoNamespace) -> Result<()> {
pub(crate) async fn dispatch(ns: RepoNamespace) -> Result<()> {
match ns.command {
RepoCommand::Init { skill } => {
init::run_init().await?;

View file

@ -28,7 +28,7 @@ const INTERVIEW_UNANSWERED_MESSAGE: &str =
/// Attach to a running (or finished) workflow run, rendering progress live.
///
/// Returns exit code 0 for success/partial_success, 1 otherwise.
pub async fn attach_run(
pub(crate) async fn attach_run(
run_dir: &Path,
kill_on_detach: bool,
styles: &'static Styles,
@ -460,6 +460,7 @@ fn determine_exit_code(conclusion_path: &Path, status_record: Option<RunStatusRe
}
}
#[allow(unsafe_code)]
fn kill_engine(run_dir: &Path) {
if let Some(pid) = read_launcher_pid(run_dir).map(|pid| pid as i32) {
#[cfg(unix)]
@ -470,6 +471,7 @@ fn kill_engine(run_dir: &Path) {
}
}
#[allow(unsafe_code)]
fn process_alive(pid: u32) -> bool {
#[cfg(unix)]
{

View file

@ -4,7 +4,7 @@ use fabro_util::terminal::Styles;
use crate::args::{GlobalArgs, RunArgs};
pub async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> {
pub(crate) async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> {
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
let cli_defaults = load_cli_config(None)?;
let cli_config: fabro_config::FabroSettings = cli_defaults.clone().try_into()?;
@ -12,7 +12,7 @@ pub async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> {
let quiet = args.detach;
let prevent_idle_sleep = cli_config.prevent_idle_sleep_enabled();
let (run_id, run_dir) = super::create::create_run(&args, cli_defaults, styles, quiet).await?;
let (run_id, run_dir) = super::create::create_run(&args, cli_defaults, styles, quiet)?;
#[cfg(feature = "sleep_inhibitor")]
let _sleep_guard = crate::sleep_inhibitor::guard(prevent_idle_sleep);

View file

@ -26,7 +26,7 @@ enum CopyDirection {
},
}
pub async fn cp_command(args: CpArgs) -> Result<()> {
pub(crate) async fn cp_command(args: CpArgs) -> Result<()> {
let direction = parse_direction(&args.src, &args.dst)?;
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());

View file

@ -12,7 +12,7 @@ use super::output::{print_diagnostics_from_error, print_workflow_report_from_per
/// Create a workflow run: allocate run directory, persist RunRecord, return (run_id, run_dir).
///
/// This does NOT execute the workflow — it only prepares the run directory.
pub async fn create_run(
pub(crate) fn create_run(
args: &RunArgs,
cli_defaults: FabroConfig,
styles: &Styles,

View file

@ -11,7 +11,7 @@ use fabro_workflows::operations::{StartServices, resume as resume_run, start as
use crate::cli_config;
use crate::shared;
pub async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> {
pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> {
let cli_config = cli_config::load_cli_settings(None)?;
let github_app = shared::github::build_github_app_credentials(cli_config.app_id());
let git_author = GitAuthor::from_options(

View file

@ -13,7 +13,7 @@ use tracing::{debug, info};
use crate::args::DiffArgs;
use crate::cli_config::load_cli_settings;
pub async fn run(args: DiffArgs) -> Result<()> {
pub(crate) async fn run(args: DiffArgs) -> Result<()> {
info!(run_id = %args.run, "Showing diff");
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());

View file

@ -9,7 +9,7 @@ use git2::Repository;
use crate::args::ForkArgs;
pub fn run(args: &ForkArgs, styles: &Styles) -> Result<()> {
pub(crate) fn run(args: &ForkArgs, styles: &Styles) -> Result<()> {
let repo = Repository::discover(".").context("not in a git repository")?;
let run_id = find_run_id_by_prefix(&repo, &args.run_id)?;
let store = Store::new(repo);

View file

@ -62,6 +62,7 @@ pub(crate) fn launcher_record_is_running(record: &LauncherRecord) -> bool {
}
#[cfg(unix)]
#[allow(unsafe_code)]
fn process_alive(pid: u32) -> bool {
unsafe { libc::kill(pid as i32, 0) == 0 }
}

View file

@ -11,7 +11,7 @@ use tracing::{debug, info};
use crate::args::LogsArgs;
use crate::cli_config::load_cli_settings;
pub fn run(args: LogsArgs, styles: &Styles) -> Result<()> {
pub(crate) fn run(args: LogsArgs, styles: &Styles) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let run = resolve_run(&base, &args.run)?;
@ -102,7 +102,7 @@ fn extract_timestamp(line: &str) -> Option<DateTime<Utc>> {
ts_str.parse::<DateTime<Utc>>().ok()
}
pub fn parse_since(s: &str) -> Result<DateTime<Utc>> {
pub(crate) fn parse_since(s: &str) -> Result<DateTime<Utc>> {
let s = s.trim();
if s.is_empty() {
bail!("empty --since value");
@ -186,7 +186,7 @@ fn render_indented_markdown(styles: &Styles, text: &str, indent: &str) -> String
.join("\n")
}
pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
let envelope: serde_json::Value = serde_json::from_str(line).ok()?;
let event = envelope.get("event")?.as_str()?;
let ts = format_timestamp(envelope.get("ts")?.as_str()?);
@ -234,7 +234,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
if let Some(usage) = envelope.get("usage") {
let total = usage
.get("total_tokens")
.and_then(|value| value.as_i64())
.and_then(serde_json::Value::as_i64)
.unwrap_or(0);
let pad = " ".repeat(ts.len() + 1);
if total > 0 {
@ -246,10 +246,13 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
.apply_to(format!("Tokens: {}", format_tokens(total as u64)))
));
}
if let Some(cache_read) = usage.get("cache_read_tokens").and_then(|v| v.as_i64()) {
if let Some(cache_read) = usage
.get("cache_read_tokens")
.and_then(serde_json::Value::as_i64)
{
let cache_write = usage
.get("cache_write_tokens")
.and_then(|v| v.as_i64())
.and_then(serde_json::Value::as_i64)
.unwrap_or(0);
lines.push(format!(
"{}{}",
@ -261,7 +264,10 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
))
));
}
if let Some(reasoning) = usage.get("reasoning_tokens").and_then(|v| v.as_i64()) {
if let Some(reasoning) = usage
.get("reasoning_tokens")
.and_then(serde_json::Value::as_i64)
{
if reasoning > 0 {
lines.push(format!(
"{}{}",
@ -321,14 +327,17 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
let label = str_field(&envelope, "node_label").unwrap_or("?");
let duration = format_duration_ms(envelope.get("duration_ms"));
let cost = format_cost(envelope.get("cost"));
let turns = envelope.get("turns").and_then(|v| v.as_u64()).unwrap_or(0);
let turns = envelope
.get("turns")
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
let tools = envelope
.get("tool_calls")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
let tokens = envelope
.get("total_tokens")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
let stats = format!("({turns} turns, {tools} tools, {})", format_tokens(tokens));
Some(format!(
@ -386,7 +395,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
let tool = str_field(&envelope, "tool_name").unwrap_or("?");
let is_error = envelope
.get("is_error")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let detail = tool_detail(&envelope);
let display = match detail {
@ -432,7 +441,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
"SetupCompleted" => {
let count = envelope
.get("command_count")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
let duration = format_duration_ms(envelope.get("duration_ms"));
Some(format!(
@ -445,11 +454,11 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
"Agent.CompactionCompleted" => {
let original = envelope
.get("original_turn_count")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
let preserved = envelope
.get("preserved_turn_count")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
Some(format!(
"{} {}",
@ -462,7 +471,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
"ParallelStarted" => {
let count = envelope
.get("branch_count")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
Some(format!(
"{} {} Parallel {} branches",
@ -502,7 +511,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
let url = str_field(&envelope, "pr_url").unwrap_or("?");
let draft = envelope
.get("draft")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let label = if draft { "Draft PR:" } else { "PR:" };
Some(format!(
@ -584,7 +593,7 @@ fn format_timestamp(ts: &str) -> String {
}
fn format_duration_ms(value: Option<&serde_json::Value>) -> String {
let ms = value.and_then(|v| v.as_u64()).unwrap_or(0);
let ms = value.and_then(serde_json::Value::as_u64).unwrap_or(0);
if ms < 1000 {
format!("{ms}ms")
} else {
@ -599,7 +608,7 @@ fn format_duration_ms(value: Option<&serde_json::Value>) -> String {
}
fn format_cost(value: Option<&serde_json::Value>) -> String {
let cost = value.and_then(|v| v.as_f64()).unwrap_or(0.0);
let cost = value.and_then(serde_json::Value::as_f64).unwrap_or(0.0);
if cost > 0.0 {
format!("${cost:.2}")
} else {

View file

@ -26,13 +26,13 @@ pub(crate) mod ssh;
pub(crate) mod start;
pub(crate) mod wait;
pub async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> {
pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> {
match cmd {
RunCommands::Run(args) => command::execute(args, globals).await,
RunCommands::Create(args) => {
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
let cli_defaults = load_cli_config(None)?;
let (run_id, _run_dir) = create::create_run(&args, cli_defaults, styles, true).await?;
let (run_id, _run_dir) = create::create_run(&args, cli_defaults, styles, true)?;
println!("{run_id}");
Ok(())
}

View file

@ -9,7 +9,7 @@ use crate::args::PreviewArgs;
use crate::cli_config::load_cli_settings;
use crate::shared::validate_daytona_provider;
pub async fn run(args: PreviewArgs) -> Result<()> {
pub(crate) async fn run(args: PreviewArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let run_dir = resolve_run(&base, &args.run)?.path;
@ -56,10 +56,12 @@ pub async fn run(args: PreviewArgs) -> Result<()> {
}
fn format_standard_output(url: &str, token: &str) -> String {
use std::fmt::Write;
let mut out = format!("URL: {url}\nToken: {token}\n");
out.push_str(&format!(
let _ = write!(
out,
"\ncurl -H \"x-daytona-preview-token: {token}\" \\\n -H \"X-Daytona-Skip-Preview-Warning: true\" \\\n {url}\n"
));
);
out
}

View file

@ -12,7 +12,10 @@ use crate::cli_config::load_cli_settings;
/// Looks up the run by ID prefix, validates a checkpoint exists, cleans stale
/// artifacts from the previous execution, then spawns an engine subprocess
/// (identical to `fabro run`'s create→start→attach flow).
pub async fn resume_command(args: ResumeArgs, styles: &'static Styles) -> anyhow::Result<()> {
pub(crate) async fn resume_command(
args: ResumeArgs,
styles: &'static Styles,
) -> anyhow::Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let run_dir = find_run_by_prefix(&base, &args.run)?;
@ -64,6 +67,7 @@ mod tests {
}
}
#[allow(unsafe_code)]
fn process_alive(pid: u32) -> bool {
#[cfg(unix)]
{

View file

@ -12,7 +12,7 @@ use git2::Repository;
use crate::args::RewindArgs;
use crate::shared::color_if;
pub fn run(args: &RewindArgs, styles: &Styles) -> Result<()> {
pub(crate) fn run(args: &RewindArgs, styles: &Styles) -> Result<()> {
let repo = Repository::discover(".").context("not in a git repository")?;
let run_id = find_run_id_by_prefix(&repo, &args.run_id)?;
let store = Store::new(repo);

View file

@ -179,7 +179,7 @@ enum ProgressRenderer {
// ── ProgressUI ──────────────────────────────────────────────────────────
pub struct ProgressUI {
pub(crate) struct ProgressUI {
renderer: ProgressRenderer,
verbose: bool,
active_stages: HashMap<String, ActiveStage>,
@ -199,7 +199,7 @@ pub struct ProgressUI {
#[allow(dead_code)]
impl ProgressUI {
pub fn new(is_tty: bool, verbose: bool) -> Self {
pub(crate) fn new(is_tty: bool, verbose: bool) -> Self {
let renderer = if is_tty {
ProgressRenderer::Tty(TtyRenderer {
multi: MultiProgress::new(),
@ -224,7 +224,7 @@ impl ProgressUI {
}
}
pub fn set_working_directory(&mut self, dir: String) {
pub(crate) fn set_working_directory(&mut self, dir: String) {
self.working_directory = Some(dir);
}
@ -266,7 +266,7 @@ impl ProgressUI {
}
/// Register event handlers on the emitter.
pub fn register(progress: &Arc<Mutex<Self>>, emitter: &EventEmitter) {
pub(crate) fn register(progress: &Arc<Mutex<Self>>, emitter: &EventEmitter) {
let p = Arc::clone(progress);
emitter.on_event(move |event| {
let mut ui = p.lock().expect("progress lock poisoned");
@ -275,21 +275,21 @@ impl ProgressUI {
}
/// Hide indicatif progress bars (for interview prompts in attach mode).
pub fn hide_bars(&self) {
pub(crate) fn hide_bars(&self) {
if let ProgressRenderer::Tty(tty) = &self.renderer {
tty.multi.set_draw_target(ProgressDrawTarget::hidden());
}
}
/// Show indicatif progress bars after an interview prompt.
pub fn show_bars(&self) {
pub(crate) fn show_bars(&self) {
if let ProgressRenderer::Tty(tty) = &self.renderer {
tty.multi.set_draw_target(ProgressDrawTarget::stderr());
}
}
/// Clear all active bars and release the terminal for normal stderr output.
pub fn finish(&mut self) {
pub(crate) fn finish(&mut self) {
for (_id, stage) in self.active_stages.drain() {
for entry in &stage.tool_calls {
if entry.is_branch || self.verbose {
@ -662,19 +662,22 @@ impl ProgressUI {
/// Parse a JSONL envelope line and dispatch to internal rendering methods.
/// Used by the attach loop to render events from progress.jsonl.
pub fn handle_json_line(&mut self, line: &str) {
pub(crate) fn handle_json_line(&mut self, line: &str) {
let envelope: serde_json::Value = match serde_json::from_str(line) {
Ok(v) => v,
Err(_) => return,
};
let event_name = match envelope.get("event").and_then(|v| v.as_str()) {
Some(name) => name,
None => return,
let Some(event_name) = envelope.get("event").and_then(|v| v.as_str()) else {
return;
};
let str_field = |key: &str| -> Option<&str> { envelope.get(key).and_then(|v| v.as_str()) };
let u64_field =
|key: &str| -> u64 { envelope.get(key).and_then(|v| v.as_u64()).unwrap_or(0) };
let u64_field = |key: &str| -> u64 {
envelope
.get(key)
.and_then(serde_json::Value::as_u64)
.unwrap_or(0)
};
match event_name {
"WorkflowRunStarted" => {
@ -697,8 +700,8 @@ impl ProgressUI {
.to_string();
let duration_ms = u64_field("duration_ms");
let name = str_field("name").map(String::from);
let cpu = envelope.get("cpu").and_then(|v| v.as_f64());
let memory = envelope.get("memory").and_then(|v| v.as_f64());
let cpu = envelope.get("cpu").and_then(serde_json::Value::as_f64);
let memory = envelope.get("memory").and_then(serde_json::Value::as_f64);
let url = str_field("url").map(String::from);
self.on_sandbox_event(&fabro_agent::SandboxEvent::Ready {
provider,
@ -741,7 +744,7 @@ impl ProgressUI {
let cost_str = envelope
.get("usage")
.and_then(|u| u.get("cost"))
.and_then(|c| c.as_f64())
.and_then(serde_json::Value::as_f64)
.map(|c| format!("{} ", format_cost(c)))
.unwrap_or_default();
@ -752,8 +755,12 @@ impl ProgressUI {
let total_tokens = envelope
.get("usage")
.map(|u| {
u.get("input_tokens").and_then(|v| v.as_i64()).unwrap_or(0)
+ u.get("output_tokens").and_then(|v| v.as_i64()).unwrap_or(0)
u.get("input_tokens")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0)
+ u.get("output_tokens")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0)
})
.unwrap_or(0);
if turn_count > 0 || tool_call_count > 0 || total_tokens > 0 {
@ -833,7 +840,7 @@ impl ProgressUI {
let tool_call_id = str_field("tool_call_id").unwrap_or("?");
let is_error = envelope
.get("is_error")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
self.on_tool_call_completed(stage, tool_call_id, is_error);
}
@ -931,7 +938,7 @@ impl ProgressUI {
let pr_url = str_field("pr_url").unwrap_or("?");
let draft = envelope
.get("draft")
.and_then(|value| value.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
self.on_pull_request_created(pr_url, draft);
}
@ -1027,7 +1034,7 @@ impl ProgressUI {
if let Some(cli_name) = str_field("cli_name") {
let already_installed = envelope
.get("already_installed")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let duration_ms = u64_field("duration_ms");
self.on_cli_ensure_completed(cli_name, already_installed, duration_ms);
@ -1213,7 +1220,7 @@ impl ProgressUI {
// ── Logs dir (called externally) ────────────────────────────────────
pub fn show_run_dir(&mut self, run_dir: &Path) {
pub(crate) fn show_run_dir(&mut self, run_dir: &Path) {
let path_str = tilde_path(run_dir);
match &self.renderer {
ProgressRenderer::Tty(tty) => {
@ -1227,7 +1234,7 @@ impl ProgressUI {
}
}
pub fn show_version(&mut self) {
pub(crate) fn show_version(&mut self) {
let version = FABRO_VERSION;
match &self.renderer {
ProgressRenderer::Tty(tty) => {
@ -1241,7 +1248,7 @@ impl ProgressUI {
}
}
pub fn show_run_id(&mut self, run_id: &str) {
pub(crate) fn show_run_id(&mut self, run_id: &str) {
match &self.renderer {
ProgressRenderer::Tty(tty) => {
let bar = tty.multi.add(ProgressBar::new_spinner());
@ -1254,7 +1261,7 @@ impl ProgressUI {
}
}
pub fn show_time(&mut self, time: &str) {
pub(crate) fn show_time(&mut self, time: &str) {
match &self.renderer {
ProgressRenderer::Tty(tty) => {
let bar = tty.multi.add(ProgressBar::new_spinner());
@ -1267,7 +1274,7 @@ impl ProgressUI {
}
}
pub fn show_worktree(&mut self, path: &Path) {
pub(crate) fn show_worktree(&mut self, path: &Path) {
let path_str = tilde_path(path);
match &self.renderer {
ProgressRenderer::Tty(tty) => {
@ -1281,7 +1288,7 @@ impl ProgressUI {
}
}
pub fn show_base_info(&mut self, branch: Option<&str>, sha: &str) {
pub(crate) fn show_base_info(&mut self, branch: Option<&str>, sha: &str) {
let short_sha = &sha[..sha.len().min(12)];
let text = match branch {
Some(b) => format!("Base: {b} ({short_sha})"),
@ -1408,7 +1415,7 @@ impl ProgressUI {
{
let usage_percent = details
.get("usage_percent")
.and_then(|v| v.as_u64())
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
let yellow = Style::new().yellow();
self.insert_info_line_for_stage(
@ -1725,14 +1732,14 @@ impl ProgressUI {
/// Wraps a `ConsoleInterviewer` so that progress bars are hidden during
/// interactive prompts (avoids garbled output from concurrent writes).
#[allow(dead_code)]
pub struct ProgressAwareInterviewer {
pub(crate) struct ProgressAwareInterviewer {
inner: ConsoleInterviewer,
progress: Arc<Mutex<ProgressUI>>,
}
#[allow(dead_code)]
impl ProgressAwareInterviewer {
pub fn new(inner: ConsoleInterviewer, progress: Arc<Mutex<ProgressUI>>) -> Self {
pub(crate) fn new(inner: ConsoleInterviewer, progress: Arc<Mutex<ProgressUI>>) -> Self {
Self { inner, progress }
}
}

View file

@ -9,7 +9,7 @@ use crate::args::SshArgs;
use crate::cli_config::load_cli_settings;
use crate::shared::validate_daytona_provider;
pub async fn run(args: SshArgs) -> Result<()> {
pub(crate) async fn run(args: SshArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let run_dir = resolve_run(&base, &args.run)?.path;

View file

@ -14,7 +14,8 @@ use super::launcher::{
///
/// The engine process reads `run.json` from the run directory and executes the
/// workflow. Returns the child process handle (use `.id()` for the PID).
pub fn start_run(run_dir: &Path, resume: bool) -> Result<std::process::Child> {
#[allow(unsafe_code)]
pub(crate) fn start_run(run_dir: &Path, resume: bool) -> Result<std::process::Child> {
let record = RunRecord::load(run_dir)
.map_err(|e| anyhow!("Cannot start run: failed to load run.json: {e}"))?;

View file

@ -12,7 +12,7 @@ use crate::args::WaitArgs;
use crate::cli_config::load_cli_settings;
use crate::shared::format_duration_ms;
pub fn run(args: WaitArgs, styles: &Styles) -> Result<()> {
pub(crate) fn run(args: WaitArgs, styles: &Styles) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let run_info = resolve_run(&base, &args.run)?;

View file

@ -14,7 +14,7 @@ use crate::args::InspectArgs;
use crate::cli_config::load_cli_settings;
#[derive(Debug, Serialize)]
pub struct InspectOutput {
pub(crate) struct InspectOutput {
pub run_id: String,
pub run_dir: PathBuf,
pub status: RunStatus,
@ -25,17 +25,17 @@ pub struct InspectOutput {
pub sandbox: Option<serde_json::Value>,
}
pub fn run(args: &InspectArgs) -> Result<()> {
pub(crate) fn run(args: &InspectArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let run = resolve_run(&base, &args.run)?;
let output = inspect_run_dir(&run.run_id, &run.path, run.status)?;
let output = inspect_run_dir(&run.run_id, &run.path, run.status);
let json = serde_json::to_string_pretty(&[output])?;
println!("{json}");
Ok(())
}
fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> Result<InspectOutput> {
fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> InspectOutput {
let run_record = RunRecord::load(run_dir)
.ok()
.and_then(|v| serde_json::to_value(v).ok());
@ -52,7 +52,7 @@ fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> Result<In
.ok()
.and_then(|v| serde_json::to_value(v).ok());
Ok(InspectOutput {
InspectOutput {
run_id: run_id.to_string(),
run_dir: run_dir.to_path_buf(),
status,
@ -61,5 +61,5 @@ fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> Result<In
conclusion,
checkpoint,
sandbox,
})
}
}

View file

@ -17,7 +17,7 @@ use crate::shared::{color_if, format_duration_ms, tilde_path};
use super::short_run_id;
pub fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> {
pub(crate) fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
let runs = scan_runs(&base)?;

View file

@ -7,7 +7,7 @@ pub(crate) mod inspect;
pub(crate) mod list;
pub(crate) mod rm;
pub async fn dispatch(cmd: RunsCommands) -> Result<()> {
pub(crate) async fn dispatch(cmd: RunsCommands) -> Result<()> {
match cmd {
RunsCommands::Ps(args) => {
let styles = Styles::detect_stdout();

View file

@ -14,7 +14,7 @@ use crate::cli_config::load_cli_settings;
use super::short_run_id;
pub async fn remove_command(args: &RunsRemoveArgs) -> Result<()> {
pub(crate) async fn remove_command(args: &RunsRemoveArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
remove_from(args, &base).await

View file

@ -3,7 +3,7 @@ use anyhow::{Result, bail};
use crate::args::SecretGetArgs;
use fabro_config::dotenv;
pub fn get_command(args: &SecretGetArgs) -> Result<()> {
pub(super) fn get_command(args: &SecretGetArgs) -> Result<()> {
let path = dotenv::env_file_path()?;
match dotenv::get_env_value(&path, &args.key)? {
Some(value) => {

View file

@ -3,7 +3,7 @@ use anyhow::{Result, bail};
use crate::args::SecretListArgs;
use fabro_config::dotenv;
pub fn list_command(args: &SecretListArgs) -> Result<()> {
pub(super) fn list_command(args: &SecretListArgs) -> Result<()> {
let path = dotenv::env_file_path()?;
let contents = match std::fs::read_to_string(&path) {
Ok(c) => c,

View file

@ -7,7 +7,7 @@ use anyhow::Result;
use crate::args::{SecretCommand, SecretNamespace};
pub fn dispatch(ns: SecretNamespace) -> Result<()> {
pub(crate) fn dispatch(ns: SecretNamespace) -> Result<()> {
match ns.command {
SecretCommand::Get(args) => get::get_command(&args),
SecretCommand::List(args) => list::list_command(&args),

View file

@ -3,7 +3,7 @@ use anyhow::{Result, bail};
use crate::args::SecretRmArgs;
use fabro_config::dotenv;
pub fn rm_command(args: &SecretRmArgs) -> Result<()> {
pub(super) fn rm_command(args: &SecretRmArgs) -> Result<()> {
let path = dotenv::env_file_path()?;
let contents = match std::fs::read_to_string(&path) {
Ok(c) => c,

View file

@ -3,7 +3,7 @@ use anyhow::Result;
use crate::args::SecretSetArgs;
use fabro_config::dotenv;
pub fn set_command(args: &SecretSetArgs) -> Result<()> {
pub(super) fn set_command(args: &SecretSetArgs) -> Result<()> {
let path = dotenv::env_file_path()?;
let existing = std::fs::read_to_string(&path).unwrap_or_default();
let merged = dotenv::merge_env(&existing, &[(&args.key, &args.value)]);

View file

@ -21,7 +21,7 @@ const SKILL_FILES: &[(&str, &str)] = &[
];
/// Install all skill files under `base_dir/fabro-create-workflow/`.
pub fn install_skill_to(base_dir: &Path) -> Result<()> {
pub(crate) fn install_skill_to(base_dir: &Path) -> Result<()> {
let skill_dir = base_dir.join("fabro-create-workflow");
for (rel_path, content) in SKILL_FILES {
@ -37,7 +37,7 @@ pub fn install_skill_to(base_dir: &Path) -> Result<()> {
Ok(())
}
pub fn run_skill_install(args: &SkillInstallArgs) -> Result<()> {
pub(super) fn run_skill_install(args: &SkillInstallArgs) -> Result<()> {
let base_dir = resolve_base_dir(&args.scope, &args.dir)?;
let skill_dir = base_dir.join("fabro-create-workflow");

View file

@ -4,9 +4,9 @@ use anyhow::Result;
use crate::args::{SkillCommand, SkillNamespace};
pub use install::install_skill_to;
pub(crate) use install::install_skill_to;
pub fn dispatch(ns: SkillNamespace) -> Result<()> {
pub(crate) fn dispatch(ns: SkillNamespace) -> Result<()> {
match ns.command {
SkillCommand::Install(args) => install::run_skill_install(&args),
}

View file

@ -13,7 +13,7 @@ use crate::args::DfArgs;
use crate::cli_config::load_cli_settings;
use crate::shared::format_size;
pub fn df_command(args: &DfArgs) -> Result<()> {
pub(super) fn df_command(args: &DfArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let data_dir = cli_config.storage_dir();
let runs_base_dir = runs_base(&data_dir);
@ -81,7 +81,12 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -
continue;
}
let name = entry.file_name().to_string_lossy().to_string();
if name.ends_with(".db") || name.ends_with(".db-wal") || name.ends_with(".db-shm") {
if std::path::Path::new(&name)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("db"))
|| name.ends_with(".db-wal")
|| name.ends_with(".db-shm")
{
if let Ok(meta) = path.metadata() {
db_count += 1;
total_db_size += meta.len();
@ -214,9 +219,9 @@ fn truncate_str(s: &str, max_len: usize) -> String {
fn dir_size(path: &Path) -> u64 {
walkdir::WalkDir::new(path)
.into_iter()
.filter_map(|entry| entry.ok())
.filter_map(std::result::Result::ok)
.filter_map(|entry| entry.metadata().ok())
.filter(|metadata| metadata.is_file())
.filter(std::fs::Metadata::is_file)
.map(|metadata| metadata.len())
.sum()
}

View file

@ -7,7 +7,7 @@ use crate::args::{SystemCommand, SystemNamespace};
pub(crate) use prune::parse_duration;
pub fn dispatch(ns: SystemNamespace) -> Result<()> {
pub(crate) fn dispatch(ns: SystemNamespace) -> Result<()> {
match ns.command {
SystemCommand::Prune(args) => prune::prune_command(&args),
SystemCommand::Df(args) => df::df_command(&args),

View file

@ -11,7 +11,7 @@ use crate::args::RunsPruneArgs;
use crate::cli_config::load_cli_settings;
use crate::shared::format_size;
pub fn prune_command(args: &RunsPruneArgs) -> Result<()> {
pub(super) fn prune_command(args: &RunsPruneArgs) -> Result<()> {
let cli_config = load_cli_settings(None)?;
let base = runs_base(&cli_config.storage_dir());
prune_from(args, &base)
@ -111,9 +111,9 @@ fn parse_label_filters(label_args: &[String]) -> Vec<(String, String)> {
fn dir_size(path: &Path) -> u64 {
walkdir::WalkDir::new(path)
.into_iter()
.filter_map(|entry| entry.ok())
.filter_map(std::result::Result::ok)
.filter_map(|entry| entry.metadata().ok())
.filter(|metadata| metadata.is_file())
.filter(std::fs::Metadata::is_file)
.map(|metadata| metadata.len())
.sum()
}

View file

@ -31,7 +31,7 @@ fn http_client() -> Result<reqwest::Client> {
impl Backend {
async fn fetch_latest_release_tag(&self) -> Result<String> {
match self {
Backend::Gh => {
Self::Gh => {
let output = TokioCommand::new("gh")
.args([
"release",
@ -52,7 +52,7 @@ impl Backend {
}
Ok(String::from_utf8(output.stdout)?.trim().to_string())
}
Backend::Http(client) => {
Self::Http(client) => {
let url = format!("https://api.github.com/repos/{GITHUB_REPO}/releases/latest");
let resp = client
.get(&url)
@ -77,7 +77,7 @@ impl Backend {
async fn download_release(&self, tag: &str, asset: &str, dest_dir: &Path) -> Result<PathBuf> {
let dest = dest_dir.join(asset);
match self {
Backend::Gh => {
Self::Gh => {
let status = TokioCommand::new("gh")
.args([
"release",
@ -98,7 +98,7 @@ impl Backend {
bail!("gh release download failed with exit code {status}");
}
}
Backend::Http(client) => {
Self::Http(client) => {
let url =
format!("https://github.com/{GITHUB_REPO}/releases/download/{tag}/{asset}");
let resp = client
@ -223,7 +223,7 @@ impl UpgradeCheckState {
// ── Main upgrade command ───────────────────────────────────────────────────
pub async fn run_upgrade(args: UpgradeArgs) -> Result<()> {
pub(crate) async fn run_upgrade(args: UpgradeArgs) -> Result<()> {
let backend = select_backend().await;
let current =
@ -346,7 +346,7 @@ pub async fn run_upgrade(args: UpgradeArgs) -> Result<()> {
/// Spawn a background task that checks for a newer version and prints a notice
/// to stderr after the main command completes. Returns a handle that should be
/// awaited at the end of `main_inner`.
pub fn spawn_upgrade_check(
pub(crate) fn spawn_upgrade_check(
no_upgrade_check: bool,
upgrade_check_enabled: bool,
) -> Option<JoinHandle<()>> {

View file

@ -9,7 +9,7 @@ use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate};
use crate::args::ValidateArgs;
use crate::shared::{print_diagnostics, relative_path};
pub fn run(args: &ValidateArgs, styles: &Styles) -> anyhow::Result<()> {
pub(crate) fn run(args: &ValidateArgs, styles: &Styles) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let cli_defaults = load_cli_config(None)?;
let settings = resolve_settings(ResolveSettingsInput {

View file

@ -7,15 +7,14 @@ use fabro_config::project::{discover_project_config, resolve_fabro_root};
use crate::args::WorkflowCreateArgs;
use crate::shared::relative_path;
pub fn create_command(args: &WorkflowCreateArgs) -> Result<()> {
pub(super) fn create_command(args: &WorkflowCreateArgs) -> Result<()> {
let cwd = std::env::current_dir()?;
let (config_path, config) = match discover_project_config(&cwd)? {
Some(found) => found,
None => bail!(
let Some((config_path, config)) = discover_project_config(&cwd)? else {
bail!(
"No fabro.toml found in {cwd} or any parent directory",
cwd = cwd.display()
),
);
};
let fabro_root = resolve_fabro_root(&config_path, &config);

View file

@ -11,16 +11,15 @@ use crate::shared::relative_path;
const GOAL_MAX_LEN: usize = 60;
pub fn list_command(_args: &WorkflowListArgs) -> Result<()> {
pub(super) fn list_command(_args: &WorkflowListArgs) -> Result<()> {
let styles = Styles::detect_stderr();
let cwd = std::env::current_dir()?;
let (config_path, config) = match discover_project_config(&cwd)? {
Some(found) => found,
None => bail!(
let Some((config_path, config)) = discover_project_config(&cwd)? else {
bail!(
"No fabro.toml found in {cwd} or any parent directory",
cwd = cwd.display()
),
);
};
let fabro_root = resolve_fabro_root(&config_path, &config);

View file

@ -5,7 +5,7 @@ use anyhow::Result;
use crate::args::{WorkflowCommand, WorkflowNamespace};
pub fn dispatch(ns: WorkflowNamespace) -> Result<()> {
pub(crate) fn dispatch(ns: WorkflowNamespace) -> Result<()> {
match ns.command {
WorkflowCommand::List(args) => list::list_command(&args),
WorkflowCommand::Create(args) => create::create_command(&args),

View file

@ -3,7 +3,11 @@ use fabro_util::run_log;
use tracing_appender::rolling;
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
pub fn init_tracing(debug: bool, config_log_level: Option<&str>, log_prefix: &str) -> Result<()> {
pub(crate) fn init_tracing(
debug: bool,
config_log_level: Option<&str>,
log_prefix: &str,
) -> Result<()> {
let default_level = if debug {
"debug"
} else {

View file

@ -1,3 +1,5 @@
#![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)]
mod args;
mod cli_config;
mod commands;
@ -220,7 +222,7 @@ async fn main_inner() -> (String, Result<()>) {
result?;
}
Commands::SendPanic { path } => {
let result = tel_panic::capture(&path).await;
let result = tel_panic::capture(&path);
let _ = std::fs::remove_file(&path);
result?;
}

View file

@ -6,12 +6,12 @@ use cli_table::Color;
use fabro_util::terminal::Styles;
use fabro_validate::{Diagnostic, Severity};
pub fn read_workflow_file(path: &Path) -> anyhow::Result<String> {
pub(crate) fn read_workflow_file(path: &Path) -> anyhow::Result<String> {
std::fs::read_to_string(path)
.map_err(|e| anyhow::anyhow!("Failed to read {}: {e}", path.display()))
}
pub fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) {
pub(crate) fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) {
for d in diagnostics {
let location = match (&d.node_id, &d.edge) {
(Some(node), _) => format!(" [node: {node}]"),
@ -41,7 +41,7 @@ pub fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) {
}
}
pub fn relative_path(path: &Path) -> String {
pub(crate) fn relative_path(path: &Path) -> String {
if let Ok(cwd) = std::env::current_dir() {
if let Ok(rel) = path.strip_prefix(&cwd) {
return rel.display().to_string();
@ -50,7 +50,7 @@ pub fn relative_path(path: &Path) -> String {
tilde_path(path)
}
pub fn format_tokens_human(tokens: i64) -> String {
pub(crate) fn format_tokens_human(tokens: i64) -> String {
if tokens >= 1_000_000 {
format!("{:.1}m", tokens as f64 / 1_000_000.0)
} else if tokens >= 1000 {
@ -60,7 +60,7 @@ pub fn format_tokens_human(tokens: i64) -> String {
}
}
pub fn tilde_path(path: &Path) -> String {
pub(crate) fn tilde_path(path: &Path) -> String {
if let Some(home) = dirs::home_dir() {
if let Ok(suffix) = path.strip_prefix(&home) {
return format!("~/{}", suffix.display());
@ -69,18 +69,18 @@ pub fn tilde_path(path: &Path) -> String {
path.display().to_string()
}
pub fn color_if(use_color: bool, color: Color) -> Option<Color> {
pub(crate) fn color_if(use_color: bool, color: Color) -> Option<Color> {
if use_color { Some(color) } else { None }
}
pub fn split_run_path(s: &str) -> Option<(&str, &str)> {
pub(crate) fn split_run_path(s: &str) -> Option<(&str, &str)> {
if s.starts_with('/') || s.starts_with("./") || s.starts_with("../") {
return None;
}
s.split_once(':')
}
pub fn validate_daytona_provider(
pub(crate) fn validate_daytona_provider(
record: &fabro_sandbox::SandboxRecord,
feature: &str,
) -> Result<()> {
@ -93,7 +93,7 @@ pub fn validate_daytona_provider(
Ok(())
}
pub fn format_duration_ms(ms: u64) -> String {
pub(crate) fn format_duration_ms(ms: u64) -> String {
let duration = Duration::from_millis(ms);
let secs = duration.as_secs();
if secs >= 60 {
@ -105,7 +105,7 @@ pub fn format_duration_ms(ms: u64) -> String {
}
}
pub fn format_size(bytes: u64) -> String {
pub(crate) fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = 1024 * KB;
const GB: u64 = 1024 * MB;

View file

@ -1,3 +1,4 @@
use std::fmt::Write;
use std::path::{Path, PathBuf};
use anyhow::{Context, bail};
@ -265,7 +266,7 @@ fn resolve_workflow_arg_impl(
available.join(", ")
);
if let Some(suggestion) = find_closest_match(&name, &available) {
msg.push_str(&format!("\n\nDid you mean '{suggestion}'?"));
let _ = write!(msg, "\n\nDid you mean '{suggestion}'?");
}
bail!("{msg}");
}

View file

@ -202,7 +202,7 @@ impl<G: Graph + 'static> Executor<G> {
.await?;
// Determine next step
let last_outcome = state.node_outcomes.get(node.id()).unwrap();
let last_outcome = &state.node_outcomes[node.id()];
let next = self
.resolve_next_step(&node, last_outcome, &state, graph)
.await?;
@ -369,40 +369,37 @@ impl<G: Graph + 'static> Executor<G> {
}
// Normal edge selection
match graph.select_edge(node, outcome, &state.context) {
Some(selection) => {
let target = selection.edge.target().to_string();
let is_restart = selection.edge.is_loop_restart();
if let Some(selection) = graph.select_edge(node, outcome, &state.context) {
let target = selection.edge.target().to_string();
let is_restart = selection.edge.is_loop_restart();
let ctx = EdgeContext {
from: node.id(),
to: &target,
edge: Some(selection.edge.clone()),
is_jump: false,
outcome,
reason: selection.reason,
};
match self.lifecycle.on_edge_selected(&ctx, state).await? {
EdgeDecision::Continue => {
if is_restart {
Ok(NextStep::LoopRestart(target))
} else {
Ok(NextStep::Edge(target))
}
}
EdgeDecision::Override(new_target) => Ok(NextStep::Edge(new_target)),
EdgeDecision::Block(msg) => Err(CoreError::blocked(msg)),
}
}
None => {
// No edge found
if outcome.status == StageStatus::Fail {
if let Some(retry_target) = graph.get_retry_target(node.id()) {
return Ok(NextStep::Edge(retry_target));
let ctx = EdgeContext {
from: node.id(),
to: &target,
edge: Some(selection.edge.clone()),
is_jump: false,
outcome,
reason: selection.reason,
};
match self.lifecycle.on_edge_selected(&ctx, state).await? {
EdgeDecision::Continue => {
if is_restart {
Ok(NextStep::LoopRestart(target))
} else {
Ok(NextStep::Edge(target))
}
}
Ok(NextStep::End)
EdgeDecision::Override(new_target) => Ok(NextStep::Edge(new_target)),
EdgeDecision::Block(msg) => Err(CoreError::blocked(msg)),
}
} else {
// No edge found
if outcome.status == StageStatus::Fail {
if let Some(retry_target) = graph.get_retry_target(node.id()) {
return Ok(NextStep::Edge(retry_target));
}
}
Ok(NextStep::End)
}
}
}

View file

@ -57,7 +57,7 @@ impl StallWatchdog {
let handle = tokio::spawn(async move {
loop {
tokio::select! {
_ = sleep(timeout) => {
() = sleep(timeout) => {
if shutdown.load(Ordering::Relaxed) {
return;
}
@ -69,12 +69,11 @@ impl StallWatchdog {
cancel_token.store(true, Ordering::Relaxed);
return;
}
_ = activity.notified() => {
() = activity.notified() => {
if shutdown.load(Ordering::Relaxed) {
return;
}
// Activity reported, restart the timer
continue;
}
}
}

View file

@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
/// Extracted configuration from a Docker Compose service.
#[derive(Debug, Clone, Default)]
pub struct ComposeServiceConfig {
pub(crate) struct ComposeServiceConfig {
pub image: Option<String>,
pub build: Option<ComposeBuild>,
pub ports: Vec<u16>,
@ -13,13 +13,13 @@ pub struct ComposeServiceConfig {
/// Build configuration from a Docker Compose service.
#[derive(Debug, Clone)]
pub struct ComposeBuild {
pub(crate) struct ComposeBuild {
pub context: String,
pub dockerfile: Option<String>,
}
/// Parse a Docker Compose file and extract config for the named service.
pub fn parse_compose(
pub(crate) fn parse_compose(
compose_path: &Path,
service_name: &str,
) -> Result<ComposeServiceConfig, String> {
@ -155,7 +155,7 @@ fn parse_environment(service: &serde_yaml::Value) -> HashMap<String, String> {
/// Parse multiple Docker Compose files and merge config for the named service.
/// Later files override earlier files for image/build/user; ports accumulate (deduped);
/// environment keys from later files override earlier ones.
pub fn parse_compose_multi(
pub(crate) fn parse_compose_multi(
compose_paths: &[PathBuf],
service_name: &str,
) -> Result<ComposeServiceConfig, String> {

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
use crate::features::FeatureLayer;
/// Generate a combined Dockerfile from base + features + env + user.
pub fn generate(
pub(crate) fn generate(
base_dockerfile: &str,
feature_layers: &[FeatureLayer],
container_env: &HashMap<String, String>,

View file

@ -1,4 +1,5 @@
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt::Write;
use std::path::Path;
use tokio::fs;
@ -10,7 +11,7 @@ use crate::types::{FeatureMetadata, LifecycleCommand};
/// A resolved feature layer ready to be inserted into a Dockerfile.
#[derive(Debug, Clone)]
pub struct FeatureLayer {
pub(crate) struct FeatureLayer {
/// Feature identifier (e.g. "ghcr.io/devcontainers/features/node:1")
pub id: String,
/// Directory name for COPY
@ -21,7 +22,7 @@ pub struct FeatureLayer {
/// All resolved feature data: layers, environment, and lifecycle hooks.
#[derive(Debug, Clone, Default)]
pub struct ResolvedFeatures {
pub(crate) struct ResolvedFeatures {
pub layers: Vec<FeatureLayer>,
pub container_env: HashMap<String, String>,
pub on_create_commands: Vec<LifecycleCommand>,
@ -133,7 +134,10 @@ async fn find_tgz(dir: &Path) -> Option<String> {
let mut entries = fs::read_dir(dir).await.ok()?;
while let Ok(Some(entry)) = entries.next_entry().await {
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(".tgz") {
if Path::new(name)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("tgz"))
{
return Some(name.to_string());
}
}
@ -332,7 +336,10 @@ fn topo_sort(
return Vec::new();
}
let id_set: HashSet<&str> = feature_ids.iter().map(|s| s.as_str()).collect();
let id_set: HashSet<&str> = feature_ids
.iter()
.map(std::string::String::as_str)
.collect();
// Build adjacency list and in-degree count.
// An edge from A -> B means "A must be installed before B".
@ -350,7 +357,11 @@ fn topo_sort(
for id in feature_ids {
if let Some(meta) = metadata_map.get(id) {
// Collect dependency refs from both installsAfter and dependsOn
let mut dep_refs: Vec<&str> = meta.installs_after.iter().map(|s| s.as_str()).collect();
let mut dep_refs: Vec<&str> = meta
.installs_after
.iter()
.map(std::string::String::as_str)
.collect();
for dep_id in meta.depends_on.keys() {
dep_refs.push(dep_id.as_str());
}
@ -511,12 +522,14 @@ fn generate_layer(
}
let mut snippet = format!("# Feature: {feature_id}\n");
snippet.push_str(&format!(
"COPY {dir_name}/ /tmp/devcontainer-features/{dir_name}/\n"
));
snippet.push_str(&format!(
"RUN cd /tmp/devcontainer-features/{dir_name} && \\\n"
));
let _ = writeln!(
snippet,
"COPY {dir_name}/ /tmp/devcontainer-features/{dir_name}/"
);
let _ = writeln!(
snippet,
"RUN cd /tmp/devcontainer-features/{dir_name} && \\"
);
for line in &env_lines {
snippet.push_str(line);
snippet.push('\n');
@ -528,7 +541,7 @@ fn generate_layer(
}
/// Fetch, order, and resolve features into Dockerfile layers.
pub async fn resolve_features(
pub(crate) async fn resolve_features(
features: &HashMap<String, serde_json::Value>,
devcontainer_dir: &Path,
remote_user: Option<&str>,

View file

@ -1,5 +1,5 @@
/// Strip JSONC comments and trailing commas, producing valid JSON.
pub fn strip_jsonc(input: &str) -> String {
pub(crate) fn strip_jsonc(input: &str) -> String {
let mut out = String::with_capacity(input.len());
let bytes = input.as_bytes();
let len = bytes.len();

View file

@ -252,20 +252,26 @@ impl DevcontainerResolver {
build_args: HashMap::new(),
build_target: None,
initialize_commands: Self::collect_commands(
&devcontainer.initialize_command,
devcontainer.initialize_command.as_ref(),
&vars,
),
on_create_commands: Self::collect_commands(
devcontainer.on_create_command.as_ref(),
&vars,
),
on_create_commands: Self::collect_commands(&devcontainer.on_create_command, &vars),
post_create_commands: Self::collect_commands(
&devcontainer.post_create_command,
devcontainer.post_create_command.as_ref(),
&vars,
),
post_start_commands: Self::collect_commands(
&devcontainer.post_start_command,
devcontainer.post_start_command.as_ref(),
&vars,
),
environment,
container_env: Self::collect_container_env(&devcontainer.container_env, &vars),
container_env: Self::collect_container_env(
devcontainer.container_env.as_ref(),
&vars,
),
remote_user: devcontainer.remote_user.clone().or(compose_config.user),
workspace_folder,
forwarded_ports: {
@ -362,11 +368,12 @@ impl DevcontainerResolver {
let forwarded_ports = Self::parse_forward_ports(&devcontainer.forward_ports);
// Collect devcontainer.json lifecycle commands, then append feature lifecycle commands
let mut on_create_commands = Self::collect_commands(&devcontainer.on_create_command, &vars);
let mut on_create_commands =
Self::collect_commands(devcontainer.on_create_command.as_ref(), &vars);
let mut post_create_commands =
Self::collect_commands(&devcontainer.post_create_command, &vars);
Self::collect_commands(devcontainer.post_create_command.as_ref(), &vars);
let mut post_start_commands =
Self::collect_commands(&devcontainer.post_start_command, &vars);
Self::collect_commands(devcontainer.post_start_command.as_ref(), &vars);
for cmd in &resolved_features.on_create_commands {
on_create_commands.push(Self::convert_lifecycle_command(cmd));
@ -383,7 +390,10 @@ impl DevcontainerResolver {
build_context,
build_args,
build_target,
initialize_commands: Self::collect_commands(&devcontainer.initialize_command, &vars),
initialize_commands: Self::collect_commands(
devcontainer.initialize_command.as_ref(),
&vars,
),
on_create_commands,
post_create_commands,
post_start_commands,
@ -438,7 +448,7 @@ impl DevcontainerResolver {
path: devcontainer_dir.clone(),
source,
})?
.filter_map(|entry| entry.ok())
.filter_map(std::result::Result::ok)
.filter(|entry| entry.path().is_dir())
.map(|entry| entry.path())
.filter(|dir| dir.join("devcontainer.json").exists())
@ -487,7 +497,7 @@ impl DevcontainerResolver {
}
fn collect_container_env(
env: &Option<HashMap<String, String>>,
env: Option<&HashMap<String, String>>,
vars: &variables::VariableContext,
) -> HashMap<String, String> {
match env {
@ -508,7 +518,7 @@ impl DevcontainerResolver {
}
fn collect_commands(
cmd: &Option<types::LifecycleCommand>,
cmd: Option<&types::LifecycleCommand>,
vars: &variables::VariableContext,
) -> Vec<Command> {
match cmd {

View file

@ -107,7 +107,7 @@ pub enum LifecycleCommand {
/// Metadata from a devcontainer-feature.json file.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeatureMetadata {
pub(crate) struct FeatureMetadata {
pub id: Option<String>,
pub name: Option<String>,
pub version: Option<String>,
@ -135,7 +135,7 @@ pub struct FeatureMetadata {
/// A single option for a devcontainer feature.
#[derive(Debug, Clone, Deserialize)]
pub struct FeatureOption {
pub(crate) struct FeatureOption {
#[serde(rename = "type")]
pub option_type: Option<String>,
pub default: Option<serde_json::Value>,

View file

@ -1,7 +1,7 @@
use fabro_util::env::Env;
/// Context for variable substitution.
pub struct VariableContext<'a> {
pub(crate) struct VariableContext<'a> {
pub local_workspace_folder: String,
pub local_workspace_folder_basename: String,
pub container_workspace_folder: String,
@ -9,7 +9,7 @@ pub struct VariableContext<'a> {
}
/// Replace devcontainer variables in a string value.
pub fn substitute(input: &str, ctx: &VariableContext) -> String {
pub(crate) fn substitute(input: &str, ctx: &VariableContext) -> String {
let mut result = String::with_capacity(input.len());
let mut rest = input;
@ -24,7 +24,7 @@ pub fn substitute(input: &str, ctx: &VariableContext) -> String {
Some(val) => result.push_str(&val),
None => {
// Unknown variable — leave as-is
result.push_str(&rest[start..start + 2 + close + 1]);
result.push_str(&rest[start..=(start + 2 + close)]);
}
}
rest = &after_open[close + 1..];

View file

@ -115,9 +115,8 @@ impl<'a> BranchStore<'a> {
/// Read a single file from the latest tree. Returns `None` if branch or path doesn't exist.
pub fn read_entry(&self, path: &str) -> Result<Option<Vec<u8>>> {
let commit_oid = match self.objects.resolve_ref(&self.branch)? {
Some(oid) => oid,
None => return Ok(None),
let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else {
return Ok(None);
};
let commit = self.objects.repo().find_commit(commit_oid)?;
let tree = commit.tree()?;
@ -134,9 +133,8 @@ impl<'a> BranchStore<'a> {
/// Read multiple paths. Missing paths are omitted from the result.
pub fn read_entries<'b>(&self, paths: &[&'b str]) -> Result<Vec<(&'b str, Vec<u8>)>> {
let commit_oid = match self.objects.resolve_ref(&self.branch)? {
Some(oid) => oid,
None => return Ok(vec![]),
let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else {
return Ok(vec![]);
};
let commit = self.objects.repo().find_commit(commit_oid)?;
let tree = commit.tree()?;
@ -157,9 +155,8 @@ impl<'a> BranchStore<'a> {
/// List all paths under a prefix in the latest tree.
pub fn list_entries(&self, prefix: &str) -> Result<Vec<String>> {
let commit_oid = match self.objects.resolve_ref(&self.branch)? {
Some(oid) => oid,
None => return Ok(vec![]),
let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else {
return Ok(vec![]);
};
let commit = self.objects.repo().find_commit(commit_oid)?;
let tree_oid = commit.tree_id();
@ -184,9 +181,8 @@ impl<'a> BranchStore<'a> {
/// Walk commits on the branch, newest first.
pub fn log(&self, limit: usize) -> Result<Vec<CommitInfo>> {
let commit_oid = match self.objects.resolve_ref(&self.branch)? {
Some(oid) => oid,
None => return Ok(vec![]),
let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else {
return Ok(vec![]);
};
let mut revwalk = self.objects.repo().revwalk()?;
revwalk.set_sorting(git2::Sort::TIME | git2::Sort::TOPOLOGICAL)?;

View file

@ -16,17 +16,17 @@ pub enum FileMode {
impl FileMode {
fn as_i32(self) -> i32 {
match self {
FileMode::Blob => 0o100644,
FileMode::BlobExecutable => 0o100755,
FileMode::Tree => 0o040000,
Self::Blob => 0o100644,
Self::BlobExecutable => 0o100755,
Self::Tree => 0o040000,
}
}
fn from_i32(mode: i32) -> Self {
match mode {
0o100755 => FileMode::BlobExecutable,
0o040000 => FileMode::Tree,
_ => FileMode::Blob,
0o100755 => Self::BlobExecutable,
0o040000 => Self::Tree,
_ => Self::Blob,
}
}
}
@ -62,7 +62,7 @@ impl TreeEntries {
self.0.get(path)
}
pub fn merge(&mut self, other: &TreeEntries) {
pub fn merge(&mut self, other: &Self) {
for (path, entry) in &other.0 {
self.0.insert(path.clone(), entry.clone());
}
@ -224,7 +224,7 @@ fn read_tree_recursive(
prefix: &str,
entries: &mut TreeEntries,
) -> Result<()> {
for entry in tree.iter() {
for entry in tree {
let name = entry.name().unwrap_or("");
let path = if prefix.is_empty() {
name.to_string()
@ -246,7 +246,7 @@ fn read_tree_recursive(
/// Intermediate structure for building nested git trees from flat paths.
struct DirNode {
files: BTreeMap<String, TreeEntry>,
dirs: BTreeMap<String, DirNode>,
dirs: BTreeMap<String, Self>,
}
impl DirNode {

View file

@ -141,9 +141,8 @@ impl<'a> SnapshotStore<'a> {
/// Tip commit of a snapshot branch. `None` if branch doesn't exist.
pub fn latest(&self, branch: &str) -> Result<Option<SnapshotInfo>> {
let commit_oid = match self.objects.resolve_ref(branch)? {
Some(oid) => oid,
None => return Ok(None),
let Some(commit_oid) = self.objects.resolve_ref(branch)? else {
return Ok(None);
};
let commit = self.objects.repo().find_commit(commit_oid)?;
let tree_oid = commit.tree_id();
@ -173,9 +172,8 @@ impl<'a> SnapshotStore<'a> {
/// Walk commits on a snapshot branch, newest first.
pub fn list_commits(&self, branch: &str, limit: usize) -> Result<Vec<SnapshotInfo>> {
let commit_oid = match self.objects.resolve_ref(branch)? {
Some(oid) => oid,
None => return Ok(vec![]),
let Some(commit_oid) = self.objects.resolve_ref(branch)? else {
return Ok(vec![]);
};
let mut revwalk = self.objects.repo().revwalk()?;
revwalk.set_sorting(git2::Sort::TIME | git2::Sort::TOPOLOGICAL)?;
@ -245,7 +243,7 @@ impl<'a> SnapshotStore<'a> {
let walker = walkdir::WalkDir::new(&disk_dir.disk_path)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok());
.filter_map(std::result::Result::ok);
for entry in walker {
// Skip symlinks

Some files were not shown because too many files have changed in this diff Show more