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

16
.gitignore vendored
View File

@@ -4,24 +4,10 @@
# Local environment (secrets) # Local environment (secrets)
.env .env
# App specific # App specific (root-level; story-kit subdirectory patterns live in .story_kit/.gitignore)
store.json store.json
.story_kit_port .story_kit_port
# Bot config (contains credentials)
.story_kit/bot.toml
# Matrix SDK state store
.story_kit/matrix_store/
.story_kit/matrix_device_id
# Agent worktrees and merge workspace (managed by the server, not tracked in git)
.story_kit/worktrees/
.story_kit/merge_workspace/
# Coverage reports (generated by cargo-llvm-cov, not tracked in git)
.story_kit/coverage/
# Rust stuff # Rust stuff
target target

13
.story_kit/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
# Bot config (contains credentials)
bot.toml
# Matrix SDK state store
matrix_store/
matrix_device_id
# Agent worktrees and merge workspace (managed by the server, not tracked in git)
worktrees/
merge_workspace/
# Coverage reports (generated by cargo-llvm-cov, not tracked in git)
coverage/

View File

@@ -313,17 +313,61 @@ fn write_script_if_missing(path: &Path, content: &str) -> Result<(), String> {
Ok(()) Ok(())
} }
/// Append Story Kit entries to `.gitignore` (or create one if missing). /// Write (or idempotently update) `.story_kit/.gitignore` with Story Kitspecific
/// Does not duplicate entries already present. /// ignore patterns for files that live inside the `.story_kit/` directory.
fn append_gitignore_entries(root: &Path) -> Result<(), String> { /// 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 = [ let entries = [
".story_kit/worktrees/", "bot.toml",
".story_kit/merge_workspace/", "matrix_store/",
".story_kit/coverage/", "matrix_device_id",
".story_kit_port", "worktrees/",
"store.json", "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 gitignore_path = root.join(".gitignore");
let existing = if gitignore_path.exists() { let existing = if gitignore_path.exists() {
fs::read_to_string(&gitignore_path) 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))?; .map_err(|e| format!("Failed to create .claude/ directory: {}", e))?;
write_file_if_missing(&claude_dir.join("settings.json"), STORY_KIT_CLAUDE_SETTINGS)?; 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 // Run `git init` if the directory is not already a git repo, then make an initial commit
if !root.join(".git").exists() { if !root.join(".git").exists() {
@@ -1122,12 +1167,17 @@ mod tests {
toml_content toml_content
); );
let gitignore = fs::read_to_string(dir.path().join(".gitignore")).unwrap(); let story_kit_gitignore =
let count = gitignore fs::read_to_string(dir.path().join(".story_kit/.gitignore")).unwrap();
let count = story_kit_gitignore
.lines() .lines()
.filter(|l| l.trim() == ".story_kit/worktrees/") .filter(|l| l.trim() == "worktrees/")
.count(); .count();
assert_eq!(count, 1, ".gitignore should not have duplicate entries"); assert_eq!(
count,
1,
".story_kit/.gitignore should not have duplicate entries"
);
} }
#[test] #[test]
@@ -1173,53 +1223,56 @@ mod tests {
} }
#[test] #[test]
fn scaffold_creates_gitignore_with_story_kit_entries() { fn scaffold_creates_story_kit_gitignore_with_relative_entries() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap(); scaffold_story_kit(dir.path()).unwrap();
let content = fs::read_to_string(dir.path().join(".gitignore")).unwrap(); // .story_kit/.gitignore must contain relative patterns for files under .story_kit/
assert!(content.contains(".story_kit/worktrees/")); let sk_content =
assert!(content.contains(".story_kit/merge_workspace/")); fs::read_to_string(dir.path().join(".story_kit/.gitignore")).unwrap();
assert!(content.contains(".story_kit/coverage/")); assert!(sk_content.contains("worktrees/"));
assert!(content.contains(".story_kit_port")); assert!(sk_content.contains("merge_workspace/"));
assert!(content.contains("store.json")); 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] #[test]
fn scaffold_gitignore_does_not_duplicate_existing_entries() { fn scaffold_story_kit_gitignore_does_not_duplicate_existing_entries() {
let dir = tempdir().unwrap(); 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( fs::write(
dir.path().join(".gitignore"), dir.path().join(".story_kit/.gitignore"),
".story_kit/worktrees/\n.story_kit/coverage/\n", "worktrees/\ncoverage/\n",
) )
.unwrap(); .unwrap();
scaffold_story_kit(dir.path()).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 let worktrees_count = content
.lines() .lines()
.filter(|l| l.trim() == ".story_kit/worktrees/") .filter(|l| l.trim() == "worktrees/")
.count(); .count();
assert_eq!( assert_eq!(worktrees_count, 1, "worktrees/ should not be duplicated");
worktrees_count,
1,
".story_kit/worktrees/ should not be duplicated"
);
let coverage_count = content let coverage_count = content
.lines() .lines()
.filter(|l| l.trim() == ".story_kit/coverage/") .filter(|l| l.trim() == "coverage/")
.count(); .count();
assert_eq!( assert_eq!(coverage_count, 1, "coverage/ should not be duplicated");
coverage_count, // The missing entry must have been added
1, assert!(content.contains("merge_workspace/"));
".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"));
} }
// --- CLAUDE.md scaffold --- // --- CLAUDE.md scaffold ---