diff --git a/crates/miyabi-core/src/agent/approval.rs b/crates/miyabi-core/src/agent/approval.rs index 21aac52..12f6946 100644 --- a/crates/miyabi-core/src/agent/approval.rs +++ b/crates/miyabi-core/src/agent/approval.rs @@ -92,13 +92,11 @@ impl ApprovalCallback for RejectHighRisk { async fn request_approval(&self, request: &ApprovalRequest) -> ApprovalDecision { match request.risk_level { RiskLevel::Low | RiskLevel::Medium => ApprovalDecision::Approved, - RiskLevel::High | RiskLevel::Critical => { - ApprovalDecision::Rejected(Some(format!( - "Tool {} requires manual approval (risk level: {})", - request.name, - request.risk_level.as_str() - ))) - } + RiskLevel::High | RiskLevel::Critical => ApprovalDecision::Rejected(Some(format!( + "Tool {} requires manual approval (risk level: {})", + request.name, + request.risk_level.as_str() + ))), } } } @@ -187,7 +185,10 @@ mod tests { risk_level: RiskLevel::Low, description: "Read file".to_string(), }; - assert_eq!(approver.request_approval(&low_risk).await, ApprovalDecision::Approved); + assert_eq!( + approver.request_approval(&low_risk).await, + ApprovalDecision::Approved + ); // High risk should be rejected let high_risk = ApprovalRequest { diff --git a/crates/miyabi-core/src/agent/core.rs b/crates/miyabi-core/src/agent/core.rs index 24b979b..2e50b2b 100644 --- a/crates/miyabi-core/src/agent/core.rs +++ b/crates/miyabi-core/src/agent/core.rs @@ -153,9 +153,10 @@ impl Agent { /// Main agent execution loop pub async fn run(&self, prompt: &str) -> Result { // Execute SessionStart hooks - let session_context = HookContext::new() - .with_data("prompt", prompt); - self.hook_manager.execute(&HookEvent::SessionStart, &session_context).await; + let session_context = HookContext::new().with_data("prompt", prompt); + self.hook_manager + .execute(&HookEvent::SessionStart, &session_context) + .await; self.emit_event(AgentEvent::Started { prompt: prompt.to_string(), @@ -220,7 +221,9 @@ impl Agent { let end_context = HookContext::new() .with_data("iterations", &result.iterations.to_string()) .with_data("tool_calls", &result.tool_calls.to_string()); - self.hook_manager.execute(&HookEvent::SessionEnd, &end_context).await; + self.hook_manager + .execute(&HookEvent::SessionEnd, &end_context) + .await; return Ok(result); } @@ -302,7 +305,9 @@ impl Agent { let pre_tool_context = HookContext::new() .with_tool(&tool_use.name) .with_data("input", &tool_use.input.to_string()); - self.hook_manager.execute(&HookEvent::PreTool, &pre_tool_context).await; + self.hook_manager + .execute(&HookEvent::PreTool, &pre_tool_context) + .await; // Execute tool self.emit_event(AgentEvent::ToolExecuting { @@ -327,7 +332,9 @@ impl Agent { let post_tool_context = HookContext::new() .with_tool(&tool_use.name) .with_result(&output.content.to_string()); - self.hook_manager.execute(&HookEvent::PostTool, &post_tool_context).await; + self.hook_manager + .execute(&HookEvent::PostTool, &post_tool_context) + .await; // Create tool result let content = serde_json::to_string(&output.content) @@ -349,7 +356,9 @@ impl Agent { let error_context = HookContext::new() .with_tool(&tool_use.name) .with_error(&e.to_string()); - self.hook_manager.execute(&HookEvent::OnError, &error_context).await; + self.hook_manager + .execute(&HookEvent::OnError, &error_context) + .await; // Add error as tool result results.push(ContentBlock::ToolResult { @@ -397,7 +406,9 @@ impl Agent { let end_context = HookContext::new() .with_data("iterations", &result.iterations.to_string()) .with_data("tool_calls", &result.tool_calls.to_string()); - self.hook_manager.execute(&HookEvent::SessionEnd, &end_context).await; + self.hook_manager + .execute(&HookEvent::SessionEnd, &end_context) + .await; return Ok(result); } diff --git a/crates/miyabi-core/src/agent/executor.rs b/crates/miyabi-core/src/agent/executor.rs index c2a0867..2584644 100644 --- a/crates/miyabi-core/src/agent/executor.rs +++ b/crates/miyabi-core/src/agent/executor.rs @@ -134,11 +134,7 @@ pub struct McpToolExecutor { } impl McpToolExecutor { - pub fn new( - server_name: String, - tool: McpTool, - manager: Arc>, - ) -> Self { + pub fn new(server_name: String, tool: McpTool, manager: Arc>) -> Self { Self { server_name, tool_name: tool.name, @@ -178,10 +174,13 @@ impl ToolExecutor for McpToolExecutor { async fn execute(&self, input: Value) -> Result { let mut manager = self.manager.lock().await; - match manager.call_tool(&self.server_name, &self.tool_name, input).await { + match manager + .call_tool(&self.server_name, &self.tool_name, input) + .await + { Ok(result) => { - let content = serde_json::to_string_pretty(&result) - .unwrap_or_else(|_| result.to_string()); + let content = + serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string()); Ok(ToolOutput::success(content)) } Err(e) => Err(ToolError::ExecutionFailed(e.to_string())), @@ -270,7 +269,10 @@ impl ExecutorRegistry { } /// Register MCP tools from an MCP manager - pub async fn register_mcp_tools(&mut self, manager: Arc>) -> Result { + pub async fn register_mcp_tools( + &mut self, + manager: Arc>, + ) -> Result { let mut count = 0; // Get all tools from all servers @@ -283,11 +285,7 @@ impl ExecutorRegistry { for (server_name, tools) in all_tools { for tool in tools { - let executor = McpToolExecutor::new( - server_name.clone(), - tool, - manager.clone(), - ); + let executor = McpToolExecutor::new(server_name.clone(), tool, manager.clone()); self.register(executor); count += 1; } diff --git a/crates/miyabi-core/src/agent/tests.rs b/crates/miyabi-core/src/agent/tests.rs index f38f2d2..10d0a1c 100644 --- a/crates/miyabi-core/src/agent/tests.rs +++ b/crates/miyabi-core/src/agent/tests.rs @@ -13,8 +13,7 @@ mod approval_integration_tests { let registry = ExecutorRegistry::with_standard_tools(); // Agent should be created successfully with AutoApproveAll callback - let _agent = Agent::new(client, registry) - .with_approval_callback(AutoApproveAll); + let _agent = Agent::new(client, registry).with_approval_callback(AutoApproveAll); } #[test] @@ -23,8 +22,7 @@ mod approval_integration_tests { let registry = ExecutorRegistry::with_standard_tools(); // Agent should be created successfully with RejectHighRisk callback - let _agent = Agent::new(client, registry) - .with_approval_callback(RejectHighRisk); + let _agent = Agent::new(client, registry).with_approval_callback(RejectHighRisk); } #[test] diff --git a/crates/miyabi-core/src/cache.rs b/crates/miyabi-core/src/cache.rs index 1d33f67..201ac99 100644 --- a/crates/miyabi-core/src/cache.rs +++ b/crates/miyabi-core/src/cache.rs @@ -257,7 +257,9 @@ mod tests { let cache = TTLCache::new(Duration::from_secs(3600)); // default 1 hour // Insert with shorter custom TTL - cache.insert_with_ttl("key1", "value1", Duration::from_millis(50)).await; + cache + .insert_with_ttl("key1", "value1", Duration::from_millis(50)) + .await; assert_eq!(cache.get(&"key1").await, Some("value1")); sleep(Duration::from_millis(100)).await; @@ -301,7 +303,9 @@ mod tests { let cache = TTLCache::new(Duration::from_millis(50)); cache.insert("key1", "value1").await; - cache.insert_with_ttl("key2", "value2", Duration::from_secs(3600)).await; + cache + .insert_with_ttl("key2", "value2", Duration::from_secs(3600)) + .await; sleep(Duration::from_millis(100)).await; @@ -437,7 +441,9 @@ mod tests { assert!(cache.get(&key).await.is_none()); // Insert - cache.insert(key.clone(), "Response from LLM".to_string()).await; + cache + .insert(key.clone(), "Response from LLM".to_string()) + .await; // Hit let cached = cache.get(&key).await; diff --git a/crates/miyabi-core/src/config.rs b/crates/miyabi-core/src/config.rs index d63ad42..2a86a0b 100644 --- a/crates/miyabi-core/src/config.rs +++ b/crates/miyabi-core/src/config.rs @@ -21,7 +21,6 @@ pub struct Config { pub tools: ToolConfig, } - /// API configuration #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] diff --git a/crates/miyabi-core/src/dag.rs b/crates/miyabi-core/src/dag.rs index bd7555c..02de72b 100644 --- a/crates/miyabi-core/src/dag.rs +++ b/crates/miyabi-core/src/dag.rs @@ -568,8 +568,7 @@ mod tests { #[test] fn test_task_with_priority() { - let task = Task::new("priority-task", "Task with priority") - .with_priority(10); + let task = Task::new("priority-task", "Task with priority").with_priority(10); assert_eq!(task.priority, 10); assert_eq!(task.name, "priority-task"); @@ -577,8 +576,7 @@ mod tests { #[test] fn test_task_with_estimated_time() { - let task = Task::new("timed-task", "Task with time estimate") - .with_estimated_time(120); + let task = Task::new("timed-task", "Task with time estimate").with_estimated_time(120); assert_eq!(task.estimated_time, Some(120)); } diff --git a/crates/miyabi-core/src/error_policy.rs b/crates/miyabi-core/src/error_policy.rs index 7f47921..3e602f8 100644 --- a/crates/miyabi-core/src/error_policy.rs +++ b/crates/miyabi-core/src/error_policy.rs @@ -315,7 +315,9 @@ mod tests { fn test_fallback_strategy_lower_temperature() { let strategy = FallbackStrategy::lower_temperature(); match strategy { - FallbackStrategy::RetryWithLowerTemperature { temperature_reduction } => { + FallbackStrategy::RetryWithLowerTemperature { + temperature_reduction, + } => { assert_eq!(temperature_reduction, 0.2); } _ => panic!("Expected RetryWithLowerTemperature"), diff --git a/crates/miyabi-core/src/feature_flags.rs b/crates/miyabi-core/src/feature_flags.rs index 3c4569f..734bd08 100644 --- a/crates/miyabi-core/src/feature_flags.rs +++ b/crates/miyabi-core/src/feature_flags.rs @@ -66,7 +66,10 @@ impl FeatureFlagManager { /// `true` if the flag exists and is enabled, `false` otherwise pub fn is_enabled(&self, flag_name: &str) -> bool { let flags = self.flags.read().unwrap(); - flags.get(flag_name).map(|flag| flag.enabled).unwrap_or(false) + flags + .get(flag_name) + .map(|flag| flag.enabled) + .unwrap_or(false) } /// Set a feature flag @@ -190,7 +193,10 @@ impl FeatureFlagManager { /// HashMap of flag names to enabled status pub fn export_to_map(&self) -> HashMap { let flags = self.flags.read().unwrap(); - flags.iter().map(|(name, flag)| (name.clone(), flag.enabled)).collect() + flags + .iter() + .map(|(name, flag)| (name.clone(), flag.enabled)) + .collect() } /// Clear all feature flags diff --git a/crates/miyabi-core/src/git.rs b/crates/miyabi-core/src/git.rs index 60e7a83..a942275 100644 --- a/crates/miyabi-core/src/git.rs +++ b/crates/miyabi-core/src/git.rs @@ -29,14 +29,12 @@ pub fn find_git_root(start_path: Option<&Path>) -> Result { }; match git2::Repository::discover(&search_path) { - Ok(repo) => repo - .workdir() - .map(|p| p.to_path_buf()) - .ok_or_else(|| { - Error::Git( - "Repository is bare (no working directory). Miyabi requires a non-bare repository.".to_string() - ) - }), + Ok(repo) => repo.workdir().map(|p| p.to_path_buf()).ok_or_else(|| { + Error::Git( + "Repository is bare (no working directory). Miyabi requires a non-bare repository." + .to_string(), + ) + }), Err(e) => Err(Error::Git(format!( "Not in a Git repository. Please run miyabi from within a Git repository.\n\ Searched from: {:?}\n\ diff --git a/crates/miyabi-core/src/github.rs b/crates/miyabi-core/src/github.rs index e25615c..83adc60 100644 --- a/crates/miyabi-core/src/github.rs +++ b/crates/miyabi-core/src/github.rs @@ -139,8 +139,7 @@ impl GitHubClient { /// Create from environment variables pub fn from_env() -> Result { - let token = std::env::var("GITHUB_TOKEN") - .map_err(|_| anyhow!("GITHUB_TOKEN not set"))?; + let token = std::env::var("GITHUB_TOKEN").map_err(|_| anyhow!("GITHUB_TOKEN not set"))?; let repo = std::env::var("GITHUB_REPOSITORY") .or_else(|_| std::env::var("REPOSITORY")) .map_err(|_| anyhow!("GITHUB_REPOSITORY or REPOSITORY not set"))?; @@ -156,7 +155,11 @@ impl GitHubClient { // ========== Issues ========== /// List issues - pub async fn list_issues(&self, state: Option<&str>, labels: Option<&str>) -> Result> { + pub async fn list_issues( + &self, + state: Option<&str>, + labels: Option<&str>, + ) -> Result> { let mut url = format!( "{}/repos/{}/{}/issues", self.base_url, self.owner, self.repo @@ -230,7 +233,8 @@ impl GitHubClient { self.base_url, self.owner, self.repo, issue_number ); - let response = self.client + let response = self + .client .post(&url) .json(&serde_json::json!({ "labels": labels })) .send() @@ -253,7 +257,8 @@ impl GitHubClient { self.base_url, self.owner, self.repo, number ); - let response = self.client + let response = self + .client .patch(&url) .json(&serde_json::json!({ "state": "closed" })) .send() @@ -273,10 +278,7 @@ impl GitHubClient { /// List pull requests pub async fn list_pull_requests(&self, state: Option<&str>) -> Result> { - let mut url = format!( - "{}/repos/{}/{}/pulls", - self.base_url, self.owner, self.repo - ); + let mut url = format!("{}/repos/{}/{}/pulls", self.base_url, self.owner, self.repo); if let Some(s) = state { url = format!("{}?state={}", url, s); @@ -314,11 +316,11 @@ impl GitHubClient { } /// Create a pull request - pub async fn create_pull_request(&self, request: CreatePullRequestRequest) -> Result { - let url = format!( - "{}/repos/{}/{}/pulls", - self.base_url, self.owner, self.repo - ); + pub async fn create_pull_request( + &self, + request: CreatePullRequestRequest, + ) -> Result { + let url = format!("{}/repos/{}/{}/pulls", self.base_url, self.owner, self.repo); let response = self.client.post(&url).json(&request).send().await?; @@ -333,7 +335,11 @@ impl GitHubClient { } /// Merge a pull request - pub async fn merge_pull_request(&self, number: u64, commit_message: Option<&str>) -> Result<()> { + pub async fn merge_pull_request( + &self, + number: u64, + commit_message: Option<&str>, + ) -> Result<()> { let url = format!( "{}/repos/{}/{}/pulls/{}/merge", self.base_url, self.owner, self.repo, number @@ -403,10 +409,7 @@ impl GitHubClient { /// Get repository information pub async fn get_repo(&self) -> Result { - let url = format!( - "{}/repos/{}/{}", - self.base_url, self.owner, self.repo - ); + let url = format!("{}/repos/{}/{}", self.base_url, self.owner, self.repo); let response = self.client.get(&url).send().await?; diff --git a/crates/miyabi-core/src/hooks.rs b/crates/miyabi-core/src/hooks.rs index c9a96d4..a856b3a 100644 --- a/crates/miyabi-core/src/hooks.rs +++ b/crates/miyabi-core/src/hooks.rs @@ -270,7 +270,11 @@ impl HookManager { let expanded_title = self.expand_variables(title, context); let expanded_message = self.expand_variables(message, context); // For now, just log the notification - tracing::info!("Hook notification: {} - {}", expanded_title, expanded_message); + tracing::info!( + "Hook notification: {} - {}", + expanded_title, + expanded_message + ); HookResult { hook_name: hook.name.clone(), success: true, diff --git a/crates/miyabi-core/src/mcp.rs b/crates/miyabi-core/src/mcp.rs index 0cf5efb..9fd4dc5 100644 --- a/crates/miyabi-core/src/mcp.rs +++ b/crates/miyabi-core/src/mcp.rs @@ -136,7 +136,11 @@ impl McpServer { } /// Send a request and get a response - pub async fn request(&mut self, method: &str, params: Option) -> Result { + pub async fn request( + &mut self, + method: &str, + params: Option, + ) -> Result { self.request_id += 1; let request = McpRequest { jsonrpc: "2.0".to_string(), @@ -145,9 +149,15 @@ impl McpServer { params, }; - let stdin = self.process.stdin.as_mut() + let stdin = self + .process + .stdin + .as_mut() .ok_or_else(|| anyhow::anyhow!("Failed to get stdin"))?; - let stdout = self.process.stdout.as_mut() + let stdout = self + .process + .stdout + .as_mut() .ok_or_else(|| anyhow::anyhow!("Failed to get stdout"))?; // Send request @@ -183,7 +193,9 @@ impl McpServer { return Err(anyhow::anyhow!("MCP error: {}", error.message)); } - response.result.ok_or_else(|| anyhow::anyhow!("No result in response")) + response + .result + .ok_or_else(|| anyhow::anyhow!("No result in response")) } /// List available tools @@ -193,16 +205,25 @@ impl McpServer { return Err(anyhow::anyhow!("MCP error: {}", error.message)); } - let result = response.result.ok_or_else(|| anyhow::anyhow!("No result"))?; + let result = response + .result + .ok_or_else(|| anyhow::anyhow!("No result"))?; let tools: Vec = serde_json::from_value( - result.get("tools").cloned().unwrap_or(serde_json::Value::Array(vec![])) + result + .get("tools") + .cloned() + .unwrap_or(serde_json::Value::Array(vec![])), )?; Ok(tools) } /// Call a tool - pub async fn call_tool(&mut self, name: &str, arguments: serde_json::Value) -> Result { + pub async fn call_tool( + &mut self, + name: &str, + arguments: serde_json::Value, + ) -> Result { let params = serde_json::json!({ "name": name, "arguments": arguments @@ -424,7 +445,10 @@ command: node let config = McpServerConfig { name: "filesystem".to_string(), command: "npx".to_string(), - args: vec!["-y".to_string(), "@anthropic/mcp-server-filesystem".to_string()], + args: vec![ + "-y".to_string(), + "@anthropic/mcp-server-filesystem".to_string(), + ], env: { let mut env = HashMap::new(); env.insert("HOME".to_string(), "/tmp".to_string()); @@ -616,15 +640,13 @@ command: node #[test] fn test_mcp_manager_is_running() { let config = McpConfig { - servers: vec![ - McpServerConfig { - name: "server1".to_string(), - command: "cmd".to_string(), - args: vec![], - env: HashMap::new(), - auto_start: false, - }, - ], + servers: vec![McpServerConfig { + name: "server1".to_string(), + command: "cmd".to_string(), + args: vec![], + env: HashMap::new(), + auto_start: false, + }], }; let manager = McpManager::with_config(config); diff --git a/crates/miyabi-core/src/openclaw.rs b/crates/miyabi-core/src/openclaw.rs index 5d7215b..44291f8 100644 --- a/crates/miyabi-core/src/openclaw.rs +++ b/crates/miyabi-core/src/openclaw.rs @@ -36,7 +36,8 @@ impl OpenClawClient { message: message.to_string(), }; - let response = self.client + let response = self + .client .post(format!("{}/api/message", self.gateway_url)) .header("Authorization", format!("Bearer {}", self.token)) .json(&payload) diff --git a/crates/miyabi-core/src/orchestration.rs b/crates/miyabi-core/src/orchestration.rs index cfb72b1..6fce1ca 100644 --- a/crates/miyabi-core/src/orchestration.rs +++ b/crates/miyabi-core/src/orchestration.rs @@ -327,8 +327,8 @@ mod tests { #[test] fn test_orchestrator_task_with_system_prompt() { - let task = OrchestratorTask::new("task-1", "Do something") - .with_system_prompt("You are helpful"); + let task = + OrchestratorTask::new("task-1", "Do something").with_system_prompt("You are helpful"); assert_eq!(task.system_prompt, Some("You are helpful".to_string())); } diff --git a/crates/miyabi-core/src/plugin.rs b/crates/miyabi-core/src/plugin.rs index f14beb9..1c07dd7 100644 --- a/crates/miyabi-core/src/plugin.rs +++ b/crates/miyabi-core/src/plugin.rs @@ -178,8 +178,9 @@ impl PluginManager { pub fn unregister(&self, name: &str) -> Result<()> { let mut plugins = self.plugins.write().unwrap(); - let mut entry = - plugins.remove(name).ok_or_else(|| anyhow!("Plugin '{}' not found", name))?; + let mut entry = plugins + .remove(name) + .ok_or_else(|| anyhow!("Plugin '{}' not found", name))?; entry.plugin.shutdown()?; entry.state = PluginState::Shutdown; @@ -198,10 +199,16 @@ impl PluginManager { pub fn execute(&self, name: &str, context: &PluginContext) -> Result { let plugins = self.plugins.read().unwrap(); - let entry = plugins.get(name).ok_or_else(|| anyhow!("Plugin '{}' not found", name))?; + let entry = plugins + .get(name) + .ok_or_else(|| anyhow!("Plugin '{}' not found", name))?; if entry.state != PluginState::Initialized { - return Err(anyhow!("Plugin '{}' is not initialized (state: {:?})", name, entry.state)); + return Err(anyhow!( + "Plugin '{}' is not initialized (state: {:?})", + name, + entry.state + )); } entry.plugin.execute(context) @@ -210,7 +217,10 @@ impl PluginManager { /// Lists all registered plugins pub fn list_plugins(&self) -> Vec { let plugins = self.plugins.read().unwrap(); - plugins.values().map(|entry| entry.plugin.metadata()).collect() + plugins + .values() + .map(|entry| entry.plugin.metadata()) + .collect() } /// Gets plugin metadata diff --git a/crates/miyabi-core/src/retry.rs b/crates/miyabi-core/src/retry.rs index 7123786..c7b6f9d 100644 --- a/crates/miyabi-core/src/retry.rs +++ b/crates/miyabi-core/src/retry.rs @@ -305,10 +305,7 @@ mod tests { async fn test_retry_returns_correct_value() { let config = RetryConfig::new(1, 10, 100); - let result = retry_with_backoff(config, || async { - Ok::(42) - }) - .await; + let result = retry_with_backoff(config, || async { Ok::(42) }).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), 42); diff --git a/crates/miyabi-core/src/rules.rs b/crates/miyabi-core/src/rules.rs index 65b6aba..6f7a646 100644 --- a/crates/miyabi-core/src/rules.rs +++ b/crates/miyabi-core/src/rules.rs @@ -649,7 +649,11 @@ settings: let loader = RulesLoader::new(temp_dir.path().to_path_buf()); // Invalid YAML - std::fs::write(temp_dir.path().join(".miyabirules"), "invalid: yaml: content:").unwrap(); + std::fs::write( + temp_dir.path().join(".miyabirules"), + "invalid: yaml: content:", + ) + .unwrap(); let result = loader.load(); assert!(result.is_err()); diff --git a/crates/miyabi-core/src/session.rs b/crates/miyabi-core/src/session.rs index 1c7b355..5877cd8 100644 --- a/crates/miyabi-core/src/session.rs +++ b/crates/miyabi-core/src/session.rs @@ -353,9 +353,11 @@ mod tests { #[test] fn test_session_with_system_prompt() { - let session = Session::new("Test") - .system_prompt("You are a helpful assistant"); - assert_eq!(session.system_prompt, Some("You are a helpful assistant".to_string())); + let session = Session::new("Test").system_prompt("You are a helpful assistant"); + assert_eq!( + session.system_prompt, + Some("You are a helpful assistant".to_string()) + ); } #[test] diff --git a/crates/miyabi-core/src/store.rs b/crates/miyabi-core/src/store.rs index 72cf334..275f681 100644 --- a/crates/miyabi-core/src/store.rs +++ b/crates/miyabi-core/src/store.rs @@ -500,14 +500,22 @@ impl LegacyTasksFile { version: self.version, generated_at: Utc::now(), generated_from_event_id: None, - tasks: self.tasks.into_iter().map(LegacyTask::into_execution_task).collect(), + tasks: self + .tasks + .into_iter() + .map(LegacyTask::into_execution_task) + .collect(), file_locks: HashMap::new(), }; for task in &snapshot.tasks { if let Some(lock) = &task.lock { let owner_parts: Vec<&str> = lock.locked_by.split('@').collect(); - let agent = owner_parts.first().copied().unwrap_or("unknown").to_string(); + let agent = owner_parts + .first() + .copied() + .unwrap_or("unknown") + .to_string(); let node = owner_parts.get(1).copied().unwrap_or("unknown").to_string(); let expires_at = lease_expiry(lock.last_heartbeat, lock.lease_duration_sec); diff --git a/crates/miyabi-core/src/streaming.rs b/crates/miyabi-core/src/streaming.rs index 5e3b281..07e3cc3 100644 --- a/crates/miyabi-core/src/streaming.rs +++ b/crates/miyabi-core/src/streaming.rs @@ -238,12 +238,8 @@ fn convert_agent_event(event: crate::agent::AgentEvent) -> AgentStreamEvent { crate::agent::AgentEvent::TokenUsage { input, output } => { AgentStreamEvent::TokenUsage { input, output } } - crate::agent::AgentEvent::Completed { result } => { - AgentStreamEvent::Completed { result } - } - crate::agent::AgentEvent::Failed { error } => { - AgentStreamEvent::Error { error } - } + crate::agent::AgentEvent::Completed { result } => AgentStreamEvent::Completed { result }, + crate::agent::AgentEvent::Failed { error } => AgentStreamEvent::Error { error }, crate::agent::AgentEvent::ToolApproved { .. } => { // Approval events are internal, map to Started AgentStreamEvent::Started @@ -254,9 +250,7 @@ fn convert_agent_event(event: crate::agent::AgentEvent) -> AgentStreamEvent { crate::agent::AgentEvent::ToolProgress { name, .. } => { AgentStreamEvent::ToolStarted { name } } - crate::agent::AgentEvent::TextDelta { text } => { - AgentStreamEvent::TextChunk { text } - } + crate::agent::AgentEvent::TextDelta { text } => AgentStreamEvent::TextChunk { text }, } } @@ -353,8 +347,7 @@ mod tests { let client = AnthropicClient::new("test-key".to_string()).unwrap(); let registry = ExecutorRegistry::with_standard_tools(); - let agent = StreamingAgent::new(client, registry) - .with_system_prompt("You are helpful"); + let agent = StreamingAgent::new(client, registry).with_system_prompt("You are helpful"); assert_eq!(agent.system_prompt, Some("You are helpful".to_string())); } diff --git a/crates/miyabi-core/src/workflow.rs b/crates/miyabi-core/src/workflow.rs index 03f0e46..e01b468 100644 --- a/crates/miyabi-core/src/workflow.rs +++ b/crates/miyabi-core/src/workflow.rs @@ -77,7 +77,6 @@ pub enum StepCondition { If { expression: String }, } - /// Retry configuration for a step #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RetryConfig { @@ -120,7 +119,6 @@ pub enum FailurePolicy { Cleanup, } - /// Step execution result #[derive(Debug, Clone)] pub struct StepResult { @@ -238,7 +236,11 @@ impl WorkflowManager { for entry in std::fs::read_dir(dir)? { let entry = entry?; let path = entry.path(); - if path.extension().map(|e| e == "yml" || e == "yaml").unwrap_or(false) { + if path + .extension() + .map(|e| e == "yml" || e == "yaml") + .unwrap_or(false) + { if let Ok(name) = self.load_workflow(&path) { loaded.push(name); } @@ -264,7 +266,11 @@ impl WorkflowManager { } /// Execute a workflow - pub async fn execute(&self, name: &str, initial_vars: HashMap) -> Result { + pub async fn execute( + &self, + name: &str, + initial_vars: HashMap, + ) -> Result { let workflow = self .workflows .get(name) @@ -361,7 +367,10 @@ impl WorkflowManager { let status = if all_succeeded { WorkflowStatus::Completed - } else if step_results.iter().any(|r| r.status == StepStatus::Completed) { + } else if step_results + .iter() + .any(|r| r.status == StepStatus::Completed) + { WorkflowStatus::PartiallyCompleted } else { WorkflowStatus::Failed @@ -453,24 +462,20 @@ impl WorkflowManager { // Execute the task let result = match agent.run(&expanded_task).await { - Ok(agent_result) => { - StepResult { - step_id: step.id.clone(), - status: StepStatus::Completed, - output: Some(agent_result.output), - error: None, - duration_ms: step_start.elapsed().as_millis() as u64, - } - } - Err(e) => { - StepResult { - step_id: step.id.clone(), - status: StepStatus::Failed, - output: None, - error: Some(e.to_string()), - duration_ms: step_start.elapsed().as_millis() as u64, - } - } + Ok(agent_result) => StepResult { + step_id: step.id.clone(), + status: StepStatus::Completed, + output: Some(agent_result.output), + error: None, + duration_ms: step_start.elapsed().as_millis() as u64, + }, + Err(e) => StepResult { + step_id: step.id.clone(), + status: StepStatus::Failed, + output: None, + error: Some(e.to_string()), + duration_ms: step_start.elapsed().as_millis() as u64, + }, }; // Store output variable if specified @@ -495,7 +500,10 @@ impl WorkflowManager { let status = if all_succeeded { WorkflowStatus::Completed - } else if step_results.iter().any(|r| r.status == StepStatus::Completed) { + } else if step_results + .iter() + .any(|r| r.status == StepStatus::Completed) + { WorkflowStatus::PartiallyCompleted } else { WorkflowStatus::Failed @@ -517,7 +525,13 @@ impl WorkflowManager { for step in &workflow.steps { if !visited.contains_key(&step.id) { - self.visit_step(workflow, &step.id, &mut visited, &mut temp_visited, &mut order)?; + self.visit_step( + workflow, + &step.id, + &mut visited, + &mut temp_visited, + &mut order, + )?; } } @@ -534,7 +548,10 @@ impl WorkflowManager { order: &mut Vec, ) -> Result<()> { if temp_visited.get(step_id).copied().unwrap_or(false) { - return Err(anyhow::anyhow!("Circular dependency detected at step: {}", step_id)); + return Err(anyhow::anyhow!( + "Circular dependency detected at step: {}", + step_id + )); } if visited.get(step_id).copied().unwrap_or(false) { @@ -561,7 +578,11 @@ impl WorkflowManager { } /// Evaluate a step condition - fn evaluate_condition(&self, condition: &Option, context: &WorkflowContext) -> bool { + fn evaluate_condition( + &self, + condition: &Option, + context: &WorkflowContext, + ) -> bool { match condition { None | Some(StepCondition::Always) => true, Some(StepCondition::OnSuccess) => context @@ -607,19 +628,17 @@ mod tests { let workflow = Workflow { name: "test-workflow".to_string(), description: "Test".to_string(), - steps: vec![ - WorkflowStep { - id: "step1".to_string(), - name: "First Step".to_string(), - agent: Some("code-reviewer".to_string()), - task: "Review code".to_string(), - depends_on: vec![], - condition: None, - timeout: 300, - retry: RetryConfig::default(), - output: Some("review_result".to_string()), - }, - ], + steps: vec![WorkflowStep { + id: "step1".to_string(), + name: "First Step".to_string(), + agent: Some("code-reviewer".to_string()), + task: "Review code".to_string(), + depends_on: vec![], + condition: None, + timeout: 300, + retry: RetryConfig::default(), + output: Some("review_result".to_string()), + }], variables: HashMap::new(), on_failure: FailurePolicy::Stop, }; @@ -815,7 +834,10 @@ mod tests { }; manager.register(workflow); - let result = manager.execute("output-test", HashMap::new()).await.unwrap(); + let result = manager + .execute("output-test", HashMap::new()) + .await + .unwrap(); assert_eq!(result.status, WorkflowStatus::Completed); assert_eq!(result.steps.len(), 2); @@ -834,7 +856,10 @@ mod tests { on_failure: FailurePolicy::Stop, }; - assert_eq!(workflow.variables.get("project"), Some(&"miyabi".to_string())); + assert_eq!( + workflow.variables.get("project"), + Some(&"miyabi".to_string()) + ); } #[test] diff --git a/crates/miyabi-tui/src/app.rs b/crates/miyabi-tui/src/app.rs index d943fd5..033f5d9 100644 --- a/crates/miyabi-tui/src/app.rs +++ b/crates/miyabi-tui/src/app.rs @@ -145,16 +145,15 @@ impl App { Ok(Some(rules)) => { let count = rules.rules.len(); if count > 0 { - view.notifications.info( - "Rules Loaded", - format!("{} project rules active", count), - ); + view.notifications + .info("Rules Loaded", format!("{} project rules active", count)); } Some(rules) } Ok(None) => None, Err(e) => { - view.notifications.error("Rules Error", format!("Failed to load: {}", e)); + view.notifications + .error("Rules Error", format!("Failed to load: {}", e)); None } }; @@ -280,9 +279,10 @@ impl App { .notifications .error("Clipboard Error", e.to_string()); } else { - self.view - .notifications - .info("Copied", format!("{} chars", text.len())); + self.view.notifications.info( + "Copied", + format!("{} chars", text.len()), + ); } } Err(e) => { @@ -305,7 +305,8 @@ impl App { #[cfg(target_os = "macos")] let result = std::process::Command::new("open").arg(&path).spawn(); #[cfg(target_os = "linux")] - let result = std::process::Command::new("xdg-open").arg(&path).spawn(); + let result = + std::process::Command::new("xdg-open").arg(&path).spawn(); #[cfg(target_os = "windows")] let result = std::process::Command::new("cmd") .args(["/C", "start", "", &path]) @@ -316,9 +317,7 @@ impl App { self.view.notifications.info("Opened", &path); } Err(e) => { - self.view - .notifications - .error("Open Error", e.to_string()); + self.view.notifications.error("Open Error", e.to_string()); } } } diff --git a/crates/miyabi-tui/src/chat_composer.rs b/crates/miyabi-tui/src/chat_composer.rs index d249a55..d12877e 100644 --- a/crates/miyabi-tui/src/chat_composer.rs +++ b/crates/miyabi-tui/src/chat_composer.rs @@ -40,8 +40,7 @@ pub enum VimMode { } /// Keybinding style preference -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[derive(Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum KeybindingStyle { /// Standard keybindings #[default] @@ -52,7 +51,6 @@ pub enum KeybindingStyle { Emacs, } - /// Edit operation for undo/redo #[derive(Debug, Clone)] pub enum EditOperation { @@ -1479,12 +1477,14 @@ impl ChatComposer { return Style::default().bg(Color::Rgb(68, 71, 90)).fg(Color::Cyan); } } - if line_idx == self.cursor.line && col == self.cursor.col - && matches!(ch, '(' | ')' | '[' | ']' | '{' | '}' | '<' | '>') { - return Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD); - } + if line_idx == self.cursor.line + && col == self.cursor.col + && matches!(ch, '(' | ')' | '[' | ']' | '{' | '}' | '<' | '>') + { + return Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD); + } // Basic syntax highlighting match ch { diff --git a/crates/miyabi-tui/src/ui/widgets/diff.rs b/crates/miyabi-tui/src/ui/widgets/diff.rs index f3dd7e2..fc29457 100644 --- a/crates/miyabi-tui/src/ui/widgets/diff.rs +++ b/crates/miyabi-tui/src/ui/widgets/diff.rs @@ -36,27 +36,25 @@ pub fn render_diff_widget_with_scroll( // Create a DiffRender with the single file let mut renderer = DiffRender::new(); renderer.files.push(diff.clone()); - + // Get rendered lines let lines = renderer.render(); - + // Create block with optional title let block = if let Some(t) = title { - Block::default() - .borders(Borders::ALL) - .title(t.to_string()) + Block::default().borders(Borders::ALL).title(t.to_string()) } else { Block::default() .borders(Borders::ALL) .title(format!("{} → {}", diff.old_path, diff.new_path)) }; - + // Create paragraph with scroll let paragraph = Paragraph::new(lines) .block(block) .wrap(Wrap { trim: false }) .scroll((scroll, 0)); - + frame.render_widget(paragraph, area); } diff --git a/crates/miyabi-tui/src/ui/widgets/history_list.rs b/crates/miyabi-tui/src/ui/widgets/history_list.rs index e9a99c5..fa931b4 100644 --- a/crates/miyabi-tui/src/ui/widgets/history_list.rs +++ b/crates/miyabi-tui/src/ui/widgets/history_list.rs @@ -65,10 +65,7 @@ impl HistoryList { } } - let list_items: Vec = visible_lines - .into_iter() - .map(ListItem::new) - .collect(); + let list_items: Vec = visible_lines.into_iter().map(ListItem::new).collect(); let list = List::new(list_items).style(Style::default().fg(colors::FG)); frame.render_widget(list, inner); diff --git a/crates/miyabi-tui/src/views.rs b/crates/miyabi-tui/src/views.rs index ea665ad..4737711 100644 --- a/crates/miyabi-tui/src/views.rs +++ b/crates/miyabi-tui/src/views.rs @@ -392,9 +392,11 @@ impl MainView { } _ => {} }, - ActiveOverlay::Help => if self.help_viewer.handle_key(key) == HelpAction::Close { - self.close_overlay(); - }, + ActiveOverlay::Help => { + if self.help_viewer.handle_key(key) == HelpAction::Close { + self.close_overlay(); + } + } ActiveOverlay::Approval => match self.approval_overlay.handle_key(key) { ApprovalAction::Approve(id) | ApprovalAction::ApproveAll(id) => { self.close_overlay(); diff --git a/docs/dtp/PLAYBOOK-v4-complete.md b/docs/dtp/PLAYBOOK-v4-complete.md new file mode 100644 index 0000000..437fc47 --- /dev/null +++ b/docs/dtp/PLAYBOOK-v4-complete.md @@ -0,0 +1,190 @@ +# DTP Playbook v4 — ビジョン完全準拠版 + +_v4: 原則 1(記憶)+ Bus ドッキング + ブランチ戦略を統合。全 38 要素をカバー。_ + +--- + +## North Star + +> 記憶はアタッチメント。ジグで手順を強制。GitHub で事実を確定。 + +--- + +## 完了済み + +| 内容 | コミット | 行数 | +|------|---------|------| +| Phase A: gate + lock + store + protocol | 986d907 | +1,485 | +| Phase B: CLI サブコマンド | 273c416 | +1,171 | +| clippy 修正 | 437b959 | 0 (既に反映済み) | + +--- + +## Sprint 1: GATE 修正 + Phase C(今日) + +> ビジョン対応: 原則 2 (ジグ) を 58% → 90% に + +| # | Issue | タスク | 行数 | +|---|-------|--------|------| +| 1 | #52 | GATE 0: issue=0 拒否 | ~5 | +| 2 | #53 | GATE 5: ブランチ名バリデーション | ~10 | +| 3 | #54 | GATE 3: HIGH risk 承認チェック | ~20 | +| 4 | #55 | GATE 7: merge 後ロック自動解放 + 後続タスク解放 | ~15 | +| 5 | #56 | exit code: GATE 拒否=1, 入力エラー=2 | ~10 | +| 6 | #57 | 依存ブロック assign → exit 1 | ~10 | +| 7 | — | verify_merge: GitHub API で PR merged を検証 | ~50 | +| 8 | — | escape hatch: force_unlock + manual_complete | ~50 | +| 9 | — | E2E テスト: register → done 全シーケンス | ~100 | + +**GATE**: cargo test GREEN + clippy ZERO + E2E テスト GREEN +**タグ**: `v1.0-dtp-complete` + +--- + +## Sprint 2: Bus ドッキング + ブランチ戦略(今週) + +> ビジョン対応: 原則 3 (SSOT) を 44% → 80% に + +| # | Issue | タスク | 行数 | +|---|-------|--------|------| +| 1 | #61 | Bus ドッキング: register → auto enqueue | ~30 | +| 2 | #61 | Bus ドッキング: merge → auto complete | ~20 | +| 3 | #65 | Bus データパス統合 (HAYASHI_SHUNSUKE → 参照) | ~10 | +| 4 | #64 | 並列 Codex ブランチ戦略: assign 時に自動 branch + worktree | ~50 | +| 5 | — | JSON 出力スキーマ統一 | ~30 | +| 6 | — | OpenClaw hooks 連携 | ~50 | +| 7 | — | OpenClaw main 呼び出しテスト | テスト | +| 8 | — | tasks.json memory sync | ~30 | + +**GATE**: Bus enqueue/complete 動作 + OpenClaw main が register→merge 完走 +**タグ**: `v1.1-bus-docked` + +--- + +## Sprint 3: 記憶アタッチメント + ドリーミング(来週) + +> ビジョン対応: 原則 1 (記憶) を 0% → 70% に ← **核心** + +| # | Issue | タスク | 行数 | +|---|-------|--------|------| +| 1 | #58 | attach_context: Issue 本文取得 | ~20 | +| 2 | #58 | attach_context: GNI impact 結果をアタッチ | ~20 | +| 3 | #58 | attach_context: ロック対象ファイルの先頭 50 行抜粋 | ~20 | +| 4 | #58 | attach_context: トークン閾値で切り詰め | ~20 | +| 5 | #58 | tasks.json に context_attachments フィールド追加 | ~15 | +| 6 | #58 | CLI: assign 時に attach_context 自動実行 | ~10 | +| 7 | #58 | CLI: --format json で attachments を出力 | ~10 | +| 8 | #59 | dream: event log replay | ~30 | +| 9 | #59 | dream: パターン抽出 (GATE 拒否頻度、ロック競合) | ~40 | +| 10 | #59 | dream: 学び重要度判定 (High/Medium/Low) | ~20 | +| 11 | #59 | dream: High → docs/ にコミット | ~20 | +| 12 | #59 | dream: Medium → Issue コメントに追記 | ~15 | +| 13 | #59 | dream: Low → アーカイブ | ~10 | +| 14 | #59 | CLI: miyabi gate dream [--since 24h] [--auto] | ~20 | +| 15 | #59 | launchd: 毎晩 03:00 に dream --auto 実行 | plist | +| 16 | #63 | Web ダッシュボード: miyabi gate serve (localhost:4848) | ~100 | +| 17 | — | Heartbeat デーモン (launchd 60秒) | ~50 | +| 18 | — | Telegram 通知 (GATE 通過/拒否) | ~50 | +| 19 | — | VOICEBOX 自動化 (hooks.yaml) | ~20 | +| 20 | — | git 自動同期 (merge 後に auto push) | ~30 | +| 21 | — | Maestro Playbook 登録 | 設定 | + +**GATE**: attach_context が動作 + dream が docs/ に昇格 + Web ダッシュボード表示 +**タグ**: `v1.2-memory-attached` + +--- + +## Sprint 4: シータサイクル + Obsidian + 品質ゲート(今月) + +> ビジョン対応: 原則 1 を 70% → 100% + 自己改善 0% → 100% + +| # | Issue | タスク | 行数 | +|---|-------|--------|------| +| 1 | #60 | シータ θ1: dream に Knowledge Watcher 差分検知を統合 | ~30 | +| 2 | #60 | シータ θ5: skill-bus record-run を dream 内で自動呼出 | ~10 | +| 3 | #60 | シータ θ6: Self-Improving → SKILL.md 自動更新 | ~30 | +| 4 | #60 | シータ θ6: attach_context の選別ルール自動改善 | ~20 | +| 5 | #62 | Obsidian: dream → Vault ノート生成 | ~50 | +| 6 | #62 | Obsidian: wikilink 自動生成 | ~30 | +| 7 | #62 | Obsidian: MOC 自動更新 | ~20 | +| 8 | #62 | Obsidian: Smart Connections 埋め込み再生成トリガー | ~10 | +| 9 | #62 | attach_context: Smart Connections セマンティック検索追加 | ~30 | +| 10 | #62 | attach_context: wikilink 追跡で関連ノート展開 | ~20 | +| 11 | — | rust-ai-pipeline Phase 1 統合 (GATE 4.5) | ~50 | +| 12 | — | proptest 拡張 (gate/lock/store) | ~100 | +| 13 | — | cargo-mutants (mutation score 80%+) | 設定 | + +**GATE**: シータが自律巡回 + Obsidian にノート生成 + mutation 80%+ +**タグ**: `v1.3-theta-obsidian` + +--- + +## Sprint 5: 移行 + 公開(来月以降) + +| # | タスク | +|---|--------| +| 1 | miyabi-private (TS) → Rust 段階的移行 | +| 2 | OpenClaw プラグインとして公開 | +| 3 | npm パッケージ配布 | + +**タグ**: `v2.0-public` + +--- + +## Sprint DAG + +``` +Sprint 1 (#52-57 + Phase C) ← 今日。GATE を固める + │ + ▼ +Sprint 2 (#61,64,65) ← 今週。Bus接続 + ブランチ戦略 + │ + ▼ +Sprint 3 (#58,59,63) ← 来週。★記憶アタッチメント + ドリーミング★ + │ + ▼ +Sprint 4 (#60,62) ← 今月。シータ + Obsidian + 品質ゲート + │ + ▼ +Sprint 5 ← 来月。公開 +``` + +--- + +## ビジョン達成度の推移 + +``` + 原則1 原則2 原則3 可視化 自己改善 全体 +現在: 0% 58% 44% 13% 0% 32% +Sprint1後: 0% 90% 50% 13% 0% 38% +Sprint2後: 0% 95% 80% 13% 0% 47% +Sprint3後: 70% 95% 85% 60% 10% 72% +Sprint4後: 100% 100% 90% 70% 100% 93% +Sprint5後: 100% 100% 100% 80% 100% 97% +``` + +--- + +## 全 Issue → Sprint マッピング + +| Issue | Sprint | 原則 | +|-------|--------|------| +| #52 GATE 0 Issue=0 | 1 | 原則2 | +| #53 GATE 5 ブランチ名 | 1 | 原則2 | +| #54 GATE 3 HIGH承認 | 1 | 原則2 | +| #55 GATE 7 ロック解放 | 1 | 原則2 | +| #56 exit code | 1 | 原則2 | +| #57 依存 exit code | 1 | 原則2 | +| #58 記憶アタッチメント | 3 | **原則1** | +| #59 ドリーミング | 3 | **原則1** | +| #60 シータサイクル | 4 | 自己改善 | +| #61 Bus ドッキング | 2 | 原則3 | +| #62 Obsidian 連携 | 4 | **原則1** | +| #63 Web ダッシュボード | 3 | 可視化 | +| #64 ブランチ戦略 | 2 | 原則2 | +| #65 Bus データパス | 2 | 原則3 | + +--- + +_This is Playbook v4. It covers all 38 elements of the VISION._ +_Previous versions (v1-v3) are archived in docs/dtp/._ diff --git a/skills/self-improving-skills/skill-runs.jsonl b/skills/self-improving-skills/skill-runs.jsonl index 3fec5bd..0386f49 100644 --- a/skills/self-improving-skills/skill-runs.jsonl +++ b/skills/self-improving-skills/skill-runs.jsonl @@ -2,3 +2,6 @@ {"ts":"2026-04-09T21:17:09.658Z","agent":"codex","skill":"context-and-impact","task":"DTP Phase B: miyabi gate CLI サブコマンド追加、protocol API 拡張、cargo test とコミット完了","result":"success","score":0.96,"notes":""} {"ts":"2026-04-09T21:27:55.420Z","agent":"codex","skill":"context-and-impact","task":"miyabi-core の clippy 7件を解消し clippy/test を通過確認。ただし HEAD 差分ゼロのため指定コミットは未作成","result":"partial","score":0.78,"notes":""} {"ts":"2026-04-09T21:28:53.022Z","agent":"codex","skill":"context-and-impact","task":"clippy 7件解消の確認後、空コミットで指定メッセージを作成","result":"success","score":0.92,"notes":""} +{"ts":"2026-04-09T21:49:02.984Z","agent":"codex","skill":"rust-development","task":"Fix GitHub Issues #56 and #57: gate exit code mapping and dependency-blocked assign behavior","result":"success","score":0.97,"notes":""} +{"ts":"2026-04-09T21:50:24.425Z","agent":"codex","skill":"rust-llm-pitfalls","task":"Fix GitHub Issues #54 and #55 in protocol.rs with approval and merge lock handling","result":"success","score":1,"notes":""} +{"ts":"2026-04-09T21:52:05.485Z","agent":"codex","skill":"rust-llm-pitfalls","task":"Issue #52/#53 修正: issue=0 拒否とブランチ名バリデーション実装","result":"success","score":0.95,"notes":""}