From d792b503435963ecb60a3f7074e305e20f3c00d3 Mon Sep 17 00:00:00 2001 From: Riz Ashraf Date: Wed, 7 Oct 2026 19:32:11 +0100 Subject: [PATCH] feat(milestones): add POST /api/milestones endpoint and resolve collapsible_if warnings (TD-fc685b13-d9c2-4a57-ac4d-5228a71a2ec0) --- justfile | 2 +- server/src/api/setup.rs | 19 +++++++++++++++++++ server/src/handlers/tasks.rs | 3 +++ server/src/handlers/vision.rs | 30 ++++++++++++++---------------- server/src/watcher.rs | 18 +++++++----------- stub/tests/negative_scenarios.rs | 6 +++--- 6 files changed, 47 insertions(+), 31 deletions(-) diff --git a/justfile b/justfile index d5ab42f..a1908da 100644 --- a/justfile +++ b/justfile @@ -102,7 +102,7 @@ build-nvim: # Stage updated server binary, perform graceful shutdown, and auto-restart (skips if binary is unchanged) deploy-server: @Write-Host "Deploying Windows Server (Staged Hot-Swap)..." -ForegroundColor Cyan - -& { $src = "target\release\mcp-memory-server.exe"; $targets = @("$env:USERPROFILE\.local\bin\mcp-memory-server.exe", "$env:USERPROFILE\.cargo\bin\mcp-memory-server.exe"); $srcHash = if (Test-Path $src) { (Get-FileHash $src -Algorithm SHA256).Hash } else { $null }; $updated = $false; foreach ($target in $targets) { $parent = Split-Path $target; if (Test-Path $parent) { if ((Test-Path $target) -and $srcHash -and ((Get-FileHash $target -Algorithm SHA256).Hash -eq $srcHash)) { Write-Host "Skipping copy to $target (identical checksum)." -ForegroundColor Yellow } else { if (Test-Path $target) { $bak = "$target.old.$([guid]::NewGuid().ToString().Substring(0,8))"; Move-Item -Force $target $bak -ErrorAction SilentlyContinue }; Copy-Item -Force $src $target; Get-ChildItem "$parent\mcp-memory-server.exe.old.*" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue; Write-Host "Copied updated binary to $target" -ForegroundColor Green; $updated = $true } } }; if ($updated -or -not (Get-NetTCPConnection -LocalPort 3000 -State Listen -ErrorAction SilentlyContinue)) { just stop; just start } else { Write-Host "Server binary unchanged and server is running. Skipping restart." -ForegroundColor Green } } + -& { $src = "target\release\mcp-memory-server.exe"; $targets = @("$env:USERPROFILE\.local\bin\mcp-memory-server.exe", "$env:USERPROFILE\.cargo\bin\mcp-memory-server.exe", "$env:USERPROFILE\.gemini\antigravity-cli\mcp\mcp-memory\mcp-memory.exe"); $srcHash = if (Test-Path $src) { (Get-FileHash $src -Algorithm SHA256).Hash } else { $null }; $updated = $false; foreach ($target in $targets) { $parent = Split-Path $target; if (Test-Path $parent) { if ((Test-Path $target) -and $srcHash -and ((Get-FileHash $target -Algorithm SHA256).Hash -eq $srcHash)) { Write-Host "Skipping copy to $target (identical checksum)." -ForegroundColor Yellow } else { if (Test-Path $target) { $bak = "$target.old.$([guid]::NewGuid().ToString().Substring(0,8))"; Move-Item -Force $target $bak -ErrorAction SilentlyContinue }; Copy-Item -Force $src $target; Get-ChildItem "$parent\mcp-memory-server.exe.old.*" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue; Write-Host "Copied updated binary to $target" -ForegroundColor Green; $updated = $true } } }; if ($updated -or -not (Get-NetTCPConnection -LocalPort 3000 -State Listen -ErrorAction SilentlyContinue)) { just stop; just start } else { Write-Host "Server binary unchanged and server is running. Skipping restart." -ForegroundColor Green } } # Deploy Windows-native stub binary (skips if identical checksum) deploy-stub-win: diff --git a/server/src/api/setup.rs b/server/src/api/setup.rs index 18e507c..4f9ff93 100644 --- a/server/src/api/setup.rs +++ b/server/src/api/setup.rs @@ -355,6 +355,25 @@ pub fn create_router(app_state: Arc) -> Router { let json = state_clone.project.milestones.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string())); ([(axum::http::header::CONTENT_TYPE, "application/json")], json) } + }) + .post({ + let state_clone = app_state.handler.state.clone(); + move |axum::Json(payload): axum::Json| async move { + use crate::router::McpTool; + let handler = crate::handlers::tasks::MilestonesHandler; + match handler.execute(payload, state_clone).await { + Ok(msg) => ( + axum::http::StatusCode::OK, + axum::Json(serde_json::json!({ "status": "success", "message": msg })), + ) + .into_response(), + Err(e) => ( + axum::http::StatusCode::BAD_REQUEST, + axum::Json(serde_json::json!({ "status": "error", "message": e.to_string() })), + ) + .into_response(), + } + } }), ) .route( diff --git a/server/src/handlers/tasks.rs b/server/src/handlers/tasks.rs index 5ebacdc..49ab6cb 100644 --- a/server/src/handlers/tasks.rs +++ b/server/src/handlers/tasks.rs @@ -484,6 +484,9 @@ impl McpTool for MilestonesHandler { } if let Some(task_ids) = &req.task_ids { m.task_ids = task_ids.clone(); + if let Some(deliverables) = &req.deliverables { + m.deliverables = deliverables.clone(); + } } found = true; break; diff --git a/server/src/handlers/vision.rs b/server/src/handlers/vision.rs index f9ffb43..aac2582 100644 --- a/server/src/handlers/vision.rs +++ b/server/src/handlers/vision.rs @@ -11,12 +11,11 @@ use std::sync::Arc; pub fn get_native_clipboard_text() -> Option { for _ in 0..3 { - if let Ok(mut clipboard) = arboard::Clipboard::new() { - if let Ok(text) = clipboard.get_text() { - if !text.trim().is_empty() { - return Some(text); - } - } + if let Ok(mut clipboard) = arboard::Clipboard::new() + && let Ok(text) = clipboard.get_text() + && !text.trim().is_empty() + { + return Some(text); } std::thread::sleep(std::time::Duration::from_millis(20)); } @@ -71,16 +70,15 @@ pub fn get_native_clipboard_image() -> Option { return Some(img); } for _ in 0..3 { - if let Ok(mut clipboard) = arboard::Clipboard::new() { - if let Ok(image_data) = clipboard.get_image() { - if let Some(img) = ImageBuffer::, _>::from_raw( - image_data.width as u32, - image_data.height as u32, - image_data.bytes.into_owned(), - ) { - return Some(image::DynamicImage::ImageRgba8(img)); - } - } + if let Ok(mut clipboard) = arboard::Clipboard::new() + && let Ok(image_data) = clipboard.get_image() + && let Some(img) = ImageBuffer::, _>::from_raw( + image_data.width as u32, + image_data.height as u32, + image_data.bytes.into_owned(), + ) + { + return Some(image::DynamicImage::ImageRgba8(img)); } std::thread::sleep(std::time::Duration::from_millis(20)); } diff --git a/server/src/watcher.rs b/server/src/watcher.rs index 84e9c52..a49b9b7 100644 --- a/server/src/watcher.rs +++ b/server/src/watcher.rs @@ -56,10 +56,10 @@ pub fn spawn_watcher(state: Arc) { for path in event.paths { if should_review(&path) { // 250ms debouncing window per file path - if let Some(last) = last_processed.get(&path) { - if now.duration_since(*last) < std::time::Duration::from_millis(250) { - continue; - } + if let Some(last) = last_processed.get(&path) + && now.duration_since(*last) < std::time::Duration::from_millis(250) + { + continue; } last_processed.insert(path.clone(), now); @@ -83,13 +83,9 @@ fn should_review(path: &Path) -> bool { return false; } - if let Some(ext) = path.extension() { - let ext_str = ext.to_string_lossy(); - if ext_str == "rs" || ext_str == "md" || ext_str == "toml" || ext_str == "lua" { - return true; - } - } - false + path.extension() + .and_then(|ext| ext.to_str()) + .is_some_and(|ext| matches!(ext, "rs" | "md" | "toml" | "lua")) } async fn trigger_autonomous_review(path: &Path, state: Arc) { diff --git a/stub/tests/negative_scenarios.rs b/stub/tests/negative_scenarios.rs index d76abd0..65285a0 100644 --- a/stub/tests/negative_scenarios.rs +++ b/stub/tests/negative_scenarios.rs @@ -40,7 +40,7 @@ async fn test_stub_connection_refused() { .spawn() .expect("Failed to execute stub"); - let res = tokio::time::timeout(Duration::from_secs(5), child.wait()).await; + let res = tokio::time::timeout(Duration::from_secs(15), child.wait()).await; let elapsed = start.elapsed(); assert!( @@ -71,7 +71,7 @@ async fn test_stub_handles_eof_cleanly() { } // stdin dropped here let start = Instant::now(); - let res = tokio::time::timeout(Duration::from_secs(5), child.wait()).await; + let res = tokio::time::timeout(Duration::from_secs(15), child.wait()).await; let elapsed = start.elapsed(); assert!(res.is_ok(), "Stub hung after EOF! Took {elapsed:?}"); @@ -102,7 +102,7 @@ async fn test_stub_sse_fallback_failure() { .spawn() .expect("Failed to execute stub"); - let res = tokio::time::timeout(Duration::from_secs(5), child.wait()).await; + let res = tokio::time::timeout(Duration::from_secs(15), child.wait()).await; let elapsed = start.elapsed(); assert!(