feat: enhance strictness of Rust compiler settings and improve code quality by treating warnings as errors
This commit is contained in:
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -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()),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user