huskies: merge 618_story_extract_mcp_only_domain_services
This commit is contained in:
@@ -1,68 +1,34 @@
|
||||
//! MCP git tools — status, diff, add, commit, and log operations on agent worktrees.
|
||||
//!
|
||||
//! This file is a thin adapter: it deserialises MCP payloads, delegates to
|
||||
//! `crate::service::git_ops` for all business logic, and serialises responses.
|
||||
use crate::http::context::AppContext;
|
||||
use serde_json::{Value, json};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Validates that `worktree_path` exists and is inside the project's
|
||||
/// `.huskies/worktrees/` directory. Returns the canonicalized path.
|
||||
///
|
||||
/// Thin wrapper that obtains the project root from `ctx` and delegates to
|
||||
/// `service::git_ops::io::validate_worktree_path`.
|
||||
fn validate_worktree_path(worktree_path: &str, ctx: &AppContext) -> Result<PathBuf, String> {
|
||||
let wd = PathBuf::from(worktree_path);
|
||||
|
||||
if !wd.is_absolute() {
|
||||
return Err("worktree_path must be an absolute path".to_string());
|
||||
}
|
||||
if !wd.exists() {
|
||||
return Err(format!("worktree_path does not exist: {worktree_path}"));
|
||||
}
|
||||
|
||||
let project_root = ctx.agents.get_project_root(&ctx.state)?;
|
||||
let worktrees_root = project_root.join(".huskies").join("worktrees");
|
||||
|
||||
let canonical_wd = wd
|
||||
.canonicalize()
|
||||
.map_err(|e| format!("Cannot canonicalize worktree_path: {e}"))?;
|
||||
|
||||
let canonical_wt = if worktrees_root.exists() {
|
||||
worktrees_root
|
||||
.canonicalize()
|
||||
.map_err(|e| format!("Cannot canonicalize worktrees root: {e}"))?
|
||||
} else {
|
||||
return Err("No worktrees directory found in project".to_string());
|
||||
};
|
||||
|
||||
if !canonical_wd.starts_with(&canonical_wt) {
|
||||
return Err(format!(
|
||||
"worktree_path must be inside .huskies/worktrees/. Got: {worktree_path}"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(canonical_wd)
|
||||
crate::service::git_ops::io::validate_worktree_path(worktree_path, &project_root)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Run a git command in the given directory and return its output.
|
||||
async fn run_git(args: Vec<&'static str>, dir: PathBuf) -> Result<std::process::Output, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
std::process::Command::new("git")
|
||||
.args(&args)
|
||||
.current_dir(&dir)
|
||||
.output()
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task join error: {e}"))?
|
||||
.map_err(|e| format!("Failed to run git: {e}"))
|
||||
crate::service::git_ops::io::run_git(args, dir)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Run a git command with owned args in the given directory.
|
||||
async fn run_git_owned(args: Vec<String>, dir: PathBuf) -> Result<std::process::Output, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
std::process::Command::new("git")
|
||||
.args(&args)
|
||||
.current_dir(&dir)
|
||||
.output()
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task join error: {e}"))?
|
||||
.map_err(|e| format!("Failed to run git: {e}"))
|
||||
crate::service::git_ops::io::run_git_owned(args, dir)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// git_status — returns working tree status (staged, unstaged, untracked files).
|
||||
@@ -86,29 +52,8 @@ pub(super) async fn tool_git_status(args: &Value, ctx: &AppContext) -> Result<St
|
||||
));
|
||||
}
|
||||
|
||||
let mut staged: Vec<String> = Vec::new();
|
||||
let mut unstaged: Vec<String> = Vec::new();
|
||||
let mut untracked: Vec<String> = Vec::new();
|
||||
|
||||
for line in stdout.lines() {
|
||||
if line.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
let x = line.chars().next().unwrap_or(' ');
|
||||
let y = line.chars().nth(1).unwrap_or(' ');
|
||||
let path = line[3..].to_string();
|
||||
|
||||
match (x, y) {
|
||||
('?', '?') => untracked.push(path),
|
||||
(' ', _) => unstaged.push(path),
|
||||
(_, ' ') => staged.push(path),
|
||||
_ => {
|
||||
// Both staged and unstaged modifications
|
||||
staged.push(path.clone());
|
||||
unstaged.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
let (staged, unstaged, untracked) =
|
||||
crate::service::git_ops::parse_git_status_porcelain(&stdout);
|
||||
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"staged": staged,
|
||||
|
||||
Reference in New Issue
Block a user