diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..7eb9fd64 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-08-02 - [Cow Optimization for String Processing] +**Learning:** In Rust CLI applications processing potentially mixed-case arguments, `str::to_lowercase()` always eagerly allocates a `String` heap buffer. If the expected valid inputs are short and typically lowercase (like known agent names), this represents a 100% redundant allocation penalty. +**Action:** Use `std::borrow::Cow` combined with `.chars().any(|c| c.is_uppercase())` to skip allocation entirely for already-lowercase input, and return `Cow::Borrowed` literals when mapping to known values. diff --git a/crates/forktty-ui-gtk/src/socket_cli/hooks.rs b/crates/forktty-ui-gtk/src/socket_cli/hooks.rs index 6092e761..05c82e9b 100644 --- a/crates/forktty-ui-gtk/src/socket_cli/hooks.rs +++ b/crates/forktty-ui-gtk/src/socket_cli/hooks.rs @@ -930,7 +930,7 @@ pub(super) fn single_agent_command( let Some(agent) = args.first() else { return Err(CliError::new(format!("{command} requires an agent"))); }; - let normalized = normalize_agent_name(agent); + let normalized = normalize_agent_name(agent).into_owned(); let spec = agent_spec(&normalized) .ok_or_else(|| CliError::new(format!("Unsupported {command} agent: {agent}")))?; Ok((spec, args[1..].to_vec())) diff --git a/crates/forktty-ui-gtk/src/socket_cli/hooks/event.rs b/crates/forktty-ui-gtk/src/socket_cli/hooks/event.rs index 24915361..cb0e0a2d 100644 --- a/crates/forktty-ui-gtk/src/socket_cli/hooks/event.rs +++ b/crates/forktty-ui-gtk/src/socket_cli/hooks/event.rs @@ -35,7 +35,7 @@ pub(in crate::socket_cli) fn handle_hook_event( ) -> CliResult<()> { let agent_name = args .first() - .map(|value| normalize_agent_name(value)) + .map(|value| normalize_agent_name(value).into_owned()) .unwrap_or_default(); let event = args .get(1) diff --git a/crates/forktty-ui-gtk/src/socket_cli/hooks/install.rs b/crates/forktty-ui-gtk/src/socket_cli/hooks/install.rs index 9dee75d3..ddcf4e92 100644 --- a/crates/forktty-ui-gtk/src/socket_cli/hooks/install.rs +++ b/crates/forktty-ui-gtk/src/socket_cli/hooks/install.rs @@ -270,12 +270,19 @@ pub(in crate::socket_cli) fn agent_spec(agent: &str) -> Option<&'static AgentSpe AGENTS.iter().find(|spec| spec.key == agent) } -pub(in crate::socket_cli) fn normalize_agent_name(agent: &str) -> String { - match agent.to_lowercase().as_str() { - "claude-code" | "claude_code" => "claude".to_string(), - "open-code" | "open_code" => "opencode".to_string(), - "agy" => "antigravity".to_string(), - other => other.to_string(), +pub(in crate::socket_cli) fn normalize_agent_name(agent: &str) -> std::borrow::Cow<'_, str> { + // Optimization: Avoid eager allocation by checking if the string is already lowercase. + let lowercased = if agent.chars().any(|c| c.is_uppercase()) { + std::borrow::Cow::Owned(agent.to_lowercase()) + } else { + std::borrow::Cow::Borrowed(agent) + }; + + match lowercased.as_ref() { + "claude-code" | "claude_code" => std::borrow::Cow::Borrowed("claude"), + "open-code" | "open_code" => std::borrow::Cow::Borrowed("opencode"), + "agy" => std::borrow::Cow::Borrowed("antigravity"), + _ => lowercased, } }