story-kit: merge 259_story_move_story_kit_ignores_into_story_kit_gitignore

This commit is contained in:
Dave
2026-03-17 13:13:01 +00:00
parent 09a71b4515
commit f89f78d77d
3 changed files with 108 additions and 56 deletions

View File

@@ -313,17 +313,61 @@ fn write_script_if_missing(path: &Path, content: &str) -> Result<(), String> {
Ok(())
}
/// Append Story Kit entries to `.gitignore` (or create one if missing).
/// Does not duplicate entries already present.
fn append_gitignore_entries(root: &Path) -> Result<(), String> {
/// Write (or idempotently update) `.story_kit/.gitignore` with Story Kitspecific
/// ignore patterns for files that live inside the `.story_kit/` directory.
/// Patterns are relative to `.story_kit/` as git resolves `.gitignore` files
/// relative to the directory that contains them.
fn write_story_kit_gitignore(root: &Path) -> Result<(), String> {
// Entries that belong inside .story_kit/.gitignore (relative to .story_kit/).
let entries = [
".story_kit/worktrees/",
".story_kit/merge_workspace/",
".story_kit/coverage/",
".story_kit_port",
"store.json",
"bot.toml",
"matrix_store/",
"matrix_device_id",
"worktrees/",
"merge_workspace/",
"coverage/",
];
let gitignore_path = root.join(".story_kit").join(".gitignore");
let existing = if gitignore_path.exists() {
fs::read_to_string(&gitignore_path)
.map_err(|e| format!("Failed to read .story_kit/.gitignore: {}", e))?
} else {
String::new()
};
let missing: Vec<&str> = entries
.iter()
.copied()
.filter(|e| !existing.lines().any(|l| l.trim() == *e))
.collect();
if missing.is_empty() {
return Ok(());
}
let mut new_content = existing;
if !new_content.is_empty() && !new_content.ends_with('\n') {
new_content.push('\n');
}
for entry in missing {
new_content.push_str(entry);
new_content.push('\n');
}
fs::write(&gitignore_path, new_content)
.map_err(|e| format!("Failed to write .story_kit/.gitignore: {}", e))?;
Ok(())
}
/// Append root-level Story Kit entries to the project `.gitignore`.
/// Only `store.json` and `.story_kit_port` remain here because they live at
/// the project root and git does not support `../` patterns in `.gitignore`
/// files, so they cannot be expressed in `.story_kit/.gitignore`.
fn append_root_gitignore_entries(root: &Path) -> Result<(), String> {
let entries = [".story_kit_port", "store.json"];
let gitignore_path = root.join(".gitignore");
let existing = if gitignore_path.exists() {
fs::read_to_string(&gitignore_path)
@@ -402,7 +446,8 @@ fn scaffold_story_kit(root: &Path) -> Result<(), String> {
.map_err(|e| format!("Failed to create .claude/ directory: {}", e))?;
write_file_if_missing(&claude_dir.join("settings.json"), STORY_KIT_CLAUDE_SETTINGS)?;
append_gitignore_entries(root)?;
write_story_kit_gitignore(root)?;
append_root_gitignore_entries(root)?;
// Run `git init` if the directory is not already a git repo, then make an initial commit
if !root.join(".git").exists() {
@@ -1122,12 +1167,17 @@ mod tests {
toml_content
);
let gitignore = fs::read_to_string(dir.path().join(".gitignore")).unwrap();
let count = gitignore
let story_kit_gitignore =
fs::read_to_string(dir.path().join(".story_kit/.gitignore")).unwrap();
let count = story_kit_gitignore
.lines()
.filter(|l| l.trim() == ".story_kit/worktrees/")
.filter(|l| l.trim() == "worktrees/")
.count();
assert_eq!(count, 1, ".gitignore should not have duplicate entries");
assert_eq!(
count,
1,
".story_kit/.gitignore should not have duplicate entries"
);
}
#[test]
@@ -1173,53 +1223,56 @@ mod tests {
}
#[test]
fn scaffold_creates_gitignore_with_story_kit_entries() {
fn scaffold_creates_story_kit_gitignore_with_relative_entries() {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
let content = fs::read_to_string(dir.path().join(".gitignore")).unwrap();
assert!(content.contains(".story_kit/worktrees/"));
assert!(content.contains(".story_kit/merge_workspace/"));
assert!(content.contains(".story_kit/coverage/"));
assert!(content.contains(".story_kit_port"));
assert!(content.contains("store.json"));
// .story_kit/.gitignore must contain relative patterns for files under .story_kit/
let sk_content =
fs::read_to_string(dir.path().join(".story_kit/.gitignore")).unwrap();
assert!(sk_content.contains("worktrees/"));
assert!(sk_content.contains("merge_workspace/"));
assert!(sk_content.contains("coverage/"));
// Must NOT contain absolute .story_kit/ prefixed paths
assert!(!sk_content.contains(".story_kit/"));
// Root .gitignore must contain root-level story-kit entries
let root_content = fs::read_to_string(dir.path().join(".gitignore")).unwrap();
assert!(root_content.contains(".story_kit_port"));
assert!(root_content.contains("store.json"));
// Root .gitignore must NOT contain .story_kit/ sub-directory patterns
assert!(!root_content.contains(".story_kit/worktrees/"));
assert!(!root_content.contains(".story_kit/merge_workspace/"));
assert!(!root_content.contains(".story_kit/coverage/"));
}
#[test]
fn scaffold_gitignore_does_not_duplicate_existing_entries() {
fn scaffold_story_kit_gitignore_does_not_duplicate_existing_entries() {
let dir = tempdir().unwrap();
// Pre-create .gitignore with some Story Kit entries already present
// Pre-create .story_kit dir and .gitignore with some entries already present
fs::create_dir_all(dir.path().join(".story_kit")).unwrap();
fs::write(
dir.path().join(".gitignore"),
".story_kit/worktrees/\n.story_kit/coverage/\n",
dir.path().join(".story_kit/.gitignore"),
"worktrees/\ncoverage/\n",
)
.unwrap();
scaffold_story_kit(dir.path()).unwrap();
let content = fs::read_to_string(dir.path().join(".gitignore")).unwrap();
let content =
fs::read_to_string(dir.path().join(".story_kit/.gitignore")).unwrap();
let worktrees_count = content
.lines()
.filter(|l| l.trim() == ".story_kit/worktrees/")
.filter(|l| l.trim() == "worktrees/")
.count();
assert_eq!(
worktrees_count,
1,
".story_kit/worktrees/ should not be duplicated"
);
assert_eq!(worktrees_count, 1, "worktrees/ should not be duplicated");
let coverage_count = content
.lines()
.filter(|l| l.trim() == ".story_kit/coverage/")
.filter(|l| l.trim() == "coverage/")
.count();
assert_eq!(
coverage_count,
1,
".story_kit/coverage/ should not be duplicated"
);
// The missing entries must have been added
assert!(content.contains(".story_kit/merge_workspace/"));
assert!(content.contains(".story_kit_port"));
assert!(content.contains("store.json"));
assert_eq!(coverage_count, 1, "coverage/ should not be duplicated");
// The missing entry must have been added
assert!(content.contains("merge_workspace/"));
}
// --- CLAUDE.md scaffold ---