style: format seluruh workspace dengan cargo fmt

Menyeragamkan format kode sesuai rustfmt (126 file). Sebelumnya
lefthook pre-commit 'cargo fmt --check' akan gagal pada commit apa pun.
This commit is contained in:
asepharyana
2026-08-27 22:10:28 +07:00
parent 884b19ccb5
commit 7b0b53671f
127 changed files with 1271 additions and 1156 deletions
+22 -6
View File
@@ -131,7 +131,8 @@ impl Tool for BestPractice {
}
// Suggest a template.
if let Some(parsed) = eng.parse_commit(&msg) {
let tpl = eng.suggest_commit_template(&parsed.type_, parsed.scope.as_deref());
let tpl =
eng.suggest_commit_template(&parsed.type_, parsed.scope.as_deref());
out.push_str(&format!("\nTemplate: {tpl}\n"));
}
Ok(out)
@@ -309,8 +310,14 @@ mod tests {
let tool = BestPractice;
let args = json!({"action": "list_skills"});
let result = tool.run(&test_ctx(), &args).unwrap();
assert!(result.contains("clean-code"), "should list clean-code: {result}");
assert!(result.contains("commit-convention"), "should list commit-convention: {result}");
assert!(
result.contains("clean-code"),
"should list clean-code: {result}"
);
assert!(
result.contains("commit-convention"),
"should list commit-convention: {result}"
);
}
#[test]
@@ -318,7 +325,10 @@ mod tests {
let tool = BestPractice;
let args = json!({"action": "get_skill", "skill_name": "clean-code"});
let result = tool.run(&test_ctx(), &args).unwrap();
assert!(result.contains("Clean Code"), "should contain skill content: {result}");
assert!(
result.contains("Clean Code"),
"should contain skill content: {result}"
);
}
#[test]
@@ -326,7 +336,10 @@ mod tests {
let tool = CommitConvention;
let args = json!({"message": "feat(tool): add best practice audit"});
let result = tool.run(&test_ctx(), &args).unwrap();
assert!(result.contains(""), "valid commit should succeed: {result}");
assert!(
result.contains(""),
"valid commit should succeed: {result}"
);
}
#[test]
@@ -334,6 +347,9 @@ mod tests {
let tool = CommitConvention;
let args = json!({"message": "Add new feature"});
let result = tool.run(&test_ctx(), &args).unwrap();
assert!(result.contains(""), "invalid commit should fail: {result}");
assert!(
result.contains(""),
"invalid commit should fail: {result}"
);
}
}
+2 -4
View File
@@ -17,8 +17,7 @@ pub struct ToolCtx {
pub origin: crate::Origin,
pub graduated_checks: Vec<crate::tools::GraduatedCheck>,
pub lsp_manager: Arc<Mutex<crate::lsp::manager::LspManager>>,
pub turn_events:
Option<Arc<Mutex<std::collections::VecDeque<crate::TurnEvent>>>>,
pub turn_events: Option<Arc<Mutex<std::collections::VecDeque<crate::TurnEvent>>>>,
pub workflow_findings: Option<Arc<Mutex<Vec<String>>>>,
pub abort_flag: Option<Arc<AtomicBool>>,
}
@@ -41,8 +40,7 @@ pub struct ToolCtxBuilder {
pub origin: crate::Origin,
pub graduated_checks: Vec<crate::tools::GraduatedCheck>,
pub lsp_manager: Arc<Mutex<crate::lsp::manager::LspManager>>,
pub turn_events:
Option<Arc<Mutex<std::collections::VecDeque<crate::TurnEvent>>>>,
pub turn_events: Option<Arc<Mutex<std::collections::VecDeque<crate::TurnEvent>>>>,
pub workflow_findings: Option<Arc<Mutex<Vec<String>>>>,
pub abort_flag: Option<Arc<AtomicBool>>,
}
+5 -9
View File
@@ -1,7 +1,7 @@
use std::future::Future;
use anyhow::Result;
use zesdex_application::agent::ToolExecutor;
use crate::tools::{all_tools, Tool, ToolCtx};
use anyhow::Result;
use std::future::Future;
use zesdex_application::agent::ToolExecutor;
pub struct InfrastructureToolExecutor {
ctx: ToolCtx,
@@ -27,14 +27,10 @@ impl ToolExecutor for InfrastructureToolExecutor {
let tool_opt = self.tools.iter().find(|t| t.name() == tool_name);
let ctx = self.ctx.clone();
let args = args.clone();
async move {
match tool_opt {
Some(tool) => {
tokio::task::block_in_place(move || {
tool.run(&ctx, &args)
})
}
Some(tool) => tokio::task::block_in_place(move || tool.run(&ctx, &args)),
None => {
anyhow::bail!("Unknown tool: {}", tool_name)
}
@@ -7,7 +7,7 @@ use crate::tools::shell_filter::git::check_git_destructive;
use crate::tools::{execute_cmd, Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use tracing::{info, warn, instrument};
use tracing::{info, instrument, warn};
/// Tool that executes safe git operations (status, log, diff, commit, branch, etc.).
///
@@ -56,15 +56,13 @@ impl Tool for GitWorktree {
let branch = crate::tools::arg_str(args, "branch")?;
info!(path, branch, "adding worktree");
let output = execute_cmd(
std::process::Command::new("git")
.args(["worktree", "add", &path, &branch]),
std::process::Command::new("git").args(["worktree", "add", &path, &branch]),
)?;
Ok(output)
}
"list" => {
let output = execute_cmd(
std::process::Command::new("git").args(["worktree", "list"]),
)?;
let output =
execute_cmd(std::process::Command::new("git").args(["worktree", "list"]))?;
Ok(output)
}
"remove" => {
@@ -77,9 +75,8 @@ impl Tool for GitWorktree {
}
"prune" => {
info!("pruning stale worktree metadata");
let output = execute_cmd(
std::process::Command::new("git").args(["worktree", "prune"]),
)?;
let output =
execute_cmd(std::process::Command::new("git").args(["worktree", "prune"]))?;
Ok(output)
}
_ => anyhow::bail!("unknown action: {}", action),
+1 -5
View File
@@ -11,11 +11,7 @@ pub struct GraduatedCheck {
}
/// Check which graduated checks apply to a given file path/content pair.
pub fn check_graduated_checks(
path: &str,
content: &str,
checks: &[GraduatedCheck],
) -> Vec<String> {
pub fn check_graduated_checks(path: &str, content: &str, checks: &[GraduatedCheck]) -> Vec<String> {
let mut matches = Vec::new();
for check in checks {
if path.contains(&check.pattern) || content.contains(&check.rule) {
@@ -6,7 +6,7 @@
use crate::tools::{Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use tracing::{info, error, instrument};
use tracing::{error, info, instrument};
/// Tool that requests code completion suggestions from an LSP server.
///
@@ -65,10 +65,13 @@ impl Tool for LspCompletion {
}
};
if let Some(client) = manager.get_client(&language) {
let result = client.send_request("textDocument/completion", &json!({
"textDocument": { "uri": format!("file://{}", path) },
"position": { "line": line, "character": character }
}))?;
let result = client.send_request(
"textDocument/completion",
&json!({
"textDocument": { "uri": format!("file://{}", path) },
"position": { "line": line, "character": character }
}),
)?;
Ok(serde_json::to_string_pretty(&result)?)
} else {
anyhow::bail!("no LSP client connected for '{language}'")
+6 -2
View File
@@ -6,7 +6,7 @@
use crate::tools::{Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use tracing::{info, error, instrument};
use tracing::{error, info, instrument};
/// Tool that connects to an LSP language server for a given language.
///
@@ -52,7 +52,11 @@ impl Tool for LspConnect {
let extra_args: Vec<String> = args
.get("args")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
info!(language, command, extra_args = ?extra_args, "LSP connect requested");
@@ -6,7 +6,7 @@
use crate::tools::{Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use tracing::{info, error, instrument};
use tracing::{error, info, instrument};
/// Tool that resolves a symbol's definition location via LSP.
///
@@ -66,10 +66,13 @@ impl Tool for LspDefinition {
}
};
if let Some(client) = manager.get_client(&language) {
let result = client.send_request("textDocument/definition", &json!({
"textDocument": { "uri": format!("file://{}", path) },
"position": { "line": line, "character": character }
}))?;
let result = client.send_request(
"textDocument/definition",
&json!({
"textDocument": { "uri": format!("file://{}", path) },
"position": { "line": line, "character": character }
}),
)?;
Ok(serde_json::to_string_pretty(&result)?)
} else {
anyhow::bail!("no LSP client connected for '{language}'")
@@ -7,7 +7,7 @@
use crate::tools::{Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use tracing::{info, error, instrument};
use tracing::{error, info, instrument};
/// Tool that retrieves diagnostics (errors, warnings) from the LSP for a file.
///
@@ -56,9 +56,12 @@ impl Tool for LspDiagnostics {
}
};
if let Some(client) = manager.get_client(&language) {
let result = client.send_request("textDocument/diagnostic", &json!({
"textDocument": { "uri": format!("file://{}", path) }
}))?;
let result = client.send_request(
"textDocument/diagnostic",
&json!({
"textDocument": { "uri": format!("file://{}", path) }
}),
)?;
Ok(serde_json::to_string_pretty(&result)?)
} else {
anyhow::bail!("no LSP client connected for '{language}'")
@@ -6,7 +6,7 @@
use crate::tools::{Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use tracing::{info, error, instrument};
use tracing::{error, info, instrument};
/// Tool that disconnects an LSP language server for a given language.
///
+8 -5
View File
@@ -6,7 +6,7 @@
use crate::tools::{Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use tracing::{info, error, instrument};
use tracing::{error, info, instrument};
/// Tool that retrieves hover information for a symbol at a position via LSP.
///
@@ -66,10 +66,13 @@ impl Tool for LspHover {
}
};
if let Some(client) = manager.get_client(&language) {
let result = client.send_request("textDocument/hover", &json!({
"textDocument": { "uri": format!("file://{}", path) },
"position": { "line": line, "character": character }
}))?;
let result = client.send_request(
"textDocument/hover",
&json!({
"textDocument": { "uri": format!("file://{}", path) },
"position": { "line": line, "character": character }
}),
)?;
Ok(serde_json::to_string_pretty(&result)?)
} else {
anyhow::bail!("no LSP client connected for '{language}'")
+4 -4
View File
@@ -9,10 +9,10 @@ pub mod disconnect;
pub mod hover;
pub mod references;
pub use connect::LspConnect;
pub use diagnostics::LspDiagnostics;
pub use hover::LspHover;
pub use completion::LspCompletion;
pub use connect::LspConnect;
pub use definition::LspDefinition;
pub use references::LspReferences;
pub use diagnostics::LspDiagnostics;
pub use disconnect::LspDisconnect;
pub use hover::LspHover;
pub use references::LspReferences;
@@ -6,7 +6,7 @@
use crate::tools::{Tool, ToolCtx};
use anyhow::Result;
use serde_json::{json, Value};
use tracing::{info, error, instrument};
use tracing::{error, info, instrument};
/// Tool that finds all references to a symbol at a position via LSP.
///
@@ -66,10 +66,13 @@ impl Tool for LspReferences {
}
};
if let Some(client) = manager.get_client(&language) {
let result = client.send_request("textDocument/references", &json!({
"textDocument": { "uri": format!("file://{}", path) },
"position": { "line": line, "character": character }
}))?;
let result = client.send_request(
"textDocument/references",
&json!({
"textDocument": { "uri": format!("file://{}", path) },
"position": { "line": line, "character": character }
}),
)?;
Ok(serde_json::to_string_pretty(&result)?)
} else {
anyhow::bail!("no LSP client connected for '{language}'")
+1 -1
View File
@@ -52,8 +52,8 @@ pub mod sequential_think;
pub mod shell;
pub mod shell_filter;
pub mod spawn;
pub mod utility;
pub mod util;
pub mod utility;
pub mod web_search;
pub mod workflow;
@@ -112,10 +112,7 @@ impl Tool for ParallelDelegate {
dirs.iter()
.filter_map(|d| {
let directive = d.get("directive").and_then(|v| v.as_str())?;
let access_str = d
.get("access")
.and_then(|v| v.as_str())
.unwrap_or("write");
let access_str = d.get("access").and_then(|v| v.as_str()).unwrap_or("write");
let access = match access_str {
"read" => AccessTier::Read,
"full" => AccessTier::Full,
@@ -162,12 +159,7 @@ impl Tool for ParallelDelegate {
);
debug!(agent_index = i, access = ?access, "spawning parallel agent");
let handle = spawn_subagent(
subagent_ctx,
directive.clone(),
*access,
ctx.clone(),
);
let handle = spawn_subagent(subagent_ctx, directive.clone(), *access, ctx.clone());
handles.push((i, handle));
}
@@ -181,11 +173,7 @@ impl Tool for ParallelDelegate {
}
Ok(Err(e)) => {
warn!(agent_index = i, error = %e, "parallel agent failed");
results.push((
i,
directives[i].0.clone(),
format!("[ERROR] {e}"),
));
results.push((i, directives[i].0.clone(), format!("[ERROR] {e}")));
}
Err(e) => {
warn!(agent_index = i, error = ?e, "parallel agent panicked");
@@ -201,7 +189,8 @@ impl Tool for ParallelDelegate {
// Consolidate results
if synthesize && results.len() > 1 {
let rt = tokio::runtime::Runtime::new()?;
let consolidated = rt.block_on(consolidate_results(&results, &base_url, &api_key, &model))?;
let consolidated =
rt.block_on(consolidate_results(&results, &base_url, &api_key, &model))?;
Ok(format!(
"## Parallel Delegation Complete\n\n**Task:** {task}\n**Parallel agents:** {}\n\n{}",
results.len(),
@@ -213,7 +202,10 @@ impl Tool for ParallelDelegate {
results.len()
);
for (i, directive, result) in &results {
output.push_str(&format!("---\n### Agent {}: {}\n\n{}\n", i, directive, result));
output.push_str(&format!(
"---\n### Agent {}: {}\n\n{}\n",
i, directive, result
));
}
Ok(output)
}
@@ -234,9 +226,8 @@ async fn auto_split_task(
Some(base_url.to_string()),
);
let sys_msg = zesdex_domain::core::ChatMessage::system(
format!(
"You are a task decomposition expert. Split the following task into {max_parallel} \
let sys_msg = zesdex_domain::core::ChatMessage::system(format!(
"You are a task decomposition expert. Split the following task into {max_parallel} \
independent sub-tasks that can run in parallel. Each sub-task must be self-contained \
and produce useful output independently.\n\n\
Output your response as a JSON array of objects, each with:\n\
@@ -244,15 +235,16 @@ async fn auto_split_task(
- \"access\": one of \"read\", \"write\", or \"full\"\n\n\
IMPORTANT: Return ONLY valid JSON, no other text. Example:\n\
[{{\"directive\": \"Create the User model with fields...\", \"access\": \"write\"}}]"
)
);
));
let user_msg =
zesdex_domain::core::ChatMessage::user(format!("Task: {task}\n\nSplit into {max_parallel} parallel directives:"));
let user_msg = zesdex_domain::core::ChatMessage::user(format!(
"Task: {task}\n\nSplit into {max_parallel} parallel directives:"
));
use zesdex_application::ports::ProviderService;
match client
.chat(&[sys_msg, user_msg], None, Some(2048), Some(0.4)).await
.chat(&[sys_msg, user_msg], None, Some(2048), Some(0.4))
.await
{
Ok((response, _)) => {
let text = response.content.unwrap_or_default();
@@ -320,10 +312,7 @@ fn fallback_split(task: &str, max_parallel: usize) -> Vec<(String, AccessTier)>
}
if task.contains("test") || task.contains("unit") {
directives.push((
format!("Write unit tests for: {task}"),
AccessTier::Read,
));
directives.push((format!("Write unit tests for: {task}"), AccessTier::Read));
}
if directives.is_empty() {
@@ -370,7 +359,10 @@ async fn consolidate_results(
));
use zesdex_application::ports::ProviderService;
match client.chat(&[sys_msg, user_msg], None, Some(2048), Some(0.3)).await {
match client
.chat(&[sys_msg, user_msg], None, Some(2048), Some(0.3))
.await
{
Ok((response, _)) => Ok(response.content.unwrap_or_else(|| summary.clone())),
Err(e) => {
warn!(error = %e, "consolidation LLM call failed, using raw concatenation");
+1 -5
View File
@@ -158,11 +158,7 @@ impl Tool for Glob {
let p = entry.path();
if glob_set.is_match(p) {
let rel_path = p.strip_prefix(&root).unwrap_or(p).display().to_string();
matches.push(format!(
"{}{}",
rel_path,
if p.is_dir() { "/" } else { "" }
));
matches.push(format!("{}{}", rel_path, if p.is_dir() { "/" } else { "" }));
}
}
matches.sort();
@@ -304,10 +304,7 @@ impl SymbolIndex {
continue;
}
let ext = file_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
let ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
let extractor = match ext_dispatch.get(ext) {
Some(f) => f,
_ => continue, // unsupported extension
@@ -704,8 +701,8 @@ fn extract_typescript(content: &str, rel_path: &str) -> Vec<CodeSymbol> {
if let Some(caps) = r.ts_var_export.captures(trimmed) {
let name = caps.get(1).unwrap().as_str().to_string();
// Only capture top-level (indentation 0) or exported
let is_top_level = line.starts_with(|c: char| !c.is_whitespace())
|| trimmed.starts_with("export");
let is_top_level =
line.starts_with(|c: char| !c.is_whitespace()) || trimmed.starts_with("export");
if is_top_level {
let kind = if trimmed.contains("const ") {
SymbolKind::Constant
@@ -963,10 +960,7 @@ fn extract_python(content: &str, rel_path: &str) -> Vec<CodeSymbol> {
&& !trimmed.contains("==");
if is_top_level {
let name = trimmed.split('=').next().unwrap_or("").trim().to_string();
if !name.is_empty()
&& !name.starts_with('_')
&& !name.contains(' ')
{
if !name.is_empty() && !name.starts_with('_') && !name.contains(' ') {
let kind = if name.chars().all(|c| c.is_uppercase() || c == '_') {
SymbolKind::Constant
} else {
@@ -1157,10 +1151,7 @@ pub fn format_symbol_listing(index: &SymbolIndex) -> String {
std::collections::BTreeMap::new();
for sym in syms {
let kind_str = sym.kind.to_string();
by_kind
.entry(kind_str)
.or_default()
.push(sym.name.as_str());
by_kind.entry(kind_str).or_default().push(sym.name.as_str());
}
for (kind, names) in &by_kind {
out.push_str(&format!(" {kind}: {}\n", names.join(", ")));
@@ -1312,7 +1303,11 @@ impl Tool for SemanticSearch {
}
let total = index.len();
info!(matched = filtered.len(), total_indexed = total, "semantic search completed");
info!(
matched = filtered.len(),
total_indexed = total,
"semantic search completed"
);
let mut by_file: std::collections::BTreeMap<String, Vec<&&CodeSymbol>> =
std::collections::BTreeMap::new();
@@ -1350,7 +1345,11 @@ impl Tool for SemanticSearch {
sym.line,
sym.context.trim(),
doc_str,
if sym.context.trim().len() > 80 { "" } else { "" }
if sym.context.trim().len() > 80 {
""
} else {
""
}
));
}
output.push('\n');
@@ -1574,12 +1573,7 @@ impl Tool for ListSymbols {
for (file, file_syms) in &by_file {
out.push_str(&format!("`{file}`:\n"));
for sym in file_syms {
out.push_str(&format!(
" `{}` {} L{}\n",
sym.kind,
sym.name,
sym.line,
));
out.push_str(&format!(" `{}` {} L{}\n", sym.kind, sym.name, sym.line,));
}
}
out.push('\n');
@@ -1633,51 +1627,82 @@ mod tests {
fn test_extract_typescript_function_and_class() {
let content = "function hello() {}\nexport class User {}\ninterface Person {}\n";
let symbols = extract_typescript(content, "test.ts");
assert!(symbols.iter().any(|s| s.name == "hello" && s.kind == SymbolKind::Function));
assert!(symbols.iter().any(|s| s.name == "User" && s.kind == SymbolKind::Class));
assert!(symbols.iter().any(|s| s.name == "Person" && s.kind == SymbolKind::Interface));
assert!(symbols
.iter()
.any(|s| s.name == "hello" && s.kind == SymbolKind::Function));
assert!(symbols
.iter()
.any(|s| s.name == "User" && s.kind == SymbolKind::Class));
assert!(symbols
.iter()
.any(|s| s.name == "Person" && s.kind == SymbolKind::Interface));
}
#[test]
fn test_extract_typescript_const() {
let content = "export const API_URL = 'http://example.com';\nconst MAX_RETRIES = 3;\n";
let symbols = extract_typescript(content, "test.ts");
assert!(symbols.iter().any(|s| s.name == "API_URL" && s.kind == SymbolKind::Constant));
assert!(symbols.iter().any(|s| s.name == "MAX_RETRIES" && s.kind == SymbolKind::Constant));
assert!(symbols
.iter()
.any(|s| s.name == "API_URL" && s.kind == SymbolKind::Constant));
assert!(symbols
.iter()
.any(|s| s.name == "MAX_RETRIES" && s.kind == SymbolKind::Constant));
}
#[test]
fn test_extract_python_def_and_class() {
let content = "class MyClass:\n def method(self):\n pass\n\ndef top_func():\n pass\n";
let content =
"class MyClass:\n def method(self):\n pass\n\ndef top_func():\n pass\n";
let symbols = extract_python(content, "test.py");
assert!(symbols.iter().any(|s| s.name == "MyClass" && s.kind == SymbolKind::Class));
assert!(symbols.iter().any(|s| s.name == "MyClass.method" && s.kind == SymbolKind::Function));
assert!(symbols.iter().any(|s| s.name == "top_func" && s.kind == SymbolKind::Function));
assert!(symbols
.iter()
.any(|s| s.name == "MyClass" && s.kind == SymbolKind::Class));
assert!(symbols
.iter()
.any(|s| s.name == "MyClass.method" && s.kind == SymbolKind::Function));
assert!(symbols
.iter()
.any(|s| s.name == "top_func" && s.kind == SymbolKind::Function));
}
#[test]
fn test_extract_python_variable() {
let content = "DATABASE_URL = 'postgres://localhost'\nconfig_path = '/etc/app'\n";
let symbols = extract_python(content, "test.py");
assert!(symbols.iter().any(|s| s.name == "DATABASE_URL" && s.kind == SymbolKind::Constant));
assert!(symbols.iter().any(|s| s.name == "config_path" && s.kind == SymbolKind::Variable));
assert!(symbols
.iter()
.any(|s| s.name == "DATABASE_URL" && s.kind == SymbolKind::Constant));
assert!(symbols
.iter()
.any(|s| s.name == "config_path" && s.kind == SymbolKind::Variable));
}
#[test]
fn test_extract_go_func_and_struct() {
let content = "func main() {}\nfunc (s *Server) Serve() {}\ntype Config struct {\n Name string\n}\n";
let symbols = extract_go(content, "test.go");
assert!(symbols.iter().any(|s| s.name == "main" && s.kind == SymbolKind::Function));
assert!(symbols.iter().any(|s| s.name == "Serve" && s.kind == SymbolKind::Function));
assert!(symbols.iter().any(|s| s.name == "Config" && s.kind == SymbolKind::Struct));
assert!(symbols
.iter()
.any(|s| s.name == "main" && s.kind == SymbolKind::Function));
assert!(symbols
.iter()
.any(|s| s.name == "Serve" && s.kind == SymbolKind::Function));
assert!(symbols
.iter()
.any(|s| s.name == "Config" && s.kind == SymbolKind::Struct));
}
#[test]
fn test_extract_go_const_and_var() {
let content = "const VERSION = \"1.0\"\nvar DefaultPort = 8080\n";
let symbols = extract_go(content, "test.go");
assert!(symbols.iter().any(|s| s.name == "VERSION" && s.kind == SymbolKind::Constant));
assert!(symbols.iter().any(|s| s.name == "DefaultPort" && s.kind == SymbolKind::Variable));
assert!(symbols
.iter()
.any(|s| s.name == "VERSION" && s.kind == SymbolKind::Constant));
assert!(symbols
.iter()
.any(|s| s.name == "DefaultPort" && s.kind == SymbolKind::Variable));
}
#[test]
+6 -2
View File
@@ -89,9 +89,13 @@ impl Tool for Bash {
let start = std::time::Instant::now();
let timeout = Duration::from_millis(timeout_ms);
let mut child_stdout = child.stdout.take()
let mut child_stdout = child
.stdout
.take()
.ok_or_else(|| anyhow::anyhow!("failed to capture stdout"))?;
let mut child_stderr = child.stderr.take()
let mut child_stderr = child
.stderr
.take()
.ok_or_else(|| anyhow::anyhow!("failed to capture stderr"))?;
let stdout_handle = std::thread::spawn(move || -> std::io::Result<Vec<u8>> {
@@ -33,8 +33,10 @@ pub fn is_credential_path(path: &str) -> bool {
/// return list of suspected credential reads.
#[instrument(skip(cmd))]
pub fn check_credential_read(cmd: &str) -> Vec<String> {
let re = Regex::new(r#"(?i)(?:cat|head|tail|less|more|vim?|nano|xdg-open|open|type|echo)\s+(~?/[\w/.@-]+)"#)
.expect("hardcoded credential-read regex is valid");
let re = Regex::new(
r#"(?i)(?:cat|head|tail|less|more|vim?|nano|xdg-open|open|type|echo)\s+(~?/[\w/.@-]+)"#,
)
.expect("hardcoded credential-read regex is valid");
let mut findings = Vec::new();
for cap in re.captures_iter(cmd) {
let path = cap.get(1).map(|m| m.as_str()).unwrap_or("");
@@ -28,10 +28,7 @@ pub fn check_git_destructive(cmd: &str) -> Result<(), String> {
for pattern in &destructive_patterns {
if cmd_lower.contains(pattern) {
return Err(format!(
"destructive git operation blocked: '{}'",
pattern
));
return Err(format!("destructive git operation blocked: '{}'", pattern));
}
}
+6 -4
View File
@@ -32,9 +32,7 @@ pub fn execute_cmd(cmd: &mut std::process::Command) -> Result<String> {
let combined = if stderr.is_empty() {
stdout
} else {
format!("{}\n{}", stdout, stderr)
.trim()
.to_string()
format!("{}\n{}", stdout, stderr).trim().to_string()
};
let code = output.status.code().unwrap_or(-1);
if output.status.success() {
@@ -151,6 +149,10 @@ pub fn log_write_edit_tool(
let _ = repo.append(session_dir, &mut el, entry);
debug!(tool = tool_name, path = path, "edit-log entry persisted");
} else {
warn!(tool = tool_name, path = path, "failed to open edit-log repository");
warn!(
tool = tool_name,
path = path,
"failed to open edit-log repository"
);
}
}
@@ -42,28 +42,33 @@ impl Tool for Todofinish {
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
let item = crate::tools::arg_str(args, "item")?;
info!(item, "todofinish invoked");
let todo_path = ctx.session_dir.join("TODO.md");
let mut content = std::fs::read_to_string(&todo_path).unwrap_or_default();
// Very basic replace to mark as finished
let mut replaced = false;
let lines: Vec<&str> = content.lines().collect();
let mut new_lines = Vec::new();
for line in lines {
if line.contains(&item) && line.starts_with("- [") {
let s = line.replacen("- [high]", "- [x]", 1)
.replacen("- [medium]", "- [x]", 1)
.replacen("- [low]", "- [x]", 1);
let s = line
.replacen("- [high]", "- [x]", 1)
.replacen("- [medium]", "- [x]", 1)
.replacen("- [low]", "- [x]", 1);
// if it didn't match those, just replace the first `[`
let s = if s == line { line.replacen("[ ]", "[x]", 1) } else { s };
let s = if s == line {
line.replacen("[ ]", "[x]", 1)
} else {
s
};
new_lines.push(s);
replaced = true;
} else {
new_lines.push(line.to_string());
}
}
if replaced {
content = new_lines.join("\n") + "\n";
let _ = std::fs::write(&todo_path, &content);
@@ -76,7 +81,7 @@ impl Tool for Todofinish {
} else {
info!(item, "TODO item not found in TODO.md — nothing to mark");
}
Ok(format!("TODO completed: {}", item))
}
}
@@ -54,11 +54,11 @@ impl Tool for Todowrite {
info!(item, priority, "todowrite invoked");
let todo_line = format!("- [{}] {}\n", priority, item);
let todo_path = ctx.session_dir.join("TODO.md");
let mut content = std::fs::read_to_string(&todo_path).unwrap_or_default();
content.push_str(&todo_line);
let _ = std::fs::write(&todo_path, &content);
if let Some(events) = &ctx.turn_events {
+8 -15
View File
@@ -75,8 +75,8 @@ impl Tool for WebSearch {
info!(query = %query, max_results, fetch_content, categories = %categories, "web search starting");
let searxng_url = std::env::var("SEARXNG_URL")
.unwrap_or_else(|_| DEFAULT_SEARXNG_URL.to_string());
let searxng_url =
std::env::var("SEARXNG_URL").unwrap_or_else(|_| DEFAULT_SEARXNG_URL.to_string());
// Build the SearXNG JSON search URL
let search_url = format!(
@@ -138,10 +138,7 @@ impl Tool for WebSearch {
.get("title")
.and_then(|v| v.as_str())
.unwrap_or("Untitled");
let url = result
.get("url")
.and_then(|v| v.as_str())
.unwrap_or("");
let url = result.get("url").and_then(|v| v.as_str()).unwrap_or("");
let snippet = result
.get("content")
.and_then(|v| v.as_str())
@@ -176,7 +173,9 @@ impl Tool for WebSearch {
// Add suggestion for more specific queries if very few results
if result_count < 3 {
output.push_str("---\n*Few results. Try a more specific query or different categories.*\n");
output.push_str(
"---\n*Few results. Try a more specific query or different categories.*\n",
);
}
Ok(output)
@@ -262,10 +261,7 @@ fn fetch_page_content(url: &str, client: &reqwest::blocking::Client) -> Result<S
}
// Clean up whitespace
let cleaned_text = text
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
let cleaned_text = text.split_whitespace().collect::<Vec<_>>().join(" ");
if cleaned_text.is_empty() {
anyhow::bail!("no readable content found on page");
@@ -276,8 +272,5 @@ fn fetch_page_content(url: &str, client: &reqwest::blocking::Client) -> Result<S
/// Limit a string to at most `max_lines` lines.
fn limit_lines(s: &str, max_lines: usize) -> String {
s.lines()
.take(max_lines)
.collect::<Vec<_>>()
.join("\n")
s.lines().take(max_lines).collect::<Vec<_>>().join("\n")
}
+8 -10
View File
@@ -239,13 +239,8 @@ impl Tool for HiveMind {
.map(|arr| {
arr.iter()
.filter_map(|d| {
let directive = d
.get("directive")
.and_then(|v| v.as_str())?;
let access = d
.get("access")
.and_then(|v| v.as_str())
.unwrap_or("read");
let directive = d.get("directive").and_then(|v| v.as_str())?;
let access = d.get("access").and_then(|v| v.as_str()).unwrap_or("read");
Some(NodeDirective {
directive: directive.to_string(),
access_tier: access.to_string(),
@@ -255,14 +250,17 @@ impl Tool for HiveMind {
})
.unwrap_or_default();
info!(cycle_index = cycle_idx, node_count = directives.len(), "executing hive-mind cycle");
info!(
cycle_index = cycle_idx,
node_count = directives.len(),
"executing hive-mind cycle"
);
let cycle = CognitiveCycle {
index: cycle_idx as u32,
directives,
};
let nodes: Vec<NodeOutput> =
rt.block_on(async { execute_cycle(&cycle, ctx).await })?;
let nodes: Vec<NodeOutput> = rt.block_on(async { execute_cycle(&cycle, ctx).await })?;
all_node_outputs.extend(nodes);
}