huskies: rename project from storkit to huskies
Rename all references from storkit to huskies across the codebase: - .storkit/ directory → .huskies/ - Binary name, Cargo package name, Docker image references - Server code, frontend code, config files, scripts - Fix script/test to build frontend before cargo clippy/test so merge worktrees have frontend/dist available for RustEmbed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,11 +15,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 `.storkit/` subdirectory, or `None`.
|
||||
/// directory that contains a `.huskies/` subdirectory, or `None`.
|
||||
pub fn find_story_kit_root(start: &Path) -> Option<PathBuf> {
|
||||
let mut current = start.to_path_buf();
|
||||
loop {
|
||||
if current.join(".storkit").is_dir() {
|
||||
if current.join(".huskies").is_dir() {
|
||||
return Some(current);
|
||||
}
|
||||
if !current.pop() {
|
||||
@@ -79,7 +79,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(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join(".huskies")).unwrap();
|
||||
|
||||
let result = find_story_kit_root(tmp.path());
|
||||
assert_eq!(result, Some(tmp.path().to_path_buf()));
|
||||
@@ -88,7 +88,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(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join(".huskies")).unwrap();
|
||||
let child = tmp.path().join("subdir").join("nested");
|
||||
std::fs::create_dir_all(&child).unwrap();
|
||||
|
||||
@@ -107,9 +107,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(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join(".huskies")).unwrap();
|
||||
let child = tmp.path().join("inner");
|
||||
std::fs::create_dir_all(child.join(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(child.join(".huskies")).unwrap();
|
||||
|
||||
let result = find_story_kit_root(&child);
|
||||
assert_eq!(result, Some(child));
|
||||
|
||||
+10
-10
@@ -33,7 +33,7 @@ pub(crate) async fn ensure_project_root_with_story_kit(
|
||||
fs::create_dir_all(&path)
|
||||
.map_err(|e| format!("Failed to create project directory: {}", e))?;
|
||||
}
|
||||
if !path.join(".storkit").is_dir() {
|
||||
if !path.join(".huskies").is_dir() {
|
||||
scaffold_story_kit(&path, port)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -230,9 +230,9 @@ mod tests {
|
||||
assert!(content.contains("localhost"), "mcp.json should reference localhost");
|
||||
}
|
||||
|
||||
/// Regression test for bug 371: no-arg `storkit` in empty directory skips scaffold.
|
||||
/// `open_project` on a directory without `.storkit/` must create all required scaffold
|
||||
/// files — the same files that `storkit .` produces.
|
||||
/// Regression test for bug 371: no-arg `huskies` in empty directory skips scaffold.
|
||||
/// `open_project` on a directory without `.huskies/` must create all required scaffold
|
||||
/// files — the same files that `huskies .` produces.
|
||||
#[tokio::test]
|
||||
async fn open_project_on_empty_dir_creates_full_scaffold() {
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -246,8 +246,8 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
project_dir.join(".storkit/project.toml").exists(),
|
||||
"open_project must create .storkit/project.toml"
|
||||
project_dir.join(".huskies/project.toml").exists(),
|
||||
"open_project must create .huskies/project.toml"
|
||||
);
|
||||
assert!(
|
||||
project_dir.join(".mcp.json").exists(),
|
||||
@@ -386,15 +386,15 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// .storkit/ should have been created automatically
|
||||
assert!(project_dir.join(".storkit").is_dir());
|
||||
// .huskies/ should have been created automatically
|
||||
assert!(project_dir.join(".huskies").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(".storkit");
|
||||
let sk_dir = project_dir.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
let readme = sk_dir.join("README.md");
|
||||
fs::write(&readme, "custom content").unwrap();
|
||||
@@ -405,7 +405,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Existing .storkit/ content should not be overwritten
|
||||
// Existing .huskies/ content should not be overwritten
|
||||
assert_eq!(fs::read_to_string(&readme).unwrap(), "custom content");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
const STORY_KIT_README: &str = include_str!("../../../../.storkit/README.md");
|
||||
const STORY_KIT_README: &str = include_str!("../../../../.huskies/README.md");
|
||||
|
||||
const BOT_TOML_MATRIX_EXAMPLE: &str =
|
||||
include_str!("../../../../.storkit/bot.toml.matrix.example");
|
||||
include_str!("../../../../.huskies/bot.toml.matrix.example");
|
||||
const BOT_TOML_WHATSAPP_META_EXAMPLE: &str =
|
||||
include_str!("../../../../.storkit/bot.toml.whatsapp-meta.example");
|
||||
include_str!("../../../../.huskies/bot.toml.whatsapp-meta.example");
|
||||
const BOT_TOML_WHATSAPP_TWILIO_EXAMPLE: &str =
|
||||
include_str!("../../../../.storkit/bot.toml.whatsapp-twilio.example");
|
||||
const BOT_TOML_SLACK_EXAMPLE: &str = include_str!("../../../../.storkit/bot.toml.slack.example");
|
||||
include_str!("../../../../.huskies/bot.toml.whatsapp-twilio.example");
|
||||
const BOT_TOML_SLACK_EXAMPLE: &str = include_str!("../../../../.huskies/bot.toml.slack.example");
|
||||
|
||||
const STORY_KIT_CONTEXT: &str = "<!-- storkit:scaffold-template -->\n\
|
||||
const STORY_KIT_CONTEXT: &str = "<!-- huskies: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 = "<!-- storkit:scaffold-template -->\n\
|
||||
const STORY_KIT_STACK: &str = "<!-- huskies:scaffold-template -->\n\
|
||||
# Tech Stack & Constraints\n\
|
||||
\n\
|
||||
## Core Stack\n\
|
||||
@@ -51,16 +51,16 @@ 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 = "<!-- storkit:scaffold-template -->\n\
|
||||
const STORY_KIT_CLAUDE_MD: &str = "<!-- huskies: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 — \
|
||||
parallel calls work fine.\n\
|
||||
\n\
|
||||
Read .storkit/README.md to see our dev process.\n\
|
||||
Read .huskies/README.md to see our dev process.\n\
|
||||
\n\
|
||||
IMPORTANT: On your first conversation, call `wizard_status` to check if \
|
||||
project setup is complete. If not, read .storkit/README.md for the full \
|
||||
project setup is complete. If not, read .huskies/README.md for the full \
|
||||
setup wizard instructions and guide the user through it conversationally.\n";
|
||||
|
||||
const STORY_KIT_CLAUDE_SETTINGS: &str = r#"{
|
||||
@@ -94,11 +94,11 @@ const STORY_KIT_CLAUDE_SETTINGS: &str = r#"{
|
||||
"Bash(./script/test:*)",
|
||||
"Edit",
|
||||
"Write",
|
||||
"mcp__storkit__*"
|
||||
"mcp__huskies__*"
|
||||
]
|
||||
},
|
||||
"enabledMcpjsonServers": [
|
||||
"storkit"
|
||||
"huskies"
|
||||
]
|
||||
}
|
||||
"#;
|
||||
@@ -114,7 +114,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 .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.\n\nIf `script/test` still contains the generic 'No tests configured' stub, update it to run the project's actual test suite before starting implementation."
|
||||
prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .huskies/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.\n\nIf `script/test` still contains the generic 'No tests configured' stub, update it to run the project's actual test suite before starting implementation."
|
||||
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]]
|
||||
@@ -275,12 +275,12 @@ fn write_script_if_missing(path: &Path, content: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write (or idempotently update) `.storkit/.gitignore` with Story Kit–specific
|
||||
/// ignore patterns for files that live inside the `.storkit/` directory.
|
||||
/// Patterns are relative to `.storkit/` as git resolves `.gitignore` files
|
||||
/// Write (or idempotently update) `.huskies/.gitignore` with Story Kit–specific
|
||||
/// ignore patterns for files that live inside the `.huskies/` directory.
|
||||
/// Patterns are relative to `.huskies/` 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 .storkit/.gitignore (relative to .storkit/).
|
||||
// Entries that belong inside .huskies/.gitignore (relative to .huskies/).
|
||||
let entries = [
|
||||
"bot.toml",
|
||||
"matrix_store/",
|
||||
@@ -299,10 +299,10 @@ fn write_story_kit_gitignore(root: &Path) -> Result<(), String> {
|
||||
"store.json",
|
||||
];
|
||||
|
||||
let gitignore_path = root.join(".storkit").join(".gitignore");
|
||||
let gitignore_path = root.join(".huskies").join(".gitignore");
|
||||
let existing = if gitignore_path.exists() {
|
||||
fs::read_to_string(&gitignore_path)
|
||||
.map_err(|e| format!("Failed to read .storkit/.gitignore: {}", e))?
|
||||
.map_err(|e| format!("Failed to read .huskies/.gitignore: {}", e))?
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
@@ -327,19 +327,19 @@ fn write_story_kit_gitignore(root: &Path) -> Result<(), String> {
|
||||
}
|
||||
|
||||
fs::write(&gitignore_path, new_content)
|
||||
.map_err(|e| format!("Failed to write .storkit/.gitignore: {}", e))?;
|
||||
.map_err(|e| format!("Failed to write .huskies/.gitignore: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Append root-level Story Kit entries to the project `.gitignore`.
|
||||
/// Only `.storkit_port` and `.mcp.json` remain here because they live at
|
||||
/// Only `.huskies_port` and `.mcp.json` remain here because they live at
|
||||
/// the project root and git does not support `../` patterns in `.gitignore`
|
||||
/// files, so they cannot be expressed in `.storkit/.gitignore`.
|
||||
/// `store.json` is excluded via `.storkit/.gitignore` since it now lives
|
||||
/// inside the `.storkit/` directory.
|
||||
/// files, so they cannot be expressed in `.huskies/.gitignore`.
|
||||
/// `store.json` is excluded via `.huskies/.gitignore` since it now lives
|
||||
/// inside the `.huskies/` directory.
|
||||
fn append_root_gitignore_entries(root: &Path) -> Result<(), String> {
|
||||
let entries = [".storkit_port", ".mcp.json"];
|
||||
let entries = [".huskies_port", ".mcp.json"];
|
||||
|
||||
let gitignore_path = root.join(".gitignore");
|
||||
let existing = if gitignore_path.exists() {
|
||||
@@ -375,7 +375,7 @@ fn append_root_gitignore_entries(root: &Path) -> Result<(), String> {
|
||||
}
|
||||
|
||||
pub(crate) fn scaffold_story_kit(root: &Path, port: u16) -> Result<(), String> {
|
||||
let story_kit_root = root.join(".storkit");
|
||||
let story_kit_root = root.join(".huskies");
|
||||
let specs_root = story_kit_root.join("specs");
|
||||
let tech_root = specs_root.join("tech");
|
||||
let functional_root = specs_root.join("functional");
|
||||
@@ -433,7 +433,7 @@ pub(crate) fn scaffold_story_kit(root: &Path, port: u16) -> Result<(), String> {
|
||||
// Only written when missing — never overwrites an existing file, because
|
||||
// the port is environment-specific and must not clobber a running instance.
|
||||
let mcp_content = format!(
|
||||
"{{\n \"mcpServers\": {{\n \"storkit\": {{\n \"type\": \"http\",\n \"url\": \"http://localhost:{port}/mcp\"\n }}\n }}\n}}\n"
|
||||
"{{\n \"mcpServers\": {{\n \"huskies\": {{\n \"type\": \"http\",\n \"url\": \"http://localhost:{port}/mcp\"\n }}\n }}\n}}\n"
|
||||
);
|
||||
write_file_if_missing(&root.join(".mcp.json"), &mcp_content)?;
|
||||
|
||||
@@ -462,7 +462,7 @@ pub(crate) fn scaffold_story_kit(root: &Path, port: u16) -> Result<(), String> {
|
||||
let add_output = std::process::Command::new("git")
|
||||
.args([
|
||||
"add",
|
||||
".storkit",
|
||||
".huskies",
|
||||
"script",
|
||||
".gitignore",
|
||||
"CLAUDE.md",
|
||||
@@ -481,7 +481,7 @@ pub(crate) fn scaffold_story_kit(root: &Path, port: u16) -> Result<(), String> {
|
||||
let commit_output = std::process::Command::new("git")
|
||||
.args([
|
||||
"-c",
|
||||
"user.email=storkit@localhost",
|
||||
"user.email=huskies@localhost",
|
||||
"-c",
|
||||
"user.name=Story Kit",
|
||||
"commit",
|
||||
@@ -514,12 +514,12 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
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());
|
||||
assert!(dir.path().join(".huskies/README.md").exists());
|
||||
assert!(dir.path().join(".huskies/project.toml").exists());
|
||||
assert!(dir.path().join(".huskies/specs/00_CONTEXT.md").exists());
|
||||
assert!(dir.path().join(".huskies/specs/tech/STACK.md").exists());
|
||||
// Old stories/ dirs should NOT be created
|
||||
assert!(!dir.path().join(".storkit/stories").exists());
|
||||
assert!(!dir.path().join(".huskies/stories").exists());
|
||||
assert!(dir.path().join("script/test").exists());
|
||||
}
|
||||
|
||||
@@ -537,7 +537,7 @@ mod tests {
|
||||
"6_archived",
|
||||
];
|
||||
for stage in &stages {
|
||||
let path = dir.path().join(".storkit/work").join(stage);
|
||||
let path = dir.path().join(".huskies/work").join(stage);
|
||||
assert!(path.is_dir(), "work/{} should be a directory", stage);
|
||||
assert!(
|
||||
path.join(".gitkeep").exists(),
|
||||
@@ -552,7 +552,7 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
let content = fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
|
||||
let content = fs::read_to_string(dir.path().join(".huskies/project.toml")).unwrap();
|
||||
assert!(content.contains("[[agent]]"));
|
||||
assert!(content.contains("stage = \"coder\""));
|
||||
assert!(content.contains("stage = \"qa\""));
|
||||
@@ -565,8 +565,8 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
let content = fs::read_to_string(dir.path().join(".storkit/specs/00_CONTEXT.md")).unwrap();
|
||||
assert!(content.contains("<!-- storkit:scaffold-template -->"));
|
||||
let content = fs::read_to_string(dir.path().join(".huskies/specs/00_CONTEXT.md")).unwrap();
|
||||
assert!(content.contains("<!-- huskies:scaffold-template -->"));
|
||||
assert!(content.contains("## High-Level Goal"));
|
||||
assert!(content.contains("## Core Features"));
|
||||
assert!(content.contains("## Domain Definition"));
|
||||
@@ -581,8 +581,8 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
let content = fs::read_to_string(dir.path().join(".storkit/specs/tech/STACK.md")).unwrap();
|
||||
assert!(content.contains("<!-- storkit:scaffold-template -->"));
|
||||
let content = fs::read_to_string(dir.path().join(".huskies/specs/tech/STACK.md")).unwrap();
|
||||
assert!(content.contains("<!-- huskies:scaffold-template -->"));
|
||||
assert!(content.contains("## Core Stack"));
|
||||
assert!(content.contains("## Coding Standards"));
|
||||
assert!(content.contains("## Quality Gates"));
|
||||
@@ -612,7 +612,7 @@ mod tests {
|
||||
#[test]
|
||||
fn scaffold_story_kit_does_not_overwrite_existing() {
|
||||
let dir = tempdir().unwrap();
|
||||
let readme = dir.path().join(".storkit/README.md");
|
||||
let readme = dir.path().join(".huskies/README.md");
|
||||
fs::create_dir_all(readme.parent().unwrap()).unwrap();
|
||||
fs::write(&readme, "custom content").unwrap();
|
||||
|
||||
@@ -626,30 +626,30 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_story_kit(dir.path(), 3001).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(".huskies/README.md")).unwrap();
|
||||
let toml_content = fs::read_to_string(dir.path().join(".huskies/project.toml")).unwrap();
|
||||
|
||||
// Run again — must not change content or add duplicate .gitignore entries
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(dir.path().join(".storkit/README.md")).unwrap(),
|
||||
fs::read_to_string(dir.path().join(".huskies/README.md")).unwrap(),
|
||||
readme_content
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap(),
|
||||
fs::read_to_string(dir.path().join(".huskies/project.toml")).unwrap(),
|
||||
toml_content
|
||||
);
|
||||
|
||||
let story_kit_gitignore =
|
||||
fs::read_to_string(dir.path().join(".storkit/.gitignore")).unwrap();
|
||||
fs::read_to_string(dir.path().join(".huskies/.gitignore")).unwrap();
|
||||
let count = story_kit_gitignore
|
||||
.lines()
|
||||
.filter(|l| l.trim() == "worktrees/")
|
||||
.count();
|
||||
assert_eq!(
|
||||
count, 1,
|
||||
".storkit/.gitignore should not have duplicate entries"
|
||||
".huskies/.gitignore should not have duplicate entries"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -699,43 +699,43 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
// .storkit/.gitignore must contain relative patterns for files under .storkit/
|
||||
let sk_content = fs::read_to_string(dir.path().join(".storkit/.gitignore")).unwrap();
|
||||
// .huskies/.gitignore must contain relative patterns for files under .huskies/
|
||||
let sk_content = fs::read_to_string(dir.path().join(".huskies/.gitignore")).unwrap();
|
||||
assert!(sk_content.contains("worktrees/"));
|
||||
assert!(sk_content.contains("merge_workspace/"));
|
||||
assert!(sk_content.contains("coverage/"));
|
||||
assert!(sk_content.contains("matrix_history.json"));
|
||||
assert!(sk_content.contains("timers.json"));
|
||||
// Must NOT contain absolute .storkit/ prefixed paths
|
||||
assert!(!sk_content.contains(".storkit/"));
|
||||
// Must NOT contain absolute .huskies/ prefixed paths
|
||||
assert!(!sk_content.contains(".huskies/"));
|
||||
|
||||
// Root .gitignore must contain root-level storkit entries
|
||||
// Root .gitignore must contain root-level huskies entries
|
||||
let root_content = fs::read_to_string(dir.path().join(".gitignore")).unwrap();
|
||||
assert!(root_content.contains(".storkit_port"));
|
||||
// store.json now lives inside .storkit/ and must NOT appear in root .gitignore
|
||||
assert!(root_content.contains(".huskies_port"));
|
||||
// store.json now lives inside .huskies/ and must NOT appear in root .gitignore
|
||||
assert!(!root_content.contains("store.json"));
|
||||
// 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/"));
|
||||
// store.json must be in .storkit/.gitignore instead
|
||||
// Root .gitignore must NOT contain .huskies/ sub-directory patterns
|
||||
assert!(!root_content.contains(".huskies/worktrees/"));
|
||||
assert!(!root_content.contains(".huskies/merge_workspace/"));
|
||||
assert!(!root_content.contains(".huskies/coverage/"));
|
||||
// store.json must be in .huskies/.gitignore instead
|
||||
assert!(sk_content.contains("store.json"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scaffold_story_kit_gitignore_does_not_duplicate_existing_entries() {
|
||||
let dir = tempdir().unwrap();
|
||||
// Pre-create .storkit dir and .gitignore with some entries already present
|
||||
fs::create_dir_all(dir.path().join(".storkit")).unwrap();
|
||||
// Pre-create .huskies dir and .gitignore with some entries already present
|
||||
fs::create_dir_all(dir.path().join(".huskies")).unwrap();
|
||||
fs::write(
|
||||
dir.path().join(".storkit/.gitignore"),
|
||||
dir.path().join(".huskies/.gitignore"),
|
||||
"worktrees/\ncoverage/\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
let content = fs::read_to_string(dir.path().join(".storkit/.gitignore")).unwrap();
|
||||
let content = fs::read_to_string(dir.path().join(".huskies/.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();
|
||||
@@ -759,12 +759,12 @@ mod tests {
|
||||
|
||||
let content = fs::read_to_string(&claude_md).unwrap();
|
||||
assert!(
|
||||
content.contains("<!-- storkit:scaffold-template -->"),
|
||||
content.contains("<!-- huskies:scaffold-template -->"),
|
||||
"CLAUDE.md should contain the scaffold sentinel"
|
||||
);
|
||||
assert!(
|
||||
content.contains("Read .storkit/README.md"),
|
||||
"CLAUDE.md should include directive to read .storkit/README.md"
|
||||
content.contains("Read .huskies/README.md"),
|
||||
"CLAUDE.md should include directive to read .huskies/README.md"
|
||||
);
|
||||
assert!(
|
||||
content.contains("Never chain shell commands"),
|
||||
@@ -801,7 +801,7 @@ mod tests {
|
||||
let content = fs::read_to_string(&mcp_path).unwrap();
|
||||
assert!(content.contains("4242"), ".mcp.json should reference the given port");
|
||||
assert!(content.contains("localhost"), ".mcp.json should reference localhost");
|
||||
assert!(content.contains("storkit"), ".mcp.json should name the storkit server");
|
||||
assert!(content.contains("huskies"), ".mcp.json should name the huskies server");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1149,7 +1149,7 @@ mod tests {
|
||||
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
let content = fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
|
||||
let content = fs::read_to_string(dir.path().join(".huskies/project.toml")).unwrap();
|
||||
assert!(
|
||||
content.contains("[[component]]"),
|
||||
"project.toml should contain a component entry"
|
||||
@@ -1169,7 +1169,7 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
let content = fs::read_to_string(dir.path().join(".storkit/project.toml")).unwrap();
|
||||
let content = fs::read_to_string(dir.path().join(".huskies/project.toml")).unwrap();
|
||||
assert!(
|
||||
content.contains("[[component]]"),
|
||||
"project.toml should always have at least one component"
|
||||
@@ -1188,7 +1188,7 @@ mod tests {
|
||||
#[test]
|
||||
fn scaffold_does_not_overwrite_existing_project_toml_with_components() {
|
||||
let dir = tempdir().unwrap();
|
||||
let sk_dir = dir.path().join(".storkit");
|
||||
let sk_dir = dir.path().join(".huskies");
|
||||
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();
|
||||
|
||||
+20
-20
@@ -4,7 +4,7 @@ use std::path::Path;
|
||||
/// Only untouched templates contain this marker — real project content
|
||||
/// will never include it, so it avoids false positives when the project
|
||||
/// itself is an "Agentic AI Code Assistant".
|
||||
const TEMPLATE_SENTINEL: &str = "<!-- storkit:scaffold-template -->";
|
||||
const TEMPLATE_SENTINEL: &str = "<!-- huskies:scaffold-template -->";
|
||||
|
||||
/// Marker found in the default `script/test` scaffold output.
|
||||
const TEMPLATE_MARKER_SCRIPT: &str = "No tests configured";
|
||||
@@ -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 `.storkit/project.toml` is missing or has no
|
||||
/// True when `.huskies/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(".storkit");
|
||||
let story_kit = project_root.join(".huskies");
|
||||
|
||||
OnboardingStatus {
|
||||
needs_context: is_template_or_missing(
|
||||
@@ -99,13 +99,13 @@ mod tests {
|
||||
|
||||
// Write content that includes the scaffold sentinel
|
||||
fs::write(
|
||||
root.join(".storkit/specs/00_CONTEXT.md"),
|
||||
"<!-- storkit:scaffold-template -->\n# Project Context\nPlaceholder...",
|
||||
root.join(".huskies/specs/00_CONTEXT.md"),
|
||||
"<!-- huskies:scaffold-template -->\n# Project Context\nPlaceholder...",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/specs/tech/STACK.md"),
|
||||
"<!-- storkit:scaffold-template -->\n# Tech Stack\nPlaceholder...",
|
||||
root.join(".huskies/specs/tech/STACK.md"),
|
||||
"<!-- huskies:scaffold-template -->\n# Tech Stack\nPlaceholder...",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -123,12 +123,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(".storkit/specs/00_CONTEXT.md"),
|
||||
root.join(".huskies/specs/00_CONTEXT.md"),
|
||||
"# Project Context\nTo build a standalone Agentic AI Code Assistant application.",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/specs/tech/STACK.md"),
|
||||
root.join(".huskies/specs/tech/STACK.md"),
|
||||
"# Tech Stack\nThis is an Agentic Code Assistant binary.",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -145,12 +145,12 @@ mod tests {
|
||||
let root = setup_project(&dir);
|
||||
|
||||
fs::write(
|
||||
root.join(".storkit/specs/00_CONTEXT.md"),
|
||||
root.join(".huskies/specs/00_CONTEXT.md"),
|
||||
"# My Project\n\nThis is an e-commerce platform for selling widgets.",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/specs/tech/STACK.md"),
|
||||
root.join(".huskies/specs/tech/STACK.md"),
|
||||
"# Tech Stack\n\n## Backend: Python + FastAPI\n## Frontend: React + TypeScript",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -166,8 +166,8 @@ mod tests {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = setup_project(&dir);
|
||||
|
||||
fs::write(root.join(".storkit/specs/00_CONTEXT.md"), " \n").unwrap();
|
||||
fs::write(root.join(".storkit/specs/tech/STACK.md"), "").unwrap();
|
||||
fs::write(root.join(".huskies/specs/00_CONTEXT.md"), " \n").unwrap();
|
||||
fs::write(root.join(".huskies/specs/tech/STACK.md"), "").unwrap();
|
||||
|
||||
let status = check_onboarding_status(&root);
|
||||
assert!(status.needs_context);
|
||||
@@ -222,7 +222,7 @@ mod tests {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = setup_project(&dir);
|
||||
|
||||
fs::write(root.join(".storkit/project.toml"), "# empty config\n").unwrap();
|
||||
fs::write(root.join(".huskies/project.toml"), "# empty config\n").unwrap();
|
||||
|
||||
let status = check_onboarding_status(&root);
|
||||
assert!(status.needs_project_toml);
|
||||
@@ -234,7 +234,7 @@ mod tests {
|
||||
let root = setup_project(&dir);
|
||||
|
||||
fs::write(
|
||||
root.join(".storkit/project.toml"),
|
||||
root.join(".huskies/project.toml"),
|
||||
"[[component]]\nname = \"app\"\npath = \".\"\nsetup = [\"cargo check\"]\n",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -252,12 +252,12 @@ mod tests {
|
||||
|
||||
// Write real content for the required onboarding files
|
||||
fs::write(
|
||||
root.join(".storkit/specs/00_CONTEXT.md"),
|
||||
root.join(".huskies/specs/00_CONTEXT.md"),
|
||||
"# My Project\n\nReal project context.",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/specs/tech/STACK.md"),
|
||||
root.join(".huskies/specs/tech/STACK.md"),
|
||||
"# My Stack\n\nReal stack content.",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -289,13 +289,13 @@ mod tests {
|
||||
|
||||
// Context still has sentinel
|
||||
fs::write(
|
||||
root.join(".storkit/specs/00_CONTEXT.md"),
|
||||
"<!-- storkit:scaffold-template -->\n# Project Context\nPlaceholder...",
|
||||
root.join(".huskies/specs/00_CONTEXT.md"),
|
||||
"<!-- huskies:scaffold-template -->\n# Project Context\nPlaceholder...",
|
||||
)
|
||||
.unwrap();
|
||||
// Stack is customised (no sentinel)
|
||||
fs::write(
|
||||
root.join(".storkit/specs/tech/STACK.md"),
|
||||
root.join(".huskies/specs/tech/STACK.md"),
|
||||
"# My Stack\nRuby on Rails + PostgreSQL",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -6,13 +6,13 @@ use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Create a minimal storkit project directory structure under `dir`.
|
||||
/// Create a minimal huskies project directory structure under `dir`.
|
||||
///
|
||||
/// Creates `.storkit/specs/tech/` and `script/`, then returns the root path.
|
||||
/// Creates `.huskies/specs/tech/` and `script/`, then returns the root path.
|
||||
/// Used by onboarding and wizard tests.
|
||||
pub(crate) fn setup_project(dir: &TempDir) -> PathBuf {
|
||||
let root = dir.path().to_path_buf();
|
||||
let sk = root.join(".storkit");
|
||||
let sk = root.join(".huskies");
|
||||
fs::create_dir_all(sk.join("specs").join("tech")).unwrap();
|
||||
fs::create_dir_all(root.join("script")).unwrap();
|
||||
root
|
||||
|
||||
+57
-57
@@ -1,10 +1,10 @@
|
||||
//! Filesystem watcher for `.storkit/work/` and `.storkit/project.toml`.
|
||||
//! Filesystem watcher for `.huskies/work/` and `.huskies/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 `.storkit/project.toml` for modifications and broadcasts
|
||||
//! Also watches `.huskies/project.toml` for modifications and broadcasts
|
||||
//! [`WatcherEvent::ConfigChanged`] so the frontend can reload the agent roster
|
||||
//! without a server restart.
|
||||
//!
|
||||
@@ -48,7 +48,7 @@ pub enum WatcherEvent {
|
||||
/// `None` for creations, deletions, or synthetic events.
|
||||
from_stage: Option<String>,
|
||||
},
|
||||
/// `.storkit/project.toml` was modified at the project root (not inside a worktree).
|
||||
/// `.huskies/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
|
||||
@@ -90,8 +90,8 @@ pub enum WatcherEvent {
|
||||
},
|
||||
}
|
||||
|
||||
/// Return `true` if `path` is the root-level `.storkit/project.toml`, i.e.
|
||||
/// `{git_root}/.storkit/project.toml`.
|
||||
/// Return `true` if `path` is the root-level `.huskies/project.toml`, i.e.
|
||||
/// `{git_root}/.huskies/project.toml`.
|
||||
///
|
||||
/// Returns `false` for paths inside worktree directories (paths containing
|
||||
/// a `worktrees` component).
|
||||
@@ -100,19 +100,19 @@ 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(".storkit").join("project.toml");
|
||||
let expected = git_root.join(".huskies").join("project.toml");
|
||||
path == expected
|
||||
}
|
||||
|
||||
/// Map a pipeline directory name to a (action, commit-message-prefix) pair.
|
||||
fn stage_metadata(stage: &str, item_id: &str) -> Option<(&'static str, String)> {
|
||||
let (action, prefix) = match stage {
|
||||
"1_backlog" => ("create", format!("storkit: create {item_id}")),
|
||||
"2_current" => ("start", format!("storkit: start {item_id}")),
|
||||
"3_qa" => ("qa", format!("storkit: queue {item_id} for QA")),
|
||||
"4_merge" => ("merge", format!("storkit: queue {item_id} for merge")),
|
||||
"5_done" => ("done", format!("storkit: done {item_id}")),
|
||||
"6_archived" => ("accept", format!("storkit: accept {item_id}")),
|
||||
"1_backlog" => ("create", format!("huskies: create {item_id}")),
|
||||
"2_current" => ("start", format!("huskies: start {item_id}")),
|
||||
"3_qa" => ("qa", format!("huskies: queue {item_id} for QA")),
|
||||
"4_merge" => ("merge", format!("huskies: queue {item_id} for merge")),
|
||||
"5_done" => ("done", format!("huskies: done {item_id}")),
|
||||
"6_archived" => ("accept", format!("huskies: accept {item_id}")),
|
||||
_ => return None,
|
||||
};
|
||||
Some((action, prefix))
|
||||
@@ -121,7 +121,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 `.storkit/worktrees/` so
|
||||
/// Explicitly returns `None` for any path under `.huskies/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> {
|
||||
@@ -146,11 +146,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 .storkit/work/` to catch both additions and deletions in
|
||||
/// Uses `git add -A .huskies/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(".storkit").join("work");
|
||||
let work_rel = PathBuf::from(".huskies").join("work");
|
||||
|
||||
let add_out = std::process::Command::new("git")
|
||||
.args(["add", "-A"])
|
||||
@@ -199,7 +199,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 .storkit/work/` automatically.
|
||||
/// captured by `git add -A .huskies/work/` automatically.
|
||||
///
|
||||
/// Only terminal stages (`1_backlog` and `6_archived`) trigger git commits.
|
||||
/// All stages broadcast a [`WatcherEvent`] so the frontend stays in sync.
|
||||
@@ -240,7 +240,7 @@ fn flush_pending(
|
||||
(
|
||||
"remove",
|
||||
item.to_string(),
|
||||
format!("storkit: remove {item}"),
|
||||
format!("huskies: remove {item}"),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -392,9 +392,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 `.storkit/work/` (watched recursively).
|
||||
/// `work_dir` — absolute path to `.huskies/work/` (watched recursively).
|
||||
/// `git_root` — project root (passed to `git` commands as cwd, and used to
|
||||
/// derive the config file path `.storkit/project.toml`).
|
||||
/// derive the config file path `.huskies/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(
|
||||
@@ -421,8 +421,8 @@ pub fn start_watcher(
|
||||
return;
|
||||
}
|
||||
|
||||
// Also watch .storkit/project.toml for hot-reload of agent config.
|
||||
let config_file = git_root.join(".storkit").join("project.toml");
|
||||
// Also watch .huskies/project.toml for hot-reload of agent config.
|
||||
let config_file = git_root.join(".huskies").join("project.toml");
|
||||
if config_file.exists()
|
||||
&& let Err(e) = watcher.watch(&config_file, RecursiveMode::NonRecursive)
|
||||
{
|
||||
@@ -575,9 +575,9 @@ mod tests {
|
||||
.expect("git initial commit");
|
||||
}
|
||||
|
||||
/// Create the `.storkit/work/{stage}/` dir tree inside `root`.
|
||||
/// Create the `.huskies/work/{stage}/` dir tree inside `root`.
|
||||
fn make_stage_dir(root: &std::path::Path, stage: &str) -> PathBuf {
|
||||
let dir = root.join(".storkit").join("work").join(stage);
|
||||
let dir = root.join(".huskies").join("work").join(stage);
|
||||
fs::create_dir_all(&dir).expect("create stage dir");
|
||||
dir
|
||||
}
|
||||
@@ -591,7 +591,7 @@ mod tests {
|
||||
let stage_dir = make_stage_dir(tmp.path(), "2_current");
|
||||
fs::write(stage_dir.join("42_story_foo.md"), "---\nname: test\n---\n").unwrap();
|
||||
|
||||
let result = git_add_work_and_commit(tmp.path(), "storkit: start 42_story_foo");
|
||||
let result = git_add_work_and_commit(tmp.path(), "huskies: start 42_story_foo");
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(true),
|
||||
@@ -607,10 +607,10 @@ mod tests {
|
||||
fs::write(stage_dir.join("42_story_foo.md"), "---\nname: test\n---\n").unwrap();
|
||||
|
||||
// First commit — should succeed.
|
||||
git_add_work_and_commit(tmp.path(), "storkit: start 42_story_foo").unwrap();
|
||||
git_add_work_and_commit(tmp.path(), "huskies: start 42_story_foo").unwrap();
|
||||
|
||||
// Second call with no changes — should return Ok(false).
|
||||
let result = git_add_work_and_commit(tmp.path(), "storkit: start 42_story_foo");
|
||||
let result = git_add_work_and_commit(tmp.path(), "huskies: start 42_story_foo");
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(false),
|
||||
@@ -646,7 +646,7 @@ mod tests {
|
||||
assert_eq!(stage, "1_backlog");
|
||||
assert_eq!(item_id, "42_story_foo");
|
||||
assert_eq!(action, "create");
|
||||
assert_eq!(commit_msg, "storkit: create 42_story_foo");
|
||||
assert_eq!(commit_msg, "huskies: create 42_story_foo");
|
||||
}
|
||||
other => panic!("unexpected event: {other:?}"),
|
||||
}
|
||||
@@ -659,7 +659,7 @@ mod tests {
|
||||
.expect("git log");
|
||||
let log_msg = String::from_utf8_lossy(&log.stdout);
|
||||
assert!(
|
||||
log_msg.contains("storkit: create 42_story_foo"),
|
||||
log_msg.contains("huskies: create 42_story_foo"),
|
||||
"terminal stage should produce a git commit"
|
||||
);
|
||||
}
|
||||
@@ -691,7 +691,7 @@ mod tests {
|
||||
assert_eq!(stage, "2_current");
|
||||
assert_eq!(item_id, "42_story_foo");
|
||||
assert_eq!(action, "start");
|
||||
assert_eq!(commit_msg, "storkit: start 42_story_foo");
|
||||
assert_eq!(commit_msg, "huskies: start 42_story_foo");
|
||||
}
|
||||
other => panic!("unexpected event: {other:?}"),
|
||||
}
|
||||
@@ -704,7 +704,7 @@ mod tests {
|
||||
.expect("git log");
|
||||
let log_msg = String::from_utf8_lossy(&log.stdout);
|
||||
assert!(
|
||||
!log_msg.contains("storkit:"),
|
||||
!log_msg.contains("huskies:"),
|
||||
"intermediate stage should NOT produce a git commit"
|
||||
);
|
||||
}
|
||||
@@ -712,11 +712,11 @@ mod tests {
|
||||
#[test]
|
||||
fn flush_pending_broadcasts_for_all_pipeline_stages() {
|
||||
let stages = [
|
||||
("1_backlog", "create", "storkit: create 10_story_x"),
|
||||
("3_qa", "qa", "storkit: queue 10_story_x for QA"),
|
||||
("4_merge", "merge", "storkit: queue 10_story_x for merge"),
|
||||
("5_done", "done", "storkit: done 10_story_x"),
|
||||
("6_archived", "accept", "storkit: accept 10_story_x"),
|
||||
("1_backlog", "create", "huskies: create 10_story_x"),
|
||||
("3_qa", "qa", "huskies: queue 10_story_x for QA"),
|
||||
("4_merge", "merge", "huskies: queue 10_story_x for merge"),
|
||||
("5_done", "done", "huskies: done 10_story_x"),
|
||||
("6_archived", "accept", "huskies: accept 10_story_x"),
|
||||
];
|
||||
|
||||
for (stage, expected_action, expected_msg) in stages {
|
||||
@@ -754,7 +754,7 @@ mod tests {
|
||||
make_stage_dir(tmp.path(), "2_current");
|
||||
let deleted_path = tmp
|
||||
.path()
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join("2_current")
|
||||
.join("42_story_foo.md");
|
||||
@@ -785,7 +785,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(".storkit").join("work").join("9_unknown");
|
||||
let unknown_dir = tmp.path().join(".huskies").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();
|
||||
@@ -961,7 +961,7 @@ mod tests {
|
||||
make_stage_dir(tmp.path(), "3_qa");
|
||||
let qa_path = tmp
|
||||
.path()
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join("3_qa")
|
||||
.join("42_story_foo.md");
|
||||
@@ -1015,7 +1015,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn stage_for_path_recognises_pipeline_dirs() {
|
||||
let base = PathBuf::from("/proj/.storkit/work");
|
||||
let base = PathBuf::from("/proj/.huskies/work");
|
||||
assert_eq!(
|
||||
stage_for_path(&base.join("2_current/42_story_foo.md")),
|
||||
Some("2_current".to_string())
|
||||
@@ -1037,7 +1037,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn stage_for_path_ignores_worktree_paths() {
|
||||
let worktrees = PathBuf::from("/proj/.storkit/worktrees");
|
||||
let worktrees = PathBuf::from("/proj/.huskies/worktrees");
|
||||
|
||||
// Code changes inside a worktree must be ignored.
|
||||
assert_eq!(
|
||||
@@ -1048,7 +1048,7 @@ 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/.storkit/work/2_current/42_story_foo.md")),
|
||||
stage_for_path(&worktrees.join("42_story_foo/.huskies/work/2_current/42_story_foo.md")),
|
||||
None,
|
||||
);
|
||||
|
||||
@@ -1056,7 +1056,7 @@ mod tests {
|
||||
// segment (not an exact component) must NOT be filtered out.
|
||||
assert_eq!(
|
||||
stage_for_path(&PathBuf::from(
|
||||
"/proj/.storkit/work/2_current/not_worktrees_story.md"
|
||||
"/proj/.huskies/work/2_current/not_worktrees_story.md"
|
||||
)),
|
||||
Some("2_current".to_string()),
|
||||
);
|
||||
@@ -1080,15 +1080,15 @@ mod tests {
|
||||
fn stage_metadata_returns_correct_actions() {
|
||||
let (action, msg) = stage_metadata("2_current", "42_story_foo").unwrap();
|
||||
assert_eq!(action, "start");
|
||||
assert_eq!(msg, "storkit: start 42_story_foo");
|
||||
assert_eq!(msg, "huskies: start 42_story_foo");
|
||||
|
||||
let (action, msg) = stage_metadata("5_done", "42_story_foo").unwrap();
|
||||
assert_eq!(action, "done");
|
||||
assert_eq!(msg, "storkit: done 42_story_foo");
|
||||
assert_eq!(msg, "huskies: done 42_story_foo");
|
||||
|
||||
let (action, msg) = stage_metadata("6_archived", "42_story_foo").unwrap();
|
||||
assert_eq!(action, "accept");
|
||||
assert_eq!(msg, "storkit: accept 42_story_foo");
|
||||
assert_eq!(msg, "huskies: accept 42_story_foo");
|
||||
|
||||
assert!(stage_metadata("unknown", "id").is_none());
|
||||
}
|
||||
@@ -1096,7 +1096,7 @@ mod tests {
|
||||
#[test]
|
||||
fn is_config_file_identifies_root_project_toml() {
|
||||
let git_root = PathBuf::from("/proj");
|
||||
let config = git_root.join(".storkit").join("project.toml");
|
||||
let config = git_root.join(".huskies").join("project.toml");
|
||||
assert!(is_config_file(&config, &git_root));
|
||||
}
|
||||
|
||||
@@ -1105,7 +1105,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/.storkit/worktrees/42_story_foo/.storkit/project.toml");
|
||||
PathBuf::from("/proj/.huskies/worktrees/42_story_foo/.huskies/project.toml");
|
||||
assert!(!is_config_file(&worktree_config, &git_root));
|
||||
}
|
||||
|
||||
@@ -1114,11 +1114,11 @@ mod tests {
|
||||
let git_root = PathBuf::from("/proj");
|
||||
// Random files must not match.
|
||||
assert!(!is_config_file(
|
||||
&PathBuf::from("/proj/.storkit/work/2_current/42_story_foo.md"),
|
||||
&PathBuf::from("/proj/.huskies/work/2_current/42_story_foo.md"),
|
||||
&git_root
|
||||
));
|
||||
assert!(!is_config_file(
|
||||
&PathBuf::from("/proj/.storkit/README.md"),
|
||||
&PathBuf::from("/proj/.huskies/README.md"),
|
||||
&git_root
|
||||
));
|
||||
}
|
||||
@@ -1126,7 +1126,7 @@ mod tests {
|
||||
#[test]
|
||||
fn is_config_file_rejects_wrong_root() {
|
||||
let git_root = PathBuf::from("/proj");
|
||||
let other_root_config = PathBuf::from("/other/.storkit/project.toml");
|
||||
let other_root_config = PathBuf::from("/other/.huskies/project.toml");
|
||||
assert!(!is_config_file(&other_root_config, &git_root));
|
||||
}
|
||||
|
||||
@@ -1135,7 +1135,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sweep_moves_old_items_to_archived() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let work_dir = tmp.path().join(".storkit").join("work");
|
||||
let work_dir = tmp.path().join(".huskies").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();
|
||||
@@ -1165,7 +1165,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sweep_keeps_recent_items_in_done() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let work_dir = tmp.path().join(".storkit").join("work");
|
||||
let work_dir = tmp.path().join(".huskies").join("work");
|
||||
let done_dir = work_dir.join("5_done");
|
||||
fs::create_dir_all(&done_dir).unwrap();
|
||||
|
||||
@@ -1182,7 +1182,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sweep_respects_custom_retention() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let work_dir = tmp.path().join(".storkit").join("work");
|
||||
let work_dir = tmp.path().join(".huskies").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();
|
||||
@@ -1211,7 +1211,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sweep_custom_retention_keeps_younger_items() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let work_dir = tmp.path().join(".storkit").join("work");
|
||||
let work_dir = tmp.path().join(".huskies").join("work");
|
||||
let done_dir = work_dir.join("5_done");
|
||||
fs::create_dir_all(&done_dir).unwrap();
|
||||
|
||||
@@ -1255,7 +1255,7 @@ mod tests {
|
||||
let git_root = tmp.path().to_path_buf();
|
||||
init_git_repo(&git_root);
|
||||
|
||||
let work_dir = git_root.join(".storkit").join("work");
|
||||
let work_dir = git_root.join(".huskies").join("work");
|
||||
let done_dir = work_dir.join("5_done");
|
||||
fs::create_dir_all(&done_dir).unwrap();
|
||||
|
||||
@@ -1298,7 +1298,7 @@ mod tests {
|
||||
let git_root = tmp.path().to_path_buf();
|
||||
init_git_repo(&git_root);
|
||||
|
||||
let work_dir = git_root.join(".storkit").join("work");
|
||||
let work_dir = git_root.join(".huskies").join("work");
|
||||
let archived_dir = work_dir.join("6_archived");
|
||||
fs::create_dir_all(&archived_dir).unwrap();
|
||||
|
||||
@@ -1339,7 +1339,7 @@ mod tests {
|
||||
let git_root = tmp.path().to_path_buf();
|
||||
init_git_repo(&git_root);
|
||||
|
||||
let work_dir = git_root.join(".storkit").join("work");
|
||||
let work_dir = git_root.join(".huskies").join("work");
|
||||
let done_dir = work_dir.join("5_done");
|
||||
fs::create_dir_all(&done_dir).unwrap();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::path::Path;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WizardStep {
|
||||
/// Step 1: scaffold .storkit/ directory structure and project.toml
|
||||
/// Step 1: scaffold .huskies/ directory structure and project.toml
|
||||
Scaffold,
|
||||
/// Step 2: generate specs/00_CONTEXT.md
|
||||
Context,
|
||||
@@ -76,7 +76,7 @@ pub struct StepState {
|
||||
pub content: Option<String>,
|
||||
}
|
||||
|
||||
/// Persistent wizard state, stored in `.storkit/wizard_state.json`.
|
||||
/// Persistent wizard state, stored in `.huskies/wizard_state.json`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WizardState {
|
||||
pub steps: Vec<StepState>,
|
||||
@@ -103,7 +103,7 @@ impl Default for WizardState {
|
||||
impl WizardState {
|
||||
/// Path to the wizard state file relative to the project root.
|
||||
fn state_path(project_root: &Path) -> std::path::PathBuf {
|
||||
project_root.join(".storkit").join("wizard_state.json")
|
||||
project_root.join(".huskies").join("wizard_state.json")
|
||||
}
|
||||
|
||||
/// Load wizard state from disk, or return None if it doesn't exist.
|
||||
@@ -122,7 +122,7 @@ impl WizardState {
|
||||
}
|
||||
|
||||
/// Create wizard state file if it doesn't already exist.
|
||||
/// Step 1 (Scaffold) is automatically confirmed since `storkit init`
|
||||
/// Step 1 (Scaffold) is automatically confirmed since `huskies init`
|
||||
/// has already run the scaffold.
|
||||
pub fn init_if_missing(project_root: &Path) {
|
||||
if Self::load(project_root).is_some() {
|
||||
|
||||
Reference in New Issue
Block a user