fix: separate workspace root from cwd

This commit is contained in:
2026-06-12 01:03:33 +09:00
parent 23a5b53807
commit 7eff9301b9
14 changed files with 314 additions and 254 deletions
+13 -7
View File
@@ -35,10 +35,14 @@ pub struct SpawnConfig {
/// Process-local Ticket role marker supplied only by Ticket role launches.
/// This does not alter prompts, manifests, or Ticket claim records.
pub ticket_role: Option<String>,
/// Explicit runtime workspace root. The child uses it as process cwd and
/// receives it via `--workspace` so startup does not infer workspace
/// identity from the parent process cwd.
/// Explicit runtime workspace root. The child receives it via
/// `--workspace` so startup does not infer workspace identity from the
/// parent process cwd.
pub workspace_root: PathBuf,
/// Optional child process cwd. This is not runtime workspace identity and
/// is not passed as a CLI argument; the child observes it as its ordinary
/// process current directory.
pub cwd: Option<PathBuf>,
/// `Some(id)` のとき `--session <id>` を付与し、当該セッションから
/// resume させる。
pub resume_from: Option<Uuid>,
@@ -149,7 +153,7 @@ where
let mut command = Command::new(config.runtime_command.program());
command
.args(config.runtime_command.prefix_args())
.current_dir(&config.workspace_root)
.current_dir(config.cwd.as_ref().unwrap_or(&config.workspace_root))
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::from(stderr_file))
@@ -335,6 +339,7 @@ mod tests {
profile: Some("project:companion".to_string()),
ticket_role: None,
workspace_root: PathBuf::from("/work/other-project"),
cwd: None,
resume_from: None,
}
}
@@ -372,9 +377,10 @@ mod tests {
}
#[test]
fn runtime_args_pass_ticket_role_marker_when_present() {
fn runtime_args_do_not_include_child_cwd() {
let mut config = base_config();
config.ticket_role = Some("intake".to_string());
config.ticket_role = Some("orchestrator".to_string());
config.cwd = Some(PathBuf::from("/work/main/.worktree/orchestration/yoi"));
assert_eq!(
runtime_args(&config),
@@ -386,7 +392,7 @@ mod tests {
"--profile",
"project:companion",
"--ticket-role",
"intake",
"orchestrator",
]
);
}
+15
View File
@@ -78,6 +78,7 @@ impl TicketIntakeHandoff {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketRoleLaunchContext {
pub workspace_root: PathBuf,
pub cwd: Option<PathBuf>,
pub original_workspace_root: Option<PathBuf>,
pub target_workspace_root: Option<PathBuf>,
pub role: TicketRole,
@@ -97,6 +98,7 @@ impl TicketRoleLaunchContext {
pub fn new(workspace_root: impl Into<PathBuf>, role: TicketRole) -> Self {
Self {
workspace_root: workspace_root.into(),
cwd: None,
original_workspace_root: None,
target_workspace_root: None,
role,
@@ -113,6 +115,11 @@ impl TicketRoleLaunchContext {
}
}
pub fn with_cwd(mut self, root: impl Into<PathBuf>) -> Self {
self.cwd = Some(root.into());
self
}
pub fn with_original_workspace_root(mut self, root: impl Into<PathBuf>) -> Self {
self.original_workspace_root = Some(root.into());
self
@@ -144,6 +151,7 @@ impl TicketRoleLaunchContext {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketRoleLaunchPlan {
pub workspace_root: PathBuf,
pub cwd: Option<PathBuf>,
pub original_workspace_root: PathBuf,
pub target_workspace_root: PathBuf,
pub implementation_worktree_root: PathBuf,
@@ -175,6 +183,7 @@ impl TicketRoleLaunchPlan {
profile: Some(self.profile.clone()),
ticket_role: Some(self.role.as_str().to_string()),
workspace_root: self.workspace_root.clone(),
cwd: self.cwd.clone(),
resume_from: None,
})
}
@@ -285,6 +294,7 @@ pub fn plan_ticket_role_launch_with_config(
Ok(TicketRoleLaunchPlan {
workspace_root: context.workspace_root,
cwd: context.cwd,
original_workspace_root,
target_workspace_root,
implementation_worktree_root,
@@ -678,6 +688,9 @@ fn append_workspace_routing_context(out: &mut String, context: &TicketRoleLaunch
"role_workspace_root",
&context.workspace_root.display().to_string(),
);
if let Some(cwd) = &context.cwd {
push_bounded_bullet(out, "role_cwd", &cwd.display().to_string());
}
push_bounded_bullet(
out,
"original_workspace_root",
@@ -875,6 +888,7 @@ mod tests {
fn test_launch_plan(workspace: &std::path::Path) -> TicketRoleLaunchPlan {
TicketRoleLaunchPlan {
workspace_root: workspace.to_path_buf(),
cwd: None,
original_workspace_root: workspace.to_path_buf(),
target_workspace_root: workspace.to_path_buf(),
implementation_worktree_root: workspace.join(".worktree"),
@@ -1330,6 +1344,7 @@ workflow = "ticket-review-workflow"
.spawn_config(PodRuntimeCommand::for_executable("/bin/yoi"))
.unwrap();
assert_eq!(spawn_config.workspace_root, temp.path());
assert_eq!(spawn_config.cwd, None);
assert!(text.contains("Workspace routing context:"));
assert!(text.contains("role_workspace_root"));