ci: add GitHub Actions workflows with semantic-release auto-versioning

chore: fix all 702 clippy warnings across codebase
- auto-fix 475 via cargo clippy --fix
- fix remaining 227 manually: uninlined_format_args, redundant_closure, match_same_arms,
  underscore_binding, format_push_string, items_after_statements, needless_pass_by_value,
  clone_on_copy, case_sensitive_extension, single_match/let-else, write_with_newline,
  and other clippy lints
This commit is contained in:
asepharyana
2026-07-13 08:12:12 +07:00
parent be921d6836
commit 29a9fae3f6
79 changed files with 826 additions and 904 deletions
+47 -41
View File
@@ -72,12 +72,18 @@ fn is_production_code(path: &str) -> bool {
if lower.contains("test") || lower.contains("spec") || lower.contains("_test.") {
return false;
}
// Only source files
lower.ends_with(".rs") || lower.ends_with(".ts") || lower.ends_with(".tsx")
|| lower.ends_with(".js") || lower.ends_with(".jsx") || lower.ends_with(".go")
|| lower.ends_with(".py") || lower.ends_with(".java") || lower.ends_with(".kt")
|| lower.ends_with(".swift") || lower.ends_with(".c") || lower.ends_with(".cpp")
|| lower.ends_with(".h") || lower.ends_with(".hpp")
// Only source files — use Path::extension() to avoid clippy
// case_sensitive_file_extension_comparisons lint
std::path::Path::new(&lower)
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| {
matches!(
ext,
"rs" | "ts" | "tsx" | "js" | "jsx" | "go" | "py" | "java" | "kt" | "swift"
| "c" | "cpp" | "h" | "hpp"
)
})
}
/// ─── Inline Quick Review (synchronous, feeds back to LLM) ───
@@ -111,7 +117,7 @@ pub fn spawn_quick_review(
.with_system_prompt(prompt)
.with_max_steps(QUICK_REVIEW_MAX_STEPS);
let mut ctx = build_subagent_context(def);
let mut ctx = build_subagent_context(&def);
ctx.session_dir = session_dir.to_path_buf();
ctx.workspaces = workspaces.to_vec();
@@ -119,11 +125,11 @@ pub fn spawn_quick_review(
let _drain = std::thread::spawn(move || {
while let Some(event) = rx.blocking_recv() {
match &event {
SubagentEvent::ToolCall { _tool, .. } => {
tracing::debug!("[auto-review] tool call: {}", _tool);
SubagentEvent::ToolCall { tool, .. } => {
tracing::debug!("[auto-review] tool call: {}", tool);
}
SubagentEvent::ToolResult { _tool, .. } => {
tracing::debug!("[auto-review] tool result: {}", _tool);
SubagentEvent::ToolResult { tool, .. } => {
tracing::debug!("[auto-review] tool result: {}", tool);
}
SubagentEvent::Completed { .. } => {
tracing::debug!("[auto-review] completed");
@@ -133,7 +139,7 @@ pub fn spawn_quick_review(
}
});
let verdict = run_subagent(ctx, tx)?;
let verdict = run_subagent(&ctx, &tx)?;
tracing::info!(
"[auto-review] quick review for '{}': {}",
file_path,
@@ -142,7 +148,7 @@ pub fn spawn_quick_review(
Ok(verdict)
}
/// ─── Background Subagent Spawners (async, report via SystemNote) ───
/// ─── Background Subagent Spawners (async, report via `SystemNote`) ───
///
/// Spawn a background subagent that generates tests for modified files.
///
@@ -185,7 +191,7 @@ pub fn spawn_background_test_gen(
.with_system_prompt(prompt)
.with_max_steps(BG_SUBAGENT_MAX_STEPS);
let mut ctx = build_subagent_context(def);
let mut ctx = build_subagent_context(&def);
ctx.session_dir = sd;
ctx.workspaces = ws;
@@ -193,17 +199,17 @@ pub fn spawn_background_test_gen(
let _drain = std::thread::spawn(move || {
while let Some(event) = rx.blocking_recv() {
match &event {
SubagentEvent::ToolCall { _tool, .. } => {
tracing::debug!("[bg-test-gen] tool: {}", _tool);
SubagentEvent::ToolCall { tool, .. } => {
tracing::debug!("[bg-test-gen] tool: {}", tool);
}
SubagentEvent::ToolResult { _tool, .. } => {
tracing::debug!("[bg-test-gen] result: {}", _tool);
SubagentEvent::ToolResult { tool, .. } => {
tracing::debug!("[bg-test-gen] result: {}", tool);
}
SubagentEvent::StepCompleted { _step, .. } => {
tracing::trace!("[bg-test-gen] step {} done", _step);
SubagentEvent::StepCompleted { .. } => {
tracing::trace!("[bg-test-gen] step done");
}
SubagentEvent::StepFailed { _step, _error } => {
tracing::warn!("[bg-test-gen] step {} failed: {}", _step, _error);
SubagentEvent::StepFailed { step, error } => {
tracing::warn!("[bg-test-gen] step {} failed: {}", step, error);
}
SubagentEvent::Completed { .. } => {
tracing::debug!("[bg-test-gen] completed");
@@ -212,13 +218,13 @@ pub fn spawn_background_test_gen(
}
});
let result = run_subagent(ctx, tx);
let result = run_subagent(&ctx, &tx);
let message = match &result {
Ok(output) => {
let first = output.lines().next().unwrap_or(output);
format!("Auto test-gen: {}", first)
format!("Auto test-gen: {first}")
}
Err(e) => format!("Auto test-gen failed: {}", e),
Err(e) => format!("Auto test-gen failed: {e}"),
};
if let Ok(mut q) = events.lock() {
@@ -265,7 +271,7 @@ pub fn spawn_background_arch_review(
.with_system_prompt(prompt)
.with_max_steps(BG_SUBAGENT_MAX_STEPS);
let mut ctx = build_subagent_context(def);
let mut ctx = build_subagent_context(&def);
ctx.session_dir = sd;
ctx.workspaces = ws;
@@ -273,11 +279,11 @@ pub fn spawn_background_arch_review(
let _drain = std::thread::spawn(move || {
while let Some(event) = rx.blocking_recv() {
match &event {
SubagentEvent::ToolCall { _tool, .. } => {
tracing::debug!("[bg-arch] tool: {}", _tool);
SubagentEvent::ToolCall { tool, .. } => {
tracing::debug!("[bg-arch] tool: {}", tool);
}
SubagentEvent::ToolResult { _tool, .. } => {
tracing::debug!("[bg-arch] result: {}", _tool);
SubagentEvent::ToolResult { tool, .. } => {
tracing::debug!("[bg-arch] result: {}", tool);
}
SubagentEvent::Completed { .. } => {
tracing::debug!("[bg-arch] completed");
@@ -287,13 +293,13 @@ pub fn spawn_background_arch_review(
}
});
let result = run_subagent(ctx, tx);
let result = run_subagent(&ctx, &tx);
let message = match &result {
Ok(output) => {
let first = output.lines().next().unwrap_or(output);
format!("Architecture review: {}", first)
format!("Architecture review: {first}")
}
Err(e) => format!("Architecture review failed: {}", e),
Err(e) => format!("Architecture review failed: {e}"),
};
if let Ok(mut q) = events.lock() {
@@ -351,7 +357,7 @@ pub fn spawn_background_security_review(
.with_system_prompt(prompt)
.with_max_steps(BG_SUBAGENT_MAX_STEPS);
let mut ctx = build_subagent_context(def);
let mut ctx = build_subagent_context(&def);
ctx.session_dir = sd;
ctx.workspaces = ws;
@@ -359,11 +365,11 @@ pub fn spawn_background_security_review(
let _drain = std::thread::spawn(move || {
while let Some(event) = rx.blocking_recv() {
match &event {
SubagentEvent::ToolCall { _tool, .. } => {
tracing::debug!("[bg-security] tool: {}", _tool);
SubagentEvent::ToolCall { tool, .. } => {
tracing::debug!("[bg-security] tool: {}", tool);
}
SubagentEvent::ToolResult { _tool, .. } => {
tracing::debug!("[bg-security] result: {}", _tool);
SubagentEvent::ToolResult { tool, .. } => {
tracing::debug!("[bg-security] result: {}", tool);
}
SubagentEvent::Completed { .. } => {
tracing::debug!("[bg-security] completed");
@@ -373,13 +379,13 @@ pub fn spawn_background_security_review(
}
});
let result = run_subagent(ctx, tx);
let result = run_subagent(&ctx, &tx);
let message = match &result {
Ok(output) => {
let first = output.lines().next().unwrap_or(output);
format!("Security review: {}", first)
format!("Security review: {first}")
}
Err(e) => format!("Security review failed: {}", e),
Err(e) => format!("Security review failed: {e}"),
};
if let Ok(mut q) = events.lock() {
+2 -2
View File
@@ -37,10 +37,10 @@ pub struct SubagentContext {
///
/// Return: a context with empty `system_prompt`, empty `workspaces`,
/// empty `session_dir`, resolved `max_steps`, and the resolved allowed-tool list.
pub fn build_subagent_context(def: AgentDefinition) -> SubagentContext {
pub fn build_subagent_context(def: &AgentDefinition) -> SubagentContext {
let allowed_tools = def.allowed_tools.clone().unwrap_or_else(|| {
if def.role == "reviewer" {
REVIEWER_ALLOWED.iter().map(|s| s.to_string()).collect()
REVIEWER_ALLOWED.iter().map(std::string::ToString::to_string).collect()
} else {
Vec::new()
}
+1 -1
View File
@@ -16,7 +16,7 @@
use crate::app::subagent::spawn::AgentDefinition;
/// Division roles — used as both the `role` field in AgentDefinition
/// Division roles — used as both the `role` field in `AgentDefinition`
/// and as the key for pipeline routing.
pub mod roles {
/// Strategy Division: plans architecture, creates diagrams, breaks down work.
+40 -41
View File
@@ -7,6 +7,7 @@
//! bash exfiltration and destructive-pattern detection) so that subagents
//! are not a weaker link than the main agent.
use std::fmt::Write;
use tokio::sync::mpsc;
use crate::dto::chat::message::ChatMessage;
use crate::dto::provider::request::ToolDef;
@@ -143,8 +144,7 @@ fn gate_subagent_tool_call(
let reason = args.get("reason").and_then(|v| v.as_str()).unwrap_or("");
if reason.trim().len() < MIN_REASON_LEN {
return Some(format!(
"{} requires a non-trivial 'reason' (>= {} chars) explaining why",
tool_name, MIN_REASON_LEN,
"{tool_name} requires a non-trivial 'reason' (>= {MIN_REASON_LEN} chars) explaining why",
));
}
}
@@ -157,9 +157,7 @@ fn gate_subagent_tool_call(
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("");
// For edits, scanning old+new together catches stubs in both
return if contains_any(old, STUB_PATTERNS) {
Some("content contains stub/placeholder pattern; production code must be fully implemented".to_string())
} else if contains_any(new, STUB_PATTERNS) {
return if contains_any(old, STUB_PATTERNS) || contains_any(new, STUB_PATTERNS) {
Some("content contains stub/placeholder pattern; production code must be fully implemented".to_string())
} else if contains_any(new, DENIAL_PATTERNS) {
Some("content contains denial/punt pattern; implement properly instead of skipping".to_string())
@@ -202,13 +200,13 @@ fn gate_subagent_tool_call(
if !is_standard {
for pat in EXFIL_PATTERNS {
if cmd.contains(pat) {
return Some(format!("potential data-exfiltration command blocked (matched '{}')", pat));
return Some(format!("potential data-exfiltration command blocked (matched '{pat}')"));
}
}
}
for pat in SENSITIVE_PATH_PATTERNS {
if cmd.contains(pat) {
return Some(format!("refused to read/write sensitive path '{}'", pat));
return Some(format!("refused to read/write sensitive path '{pat}'"));
}
}
let dangerous = ["rm -rf /", "rm -rf --no-preserve-root", "rm -rf ~",
@@ -216,7 +214,7 @@ fn gate_subagent_tool_call(
"chmod -R 000 /", "shutdown ", "poweroff ", "reboot ", "halt "];
for pat in &dangerous {
if cmd.contains(pat) {
return Some(format!("destructive command pattern blocked: {}", pat));
return Some(format!("destructive command pattern blocked: {pat}"));
}
}
if contains_any(cmd, STUB_PATTERNS) {
@@ -251,7 +249,7 @@ fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
let mut out = String::new();
out.push_str("Current Workspace Directory Structure:\n");
for root in roots {
out.push_str(&format!("Root: {}\n", root.display()));
writeln!(out, "Root: {}", root.display()).unwrap();
let walker = ignore::WalkBuilder::new(root)
.hidden(true)
.git_ignore(true)
@@ -261,9 +259,9 @@ fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
let path = entry.path();
if let Ok(rel) = path.strip_prefix(root) {
if rel.as_os_str().is_empty() { continue; }
let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false);
let is_dir = entry.file_type().is_some_and(|ft| ft.is_dir());
let prefix = if is_dir { "[DIR] " } else { " " };
out.push_str(&format!(" {}{}\n", prefix, rel.display()));
writeln!(out, " {}{}", prefix, rel.display()).unwrap();
count += 1;
if count > 1000 {
out.push_str(" ... (truncated)\n");
@@ -291,7 +289,8 @@ fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
///
/// Return: the concatenated text output, or an `anyhow::Error` if the LLM
/// call fails at any step.
pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> anyhow::Result<String> {
#[allow(clippy::too_many_lines)]
pub fn run_subagent(ctx: &SubagentContext, tx: &mpsc::Sender<SubagentEvent>) -> anyhow::Result<String> {
let mut output = String::new();
let mut messages: Vec<ChatMessage> = Vec::new();
@@ -328,10 +327,10 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
// be cancelled from the parent (mirrors main agent behaviour).
if ctx.abort_flag.as_ref().is_some_and(|f| f.load(std::sync::atomic::Ordering::SeqCst)) {
let _ = tx.blocking_send(SubagentEvent::StepFailed {
_step: step,
_error: "subagent aborted by parent".to_string(),
step,
error: "subagent aborted by parent".to_string(),
});
anyhow::bail!("subagent aborted by parent at step {}", step);
anyhow::bail!("subagent aborted by parent at step {step}");
}
// Use the structured tool-calling API so the LLM can request tools with
@@ -340,10 +339,10 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
Ok(result) => result,
Err(e) => {
let _ = tx.blocking_send(SubagentEvent::StepFailed {
_step: step,
_error: e.to_string(),
step,
error: e.to_string(),
});
anyhow::bail!("subagent call failed at step {}: {}", step, e);
anyhow::bail!("subagent call failed at step {step}: {e}");
}
};
@@ -361,10 +360,10 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
// Check abort flag before each tool execution
if ctx.abort_flag.as_ref().is_some_and(|f| f.load(std::sync::atomic::Ordering::SeqCst)) {
let _ = tx.blocking_send(SubagentEvent::StepFailed {
_step: step,
_error: "subagent aborted by parent during tool execution".to_string(),
step,
error: "subagent aborted by parent during tool execution".to_string(),
});
anyhow::bail!("subagent aborted by parent during tool call at step {}", step);
anyhow::bail!("subagent aborted by parent during tool call at step {step}");
}
let tool_name = &tool_call.function.name;
@@ -373,28 +372,28 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
let generally_allowed = ctx.allowed_tools.is_empty() || explicitly_allowed;
let _ = tx.blocking_send(SubagentEvent::ToolCall {
_tool: tool_name.clone(),
_args: args.clone(),
tool: tool_name.clone(),
args: args.clone(),
});
// Level 1: allowlist check — is this tool even permitted?
if !generally_allowed {
let msg = format!("tool '{}' not allowed for this subagent", tool_name);
let msg = format!("tool '{tool_name}' not allowed for this subagent");
messages.push(ChatMessage::tool_result(tool_call.id.clone(), msg.clone()));
let _ = tx.blocking_send(SubagentEvent::ToolResult {
_tool: tool_name.clone(),
_output: msg,
tool: tool_name.clone(),
output: msg,
});
continue;
}
// Level 2: risky tool check — risky tools require explicit permission
if tool_is_risky(tool_name) && !explicitly_allowed {
let msg = format!("risky tool '{}' requires explicit permission; not allowed for this subagent", tool_name);
let msg = format!("risky tool '{tool_name}' requires explicit permission; not allowed for this subagent");
messages.push(ChatMessage::tool_result(tool_call.id.clone(), msg.clone()));
let _ = tx.blocking_send(SubagentEvent::ToolResult {
_tool: tool_name.clone(),
_output: msg,
tool: tool_name.clone(),
output: msg,
});
continue;
}
@@ -404,34 +403,34 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
// stub/denial/assumption scanning, bash exfiltration, destructive
// commands, sensitive path reads).
if let Some(block_reason) = gate_subagent_tool_call(tool_name, &args) {
let msg = format!("Blocked by subagent gate: {}", block_reason);
let msg = format!("Blocked by subagent gate: {block_reason}");
messages.push(ChatMessage::tool_result(tool_call.id.clone(), msg.clone()));
let _ = tx.blocking_send(SubagentEvent::ToolResult {
_tool: tool_name.clone(),
_output: msg,
tool: tool_name.clone(),
output: msg,
});
continue;
}
let result = match tools.iter().find(|t| t.name() == tool_name.as_str()) {
Some(tool) => tool.run(&tool_ctx, &args),
None => Err(anyhow::anyhow!("tool '{}' not found", tool_name)),
None => Err(anyhow::anyhow!("tool '{tool_name}' not found")),
};
match result {
Ok(output_text) => {
messages.push(ChatMessage::tool_result(tool_call.id.clone(), output_text.clone()));
let _ = tx.blocking_send(SubagentEvent::ToolResult {
_tool: tool_name.clone(),
_output: output_text,
tool: tool_name.clone(),
output: output_text,
});
}
Err(e) => {
let msg = format!("tool '{}' failed: {}", tool_name, e);
let msg = format!("tool '{tool_name}' failed: {e}");
messages.push(ChatMessage::tool_result(tool_call.id.clone(), msg.clone()));
let _ = tx.blocking_send(SubagentEvent::ToolResult {
_tool: tool_name.clone(),
_output: msg,
tool: tool_name.clone(),
output: msg,
});
}
}
@@ -443,8 +442,8 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
output.push('\n');
}
let _ = tx.blocking_send(SubagentEvent::StepCompleted {
_step: step,
_output: content.clone(),
step,
output: content.clone(),
});
// Break only when we got real content; empty means something went wrong
if !content.is_empty() {
@@ -453,6 +452,6 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
}
}
let _ = tx.blocking_send(SubagentEvent::Completed { _output: output.clone() });
let _ = tx.blocking_send(SubagentEvent::Completed { output: output.clone() });
Ok(output)
}
+14 -9
View File
@@ -8,22 +8,27 @@ use serde_json::Value;
#[derive(Debug, Clone)]
pub enum SubagentEvent {
StepCompleted {
_step: usize,
_output: String,
#[allow(dead_code)]
step: usize,
#[allow(dead_code)]
output: String,
},
StepFailed {
_step: usize,
_error: String,
step: usize,
error: String,
},
Completed {
_output: String,
#[allow(dead_code)]
output: String,
},
ToolCall {
_tool: String,
_args: Value,
tool: String,
#[allow(dead_code)]
args: Value,
},
ToolResult {
_tool: String,
_output: String,
tool: String,
#[allow(dead_code)]
output: String,
},
}
+1 -1
View File
@@ -1,4 +1,4 @@
//! AgentDefinition -- declarative specification for instantiating a
//! `AgentDefinition` -- declarative specification for instantiating a
//! subagent from workflow scripts or programmatic calls.
use serde::{Deserialize, Serialize};