2026-04-12 13:11:23 +00:00
|
|
|
//! Agent worktree management — creates and configures git worktrees for agents.
|
2026-03-27 15:53:32 +00:00
|
|
|
use crate::config::ProjectConfig;
|
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
|
|
|
|
use super::AgentPool;
|
|
|
|
|
|
|
|
|
|
impl AgentPool {
|
|
|
|
|
/// Create a worktree for the given story using the server port (writes .mcp.json).
|
|
|
|
|
pub async fn create_worktree(
|
|
|
|
|
&self,
|
|
|
|
|
project_root: &Path,
|
|
|
|
|
story_id: &str,
|
|
|
|
|
) -> Result<crate::worktree::WorktreeInfo, String> {
|
|
|
|
|
let config = ProjectConfig::load(project_root)?;
|
|
|
|
|
crate::worktree::create_worktree(project_root, story_id, &config, self.port).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get project root helper.
|
|
|
|
|
pub fn get_project_root(&self, state: &crate::state::SessionState) -> Result<PathBuf, String> {
|
|
|
|
|
state.get_project_root()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Return the active pipeline stage directory name for `story_id`, or `None` if the
|
|
|
|
|
/// story is not in any active stage (`2_current/`, `3_qa/`, `4_merge/`).
|
2026-04-10 14:56:13 +00:00
|
|
|
pub(super) fn find_active_story_stage(_project_root: &Path, story_id: &str) -> Option<&'static str> {
|
2026-04-09 21:24:11 +00:00
|
|
|
if let Ok(Some(item)) = crate::pipeline_state::read_typed(story_id)
|
|
|
|
|
&& item.stage.is_active()
|
|
|
|
|
{
|
|
|
|
|
return Some(item.stage.dir_name());
|
2026-04-08 03:03:59 +00:00
|
|
|
}
|
2026-03-27 15:53:32 +00:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::find_active_story_stage;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn find_active_story_stage_detects_current() {
|
2026-04-10 14:56:13 +00:00
|
|
|
crate::db::ensure_content_store();
|
|
|
|
|
crate::db::write_item_with_content(
|
|
|
|
|
"10_story_test",
|
|
|
|
|
"2_current",
|
|
|
|
|
"---\nname: Test\n---\n",
|
|
|
|
|
);
|
2026-03-27 15:53:32 +00:00
|
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
|
|
|
assert_eq!(
|
2026-04-10 14:56:13 +00:00
|
|
|
find_active_story_stage(tmp.path(), "10_story_test"),
|
2026-03-27 15:53:32 +00:00
|
|
|
Some("2_current")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn find_active_story_stage_detects_qa() {
|
2026-04-10 14:56:13 +00:00
|
|
|
crate::db::ensure_content_store();
|
|
|
|
|
crate::db::write_item_with_content(
|
|
|
|
|
"11_story_test",
|
|
|
|
|
"3_qa",
|
|
|
|
|
"---\nname: Test\n---\n",
|
|
|
|
|
);
|
2026-03-27 15:53:32 +00:00
|
|
|
let tmp = tempfile::tempdir().unwrap();
|
2026-04-10 14:56:13 +00:00
|
|
|
assert_eq!(find_active_story_stage(tmp.path(), "11_story_test"), Some("3_qa"));
|
2026-03-27 15:53:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn find_active_story_stage_detects_merge() {
|
2026-04-10 14:56:13 +00:00
|
|
|
crate::db::ensure_content_store();
|
|
|
|
|
crate::db::write_item_with_content(
|
|
|
|
|
"12_story_test",
|
|
|
|
|
"4_merge",
|
|
|
|
|
"---\nname: Test\n---\n",
|
|
|
|
|
);
|
2026-03-27 15:53:32 +00:00
|
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
|
|
|
assert_eq!(
|
2026-04-10 14:56:13 +00:00
|
|
|
find_active_story_stage(tmp.path(), "12_story_test"),
|
2026-03-27 15:53:32 +00:00
|
|
|
Some("4_merge")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn find_active_story_stage_returns_none_for_unknown_story() {
|
|
|
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
|
|
|
assert_eq!(find_active_story_stage(tmp.path(), "99_nonexistent"), None);
|
|
|
|
|
}
|
|
|
|
|
}
|