diff options
| author | soryu <soryu@soryu.co> | 2026-01-21 17:32:49 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-21 17:32:59 +0000 |
| commit | 9e286c146e29e714b3b209b4d948d75cce179b05 (patch) | |
| tree | e833c5314cfc089f9cd2c34dff8376cb083cbaad | |
| parent | 94e5604e770d6589f786ea71e51738e21492f301 (diff) | |
| download | soryu-9e286c146e29e714b3b209b4d948d75cce179b05.tar.gz soryu-9e286c146e29e714b3b209b4d948d75cce179b05.zip | |
Enforce phaseguard
| -rw-r--r-- | makima/src/bin/makima.rs | 77 | ||||
| -rw-r--r-- | makima/src/daemon/api/contract.rs | 25 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh_supervisor.rs | 9 |
3 files changed, 93 insertions, 18 deletions
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs index 29388e1..67eefc6 100644 --- a/makima/src/bin/makima.rs +++ b/makima/src/bin/makima.rs @@ -962,24 +962,44 @@ async fn run_tui_loop( Ok(result) => { let contract_name = result.0.get("name") .and_then(|v| v.as_str()) - .unwrap_or(&name); - app.status_message = Some(format!("Created contract: {}", contract_name)); + .unwrap_or(&name) + .to_string(); + let contract_id = result.0.get("id") + .and_then(|v| v.as_str()) + .and_then(|s| uuid::Uuid::parse_str(s).ok()); + + // Add repository if provided + if let (Some(repo_url), Some(cid)) = (repository_url.as_ref(), contract_id) { + if !repo_url.is_empty() { + // Extract repo name from URL (e.g., "owner/repo" from GitHub URL) + let repo_name = extract_repo_name(repo_url); + match client.add_remote_repository(cid, &repo_name, repo_url, true).await { + Ok(_) => { + app.status_message = Some(format!( + "Created contract '{}' with repository", + contract_name + )); + } + Err(e) => { + app.status_message = Some(format!( + "Created contract but failed to add repository: {}", + e + )); + } + } + } else { + app.status_message = Some(format!("Created contract: {}", contract_name)); + } + } else { + app.status_message = Some(format!("Created contract: {}", contract_name)); + } // Refresh the contracts list match load_contracts(client).await { Ok(items) => app.set_items(items), - Err(e) => app.status_message = Some(format!("Created but refresh failed: {}", e)), - } - - // TODO: If repository_url was provided, add it to the contract - if let Some(repo_url) = repository_url { - if !repo_url.is_empty() { - // We'd need to add a method to add repository to contract - // For now, just note it in the status - app.status_message = Some(format!( - "Created contract: {} (Note: Add repository {} manually)", - contract_name, repo_url - )); + Err(e) => { + let msg = app.status_message.take().unwrap_or_default(); + app.status_message = Some(format!("{} (refresh failed: {})", msg, e)); } } } @@ -1070,6 +1090,35 @@ async fn run_tui_loop( Ok(None) } +/// Extract a repository name from a URL. +/// E.g., "https://github.com/owner/repo.git" -> "owner/repo" +fn extract_repo_name(url: &str) -> String { + // Remove .git suffix if present + let url = url.trim_end_matches(".git"); + + // Try to extract owner/repo from common Git hosting URLs + if let Some(path) = url.strip_prefix("https://github.com/") + .or_else(|| url.strip_prefix("https://gitlab.com/")) + .or_else(|| url.strip_prefix("https://bitbucket.org/")) + .or_else(|| url.strip_prefix("git@github.com:")) + .or_else(|| url.strip_prefix("git@gitlab.com:")) + .or_else(|| url.strip_prefix("git@bitbucket.org:")) + { + // Return owner/repo + return path.to_string(); + } + + // Fallback: try to get the last path segment + if let Some(last_segment) = url.rsplit('/').next() { + if !last_segment.is_empty() { + return last_segment.to_string(); + } + } + + // Last resort: use the full URL as the name + url.to_string() +} + /// Parse an output entry from the API response into an OutputLine fn parse_output_entry(entry: &serde_json::Value) -> Option<OutputLine> { let message_type = entry.get("messageType") diff --git a/makima/src/daemon/api/contract.rs b/makima/src/daemon/api/contract.rs index 50fd64f..12ebe95 100644 --- a/makima/src/daemon/api/contract.rs +++ b/makima/src/daemon/api/contract.rs @@ -247,4 +247,29 @@ impl ApiClient { self.get(&format!("/api/v1/settings/repository-history/suggestions{}", query_string)) .await } + + /// Add a remote repository to a contract. + pub async fn add_remote_repository( + &self, + contract_id: Uuid, + name: &str, + repository_url: &str, + is_primary: bool, + ) -> Result<JsonValue, ApiError> { + let req = AddRemoteRepositoryRequest { + name: name.to_string(), + repository_url: repository_url.to_string(), + is_primary, + }; + self.post(&format!("/api/v1/contracts/{}/repositories/remote", contract_id), &req) + .await + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct AddRemoteRepositoryRequest { + name: String, + repository_url: String, + is_primary: bool, } diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs index 6cdbba6..668ea7b 100644 --- a/makima/src/server/handlers/mesh_supervisor.rs +++ b/makima/src/server/handlers/mesh_supervisor.rs @@ -1589,8 +1589,9 @@ pub async fn ask_question( ).await; } - // If non_blocking mode is enabled, return immediately with the question_id - if request.non_blocking { + // If non_blocking mode or phaseguard is enabled, return immediately with the question_id + // Phaseguard questions persist until answered (no timeout) and are displayed by the frontend + if request.non_blocking || request.phaseguard { return ( StatusCode::OK, Json(AskQuestionResponse { @@ -1622,8 +1623,8 @@ pub async fn ask_question( ).into_response(); } - // Check timeout (skip timeout if phaseguard is enabled - wait indefinitely) - if !request.phaseguard && start.elapsed() >= timeout_duration { + // Check timeout + if start.elapsed() >= timeout_duration { // Remove the pending question on timeout state.remove_pending_question(question_id); |
