feat(milestones): add POST /api/milestones endpoint and resolve collapsible_if warnings (TD-fc685b13-d9c2-4a57-ac4d-5228a71a2ec0)

This commit is contained in:
Riz Ashraf committed 2026-10-07 19:32:11 +01:00
1 parent 8f924b793a
commit d792b50343
6 files changed
+41 -25

No files matched your search

+1 -1
View File
@@ -102,7 +102,7 @@ build-nvim:
# Stage updated server binary, perform graceful shutdown, and auto-restart (skips if binary is unchanged) # Stage updated server binary, perform graceful shutdown, and auto-restart (skips if binary is unchanged)
deploy-server: deploy-server:
@Write-Host "Deploying Windows Server (Staged Hot-Swap)..." -ForegroundColor Cyan @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 Windows-native stub binary (skips if identical checksum)
deploy-stub-win: deploy-stub-win:
+19
View File
@@ -355,6 +355,25 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
let json = state_clone.project.milestones.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string())); 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) ([(axum::http::header::CONTENT_TYPE, "application/json")], json)
} }
})
.post({
let state_clone = app_state.handler.state.clone();
move |axum::Json(payload): axum::Json<serde_json::Value>| 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( .route(
+3
View File
@@ -484,6 +484,9 @@ impl McpTool for MilestonesHandler {
} }
if let Some(task_ids) = &req.task_ids { if let Some(task_ids) = &req.task_ids {
m.task_ids = task_ids.clone(); m.task_ids = task_ids.clone();
if let Some(deliverables) = &req.deliverables {
m.deliverables = deliverables.clone();
}
} }
found = true; found = true;
break; break;
+9 -11
View File
@@ -11,13 +11,12 @@ use std::sync::Arc;
pub fn get_native_clipboard_text() -> Option<String> { pub fn get_native_clipboard_text() -> Option<String> {
for _ in 0..3 { for _ in 0..3 {
if let Ok(mut clipboard) = arboard::Clipboard::new() { if let Ok(mut clipboard) = arboard::Clipboard::new()
if let Ok(text) = clipboard.get_text() { && let Ok(text) = clipboard.get_text()
if !text.trim().is_empty() { && !text.trim().is_empty()
{
return Some(text); return Some(text);
} }
}
}
std::thread::sleep(std::time::Duration::from_millis(20)); std::thread::sleep(std::time::Duration::from_millis(20));
} }
None None
@@ -71,17 +70,16 @@ pub fn get_native_clipboard_image() -> Option<image::DynamicImage> {
return Some(img); return Some(img);
} }
for _ in 0..3 { for _ in 0..3 {
if let Ok(mut clipboard) = arboard::Clipboard::new() { if let Ok(mut clipboard) = arboard::Clipboard::new()
if let Ok(image_data) = clipboard.get_image() { && let Ok(image_data) = clipboard.get_image()
if let Some(img) = ImageBuffer::<image::Rgba<u8>, _>::from_raw( && let Some(img) = ImageBuffer::<image::Rgba<u8>, _>::from_raw(
image_data.width as u32, image_data.width as u32,
image_data.height as u32, image_data.height as u32,
image_data.bytes.into_owned(), image_data.bytes.into_owned(),
) { )
{
return Some(image::DynamicImage::ImageRgba8(img)); return Some(image::DynamicImage::ImageRgba8(img));
} }
}
}
std::thread::sleep(std::time::Duration::from_millis(20)); std::thread::sleep(std::time::Duration::from_millis(20));
} }
None None
+6 -10
View File
@@ -56,11 +56,11 @@ pub fn spawn_watcher(state: Arc<MemoryState>) {
for path in event.paths { for path in event.paths {
if should_review(&path) { if should_review(&path) {
// 250ms debouncing window per file path // 250ms debouncing window per file path
if let Some(last) = last_processed.get(&path) { if let Some(last) = last_processed.get(&path)
if now.duration_since(*last) < std::time::Duration::from_millis(250) { && now.duration_since(*last) < std::time::Duration::from_millis(250)
{
continue; continue;
} }
}
last_processed.insert(path.clone(), now); last_processed.insert(path.clone(), now);
info!("Proactive Daemon Hooks: File modified: {:?}", path); info!("Proactive Daemon Hooks: File modified: {:?}", path);
@@ -83,13 +83,9 @@ fn should_review(path: &Path) -> bool {
return false; return false;
} }
if let Some(ext) = path.extension() { path.extension()
let ext_str = ext.to_string_lossy(); .and_then(|ext| ext.to_str())
if ext_str == "rs" || ext_str == "md" || ext_str == "toml" || ext_str == "lua" { .is_some_and(|ext| matches!(ext, "rs" | "md" | "toml" | "lua"))
return true;
}
}
false
} }
async fn trigger_autonomous_review(path: &Path, state: Arc<MemoryState>) { async fn trigger_autonomous_review(path: &Path, state: Arc<MemoryState>) {
+3 -3
View File
@@ -40,7 +40,7 @@ async fn test_stub_connection_refused() {
.spawn() .spawn()
.expect("Failed to execute stub"); .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(); let elapsed = start.elapsed();
assert!( assert!(
@@ -71,7 +71,7 @@ async fn test_stub_handles_eof_cleanly() {
} // stdin dropped here } // stdin dropped here
let start = Instant::now(); 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(); let elapsed = start.elapsed();
assert!(res.is_ok(), "Stub hung after EOF! Took {elapsed:?}"); assert!(res.is_ok(), "Stub hung after EOF! Took {elapsed:?}");
@@ -102,7 +102,7 @@ async fn test_stub_sse_fallback_failure() {
.spawn() .spawn()
.expect("Failed to execute stub"); .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(); let elapsed = start.elapsed();
assert!( assert!(