swarm repositories / source
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormain <main@swarm.moe>2026-03-20 18:01:43 -0400
committermain <main@swarm.moe>2026-03-20 18:01:43 -0400
commita424b9700e271c7ace8f0954671bdee5903555a8 (patch)
tree5e8fb2ce0a5da6623da8f2160333deb9e96768d1
parent9d63844f3a28fde70b19500422f17379e99e588a (diff)
downloadfidget_spinner-a424b9700e271c7ace8f0954671bdee5903555a8.zip
Refresh workers on same-path project rebinding
-rw-r--r--crates/fidget-spinner-cli/src/mcp/host/process.rs5
-rw-r--r--crates/fidget-spinner-cli/src/mcp/host/runtime.rs3
-rw-r--r--crates/fidget-spinner-cli/tests/mcp_hardening.rs64
3 files changed, 69 insertions, 3 deletions
diff --git a/crates/fidget-spinner-cli/src/mcp/host/process.rs b/crates/fidget-spinner-cli/src/mcp/host/process.rs
index d4cbb4b..fdbd873 100644
--- a/crates/fidget-spinner-cli/src/mcp/host/process.rs
+++ b/crates/fidget-spinner-cli/src/mcp/host/process.rs
@@ -55,6 +55,11 @@ impl WorkerSupervisor {
self.bound_project_root = Some(project_root);
}
+ pub(super) fn refresh_binding(&mut self, project_root: PathBuf) {
+ self.kill_current_worker();
+ self.bound_project_root = Some(project_root);
+ }
+
pub(super) fn execute(
&mut self,
request_id: HostRequestId,
diff --git a/crates/fidget-spinner-cli/src/mcp/host/runtime.rs b/crates/fidget-spinner-cli/src/mcp/host/runtime.rs
index bf0484a..f2f10b7 100644
--- a/crates/fidget-spinner-cli/src/mcp/host/runtime.rs
+++ b/crates/fidget-spinner-cli/src/mcp/host/runtime.rs
@@ -392,7 +392,8 @@ impl HostRuntime {
let args = deserialize::<ProjectBindArgs>(arguments, "tools/call:project.bind")?;
let resolved = resolve_project_binding(PathBuf::from(args.path))
.map_err(host_store_fault("tools/call:project.bind"))?;
- self.worker.rebind(resolved.binding.project_root.clone());
+ self.worker
+ .refresh_binding(resolved.binding.project_root.clone());
self.binding = Some(resolved.binding);
tool_success(
project_bind_output(&resolved.status)?,
diff --git a/crates/fidget-spinner-cli/tests/mcp_hardening.rs b/crates/fidget-spinner-cli/tests/mcp_hardening.rs
index fad4937..fc744c5 100644
--- a/crates/fidget-spinner-cli/tests/mcp_hardening.rs
+++ b/crates/fidget-spinner-cli/tests/mcp_hardening.rs
@@ -7,8 +7,8 @@ use std::path::PathBuf;
use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
use camino::Utf8PathBuf;
-use fidget_spinner_core::NonEmptyText;
-use fidget_spinner_store_sqlite::ProjectStore;
+use fidget_spinner_core::{NonEmptyText, Slug};
+use fidget_spinner_store_sqlite::{CreateFrontierRequest, ProjectStore};
use libmcp as _;
use maud as _;
use percent_encoding as _;
@@ -199,6 +199,15 @@ fn tool_names(response: &Value) -> Vec<&str> {
.collect()
}
+fn frontier_slugs(response: &Value) -> Vec<&str> {
+ tool_content(response)["frontiers"]
+ .as_array()
+ .into_iter()
+ .flatten()
+ .filter_map(|frontier| frontier["slug"].as_str())
+ .collect()
+}
+
#[test]
fn cold_start_exposes_bound_surface_and_new_toolset() -> TestResult {
let project_root = temp_project_root("cold_start")?;
@@ -596,3 +605,54 @@ fn experiment_close_drives_metric_best_and_analysis() -> TestResult {
);
Ok(())
}
+
+#[test]
+fn same_path_project_bind_refreshes_destructive_reseed() -> TestResult {
+ let project_root = temp_project_root("same_path_reseed")?;
+
+ let mut harness = McpHarness::spawn(None)?;
+ let _ = harness.initialize()?;
+ harness.notify_initialized()?;
+
+ let bind = harness.bind_project(60, &project_root)?;
+ assert_tool_ok(&bind);
+
+ assert_tool_ok(&harness.call_tool(
+ 61,
+ "frontier.create",
+ json!({
+ "label": "alpha frontier",
+ "objective": "first seeded frontier",
+ "slug": "alpha",
+ }),
+ )?);
+ let alpha_list = harness.call_tool_full(62, "frontier.list", json!({}))?;
+ assert_tool_ok(&alpha_list);
+ assert_eq!(frontier_slugs(&alpha_list), vec!["alpha"]);
+
+ must(
+ fs::remove_dir_all(project_root.join(fidget_spinner_store_sqlite::STORE_DIR_NAME)),
+ "remove project store",
+ )?;
+ init_project(&project_root)?;
+ let mut reopened = must(ProjectStore::open(&project_root), "open recreated store")?;
+ let _beta = must(
+ reopened.create_frontier(CreateFrontierRequest {
+ label: must(NonEmptyText::new("beta frontier"), "beta label")?,
+ objective: must(
+ NonEmptyText::new("second seeded frontier"),
+ "beta objective",
+ )?,
+ slug: Some(must(Slug::new("beta"), "beta slug")?),
+ }),
+ "create beta frontier directly in recreated store",
+ )?;
+
+ let rebind = harness.bind_project(63, &project_root)?;
+ assert_tool_ok(&rebind);
+
+ let beta_list = harness.call_tool_full(64, "frontier.list", json!({}))?;
+ assert_tool_ok(&beta_list);
+ assert_eq!(frontier_slugs(&beta_list), vec!["beta"]);
+ Ok(())
+}