rename .story_kit directory to .storkit and update all references

Renames the config directory and updates 514 references across 42 Rust
source files, plus CLAUDE.md, .gitignore, Makefile, script/release,
and .mcp.json files. All 1205 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dave
2026-03-20 11:34:53 +00:00
parent 375277f86e
commit 9581e5d51a
406 changed files with 531 additions and 530 deletions

View File

@@ -9,7 +9,7 @@ const KEY_LAST_PROJECT: &str = "last_project_path";
const KEY_SELECTED_MODEL: &str = "selected_model";
const KEY_KNOWN_PROJECTS: &str = "known_projects";
const STORY_KIT_README: &str = include_str!("../../../.story_kit/README.md");
const STORY_KIT_README: &str = include_str!("../../../.storkit/README.md");
const STORY_KIT_CONTEXT: &str = "<!-- story-kit:scaffold-template -->\n\
# Project Context\n\
@@ -57,7 +57,7 @@ The permission system validates the entire command string, and chained commands
won't match allow rules like `Bash(git *)`. Use separate Bash calls instead — \
parallel calls work fine.\n\
\n\
Read .story_kit/README.md to see our dev process.\n";
Read .storkit/README.md to see our dev process.\n";
const STORY_KIT_CLAUDE_SETTINGS: &str = r#"{
"permissions": {
@@ -110,7 +110,7 @@ role = "Full-stack engineer. Implements features across all components."
model = "sonnet"
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. Follow the workflow through implementation and verification. 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.\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates when your process exits."
prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .storkit/README.md to understand the dev process. Follow the workflow through implementation and verification. 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.\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates when your process exits."
system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Follow the Story-Driven Test Workflow strictly. Commit all your work before finishing. Do not accept stories, move them to archived, or merge to master."
[[agent]]
@@ -238,11 +238,11 @@ pub fn resolve_cli_path(cwd: &Path, path_arg: &str) -> PathBuf {
}
/// Walk from `start` up through parent directories, returning the first
/// directory that contains a `.story_kit/` subdirectory, or `None`.
/// directory that contains a `.storkit/` subdirectory, or `None`.
pub fn find_story_kit_root(start: &Path) -> Option<PathBuf> {
let mut current = start.to_path_buf();
loop {
if current.join(".story_kit").is_dir() {
if current.join(".storkit").is_dir() {
return Some(current);
}
if !current.pop() {
@@ -316,12 +316,12 @@ fn write_script_if_missing(path: &Path, content: &str) -> Result<(), String> {
Ok(())
}
/// 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
/// Write (or idempotently update) `.storkit/.gitignore` with Story Kitspecific
/// ignore patterns for files that live inside the `.storkit/` directory.
/// Patterns are relative to `.storkit/` 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/).
// Entries that belong inside .storkit/.gitignore (relative to .storkit/).
let entries = [
"bot.toml",
"matrix_store/",
@@ -331,10 +331,10 @@ fn write_story_kit_gitignore(root: &Path) -> Result<(), String> {
"coverage/",
];
let gitignore_path = root.join(".story_kit").join(".gitignore");
let gitignore_path = root.join(".storkit").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))?
.map_err(|e| format!("Failed to read .storkit/.gitignore: {}", e))?
} else {
String::new()
};
@@ -359,17 +359,17 @@ fn write_story_kit_gitignore(root: &Path) -> Result<(), String> {
}
fs::write(&gitignore_path, new_content)
.map_err(|e| format!("Failed to write .story_kit/.gitignore: {}", e))?;
.map_err(|e| format!("Failed to write .storkit/.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
/// Only `store.json` and `.storkit_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`.
/// files, so they cannot be expressed in `.storkit/.gitignore`.
fn append_root_gitignore_entries(root: &Path) -> Result<(), String> {
let entries = [".story_kit_port", "store.json"];
let entries = [".storkit_port", "store.json"];
let gitignore_path = root.join(".gitignore");
let existing = if gitignore_path.exists() {
@@ -405,7 +405,7 @@ fn append_root_gitignore_entries(root: &Path) -> Result<(), String> {
}
fn scaffold_story_kit(root: &Path) -> Result<(), String> {
let story_kit_root = root.join(".story_kit");
let story_kit_root = root.join(".storkit");
let specs_root = story_kit_root.join("specs");
let tech_root = specs_root.join("tech");
let functional_root = specs_root.join("functional");
@@ -464,7 +464,7 @@ fn scaffold_story_kit(root: &Path) -> Result<(), String> {
}
let add_output = std::process::Command::new("git")
.args(["add", ".story_kit", "script", ".gitignore", "CLAUDE.md", ".claude"])
.args(["add", ".storkit", "script", ".gitignore", "CLAUDE.md", ".claude"])
.current_dir(root)
.output()
.map_err(|e| format!("Failed to run git add: {}", e))?;
@@ -505,7 +505,7 @@ async fn ensure_project_root_with_story_kit(path: PathBuf) -> Result<(), String>
fs::create_dir_all(&path)
.map_err(|e| format!("Failed to create project directory: {}", e))?;
}
if !path.join(".story_kit").is_dir() {
if !path.join(".storkit").is_dir() {
scaffold_story_kit(&path)?;
}
Ok(())
@@ -1032,7 +1032,7 @@ mod tests {
#[test]
fn find_story_kit_root_returns_cwd_when_story_kit_in_cwd() {
let tmp = tempfile::tempdir().unwrap();
std::fs::create_dir_all(tmp.path().join(".story_kit")).unwrap();
std::fs::create_dir_all(tmp.path().join(".storkit")).unwrap();
let result = find_story_kit_root(tmp.path());
assert_eq!(result, Some(tmp.path().to_path_buf()));
@@ -1041,7 +1041,7 @@ mod tests {
#[test]
fn find_story_kit_root_returns_parent_when_story_kit_in_parent() {
let tmp = tempfile::tempdir().unwrap();
std::fs::create_dir_all(tmp.path().join(".story_kit")).unwrap();
std::fs::create_dir_all(tmp.path().join(".storkit")).unwrap();
let child = tmp.path().join("subdir").join("nested");
std::fs::create_dir_all(&child).unwrap();
@@ -1060,9 +1060,9 @@ mod tests {
#[test]
fn find_story_kit_root_prefers_nearest_ancestor() {
let tmp = tempfile::tempdir().unwrap();
std::fs::create_dir_all(tmp.path().join(".story_kit")).unwrap();
std::fs::create_dir_all(tmp.path().join(".storkit")).unwrap();
let child = tmp.path().join("inner");
std::fs::create_dir_all(child.join(".story_kit")).unwrap();
std::fs::create_dir_all(child.join(".storkit")).unwrap();
let result = find_story_kit_root(&child);
assert_eq!(result, Some(child));
@@ -1075,12 +1075,12 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
assert!(dir.path().join(".story_kit/README.md").exists());
assert!(dir.path().join(".story_kit/project.toml").exists());
assert!(dir.path().join(".story_kit/specs/00_CONTEXT.md").exists());
assert!(dir.path().join(".story_kit/specs/tech/STACK.md").exists());
assert!(dir.path().join(".storkit/README.md").exists());
assert!(dir.path().join(".storkit/project.toml").exists());
assert!(dir.path().join(".storkit/specs/00_CONTEXT.md").exists());
assert!(dir.path().join(".storkit/specs/tech/STACK.md").exists());
// Old stories/ dirs should NOT be created
assert!(!dir.path().join(".story_kit/stories").exists());
assert!(!dir.path().join(".storkit/stories").exists());
assert!(dir.path().join("script/test").exists());
}
@@ -1091,7 +1091,7 @@ mod tests {
let stages = ["1_backlog", "2_current", "3_qa", "4_merge", "5_done", "6_archived"];
for stage in &stages {
let path = dir.path().join(".story_kit/work").join(stage);
let path = dir.path().join(".storkit/work").join(stage);
assert!(path.is_dir(), "work/{} should be a directory", stage);
assert!(
path.join(".gitkeep").exists(),
@@ -1107,7 +1107,7 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".story_kit/project.toml")).unwrap();
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
assert!(content.contains("[[agent]]"));
assert!(content.contains("stage = \"coder\""));
assert!(content.contains("stage = \"qa\""));
@@ -1121,7 +1121,7 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".story_kit/specs/00_CONTEXT.md")).unwrap();
fs::read_to_string(dir.path().join(".storkit/specs/00_CONTEXT.md")).unwrap();
assert!(content.contains("<!-- story-kit:scaffold-template -->"));
assert!(content.contains("## High-Level Goal"));
assert!(content.contains("## Core Features"));
@@ -1138,7 +1138,7 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".story_kit/specs/tech/STACK.md")).unwrap();
fs::read_to_string(dir.path().join(".storkit/specs/tech/STACK.md")).unwrap();
assert!(content.contains("<!-- story-kit:scaffold-template -->"));
assert!(content.contains("## Core Stack"));
assert!(content.contains("## Coding Standards"));
@@ -1169,7 +1169,7 @@ mod tests {
#[test]
fn scaffold_story_kit_does_not_overwrite_existing() {
let dir = tempdir().unwrap();
let readme = dir.path().join(".story_kit/README.md");
let readme = dir.path().join(".storkit/README.md");
fs::create_dir_all(readme.parent().unwrap()).unwrap();
fs::write(&readme, "custom content").unwrap();
@@ -1184,24 +1184,24 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let readme_content =
fs::read_to_string(dir.path().join(".story_kit/README.md")).unwrap();
fs::read_to_string(dir.path().join(".storkit/README.md")).unwrap();
let toml_content =
fs::read_to_string(dir.path().join(".story_kit/project.toml")).unwrap();
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
// Run again — must not change content or add duplicate .gitignore entries
scaffold_story_kit(dir.path()).unwrap();
assert_eq!(
fs::read_to_string(dir.path().join(".story_kit/README.md")).unwrap(),
fs::read_to_string(dir.path().join(".storkit/README.md")).unwrap(),
readme_content
);
assert_eq!(
fs::read_to_string(dir.path().join(".story_kit/project.toml")).unwrap(),
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap(),
toml_content
);
let story_kit_gitignore =
fs::read_to_string(dir.path().join(".story_kit/.gitignore")).unwrap();
fs::read_to_string(dir.path().join(".storkit/.gitignore")).unwrap();
let count = story_kit_gitignore
.lines()
.filter(|l| l.trim() == "worktrees/")
@@ -1209,7 +1209,7 @@ mod tests {
assert_eq!(
count,
1,
".story_kit/.gitignore should not have duplicate entries"
".storkit/.gitignore should not have duplicate entries"
);
}
@@ -1260,32 +1260,32 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
// .story_kit/.gitignore must contain relative patterns for files under .story_kit/
// .storkit/.gitignore must contain relative patterns for files under .storkit/
let sk_content =
fs::read_to_string(dir.path().join(".story_kit/.gitignore")).unwrap();
fs::read_to_string(dir.path().join(".storkit/.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/"));
// Must NOT contain absolute .storkit/ prefixed paths
assert!(!sk_content.contains(".storkit/"));
// 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(".storkit_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/"));
// Root .gitignore must NOT contain .storkit/ sub-directory patterns
assert!(!root_content.contains(".storkit/worktrees/"));
assert!(!root_content.contains(".storkit/merge_workspace/"));
assert!(!root_content.contains(".storkit/coverage/"));
}
#[test]
fn scaffold_story_kit_gitignore_does_not_duplicate_existing_entries() {
let dir = tempdir().unwrap();
// Pre-create .story_kit dir and .gitignore with some entries already present
fs::create_dir_all(dir.path().join(".story_kit")).unwrap();
// Pre-create .storkit dir and .gitignore with some entries already present
fs::create_dir_all(dir.path().join(".storkit")).unwrap();
fs::write(
dir.path().join(".story_kit/.gitignore"),
dir.path().join(".storkit/.gitignore"),
"worktrees/\ncoverage/\n",
)
.unwrap();
@@ -1293,7 +1293,7 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".story_kit/.gitignore")).unwrap();
fs::read_to_string(dir.path().join(".storkit/.gitignore")).unwrap();
let worktrees_count = content
.lines()
.filter(|l| l.trim() == "worktrees/")
@@ -1324,8 +1324,8 @@ mod tests {
"CLAUDE.md should contain the scaffold sentinel"
);
assert!(
content.contains("Read .story_kit/README.md"),
"CLAUDE.md should include directive to read .story_kit/README.md"
content.contains("Read .storkit/README.md"),
"CLAUDE.md should include directive to read .storkit/README.md"
);
assert!(
content.contains("Never chain shell commands"),
@@ -1366,15 +1366,15 @@ mod tests {
.await
.unwrap();
// .story_kit/ should have been created automatically
assert!(project_dir.join(".story_kit").is_dir());
// .storkit/ should have been created automatically
assert!(project_dir.join(".storkit").is_dir());
}
#[tokio::test]
async fn open_project_does_not_overwrite_existing_story_kit() {
let dir = tempdir().unwrap();
let project_dir = dir.path().join("myproject");
let sk_dir = project_dir.join(".story_kit");
let sk_dir = project_dir.join(".storkit");
fs::create_dir_all(&sk_dir).unwrap();
let readme = sk_dir.join("README.md");
fs::write(&readme, "custom content").unwrap();
@@ -1389,7 +1389,7 @@ mod tests {
.await
.unwrap();
// Existing .story_kit/ content should not be overwritten
// Existing .storkit/ content should not be overwritten
assert_eq!(fs::read_to_string(&readme).unwrap(), "custom content");
}
@@ -1570,7 +1570,7 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".story_kit/project.toml")).unwrap();
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
assert!(
content.contains("[[component]]"),
"project.toml should contain a component entry"
@@ -1591,7 +1591,7 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".story_kit/project.toml")).unwrap();
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
assert!(
content.contains("[[component]]"),
"project.toml should always have at least one component"
@@ -1606,7 +1606,7 @@ mod tests {
#[test]
fn scaffold_does_not_overwrite_existing_project_toml_with_components() {
let dir = tempdir().unwrap();
let sk_dir = dir.path().join(".story_kit");
let sk_dir = dir.path().join(".storkit");
fs::create_dir_all(&sk_dir).unwrap();
let existing = "[[component]]\nname = \"custom\"\npath = \".\"\nsetup = [\"make build\"]\n";
fs::write(sk_dir.join("project.toml"), existing).unwrap();

View File

@@ -18,7 +18,7 @@ pub struct OnboardingStatus {
pub needs_stack: bool,
/// True when `script/test` still contains the scaffold placeholder.
pub needs_test_script: bool,
/// True when `.story_kit/project.toml` is missing or has no
/// True when `.storkit/project.toml` is missing or has no
/// `[[component]]` entries.
pub needs_project_toml: bool,
}
@@ -33,7 +33,7 @@ impl OnboardingStatus {
/// Inspect the project at `project_root` and determine which onboarding
/// steps are still required.
pub fn check_onboarding_status(project_root: &Path) -> OnboardingStatus {
let story_kit = project_root.join(".story_kit");
let story_kit = project_root.join(".storkit");
OnboardingStatus {
needs_context: is_template_or_missing(
@@ -79,7 +79,7 @@ mod tests {
fn setup_project(dir: &TempDir) -> std::path::PathBuf {
let root = dir.path().to_path_buf();
let sk = root.join(".story_kit");
let sk = root.join(".storkit");
fs::create_dir_all(sk.join("specs").join("tech")).unwrap();
fs::create_dir_all(root.join("script")).unwrap();
root
@@ -106,12 +106,12 @@ mod tests {
// Write content that includes the scaffold sentinel
fs::write(
root.join(".story_kit/specs/00_CONTEXT.md"),
root.join(".storkit/specs/00_CONTEXT.md"),
"<!-- story-kit:scaffold-template -->\n# Project Context\nPlaceholder...",
)
.unwrap();
fs::write(
root.join(".story_kit/specs/tech/STACK.md"),
root.join(".storkit/specs/tech/STACK.md"),
"<!-- story-kit:scaffold-template -->\n# Tech Stack\nPlaceholder...",
)
.unwrap();
@@ -130,12 +130,12 @@ mod tests {
// Real project content that happens to mention "Agentic AI Code Assistant"
// but does NOT contain the scaffold sentinel — should NOT trigger onboarding.
fs::write(
root.join(".story_kit/specs/00_CONTEXT.md"),
root.join(".storkit/specs/00_CONTEXT.md"),
"# Project Context\nTo build a standalone Agentic AI Code Assistant application.",
)
.unwrap();
fs::write(
root.join(".story_kit/specs/tech/STACK.md"),
root.join(".storkit/specs/tech/STACK.md"),
"# Tech Stack\nThis is an Agentic Code Assistant binary.",
)
.unwrap();
@@ -152,12 +152,12 @@ mod tests {
let root = setup_project(&dir);
fs::write(
root.join(".story_kit/specs/00_CONTEXT.md"),
root.join(".storkit/specs/00_CONTEXT.md"),
"# My Project\n\nThis is an e-commerce platform for selling widgets.",
)
.unwrap();
fs::write(
root.join(".story_kit/specs/tech/STACK.md"),
root.join(".storkit/specs/tech/STACK.md"),
"# Tech Stack\n\n## Backend: Python + FastAPI\n## Frontend: React + TypeScript",
)
.unwrap();
@@ -173,8 +173,8 @@ mod tests {
let dir = TempDir::new().unwrap();
let root = setup_project(&dir);
fs::write(root.join(".story_kit/specs/00_CONTEXT.md"), " \n").unwrap();
fs::write(root.join(".story_kit/specs/tech/STACK.md"), "").unwrap();
fs::write(root.join(".storkit/specs/00_CONTEXT.md"), " \n").unwrap();
fs::write(root.join(".storkit/specs/tech/STACK.md"), "").unwrap();
let status = check_onboarding_status(&root);
assert!(status.needs_context);
@@ -230,7 +230,7 @@ mod tests {
let root = setup_project(&dir);
fs::write(
root.join(".story_kit/project.toml"),
root.join(".storkit/project.toml"),
"# empty config\n",
)
.unwrap();
@@ -245,7 +245,7 @@ mod tests {
let root = setup_project(&dir);
fs::write(
root.join(".story_kit/project.toml"),
root.join(".storkit/project.toml"),
"[[component]]\nname = \"app\"\npath = \".\"\nsetup = [\"cargo check\"]\n",
)
.unwrap();
@@ -263,12 +263,12 @@ mod tests {
// Write real content for the required onboarding files
fs::write(
root.join(".story_kit/specs/00_CONTEXT.md"),
root.join(".storkit/specs/00_CONTEXT.md"),
"# My Project\n\nReal project context.",
)
.unwrap();
fs::write(
root.join(".story_kit/specs/tech/STACK.md"),
root.join(".storkit/specs/tech/STACK.md"),
"# My Stack\n\nReal stack content.",
)
.unwrap();
@@ -300,13 +300,13 @@ mod tests {
// Context still has sentinel
fs::write(
root.join(".story_kit/specs/00_CONTEXT.md"),
root.join(".storkit/specs/00_CONTEXT.md"),
"<!-- story-kit:scaffold-template -->\n# Project Context\nPlaceholder...",
)
.unwrap();
// Stack is customised (no sentinel)
fs::write(
root.join(".story_kit/specs/tech/STACK.md"),
root.join(".storkit/specs/tech/STACK.md"),
"# My Stack\nRuby on Rails + PostgreSQL",
)
.unwrap();

View File

@@ -1,10 +1,10 @@
//! Filesystem watcher for `.story_kit/work/` and `.story_kit/project.toml`.
//! Filesystem watcher for `.storkit/work/` and `.storkit/project.toml`.
//!
//! Watches the work pipeline directories for file changes, infers the lifecycle
//! stage from the target directory name, auto-commits with a deterministic message,
//! and broadcasts a [`WatcherEvent`] to all connected WebSocket clients.
//!
//! Also watches `.story_kit/project.toml` for modifications and broadcasts
//! Also watches `.storkit/project.toml` for modifications and broadcasts
//! [`WatcherEvent::ConfigChanged`] so the frontend can reload the agent roster
//! without a server restart.
//!
@@ -45,7 +45,7 @@ pub enum WatcherEvent {
/// The deterministic git commit message used (or that would have been used).
commit_msg: String,
},
/// `.story_kit/project.toml` was modified at the project root (not inside a worktree).
/// `.storkit/project.toml` was modified at the project root (not inside a worktree).
ConfigChanged,
/// An agent's state changed (started, stopped, completed, etc.).
/// Triggers a pipeline state refresh so the frontend can update agent
@@ -61,8 +61,8 @@ pub enum WatcherEvent {
},
}
/// Return `true` if `path` is the root-level `.story_kit/project.toml`, i.e.
/// `{git_root}/.story_kit/project.toml`.
/// Return `true` if `path` is the root-level `.storkit/project.toml`, i.e.
/// `{git_root}/.storkit/project.toml`.
///
/// Returns `false` for paths inside worktree directories (paths containing
/// a `worktrees` component).
@@ -71,7 +71,7 @@ pub fn is_config_file(path: &Path, git_root: &Path) -> bool {
if path.components().any(|c| c.as_os_str() == "worktrees") {
return false;
}
let expected = git_root.join(".story_kit").join("project.toml");
let expected = git_root.join(".storkit").join("project.toml");
path == expected
}
@@ -92,7 +92,7 @@ fn stage_metadata(stage: &str, item_id: &str) -> Option<(&'static str, String)>
/// Return the pipeline stage name for a path if it is a `.md` file living
/// directly inside one of the known work subdirectories, otherwise `None`.
///
/// Explicitly returns `None` for any path under `.story_kit/worktrees/` so
/// Explicitly returns `None` for any path under `.storkit/worktrees/` so
/// that code changes made by agents in their isolated worktrees are never
/// auto-committed to master by the watcher.
fn stage_for_path(path: &Path) -> Option<String> {
@@ -117,11 +117,11 @@ fn stage_for_path(path: &Path) -> Option<String> {
/// Stage all changes in the work directory and commit with the given message.
///
/// Uses `git add -A .story_kit/work/` to catch both additions and deletions in
/// Uses `git add -A .storkit/work/` to catch both additions and deletions in
/// a single commit. Returns `Ok(true)` if a commit was made, `Ok(false)` if
/// there was nothing to commit, and `Err` for unexpected failures.
fn git_add_work_and_commit(git_root: &Path, message: &str) -> Result<bool, String> {
let work_rel = PathBuf::from(".story_kit").join("work");
let work_rel = PathBuf::from(".storkit").join("work");
let add_out = std::process::Command::new("git")
.args(["add", "-A"])
@@ -170,7 +170,7 @@ fn should_commit_stage(stage: &str) -> bool {
///
/// Only files that still exist on disk are used to derive the commit message
/// (they represent the destination of a move or a new file). Deletions are
/// captured by `git add -A .story_kit/work/` automatically.
/// captured by `git add -A .storkit/work/` automatically.
///
/// Only terminal stages (`1_backlog` and `6_archived`) trigger git commits.
/// All stages broadcast a [`WatcherEvent`] so the frontend stays in sync.
@@ -338,9 +338,9 @@ fn sweep_done_to_archived(work_dir: &Path, git_root: &Path, done_retention: Dura
/// Start the filesystem watcher on a dedicated OS thread.
///
/// `work_dir` — absolute path to `.story_kit/work/` (watched recursively).
/// `work_dir` — absolute path to `.storkit/work/` (watched recursively).
/// `git_root` — project root (passed to `git` commands as cwd, and used to
/// derive the config file path `.story_kit/project.toml`).
/// derive the config file path `.storkit/project.toml`).
/// `event_tx` — broadcast sender; each connected WebSocket client holds a receiver.
/// `watcher_config` — initial sweep configuration loaded from `project.toml`.
pub fn start_watcher(
@@ -367,8 +367,8 @@ pub fn start_watcher(
return;
}
// Also watch .story_kit/project.toml for hot-reload of agent config.
let config_file = git_root.join(".story_kit").join("project.toml");
// Also watch .storkit/project.toml for hot-reload of agent config.
let config_file = git_root.join(".storkit").join("project.toml");
if config_file.exists()
&& let Err(e) = watcher.watch(&config_file, RecursiveMode::NonRecursive)
{
@@ -521,9 +521,9 @@ mod tests {
.expect("git initial commit");
}
/// Create the `.story_kit/work/{stage}/` dir tree inside `root`.
/// Create the `.storkit/work/{stage}/` dir tree inside `root`.
fn make_stage_dir(root: &std::path::Path, stage: &str) -> PathBuf {
let dir = root.join(".story_kit").join("work").join(stage);
let dir = root.join(".storkit").join("work").join(stage);
fs::create_dir_all(&dir).expect("create stage dir");
dir
}
@@ -702,7 +702,7 @@ mod tests {
make_stage_dir(tmp.path(), "2_current");
let deleted_path = tmp
.path()
.join(".story_kit")
.join(".storkit")
.join("work")
.join("2_current")
.join("42_story_foo.md");
@@ -731,7 +731,7 @@ mod tests {
let tmp = TempDir::new().unwrap();
init_git_repo(tmp.path());
// File sits in an unrecognised directory.
let unknown_dir = tmp.path().join(".story_kit").join("work").join("9_unknown");
let unknown_dir = tmp.path().join(".storkit").join("work").join("9_unknown");
fs::create_dir_all(&unknown_dir).unwrap();
let path = unknown_dir.join("42_story_foo.md");
fs::write(&path, "---\nname: test\n---\n").unwrap();
@@ -889,7 +889,7 @@ mod tests {
#[test]
fn stage_for_path_recognises_pipeline_dirs() {
let base = PathBuf::from("/proj/.story_kit/work");
let base = PathBuf::from("/proj/.storkit/work");
assert_eq!(
stage_for_path(&base.join("2_current/42_story_foo.md")),
Some("2_current".to_string())
@@ -911,7 +911,7 @@ mod tests {
#[test]
fn stage_for_path_ignores_worktree_paths() {
let worktrees = PathBuf::from("/proj/.story_kit/worktrees");
let worktrees = PathBuf::from("/proj/.storkit/worktrees");
// Code changes inside a worktree must be ignored.
assert_eq!(
@@ -922,14 +922,14 @@ mod tests {
// Even if a worktree happens to contain a path component that looks
// like a pipeline stage, it must still be ignored.
assert_eq!(
stage_for_path(&worktrees.join("42_story_foo/.story_kit/work/2_current/42_story_foo.md")),
stage_for_path(&worktrees.join("42_story_foo/.storkit/work/2_current/42_story_foo.md")),
None,
);
// A path that only contains the word "worktrees" as part of a longer
// segment (not an exact component) must NOT be filtered out.
assert_eq!(
stage_for_path(&PathBuf::from("/proj/.story_kit/work/2_current/not_worktrees_story.md")),
stage_for_path(&PathBuf::from("/proj/.storkit/work/2_current/not_worktrees_story.md")),
Some("2_current".to_string()),
);
}
@@ -968,7 +968,7 @@ mod tests {
#[test]
fn is_config_file_identifies_root_project_toml() {
let git_root = PathBuf::from("/proj");
let config = git_root.join(".story_kit").join("project.toml");
let config = git_root.join(".storkit").join("project.toml");
assert!(is_config_file(&config, &git_root));
}
@@ -977,7 +977,7 @@ mod tests {
let git_root = PathBuf::from("/proj");
// project.toml inside a worktree must NOT be treated as the root config.
let worktree_config = PathBuf::from(
"/proj/.story_kit/worktrees/42_story_foo/.story_kit/project.toml",
"/proj/.storkit/worktrees/42_story_foo/.storkit/project.toml",
);
assert!(!is_config_file(&worktree_config, &git_root));
}
@@ -987,11 +987,11 @@ mod tests {
let git_root = PathBuf::from("/proj");
// Random files must not match.
assert!(!is_config_file(
&PathBuf::from("/proj/.story_kit/work/2_current/42_story_foo.md"),
&PathBuf::from("/proj/.storkit/work/2_current/42_story_foo.md"),
&git_root
));
assert!(!is_config_file(
&PathBuf::from("/proj/.story_kit/README.md"),
&PathBuf::from("/proj/.storkit/README.md"),
&git_root
));
}
@@ -999,7 +999,7 @@ mod tests {
#[test]
fn is_config_file_rejects_wrong_root() {
let git_root = PathBuf::from("/proj");
let other_root_config = PathBuf::from("/other/.story_kit/project.toml");
let other_root_config = PathBuf::from("/other/.storkit/project.toml");
assert!(!is_config_file(&other_root_config, &git_root));
}
@@ -1008,7 +1008,7 @@ mod tests {
#[test]
fn sweep_moves_old_items_to_archived() {
let tmp = TempDir::new().unwrap();
let work_dir = tmp.path().join(".story_kit").join("work");
let work_dir = tmp.path().join(".storkit").join("work");
let done_dir = work_dir.join("5_done");
let archived_dir = work_dir.join("6_archived");
fs::create_dir_all(&done_dir).unwrap();
@@ -1036,7 +1036,7 @@ mod tests {
#[test]
fn sweep_keeps_recent_items_in_done() {
let tmp = TempDir::new().unwrap();
let work_dir = tmp.path().join(".story_kit").join("work");
let work_dir = tmp.path().join(".storkit").join("work");
let done_dir = work_dir.join("5_done");
fs::create_dir_all(&done_dir).unwrap();
@@ -1053,7 +1053,7 @@ mod tests {
#[test]
fn sweep_respects_custom_retention() {
let tmp = TempDir::new().unwrap();
let work_dir = tmp.path().join(".story_kit").join("work");
let work_dir = tmp.path().join(".storkit").join("work");
let done_dir = work_dir.join("5_done");
let archived_dir = work_dir.join("6_archived");
fs::create_dir_all(&done_dir).unwrap();
@@ -1083,7 +1083,7 @@ mod tests {
#[test]
fn sweep_custom_retention_keeps_younger_items() {
let tmp = TempDir::new().unwrap();
let work_dir = tmp.path().join(".story_kit").join("work");
let work_dir = tmp.path().join(".storkit").join("work");
let done_dir = work_dir.join("5_done");
fs::create_dir_all(&done_dir).unwrap();
@@ -1128,7 +1128,7 @@ mod tests {
let git_root = tmp.path().to_path_buf();
init_git_repo(&git_root);
let work_dir = git_root.join(".story_kit").join("work");
let work_dir = git_root.join(".storkit").join("work");
let done_dir = work_dir.join("5_done");
fs::create_dir_all(&done_dir).unwrap();
@@ -1169,7 +1169,7 @@ mod tests {
let git_root = tmp.path().to_path_buf();
init_git_repo(&git_root);
let work_dir = git_root.join(".story_kit").join("work");
let work_dir = git_root.join(".storkit").join("work");
let archived_dir = work_dir.join("6_archived");
fs::create_dir_all(&archived_dir).unwrap();
@@ -1204,7 +1204,7 @@ mod tests {
let git_root = tmp.path().to_path_buf();
init_git_repo(&git_root);
let work_dir = git_root.join(".story_kit").join("work");
let work_dir = git_root.join(".storkit").join("work");
let done_dir = work_dir.join("5_done");
fs::create_dir_all(&done_dir).unwrap();