The great storkit name conversion

This commit is contained in:
Dave
2026-03-20 12:26:02 +00:00
parent 51d878e117
commit c4e45b2841
25 changed files with 1522 additions and 1333 deletions
+88 -85
View File
@@ -11,7 +11,7 @@ const KEY_KNOWN_PROJECTS: &str = "known_projects";
const STORY_KIT_README: &str = include_str!("../../../.storkit/README.md");
const STORY_KIT_CONTEXT: &str = "<!-- story-kit:scaffold-template -->\n\
const STORY_KIT_CONTEXT: &str = "<!-- storkit:scaffold-template -->\n\
# Project Context\n\
\n\
## High-Level Goal\n\
@@ -30,7 +30,7 @@ TODO: Define the key domain concepts and entities.\n\
\n\
TODO: Define abbreviations and technical terms.\n";
const STORY_KIT_STACK: &str = "<!-- story-kit:scaffold-template -->\n\
const STORY_KIT_STACK: &str = "<!-- storkit:scaffold-template -->\n\
# Tech Stack & Constraints\n\
\n\
## Core Stack\n\
@@ -51,7 +51,7 @@ TODO: List approved libraries and their purpose.\n";
const STORY_KIT_SCRIPT_TEST: &str = "#!/usr/bin/env bash\nset -euo pipefail\n\n# Add your project's test commands here.\n# Story Kit agents invoke this script as the canonical test runner.\n# Exit 0 on success, non-zero on failure.\necho \"No tests configured\"\n";
const STORY_KIT_CLAUDE_MD: &str = "<!-- story-kit:scaffold-template -->\n\
const STORY_KIT_CLAUDE_MD: &str = "<!-- storkit:scaffold-template -->\n\
Never chain shell commands with `&&`, `||`, or `;` in a single Bash call. \
The permission system validates the entire command string, and chained commands \
won't match allow rules like `Bash(git *)`. Use separate Bash calls instead — \
@@ -90,7 +90,7 @@ const STORY_KIT_CLAUDE_SETTINGS: &str = r#"{
"Bash(./script/test:*)",
"Edit",
"Write",
"mcp__story-kit__*"
"mcp__storkit__*"
]
},
"enabledMcpjsonServers": [
@@ -422,8 +422,7 @@ fn scaffold_story_kit(root: &Path) -> Result<(), String> {
];
for stage in &work_stages {
let dir = story_kit_root.join("work").join(stage);
fs::create_dir_all(&dir)
.map_err(|e| format!("Failed to create work/{}: {}", stage, e))?;
fs::create_dir_all(&dir).map_err(|e| format!("Failed to create work/{}: {}", stage, e))?;
write_file_if_missing(&dir.join(".gitkeep"), "")?;
}
@@ -464,7 +463,14 @@ fn scaffold_story_kit(root: &Path) -> Result<(), String> {
}
let add_output = std::process::Command::new("git")
.args(["add", ".storkit", "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))?;
@@ -478,7 +484,7 @@ fn scaffold_story_kit(root: &Path) -> Result<(), String> {
let commit_output = std::process::Command::new("git")
.args([
"-c",
"user.email=story-kit@localhost",
"user.email=storkit@localhost",
"-c",
"user.name=Story Kit",
"commit",
@@ -526,7 +532,10 @@ pub async fn open_project(
{
// TRACE:MERGE-DEBUG — remove once root cause is found
crate::slog!("[MERGE-DEBUG] open_project: setting project_root to {:?}", p);
crate::slog!(
"[MERGE-DEBUG] open_project: setting project_root to {:?}",
p
);
let mut root = state.project_root.lock().map_err(|e| e.to_string())?;
*root = Some(p);
}
@@ -807,12 +816,7 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
let result = open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
)
.await;
let result = open_project(project_dir.to_string_lossy().to_string(), &state, &store).await;
assert!(result.is_ok());
let root = state.get_project_root().unwrap();
@@ -831,13 +835,9 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
)
.await
.unwrap();
open_project(project_dir.to_string_lossy().to_string(), &state, &store)
.await
.unwrap();
let mcp_path = project_dir.join(".mcp.json");
assert!(
@@ -898,13 +898,9 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
)
.await
.unwrap();
open_project(project_dir.to_string_lossy().to_string(), &state, &store)
.await
.unwrap();
let projects = get_known_projects(&store).unwrap();
assert_eq!(projects.len(), 1);
@@ -978,7 +974,9 @@ mod tests {
let dir = tempdir().unwrap();
let file = dir.path().join("sub").join("output.txt");
write_file_impl(file.clone(), "content".to_string()).await.unwrap();
write_file_impl(file.clone(), "content".to_string())
.await
.unwrap();
assert_eq!(fs::read_to_string(&file).unwrap(), "content");
}
@@ -1089,7 +1087,14 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
let stages = ["1_backlog", "2_current", "3_qa", "4_merge", "5_done", "6_archived"];
let stages = [
"1_backlog",
"2_current",
"3_qa",
"4_merge",
"5_done",
"6_archived",
];
for stage in &stages {
let path = dir.path().join(".storkit/work").join(stage);
assert!(path.is_dir(), "work/{} should be a directory", stage);
@@ -1106,8 +1111,7 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
let content = 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\""));
@@ -1120,9 +1124,8 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".storkit/specs/00_CONTEXT.md")).unwrap();
assert!(content.contains("<!-- story-kit:scaffold-template -->"));
let content = fs::read_to_string(dir.path().join(".storkit/specs/00_CONTEXT.md")).unwrap();
assert!(content.contains("<!-- storkit:scaffold-template -->"));
assert!(content.contains("## High-Level Goal"));
assert!(content.contains("## Core Features"));
assert!(content.contains("## Domain Definition"));
@@ -1137,9 +1140,8 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".storkit/specs/tech/STACK.md")).unwrap();
assert!(content.contains("<!-- story-kit:scaffold-template -->"));
let content = fs::read_to_string(dir.path().join(".storkit/specs/tech/STACK.md")).unwrap();
assert!(content.contains("<!-- storkit:scaffold-template -->"));
assert!(content.contains("## Core Stack"));
assert!(content.contains("## Coding Standards"));
assert!(content.contains("## Quality Gates"));
@@ -1183,10 +1185,8 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
let readme_content =
fs::read_to_string(dir.path().join(".storkit/README.md")).unwrap();
let toml_content =
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
let readme_content = fs::read_to_string(dir.path().join(".storkit/README.md")).unwrap();
let toml_content = 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();
@@ -1207,8 +1207,7 @@ mod tests {
.filter(|l| l.trim() == "worktrees/")
.count();
assert_eq!(
count,
1,
count, 1,
".storkit/.gitignore should not have duplicate entries"
);
}
@@ -1249,8 +1248,7 @@ mod tests {
let log = String::from_utf8_lossy(&log_output.stdout);
let commit_count = log.lines().count();
assert_eq!(
commit_count,
1,
commit_count, 1,
"scaffold should not create a commit in an existing git repo"
);
}
@@ -1261,15 +1259,14 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
// .storkit/.gitignore must contain relative patterns for files under .storkit/
let sk_content =
fs::read_to_string(dir.path().join(".storkit/.gitignore")).unwrap();
let sk_content = 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 .storkit/ prefixed paths
assert!(!sk_content.contains(".storkit/"));
// Root .gitignore must contain root-level story-kit entries
// Root .gitignore must contain root-level storkit entries
let root_content = fs::read_to_string(dir.path().join(".gitignore")).unwrap();
assert!(root_content.contains(".storkit_port"));
assert!(root_content.contains("store.json"));
@@ -1292,17 +1289,10 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".storkit/.gitignore")).unwrap();
let worktrees_count = content
.lines()
.filter(|l| l.trim() == "worktrees/")
.count();
let content = fs::read_to_string(dir.path().join(".storkit/.gitignore")).unwrap();
let worktrees_count = content.lines().filter(|l| l.trim() == "worktrees/").count();
assert_eq!(worktrees_count, 1, "worktrees/ should not be duplicated");
let coverage_count = content
.lines()
.filter(|l| l.trim() == "coverage/")
.count();
let coverage_count = content.lines().filter(|l| l.trim() == "coverage/").count();
assert_eq!(coverage_count, 1, "coverage/ should not be duplicated");
// The missing entry must have been added
assert!(content.contains("merge_workspace/"));
@@ -1316,11 +1306,14 @@ mod tests {
scaffold_story_kit(dir.path()).unwrap();
let claude_md = dir.path().join("CLAUDE.md");
assert!(claude_md.exists(), "CLAUDE.md should be created at project root");
assert!(
claude_md.exists(),
"CLAUDE.md should be created at project root"
);
let content = fs::read_to_string(&claude_md).unwrap();
assert!(
content.contains("<!-- story-kit:scaffold-template -->"),
content.contains("<!-- storkit:scaffold-template -->"),
"CLAUDE.md should contain the scaffold sentinel"
);
assert!(
@@ -1358,13 +1351,9 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
)
.await
.unwrap();
open_project(project_dir.to_string_lossy().to_string(), &state, &store)
.await
.unwrap();
// .storkit/ should have been created automatically
assert!(project_dir.join(".storkit").is_dir());
@@ -1381,13 +1370,9 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
)
.await
.unwrap();
open_project(project_dir.to_string_lossy().to_string(), &state, &store)
.await
.unwrap();
// Existing .storkit/ content should not be overwritten
assert_eq!(fs::read_to_string(&readme).unwrap(), "custom content");
@@ -1451,7 +1436,11 @@ mod tests {
#[test]
fn detect_cargo_toml_generates_rust_component() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"test\"\n").unwrap();
fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\n",
)
.unwrap();
let toml = detect_components_toml(dir.path());
assert!(toml.contains("name = \"server\""));
@@ -1482,7 +1471,11 @@ mod tests {
#[test]
fn detect_pyproject_toml_generates_python_component() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("pyproject.toml"), "[project]\nname = \"test\"\n").unwrap();
fs::write(
dir.path().join("pyproject.toml"),
"[project]\nname = \"test\"\n",
)
.unwrap();
let toml = detect_components_toml(dir.path());
assert!(toml.contains("name = \"python\""));
@@ -1512,7 +1505,11 @@ mod tests {
#[test]
fn detect_gemfile_generates_ruby_component() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("Gemfile"), "source \"https://rubygems.org\"\n").unwrap();
fs::write(
dir.path().join("Gemfile"),
"source \"https://rubygems.org\"\n",
)
.unwrap();
let toml = detect_components_toml(dir.path());
assert!(toml.contains("name = \"ruby\""));
@@ -1522,7 +1519,11 @@ mod tests {
#[test]
fn detect_multiple_markers_generates_multiple_components() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"server\"\n").unwrap();
fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"server\"\n",
)
.unwrap();
fs::write(dir.path().join("package.json"), "{}").unwrap();
let toml = detect_components_toml(dir.path());
@@ -1565,12 +1566,15 @@ mod tests {
fn scaffold_project_toml_contains_detected_components() {
let dir = tempdir().unwrap();
// Place a Cargo.toml in the project root before scaffolding
fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"myapp\"\n").unwrap();
fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"myapp\"\n",
)
.unwrap();
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
let content = fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
assert!(
content.contains("[[component]]"),
"project.toml should contain a component entry"
@@ -1590,8 +1594,7 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path()).unwrap();
let content =
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
let content = fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
assert!(
content.contains("[[component]]"),
"project.toml should always have at least one component"