From 2eb5bfb8fe6f1d480f16fa358cc3e5d186efeba3 Mon Sep 17 00:00:00 2001 From: Dave Date: Mon, 23 Feb 2026 16:36:15 +0000 Subject: [PATCH] Sparse checkout excludes .story_kit/work/ from agent worktrees Configures sparse checkout on new and existing worktrees to exclude the pipeline state directory. This prevents feature branches from containing .story_kit/work/ file moves that cause rename/delete merge conflicts when merging back to master. Also removes "pick up the story from .story_kit/work/" instruction from agent prompts since the story content is already in the prompt. Co-Authored-By: Claude Opus 4.6 --- .story_kit/project.toml | 8 ++-- server/src/worktree.rs | 98 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 5 deletions(-) diff --git a/.story_kit/project.toml b/.story_kit/project.toml index 75bf62f..1e6a576 100644 --- a/.story_kit/project.toml +++ b/.story_kit/project.toml @@ -54,7 +54,7 @@ role = "Full-stack engineer. Implements features across all components." model = "sonnet-4.6" max_turns = 50 max_budget_usd = 5.00 -prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .story_kit/README.md to understand the dev process. Pick up the story from .story_kit/work/ - move it to work/2_current/ if needed. Follow the SDTW process through implementation and verification (Steps 1-3). The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop. If the user asks to review your changes, tell them to run: cd \"{{worktree_path}}\" && git difftool {{base_branch}}...HEAD\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates (cargo clippy + tests) when your process exits and advance the pipeline based on the results." +prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .story_kit/README.md to understand the dev process. The story details are in your prompt above. Follow the SDTW process through implementation and verification (Steps 1-3). The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop. If the user asks to review your changes, tell them to run: cd \"{{worktree_path}}\" && git difftool {{base_branch}}...HEAD\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates (cargo clippy + tests) when your process exits and advance the pipeline based on the results." system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Follow the Story-Driven Test Workflow strictly. Run cargo clippy and biome checks before considering work complete. Commit all your work before finishing - use a descriptive commit message. Do not accept stories, move them to archived, or merge to master - a human will do that. Do not coordinate with other agents - focus on your assigned story. The server automatically runs acceptance gates when your process exits." [[agent]] @@ -63,7 +63,7 @@ role = "Full-stack engineer. Implements features across all components." model = "sonnet-4.6" max_turns = 50 max_budget_usd = 5.00 -prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .story_kit/README.md to understand the dev process. Pick up the story from .story_kit/work/ - move it to work/2_current/ if needed. Follow the SDTW process through implementation and verification (Steps 1-3). The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop. If the user asks to review your changes, tell them to run: cd \"{{worktree_path}}\" && git difftool {{base_branch}}...HEAD\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates (cargo clippy + tests) when your process exits and advance the pipeline based on the results." +prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .story_kit/README.md to understand the dev process. The story details are in your prompt above. Follow the SDTW process through implementation and verification (Steps 1-3). The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop. If the user asks to review your changes, tell them to run: cd \"{{worktree_path}}\" && git difftool {{base_branch}}...HEAD\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates (cargo clippy + tests) when your process exits and advance the pipeline based on the results." system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Follow the Story-Driven Test Workflow strictly. Run cargo clippy and biome checks before considering work complete. Commit all your work before finishing - use a descriptive commit message. Do not accept stories, move them to archived, or merge to master - a human will do that. Do not coordinate with other agents - focus on your assigned story. The server automatically runs acceptance gates when your process exits." [[agent]] @@ -72,7 +72,7 @@ role = "Full-stack engineer. Implements features across all components." model = "sonnet-4.6" max_turns = 50 max_budget_usd = 5.00 -prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .story_kit/README.md to understand the dev process. Pick up the story from .story_kit/work/ - move it to work/2_current/ if needed. Follow the SDTW process through implementation and verification (Steps 1-3). The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop. If the user asks to review your changes, tell them to run: cd \"{{worktree_path}}\" && git difftool {{base_branch}}...HEAD\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates (cargo clippy + tests) when your process exits and advance the pipeline based on the results." +prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .story_kit/README.md to understand the dev process. The story details are in your prompt above. Follow the SDTW process through implementation and verification (Steps 1-3). The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop. If the user asks to review your changes, tell them to run: cd \"{{worktree_path}}\" && git difftool {{base_branch}}...HEAD\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates (cargo clippy + tests) when your process exits and advance the pipeline based on the results." system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Follow the Story-Driven Test Workflow strictly. Run cargo clippy and biome checks before considering work complete. Commit all your work before finishing - use a descriptive commit message. Do not accept stories, move them to archived, or merge to master - a human will do that. Do not coordinate with other agents - focus on your assigned story. The server automatically runs acceptance gates when your process exits." [[agent]] @@ -81,7 +81,7 @@ role = "Senior full-stack engineer for complex tasks. Implements features across model = "opus" max_turns = 80 max_budget_usd = 20.00 -prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .story_kit/README.md to understand the dev process. Pick up the story from .story_kit/work/ - move it to work/2_current/ if needed. Follow the SDTW process through implementation and verification (Steps 1-3). The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop. If the user asks to review your changes, tell them to run: cd \"{{worktree_path}}\" && git difftool {{base_branch}}...HEAD\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates (cargo clippy + tests) when your process exits and advance the pipeline based on the results." +prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .story_kit/README.md to understand the dev process. The story details are in your prompt above. Follow the SDTW process through implementation and verification (Steps 1-3). The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop. If the user asks to review your changes, tell them to run: cd \"{{worktree_path}}\" && git difftool {{base_branch}}...HEAD\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates (cargo clippy + tests) when your process exits and advance the pipeline based on the results." system_prompt = "You are a senior full-stack engineer working autonomously in a git worktree. You handle complex tasks requiring deep architectural understanding. Follow the Story-Driven Test Workflow strictly. Run cargo clippy and biome checks before considering work complete. Commit all your work before finishing - use a descriptive commit message. Do not accept stories, move them to archived, or merge to master - a human will do that. Do not coordinate with other agents - focus on your assigned story. The server automatically runs acceptance gates when your process exits." [[agent]] diff --git a/server/src/worktree.rs b/server/src/worktree.rs index 0ce5f1d..b622d2f 100644 --- a/server/src/worktree.rs +++ b/server/src/worktree.rs @@ -71,8 +71,12 @@ pub async fn create_worktree( let base_branch = detect_base_branch(project_root); let root = project_root.to_path_buf(); - // Already exists — reuse + // Already exists — reuse (ensure sparse checkout is configured) if wt_path.exists() { + let wt_clone = wt_path.clone(); + tokio::task::spawn_blocking(move || configure_sparse_checkout(&wt_clone)) + .await + .map_err(|e| format!("spawn_blocking: {e}"))??; write_mcp_json(&wt_path, port)?; run_setup_commands(&wt_path, config).await?; return Ok(WorktreeInfo { @@ -143,6 +147,62 @@ fn create_worktree_sync( return Err(format!("git worktree add failed: {stderr}")); } + // Enable sparse checkout to exclude pipeline files from the worktree. + // This prevents .story_kit/work/ changes from ending up in feature branches, + // which cause rename/delete merge conflicts when merging back to master. + configure_sparse_checkout(wt_path)?; + + Ok(()) +} + +/// Configure sparse checkout on a worktree to exclude `.story_kit/work/`. +/// +/// This prevents pipeline file moves (upcoming → current → qa → merge → archived) +/// from being committed on feature branches, which avoids rename/delete merge +/// conflicts when merging back to master. +fn configure_sparse_checkout(wt_path: &Path) -> Result<(), String> { + // Enable sparse checkout via git config (non-cone mode for pattern support) + let output = Command::new("git") + .args(["config", "core.sparseCheckout", "true"]) + .current_dir(wt_path) + .output() + .map_err(|e| format!("sparse-checkout config: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("sparse-checkout config failed: {stderr}")); + } + + // Resolve the actual git dir (worktrees use a .git file pointing elsewhere) + let git_dir_output = Command::new("git") + .args(["rev-parse", "--git-dir"]) + .current_dir(wt_path) + .output() + .map_err(|e| format!("git rev-parse --git-dir: {e}"))?; + + let git_dir = PathBuf::from( + String::from_utf8_lossy(&git_dir_output.stdout).trim().to_string(), + ); + + // Write sparse-checkout patterns: include everything, exclude .story_kit/work/ + let info_dir = git_dir.join("info"); + std::fs::create_dir_all(&info_dir) + .map_err(|e| format!("Create sparse-checkout dir: {e}"))?; + std::fs::write(info_dir.join("sparse-checkout"), "/*\n!.story_kit/work/\n") + .map_err(|e| format!("Write sparse-checkout: {e}"))?; + + // Re-read the working tree to apply sparse checkout rules + let output = Command::new("git") + .args(["read-tree", "-mu", "HEAD"]) + .current_dir(wt_path) + .output() + .map_err(|e| format!("git read-tree: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("git read-tree failed: {stderr}")); + } + Ok(()) } @@ -380,4 +440,40 @@ mod tests { ); assert!(wt_path.exists()); } + + #[test] + fn sparse_checkout_excludes_story_kit_work() { + let tmp = TempDir::new().unwrap(); + let project_root = tmp.path().join("my-project"); + fs::create_dir_all(&project_root).unwrap(); + init_git_repo(&project_root); + + // Create a tracked file under .story_kit/work/ on the initial branch + let work_dir = project_root.join(".story_kit").join("work"); + fs::create_dir_all(&work_dir).unwrap(); + fs::write(work_dir.join("test_story.md"), "# Test").unwrap(); + Command::new("git") + .args(["add", "."]) + .current_dir(&project_root) + .output() + .unwrap(); + Command::new("git") + .args(["commit", "-m", "add work file"]) + .current_dir(&project_root) + .output() + .unwrap(); + + let wt_path = tmp.path().join("my-worktree"); + let branch = "feature/test-sparse"; + create_worktree_sync(&project_root, &wt_path, branch).unwrap(); + + // .story_kit/work/ should not exist in the worktree + assert!( + !wt_path.join(".story_kit").join("work").exists(), + ".story_kit/work/ should be excluded by sparse checkout" + ); + + // Other files should still exist + assert!(wt_path.join(".git").exists()); + } }