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 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-23 16:36:15 +00:00
parent fce745cb3c
commit 2eb5bfb8fe
2 changed files with 101 additions and 5 deletions

View File

@@ -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]]

View File

@@ -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());
}
}