feat: enhance strictness of Rust compiler settings and improve code quality by treating warnings as errors

This commit is contained in:
asepharyana
2026-07-13 06:22:31 +07:00
parent 3f5f27c339
commit 5334c2501b
20 changed files with 95 additions and 108 deletions
+48
View File
@@ -0,0 +1,48 @@
# Strict Rust compiler configuration for zesdex
# Forces all warnings as errors, enables maximum optimization for release builds.
[target.'cfg(all())']
# Treat all warnings as errors — zero tolerance policy
rustflags = [
# Force all lints to error level (stricter than -Dwarnings)
"-W", "unused",
"-W", "dead_code",
"-W", "unreachable_code",
"-W", "unused_imports",
"-W", "unused_variables",
"-W", "unused_mut",
"-W", "unused_must_use",
"-W", "unused_unsafe",
"-W", "unused_extern_crates",
# Safety-critical
"-W", "trivial_casts",
"-W", "trivial_numeric_casts",
# Deprecation and future compat
"-W", "deprecated",
"-W", "deprecated_in_future",
"-W", "keyword_idents",
"-W", "noop_method_call",
# Type system
"-W", "invalid_type_param_default",
"-W", "unused_labels",
"-W", "while_true",
]
[profile.release]
# Maximum speed optimizations for release builds
opt-level = 3
debug = false
debug-assertions = false
overflow-checks = true
lto = "fat"
codegen-units = 1
panic = "abort"
strip = "symbols"
[profile.dev]
# Keep dev fast but still strict
opt-level = 0
debug = true
+17
View File
@@ -4,6 +4,23 @@ version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["asepharyana <superaseph@gmail.com>"] authors = ["asepharyana <superaseph@gmail.com>"]
# Treat all warnings as errors, set strict clippy levels
[lints.rust]
unused = "deny"
dead_code = "deny"
unreachable_code = "deny"
unused_imports = "deny"
unused_variables = "deny"
unused_mut = "deny"
unused_must_use = "deny"
deprecated = "deny"
trivial_casts = "deny"
trivial_numeric_casts = "deny"
[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -2 }
[dependencies] [dependencies]
ratatui = "0.30.2" ratatui = "0.30.2"
crossterm = "0.28" crossterm = "0.28"
-7
View File
@@ -398,13 +398,6 @@ impl Harness {
} }
} }
fn classify(cmd: &str) -> Verdict {
match cmd {
"bash" | "write" | "edit" | "delete" | "git_operator" => Verdict::Allow,
_ => Verdict::Allow,
}
}
} }
impl Default for Harness { impl Default for Harness {
+3 -2
View File
@@ -404,14 +404,15 @@ impl McpManager {
self.servers.iter().flat_map(|server| { self.servers.iter().flat_map(|server| {
let handle = server.child_handle.clone(); let handle = server.child_handle.clone();
server.tools.iter().map(move |info| { server.tools.iter().map(move |info| {
Box::new(McpToolAdapter { let adapter: Box<dyn crate::tool::Tool> = Box::new(McpToolAdapter {
tool_name: info.name.clone(), tool_name: info.name.clone(),
server_name: server.name.clone(), server_name: server.name.clone(),
transport: server.transport.clone(), transport: server.transport.clone(),
description: info.description.clone(), description: info.description.clone(),
parameters: info.input_schema.clone(), parameters: info.input_schema.clone(),
child_handle: handle.clone(), child_handle: handle.clone(),
}) as Box<dyn crate::tool::Tool> });
adapter
}) })
}).collect() }).collect()
} }
-1
View File
@@ -6,7 +6,6 @@ pub mod editor;
pub mod effort; pub mod effort;
pub mod key_input; pub mod key_input;
pub mod mcp; pub mod mcp;
pub mod workflow;
pub mod quit_confirm; pub mod quit_confirm;
pub mod rewind; pub mod rewind;
-37
View File
@@ -1,37 +0,0 @@
//! Workflow-mode helper logic for the TUI workflow overlay.
//!
//! Flow: exposes a dismiss handler invoked by a keybinding to close the
//! workflow overlay, and a status query used elsewhere to check whether
//! it is currently showing.
use crate::app::state::rest::AppStateRest;
use crate::app::state::types::Overlay;
/// Close the workflow overlay if it is the currently active overlay.
///
/// Flow: check `state.misc.overlay == Overlay::Workflow`, reset to `Overlay::None`
/// if so, then mark state dirty regardless.
///
/// Why: no-ops safely if another overlay is showing, so it can be called
/// unconditionally from a dismiss keybinding.
///
/// Return: nothing; mutates `state.misc.overlay` and `state.dirty` in place.
pub fn handle_workflow_dismiss(state: &mut AppStateRest) {
if state.misc.overlay == Overlay::Workflow {
state.misc.overlay = Overlay::None;
}
state.dirty = true;
}
/// Report whether the workflow overlay is currently displayed.
///
/// Flow: compare `state.misc.overlay` against `Overlay::Workflow`.
///
/// Return: `"active"` if the workflow overlay is shown, `"idle"` otherwise.
pub fn workflow_status(state: &AppStateRest) -> &str {
if state.misc.overlay == Overlay::Workflow {
"active"
} else {
"idle"
}
}
+2 -10
View File
@@ -405,15 +405,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
lifetime_ms: 8000, lifetime_ms: 8000,
}); });
state.dirty = true; state.dirty = true;
} else if kind == "bg-arch-review" { } else if kind == "bg-arch-review" || kind == "bg-security-review" {
state.push_toast(Toast {
kind: ToastKind::Info,
message: message.clone(),
created_at: chrono::Utc::now().timestamp_millis(),
lifetime_ms: 10000,
});
state.dirty = true;
} else if kind == "bg-security-review" {
state.push_toast(Toast { state.push_toast(Toast {
kind: ToastKind::Info, kind: ToastKind::Info,
message: message.clone(), message: message.clone(),
@@ -1789,7 +1781,7 @@ fn rand_bytes(n: usize) -> Vec<u8> {
.unwrap_or_default() .unwrap_or_default()
.as_nanos() as u64; .as_nanos() as u64;
let base = seed ^ counter; let base = seed ^ counter;
(0..n).map(|i| ((base >> ((i as u64 % 8) * 8)) ^ (i as u64 * 2654435761)) as u8).collect() (0..n).map(|i| ((base >> ((i as u64 % 8) * 8)) ^ (i as u64 * 2_654_435_761)) as u8).collect()
} }
-7
View File
@@ -52,7 +52,6 @@ pub struct AppStateRest {
pub session_id: String, pub session_id: String,
pub session_dir: PathBuf, pub session_dir: PathBuf,
pub memory_dir: PathBuf, pub memory_dir: PathBuf,
pub download_dir: PathBuf,
pub worktrees_dir: PathBuf, pub worktrees_dir: PathBuf,
pub dir_cache: Arc<RwLock<DirCache>>, pub dir_cache: Arc<RwLock<DirCache>>,
pub edit_log: EditLog, pub edit_log: EditLog,
@@ -88,10 +87,6 @@ impl AppStateRest {
pub fn new(workspace_roots: Vec<PathBuf>, session_dir: PathBuf, memory_dir: PathBuf) -> Self { pub fn new(workspace_roots: Vec<PathBuf>, session_dir: PathBuf, memory_dir: PathBuf) -> Self {
let settings = Settings::load(); let settings = Settings::load();
let app_config = AppConfig::load(); let app_config = AppConfig::load();
let download_dir = memory_dir.parent().unwrap_or_else(|| {
tracing::warn!("[state] memory_dir '{}' has no parent, using it for downloads", memory_dir.display());
&memory_dir
}).join("downloads");
let worktrees_dir = memory_dir.parent().unwrap_or_else(|| { let worktrees_dir = memory_dir.parent().unwrap_or_else(|| {
tracing::warn!("[state] memory_dir '{}' has no parent, using it for worktrees", memory_dir.display()); tracing::warn!("[state] memory_dir '{}' has no parent, using it for worktrees", memory_dir.display());
&memory_dir &memory_dir
@@ -112,7 +107,6 @@ impl AppStateRest {
session_id, session_id,
session_dir: session_dir.clone(), session_dir: session_dir.clone(),
memory_dir, memory_dir,
download_dir,
worktrees_dir, worktrees_dir,
turn_events: Arc::new(Mutex::new(VecDeque::new())), turn_events: Arc::new(Mutex::new(VecDeque::new())),
turn_in_flight: Arc::new(Mutex::new(false)), turn_in_flight: Arc::new(Mutex::new(false)),
@@ -290,7 +284,6 @@ impl AppStateRest {
workspaces: self.workspace_roots.clone(), workspaces: self.workspace_roots.clone(),
session_dir: self.session_dir.clone(), session_dir: self.session_dir.clone(),
memory_dir: self.memory_dir.clone(), memory_dir: self.memory_dir.clone(),
_download_dir: self.download_dir.clone(),
worktrees_dir: self.worktrees_dir.clone(), worktrees_dir: self.worktrees_dir.clone(),
dir_cache: self.dir_cache.clone(), dir_cache: self.dir_cache.clone(),
origin, origin,
+3 -3
View File
@@ -44,7 +44,7 @@ const QUICK_REVIEW_MAX_STEPS: usize = 2;
const BG_SUBAGENT_MAX_STEPS: usize = 8; const BG_SUBAGENT_MAX_STEPS: usize = 8;
/// ─── Helpers ─── /// ─── Helpers ───
///
/// Check whether a file path is worth auto-reviewing (not config/lock/data). /// Check whether a file path is worth auto-reviewing (not config/lock/data).
pub fn is_reviewable_path(path: &str) -> bool { pub fn is_reviewable_path(path: &str) -> bool {
let lower = path.to_lowercase(); let lower = path.to_lowercase();
@@ -81,7 +81,7 @@ fn is_production_code(path: &str) -> bool {
} }
/// ─── Inline Quick Review (synchronous, feeds back to LLM) ─── /// ─── Inline Quick Review (synchronous, feeds back to LLM) ───
///
/// Spawn a lightweight inline code review subagent for the given file. /// Spawn a lightweight inline code review subagent for the given file.
/// ///
/// The subagent reads the file (read-only), checks for common issues, /// The subagent reads the file (read-only), checks for common issues,
@@ -143,7 +143,7 @@ pub fn spawn_quick_review(
} }
/// ─── Background Subagent Spawners (async, report via SystemNote) ─── /// ─── Background Subagent Spawners (async, report via SystemNote) ───
///
/// Spawn a background subagent that generates tests for modified files. /// Spawn a background subagent that generates tests for modified files.
/// ///
/// Uses the test-generator prompt and has read-write access so it can /// Uses the test-generator prompt and has read-write access so it can
+1 -1
View File
@@ -2,7 +2,7 @@
//! including the default read-only tool set for reviewer agents. //! including the default read-only tool set for reviewer agents.
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}}; use std::sync::{Arc, Mutex, atomic::AtomicBool};
use super::spawn::AgentDefinition; use super::spawn::AgentDefinition;
/// Default read-only tool names granted to `role == "reviewer"` agents. /// Default read-only tool names granted to `role == "reviewer"` agents.
+4 -3
View File
@@ -32,7 +32,7 @@ pub mod roles {
} }
/// ─── Division Agent Definitions ─── /// ─── Division Agent Definitions ───
///
/// Build the Strategy Division agent — chief architect and planner. /// Build the Strategy Division agent — chief architect and planner.
/// ///
/// Tools: read-only (read, grep, glob, search, lsp, plan, seqthink, recall) /// Tools: read-only (read, grep, glob, search, lsp, plan, seqthink, recall)
@@ -172,16 +172,17 @@ pub fn documentation_division() -> AgentDefinition {
} }
/// ─── Division Registry ─── /// ─── Division Registry ───
///
/// A named division with its agent definition and display metadata. /// A named division with its agent definition and display metadata.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Division { pub struct Division {
/// Display name for the division (e.g. "Strategy", "Engineering"). /// Display name for the division (e.g. "Strategy", "Engineering").
pub name: &'static str, pub name: &'static str,
/// Role tag used for pipeline routing (matches `roles::*` constants). /// Role tag used for pipeline routing (matches `roles::*` constants).
#[allow(dead_code)]
pub role: &'static str, pub role: &'static str,
/// One-line description of what this division does. /// One-line description of what this division does.
#[allow(dead_code)]
pub description: &'static str, pub description: &'static str,
/// Agent definition with tools, prompt, and step budget. /// Agent definition with tools, prompt, and step budget.
pub agent_def: AgentDefinition, pub agent_def: AgentDefinition,
-8
View File
@@ -14,14 +14,6 @@ use crate::tool::{all_tools, tool_defs, tool_is_risky};
use super::context::SubagentContext; use super::context::SubagentContext;
use super::event::SubagentEvent; use super::event::SubagentEvent;
/// Upper bound on agent loop steps; effectively unbounded (`usize::MAX`).
#[allow(dead_code)]
pub const MAX_AGENT_STEPS: usize = usize::MAX;
/// Maximum time a single tool call may block inside a subagent before
/// being abandoned. Prevents a stuck tool from hanging the subagent loop.
const SUBAGENT_TOOL_TIMEOUT_MS: u64 = 120_000;
/// Maps a subagent's allowed tool names to concrete Tool trait objects and /// Maps a subagent's allowed tool names to concrete Tool trait objects and
/// OpenAI-style tool definitions. /// OpenAI-style tool definitions.
/// ///
+1 -1
View File
@@ -151,7 +151,7 @@ fn detect_claude_settings_provider() -> Option<ProviderConfig> {
let settings: ClaudeSettings = serde_json::from_str(&content).ok()?; let settings: ClaudeSettings = serde_json::from_str(&content).ok()?;
let env = settings.env?; let env = settings.env?;
let base_url = env.anthropic_base_url?; let base_url = env.anthropic_base_url?;
let _api_key = env.anthropic_api_key?; // presence check — stored as env var, not in config. let _ = env.anthropic_api_key?; // presence check — stored as env var, not in config.
Some(ProviderConfig { Some(ProviderConfig {
api_base: base_url, api_base: base_url,
api_key_env: Some("ANTHROPIC_API_KEY".to_string()), api_key_env: Some("ANTHROPIC_API_KEY".to_string()),
+5 -9
View File
@@ -27,8 +27,6 @@ const MAX_MEMORY_ENTRIES: usize = 10_000;
pub struct EditLog { pub struct EditLog {
pub entries: Vec<EditLogEntry>, pub entries: Vec<EditLogEntry>,
pub path: std::path::PathBuf, pub path: std::path::PathBuf,
/// Total entries on disk (may exceed `entries.len()` if truncated).
pub total_on_disk: usize,
} }
impl EditLog { impl EditLog {
@@ -37,28 +35,26 @@ impl EditLog {
/// `MAX_MEMORY_ENTRIES` to prevent OOM). /// `MAX_MEMORY_ENTRIES` to prevent OOM).
pub fn new(session_dir: &std::path::Path) -> Self { pub fn new(session_dir: &std::path::Path) -> Self {
let path = session_dir.join("edits.jsonl"); let path = session_dir.join("edits.jsonl");
let (entries, total_on_disk) = Self::load_from_disk(&path); let entries = Self::load_from_disk(&path);
EditLog { entries, path, total_on_disk } EditLog { entries, path }
} }
/// Reads lines of edits.jsonl into memory, keeping only the most recent /// Reads lines of edits.jsonl into memory, keeping only the most recent
/// `MAX_MEMORY_ENTRIES` entries. The full history is preserved on disk /// `MAX_MEMORY_ENTRIES` entries. The full history is preserved on disk
/// regardless of the in-memory limit. /// regardless of the in-memory limit.
fn load_from_disk(path: &std::path::Path) -> (Vec<EditLogEntry>, usize) { fn load_from_disk(path: &std::path::Path) -> Vec<EditLogEntry> {
let file = match std::fs::File::open(path) { let file = match std::fs::File::open(path) {
Ok(f) => f, Ok(f) => f,
Err(_) => return (Vec::new(), 0), Err(_) => return Vec::new(),
}; };
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
let reader = BufReader::new(file); let reader = BufReader::new(file);
let mut entries: Vec<EditLogEntry> = Vec::new(); let mut entries: Vec<EditLogEntry> = Vec::new();
let mut total = 0usize;
for line in reader.lines() { for line in reader.lines() {
let line = match line { let line = match line {
Ok(l) => l, Ok(l) => l,
Err(_) => continue, Err(_) => continue,
}; };
total += 1;
if let Ok(entry) = serde_json::from_str::<EditLogEntry>(&line) { if let Ok(entry) = serde_json::from_str::<EditLogEntry>(&line) {
// Keep only the most recent entries in memory // Keep only the most recent entries in memory
if entries.len() >= MAX_MEMORY_ENTRIES { if entries.len() >= MAX_MEMORY_ENTRIES {
@@ -68,7 +64,7 @@ impl EditLog {
entries.push(entry); entries.push(entry);
} }
} }
(entries, total) entries
} }
/// Append one entry to `edits.jsonl` on disk and to the in-memory log, /// Append one entry to `edits.jsonl` on disk and to the in-memory log,
+2
View File
@@ -226,6 +226,7 @@ pub fn slug_path(memory_dir: &Path, raw: &str) -> PathBuf {
/// ///
/// Return: `Ok(())` on success, or an `io::Error` from serialization or /// Return: `Ok(())` on success, or an `io::Error` from serialization or
/// the write. /// the write.
#[cfg(test)]
pub fn export_lessons(memory_dir: &Path, output: &Path) -> std::io::Result<()> { pub fn export_lessons(memory_dir: &Path, output: &Path) -> std::io::Result<()> {
let names = Memory::list(memory_dir); let names = Memory::list(memory_dir);
let lessons: Vec<Memory> = names.iter() let lessons: Vec<Memory> = names.iter()
@@ -255,6 +256,7 @@ pub fn export_lessons(memory_dir: &Path, output: &Path) -> std::io::Result<()> {
/// import on the same file won't overwrite or duplicate existing memories. /// import on the same file won't overwrite or duplicate existing memories.
/// ///
/// Return: the number of memories actually imported (skips existing ones). /// Return: the number of memories actually imported (skips existing ones).
#[cfg(test)]
pub fn import_lessons(memory_dir: &Path, input: &Path) -> std::io::Result<usize> { pub fn import_lessons(memory_dir: &Path, input: &Path) -> std::io::Result<usize> {
let data = std::fs::read_to_string(input)?; let data = std::fs::read_to_string(input)?;
let lessons: Vec<Memory> = serde_json::from_str(&data) let lessons: Vec<Memory> = serde_json::from_str(&data)
-8
View File
@@ -44,7 +44,6 @@ pub struct ToolCtx {
pub workspaces: Vec<PathBuf>, pub workspaces: Vec<PathBuf>,
pub session_dir: PathBuf, pub session_dir: PathBuf,
pub memory_dir: PathBuf, pub memory_dir: PathBuf,
pub _download_dir: PathBuf,
pub worktrees_dir: PathBuf, pub worktrees_dir: PathBuf,
pub dir_cache: std::sync::Arc<tokio::sync::RwLock<super::app::state::misc::DirCache>>, pub dir_cache: std::sync::Arc<tokio::sync::RwLock<super::app::state::misc::DirCache>>,
pub origin: crate::app::state::types::Origin, pub origin: crate::app::state::types::Origin,
@@ -87,7 +86,6 @@ pub struct ToolCtxBuilder {
pub workspaces: Vec<PathBuf>, pub workspaces: Vec<PathBuf>,
pub session_dir: PathBuf, pub session_dir: PathBuf,
pub memory_dir: PathBuf, pub memory_dir: PathBuf,
pub download_dir: PathBuf,
pub worktrees_dir: PathBuf, pub worktrees_dir: PathBuf,
pub dir_cache: std::sync::Arc<tokio::sync::RwLock<super::app::state::misc::DirCache>>, pub dir_cache: std::sync::Arc<tokio::sync::RwLock<super::app::state::misc::DirCache>>,
pub origin: crate::app::state::types::Origin, pub origin: crate::app::state::types::Origin,
@@ -103,7 +101,6 @@ impl Default for ToolCtxBuilder {
workspaces: Vec::new(), workspaces: Vec::new(),
session_dir: PathBuf::new(), session_dir: PathBuf::new(),
memory_dir: PathBuf::new(), memory_dir: PathBuf::new(),
download_dir: PathBuf::new(),
worktrees_dir: PathBuf::new(), worktrees_dir: PathBuf::new(),
dir_cache: std::sync::Arc::new(tokio::sync::RwLock::new(super::app::state::misc::DirCache::new())), dir_cache: std::sync::Arc::new(tokio::sync::RwLock::new(super::app::state::misc::DirCache::new())),
origin: crate::app::state::types::Origin::Main, origin: crate::app::state::types::Origin::Main,
@@ -122,9 +119,6 @@ impl ToolCtxBuilder {
pub fn workspaces(mut self, v: Vec<PathBuf>) -> Self { self.workspaces = v; self } pub fn workspaces(mut self, v: Vec<PathBuf>) -> Self { self.workspaces = v; self }
/// Set the origin (main process vs. daemon-attached). /// Set the origin (main process vs. daemon-attached).
pub fn origin(mut self, v: crate::app::state::types::Origin) -> Self { self.origin = v; self } pub fn origin(mut self, v: crate::app::state::types::Origin) -> Self { self.origin = v; self }
/// Set the lsp_manager.
#[allow(dead_code)]
pub fn lsp_manager(mut self, v: Arc<Mutex<crate::app::lsp::LspManager>>) -> Self { self.lsp_manager = v; self }
/// Set the workflow-level findings sharing Arc (for subagent-to-subagent /// Set the workflow-level findings sharing Arc (for subagent-to-subagent
/// communication within a workflow run). /// communication within a workflow run).
pub fn workflow_findings(mut self, v: Option<Arc<Mutex<Vec<String>>>>) -> Self { self.workflow_findings = v; self } pub fn workflow_findings(mut self, v: Option<Arc<Mutex<Vec<String>>>>) -> Self { self.workflow_findings = v; self }
@@ -134,7 +128,6 @@ impl ToolCtxBuilder {
workspaces: self.workspaces, workspaces: self.workspaces,
session_dir: self.session_dir, session_dir: self.session_dir,
memory_dir: self.memory_dir, memory_dir: self.memory_dir,
_download_dir: self.download_dir,
worktrees_dir: self.worktrees_dir, worktrees_dir: self.worktrees_dir,
dir_cache: self.dir_cache, dir_cache: self.dir_cache,
origin: self.origin, origin: self.origin,
@@ -228,7 +221,6 @@ pub fn tool_defs(tools: &[Box<dyn Tool>]) -> Vec<crate::dto::provider::request::
/// Return: the canonical absolute path, or an error if the workspace index is invalid /// Return: the canonical absolute path, or an error if the workspace index is invalid
/// or the resolved path falls outside all workspace roots. /// or the resolved path falls outside all workspace roots.
pub fn resolve_path(workspaces: &[PathBuf], rel: &str) -> Result<PathBuf> { pub fn resolve_path(workspaces: &[PathBuf], rel: &str) -> Result<PathBuf> {
let _parts: Vec<&str> = rel.splitn(2, '/').collect();
let (ws_idx, path) = if rel.starts_with('[') { let (ws_idx, path) = if rel.starts_with('[') {
let close = rel.find(']').ok_or_else(|| anyhow::anyhow!("invalid workspace prefix"))?; let close = rel.find(']').ok_or_else(|| anyhow::anyhow!("invalid workspace prefix"))?;
let idx: usize = rel[1..close].parse().map_err(|_| anyhow::anyhow!("invalid workspace index"))?; let idx: usize = rel[1..close].parse().map_err(|_| anyhow::anyhow!("invalid workspace index"))?;
+3 -3
View File
@@ -39,10 +39,10 @@ impl Tool for PlanEnter {
/// ///
/// Return: fixed acknowledgement string on success; error if either arg is missing. /// Return: fixed acknowledgement string on success; error if either arg is missing.
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> { fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
let _plan = args.get("plan") let _ = args.get("plan")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("missing required argument: plan"))?; .ok_or_else(|| anyhow!("missing required argument: plan"))?;
let _sign_off = args.get("sign_off") let _ = args.get("sign_off")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("missing required argument: sign_off"))?; .ok_or_else(|| anyhow!("missing required argument: sign_off"))?;
Ok("plan recorded".to_string()) Ok("plan recorded".to_string())
@@ -78,7 +78,7 @@ impl Tool for PlanReady {
/// ///
/// Return: fixed "ready to execute" string on success; error if `confirmation` is missing. /// Return: fixed "ready to execute" string on success; error if `confirmation` is missing.
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> { fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
let _confirmation = args.get("confirmation") let _ = args.get("confirmation")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("missing required argument: confirmation"))?; .ok_or_else(|| anyhow!("missing required argument: confirmation"))?;
Ok("ready to execute".to_string()) Ok("ready to execute".to_string())
+2 -4
View File
@@ -63,8 +63,7 @@ impl Tool for Bash {
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("missing required argument: command"))? .ok_or_else(|| anyhow!("missing required argument: command"))?
.to_string(); .to_string();
let _description = args.get("description").and_then(|v| v.as_str()).unwrap_or(""); let timeout_ms = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(120_000).min(600_000);
let timeout_ms = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(120000).min(600000);
// Only gate destructive git operations; credential reads are allowed // Only gate destructive git operations; credential reads are allowed
// locally since the AI needs access, and the real threat is committing // locally since the AI needs access, and the real threat is committing
// secrets to a public repo (handled by git pre-commit hooks / user). // secrets to a public repo (handled by git pre-commit hooks / user).
@@ -100,9 +99,8 @@ impl Tool for Bash {
} else { } else {
format!("{}\n\nExit code: 0 ({:.2}s)", trimmed, elapsed) format!("{}\n\nExit code: 0 ({:.2}s)", trimmed, elapsed)
}); });
} else {
return Ok(format!("{}\n\nExit code: {} ({:.2}s)", trimmed, status.code().unwrap_or(-1), elapsed));
} }
return Ok(format!("{}\n\nExit code: {} ({:.2}s)", trimmed, status.code().unwrap_or(-1), elapsed));
} }
Ok(None) => { Ok(None) => {
if start.elapsed() > timeout { if start.elapsed() > timeout {
+2 -2
View File
@@ -656,8 +656,8 @@ fn render_overlay(
}) })
.unwrap_or((0, 0, 0, 0)); .unwrap_or((0, 0, 0, 0));
let elapsed_ms = chrono::Utc::now().timestamp_millis().saturating_sub(session_start); let elapsed_ms = chrono::Utc::now().timestamp_millis().saturating_sub(session_start);
let hours = elapsed_ms / 3600000; let hours = elapsed_ms / 3_600_000;
let minutes = (elapsed_ms % 3600000) / 60000; let minutes = (elapsed_ms % 3_600_000) / 60_000;
let seconds = (elapsed_ms % 60000) / 1000; let seconds = (elapsed_ms % 60000) / 1000;
let total_tokens = tokens_in.saturating_add(tokens_out); let total_tokens = tokens_in.saturating_add(tokens_out);
let self_learning_total = review_tokens; let self_learning_total = review_tokens;
+2 -2
View File
@@ -25,7 +25,7 @@ pub fn draw_status_bar(frame: &mut Frame, area: Rect, state: &crate::app::state:
let spinner_frames = ["", "", "", "", "", "", "", "", "", ""]; let spinner_frames = ["", "", "", "", "", "", "", "", "", ""];
// ── Agent status badge ──────────────────────────────────────────────── // ── Agent status badge ────────────────────────────────────────────────
let (status_text, status_bg, status_fg) = if state.turn_in_flight() { let (status_text, badge_bg, status_fg) = if state.turn_in_flight() {
let f = spinner_frames[(state.misc.tick_count as usize / 2) % spinner_frames.len()]; let f = spinner_frames[(state.misc.tick_count as usize / 2) % spinner_frames.len()];
(format!(" {} PROG ", f), Theme::MODE_YOLO, Theme::BG) (format!(" {} PROG ", f), Theme::MODE_YOLO, Theme::BG)
} else if state.misc.api_connected { } else if state.misc.api_connected {
@@ -38,7 +38,7 @@ pub fn draw_status_bar(frame: &mut Frame, area: Rect, state: &crate::app::state:
status_text, status_text,
Style::default() Style::default()
.fg(status_fg) .fg(status_fg)
.bg(status_bg) .bg(badge_bg)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
); );