huskies: merge 618_story_extract_mcp_only_domain_services
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
//! Shell I/O — the ONLY place in `service::shell/` that may perform side effects.
|
||||
//!
|
||||
//! Side effects here include: filesystem existence and canonicalization checks,
|
||||
//! process spawning via `std::process::Command`, and reading pipe output.
|
||||
//! All pure logic (pattern matching, output truncation, count parsing) lives in
|
||||
//! `path_guard.rs`.
|
||||
|
||||
use super::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Validate that `working_dir` is an absolute path that exists on disk and
|
||||
/// lies inside the project's `.huskies/worktrees/` or `.huskies/merge_workspace/`
|
||||
/// directory. Returns the canonicalized path on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// - [`Error::Validation`] if the path is relative or does not exist.
|
||||
/// - [`Error::PathNotAllowed`] if the path is outside the allowed roots.
|
||||
/// - [`Error::Io`] if canonicalization fails.
|
||||
pub fn validate_working_dir(working_dir: &str, project_root: &Path) -> Result<PathBuf, Error> {
|
||||
let wd = PathBuf::from(working_dir);
|
||||
|
||||
if !wd.is_absolute() {
|
||||
return Err(Error::Validation(
|
||||
"working_dir must be an absolute path".to_string(),
|
||||
));
|
||||
}
|
||||
if !wd.exists() {
|
||||
return Err(Error::Validation(format!(
|
||||
"working_dir does not exist: {working_dir}"
|
||||
)));
|
||||
}
|
||||
|
||||
let worktrees_root = project_root.join(".huskies").join("worktrees");
|
||||
|
||||
let canonical_wd = wd
|
||||
.canonicalize()
|
||||
.map_err(|e| Error::Io(format!("Cannot canonicalize working_dir: {e}")))?;
|
||||
|
||||
let canonical_wt = if worktrees_root.exists() {
|
||||
worktrees_root
|
||||
.canonicalize()
|
||||
.map_err(|e| Error::Io(format!("Cannot canonicalize worktrees root: {e}")))?
|
||||
} else {
|
||||
return Err(Error::PathNotAllowed(
|
||||
"No worktrees directory found in project".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
// Also allow the merge workspace so mergemaster can fix conflicts.
|
||||
let merge_workspace = project_root.join(".huskies").join("merge_workspace");
|
||||
let canonical_mw = merge_workspace.canonicalize().unwrap_or_default();
|
||||
|
||||
let in_worktrees = canonical_wd.starts_with(&canonical_wt);
|
||||
let in_merge_ws =
|
||||
!canonical_mw.as_os_str().is_empty() && canonical_wd.starts_with(&canonical_mw);
|
||||
|
||||
if !in_worktrees && !in_merge_ws {
|
||||
return Err(Error::PathNotAllowed(format!(
|
||||
"working_dir must be inside .huskies/worktrees/ or .huskies/merge_workspace/. Got: {working_dir}"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(canonical_wd)
|
||||
}
|
||||
Reference in New Issue
Block a user