refactor: DRY cleanup — extract shared helpers, remove duplication across tools, LSP, overlays, and runtime
Eliminate ~500 lines of duplicate code across 31 files by extracting shared functions, helpers, and consolidating repeated patterns. Highlights: - Toast helpers (toast_info/success/warning/error) on AppStateRest - push_event() helper for turn-event queue (19 callers consolidated) - log_write_edit_tool() shared fn (turn.rs + engine.rs ~50 lines saved) - resolve_api_key() shared fn (spawn.rs + provider.rs) - LSP call_positional() helper on LspClient - lsp_cursor_params() shared schema for 4 tool files -overlay_block() helper for consistent overlay title/border styling - cycle_selected_index(), path_not_found/a_directory() helpers - mark_dirty(), save_settings() on AppStateRest - Remove redundant Err(e) => Err(e) arms in LSP tools - Consolidate generate_workspace_tree (turn.rs → workspace.rs) - Simplify background-review wrapper args in auto/mod.rs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b02754acd2
commit
9a6ab62562
@@ -248,16 +248,24 @@ fn spawn_background_review(
|
||||
});
|
||||
}
|
||||
|
||||
/// Collect the trailing arguments shared by all background-review spawners.
|
||||
fn review_args<'a>(
|
||||
file_paths: &'a [String],
|
||||
session_dir: &'a Path,
|
||||
workspaces: &'a [std::path::PathBuf],
|
||||
turn_events: &'a Arc<Mutex<VecDeque<TurnEvent>>>,
|
||||
abort_flag: Arc<AtomicBool>,
|
||||
) -> (Vec<String>, std::path::PathBuf, Vec<std::path::PathBuf>, Arc<Mutex<VecDeque<TurnEvent>>>, Arc<AtomicBool>) {
|
||||
(
|
||||
file_paths.to_vec(),
|
||||
session_dir.to_path_buf(),
|
||||
workspaces.to_vec(),
|
||||
turn_events.clone(),
|
||||
abort_flag,
|
||||
)
|
||||
}
|
||||
|
||||
/// Spawn a background subagent that generates tests for modified files.
|
||||
///
|
||||
/// Uses the test-generator prompt and has read-write access so it can
|
||||
/// create test files. Runs in a separate OS thread and reports completion
|
||||
/// via `TurnEvent::SystemNote { kind: "bg-test-gen" }`.
|
||||
///
|
||||
/// Skipped (no-op) if a test-gen run is already in flight (guarded by
|
||||
/// `TEST_GEN_RUNNING`) — prevents a chatty multi-turn edit session from
|
||||
/// stacking overlapping runs. `abort_flag` is forwarded to the generic
|
||||
/// spawner so the run can be cancelled if the turn aborts.
|
||||
pub fn spawn_background_test_gen(
|
||||
file_paths: &[String],
|
||||
session_dir: &Path,
|
||||
@@ -265,29 +273,15 @@ pub fn spawn_background_test_gen(
|
||||
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
|
||||
abort_flag: Arc<AtomicBool>,
|
||||
) {
|
||||
let (fps, sd, ws, te, af) = review_args(file_paths, session_dir, workspaces, turn_events, abort_flag);
|
||||
spawn_background_review(
|
||||
"bg-test-gen",
|
||||
&TEST_GEN_RUNNING,
|
||||
crate::prompts::TEST_GENERATOR_PROMPT,
|
||||
"test-generator",
|
||||
"coder",
|
||||
file_paths.to_vec(),
|
||||
session_dir.to_path_buf(),
|
||||
workspaces.to_vec(),
|
||||
turn_events.clone(),
|
||||
abort_flag,
|
||||
"bg-test-gen", &TEST_GEN_RUNNING,
|
||||
crate::prompts::TEST_GENERATOR_PROMPT, "test-generator", "coder",
|
||||
fps, sd, ws, te, af,
|
||||
);
|
||||
}
|
||||
|
||||
/// Spawn a background architecture-review subagent.
|
||||
///
|
||||
/// Inspects the modified files for architectural consistency (layering,
|
||||
/// coupling, module boundaries). Reports via
|
||||
/// `TurnEvent::SystemNote { kind: "bg-arch-review" }`.
|
||||
///
|
||||
/// Skipped (no-op) if an arch-review run is already in flight (guarded by
|
||||
/// `ARCH_REVIEW_RUNNING`). `abort_flag` is forwarded to the generic
|
||||
/// spawner so the run can be cancelled if the turn aborts.
|
||||
pub fn spawn_background_arch_review(
|
||||
file_paths: &[String],
|
||||
session_dir: &Path,
|
||||
@@ -295,31 +289,18 @@ pub fn spawn_background_arch_review(
|
||||
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
|
||||
abort_flag: Arc<AtomicBool>,
|
||||
) {
|
||||
let (fps, sd, ws, te, af) = review_args(file_paths, session_dir, workspaces, turn_events, abort_flag);
|
||||
spawn_background_review(
|
||||
"bg-arch-review",
|
||||
&ARCH_REVIEW_RUNNING,
|
||||
crate::prompts::ARCH_REVIEWER_PROMPT,
|
||||
"arch-reviewer",
|
||||
"reviewer",
|
||||
file_paths.to_vec(),
|
||||
session_dir.to_path_buf(),
|
||||
workspaces.to_vec(),
|
||||
turn_events.clone(),
|
||||
abort_flag,
|
||||
"bg-arch-review", &ARCH_REVIEW_RUNNING,
|
||||
crate::prompts::ARCH_REVIEWER_PROMPT, "arch-reviewer", "reviewer",
|
||||
fps, sd, ws, te, af,
|
||||
);
|
||||
}
|
||||
|
||||
/// Spawn a background security-review subagent.
|
||||
///
|
||||
/// Checks modified files for security vulnerabilities. Reports via
|
||||
/// `TurnEvent::SystemNote { kind: "bg-security-review" }`.
|
||||
///
|
||||
/// Only reviews production code files for security — test files and
|
||||
/// config files are out of scope for security review.
|
||||
///
|
||||
/// Skipped (no-op) if a security-review run is already in flight (guarded by
|
||||
/// `SECURITY_REVIEW_RUNNING`). `abort_flag` is forwarded to the generic
|
||||
/// spawner so the run can be cancelled if the turn aborts.
|
||||
pub fn spawn_background_security_review(
|
||||
file_paths: &[String],
|
||||
session_dir: &Path,
|
||||
@@ -327,25 +308,16 @@ pub fn spawn_background_security_review(
|
||||
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
|
||||
abort_flag: Arc<AtomicBool>,
|
||||
) {
|
||||
// Only review production code files for security — test files and
|
||||
// config files are out of scope for security review.
|
||||
let (_, sd, ws, te, af) = review_args(file_paths, session_dir, workspaces, turn_events, abort_flag);
|
||||
let prod_paths: Vec<String> = file_paths
|
||||
.iter()
|
||||
.filter(|p| is_production_code(p))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
spawn_background_review(
|
||||
"bg-security-review",
|
||||
&SECURITY_REVIEW_RUNNING,
|
||||
crate::prompts::SECURITY_REVIEWER_PROMPT,
|
||||
"security-reviewer",
|
||||
"reviewer",
|
||||
prod_paths,
|
||||
session_dir.to_path_buf(),
|
||||
workspaces.to_vec(),
|
||||
turn_events.clone(),
|
||||
abort_flag,
|
||||
"bg-security-review", &SECURITY_REVIEW_RUNNING,
|
||||
crate::prompts::SECURITY_REVIEWER_PROMPT, "security-reviewer", "reviewer",
|
||||
prod_paths, sd, ws, te, af,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,8 @@ use super::workspace::generate_workspace_tree;
|
||||
use crate::dto::chat::message::ChatMessage;
|
||||
use crate::dto::provider::request::ToolDef;
|
||||
use crate::tool::tool_is_risky;
|
||||
use sha2::Digest;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use tokio::sync::mpsc;
|
||||
use zesdex_cms::domain::repository::EditLogRepository;
|
||||
|
||||
/// Tiny jitter helper so retry backoffs don't arrive in lockstep.
|
||||
fn retry_jitter_ns(range_ns: u64) -> u64 {
|
||||
@@ -208,6 +206,7 @@ pub fn run_subagent(
|
||||
}
|
||||
true
|
||||
},
|
||||
ctx.abort_flag.as_deref(),
|
||||
);
|
||||
|
||||
match stream_result {
|
||||
@@ -346,49 +345,14 @@ pub fn run_subagent(
|
||||
let run_res = tool.run(tool_ctx_ref, &args);
|
||||
|
||||
if is_edit && run_res.is_ok() {
|
||||
let reason = args
|
||||
.get("reason")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unnamed");
|
||||
let path = args
|
||||
.get("path")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown");
|
||||
let content_sha256 = {
|
||||
let content = args.get("content").or_else(|| args.get("new"));
|
||||
let hash = sha2::Sha256::digest(
|
||||
content.and_then(|v| v.as_str()).unwrap_or("").as_bytes(),
|
||||
);
|
||||
hex::encode(hash)
|
||||
};
|
||||
let bytes_delta = if tool_name == "write" {
|
||||
args.get("content")
|
||||
.and_then(|v| v.as_str())
|
||||
.map_or(0, |s| s.len() as i64)
|
||||
} else {
|
||||
let old = args.get("old").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let new = args.get("new").and_then(|v| v.as_str()).unwrap_or("");
|
||||
(new.len() as i64 - old.len() as i64).abs()
|
||||
};
|
||||
let session_id = ctx.session_dir
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let entry = zesdex_cms::domain::edit_log::EditLogEntry {
|
||||
ts: chrono::Utc::now().timestamp_millis(),
|
||||
tool: tool_name.clone(),
|
||||
path: path.to_string(),
|
||||
reason: reason.to_string(),
|
||||
content_sha256,
|
||||
bytes_delta,
|
||||
origin: tool_ctx_ref.origin.tag(),
|
||||
session_id,
|
||||
};
|
||||
let repo = zesdex_cms::infrastructure::persistence::edit_log_repo::JsonlEditLogRepository::new();
|
||||
if let Ok(mut el) = repo.open(&ctx.session_dir) {
|
||||
let _ = repo.append(&ctx.session_dir, &mut el, entry);
|
||||
}
|
||||
.unwrap_or("unknown");
|
||||
crate::tool::log_write_edit_tool(
|
||||
&args, tool_name, &tool_ctx_ref.origin.tag(),
|
||||
&ctx.session_dir, session_id,
|
||||
);
|
||||
}
|
||||
run_res
|
||||
}
|
||||
|
||||
@@ -27,40 +27,19 @@ pub(crate) fn resolve_provider_config() -> (String, String, Option<String>, Stri
|
||||
.load(&store_base_dir)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut api_key = settings
|
||||
.api_keys
|
||||
.get(&settings.provider)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
tracing::warn!(
|
||||
"[subagent] no API key for provider '{}' in settings, trying env/default",
|
||||
settings.provider
|
||||
);
|
||||
String::new()
|
||||
});
|
||||
let api_key = crate::service::provider::resolve_api_key(&settings, &app_config);
|
||||
if api_key.is_empty() {
|
||||
tracing::warn!(
|
||||
"[subagent] all API key resolution paths exhausted for '{}'",
|
||||
settings.provider
|
||||
);
|
||||
}
|
||||
let model = settings.model.clone();
|
||||
let base_url = app_config
|
||||
.providers
|
||||
.get(&settings.provider)
|
||||
.map(|p| p.api_base.clone());
|
||||
|
||||
if api_key.is_empty() {
|
||||
if let Some(provider_cfg) = app_config.providers.get(&settings.provider) {
|
||||
api_key = provider_cfg
|
||||
.api_key_env
|
||||
.as_ref()
|
||||
.and_then(|env| std::env::var(env).ok())
|
||||
.or_else(|| provider_cfg.default_api_key.clone())
|
||||
.unwrap_or_else(|| {
|
||||
tracing::warn!(
|
||||
"[subagent] all API key resolution paths exhausted for '{}'",
|
||||
settings.provider
|
||||
);
|
||||
String::new()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(api_key, model, base_url, settings.provider)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user