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:
@@ -18,12 +18,12 @@ pub(super) fn item_type_from_id(item_id: &str) -> &'static str {
|
||||
|
||||
/// Return the source directory path for a work item (always work/1_backlog/).
|
||||
fn item_source_dir(project_root: &Path, _item_id: &str) -> PathBuf {
|
||||
project_root.join(".storkit").join("work").join("1_backlog")
|
||||
project_root.join(".huskies").join("work").join("1_backlog")
|
||||
}
|
||||
|
||||
/// Return the done directory path for a work item (always work/5_done/).
|
||||
fn item_archive_dir(project_root: &Path, _item_id: &str) -> PathBuf {
|
||||
project_root.join(".storkit").join("work").join("5_done")
|
||||
project_root.join(".huskies").join("work").join("5_done")
|
||||
}
|
||||
|
||||
/// Move a work item (story, bug, or spike) from `work/1_backlog/` to `work/2_current/`.
|
||||
@@ -31,7 +31,7 @@ fn item_archive_dir(project_root: &Path, _item_id: &str) -> PathBuf {
|
||||
/// Idempotent: if the item is already in `2_current/`, returns Ok without committing.
|
||||
/// If the item is not found in `1_backlog/`, logs a warning and returns Ok.
|
||||
pub fn move_story_to_current(project_root: &Path, story_id: &str) -> Result<(), String> {
|
||||
let sk = project_root.join(".storkit").join("work");
|
||||
let sk = project_root.join(".huskies").join("work");
|
||||
let current_dir = sk.join("2_current");
|
||||
let current_path = current_dir.join(format!("{story_id}.md"));
|
||||
|
||||
@@ -103,7 +103,7 @@ pub fn feature_branch_has_unmerged_changes(project_root: &Path, story_id: &str)
|
||||
/// * If the story is already in `5_done/` or `6_archived/`, this is a no-op (idempotent).
|
||||
/// * If the story is not found in `2_current/`, `4_merge/`, `5_done/`, or `6_archived/`, an error is returned.
|
||||
pub fn move_story_to_done(project_root: &Path, story_id: &str) -> Result<(), String> {
|
||||
let sk = project_root.join(".storkit").join("work");
|
||||
let sk = project_root.join(".huskies").join("work");
|
||||
let current_path = sk.join("2_current").join(format!("{story_id}.md"));
|
||||
let merge_path = sk.join("4_merge").join(format!("{story_id}.md"));
|
||||
let done_dir = sk.join("5_done");
|
||||
@@ -153,7 +153,7 @@ pub fn move_story_to_done(project_root: &Path, story_id: &str) -> Result<(), Str
|
||||
/// This stages a work item as ready for the mergemaster to pick up and merge into master.
|
||||
/// Idempotent: if already in `4_merge/`, returns Ok without committing.
|
||||
pub fn move_story_to_merge(project_root: &Path, story_id: &str) -> Result<(), String> {
|
||||
let sk = project_root.join(".storkit").join("work");
|
||||
let sk = project_root.join(".huskies").join("work");
|
||||
let current_path = sk.join("2_current").join(format!("{story_id}.md"));
|
||||
let qa_path = sk.join("3_qa").join(format!("{story_id}.md"));
|
||||
let merge_dir = sk.join("4_merge");
|
||||
@@ -203,7 +203,7 @@ pub fn move_story_to_merge(project_root: &Path, story_id: &str) -> Result<(), St
|
||||
/// This stages a work item for QA review before merging to master.
|
||||
/// Idempotent: if already in `3_qa/`, returns Ok without committing.
|
||||
pub fn move_story_to_qa(project_root: &Path, story_id: &str) -> Result<(), String> {
|
||||
let sk = project_root.join(".storkit").join("work");
|
||||
let sk = project_root.join(".huskies").join("work");
|
||||
let current_path = sk.join("2_current").join(format!("{story_id}.md"));
|
||||
let qa_dir = sk.join("3_qa");
|
||||
let qa_path = qa_dir.join(format!("{story_id}.md"));
|
||||
@@ -246,7 +246,7 @@ pub fn reject_story_from_qa(
|
||||
story_id: &str,
|
||||
notes: &str,
|
||||
) -> Result<(), String> {
|
||||
let sk = project_root.join(".storkit").join("work");
|
||||
let sk = project_root.join(".huskies").join("work");
|
||||
let qa_path = sk.join("3_qa").join(format!("{story_id}.md"));
|
||||
let current_dir = sk.join("2_current");
|
||||
let current_path = current_dir.join(format!("{story_id}.md"));
|
||||
@@ -311,7 +311,7 @@ pub fn move_story_to_stage(
|
||||
)
|
||||
})?;
|
||||
|
||||
let sk = project_root.join(".storkit").join("work");
|
||||
let sk = project_root.join(".huskies").join("work");
|
||||
let target_dir = sk.join(target_dir_name);
|
||||
let target_path = target_dir.join(format!("{story_id}.md"));
|
||||
|
||||
@@ -362,7 +362,7 @@ pub fn move_story_to_stage(
|
||||
/// * If the bug is already in `5_done/`, this is a no-op (idempotent).
|
||||
/// * If the bug is not found anywhere, an error is returned.
|
||||
pub fn close_bug_to_archive(project_root: &Path, bug_id: &str) -> Result<(), String> {
|
||||
let sk = project_root.join(".storkit").join("work");
|
||||
let sk = project_root.join(".huskies").join("work");
|
||||
let current_path = sk.join("2_current").join(format!("{bug_id}.md"));
|
||||
let backlog_path = sk.join("1_backlog").join(format!("{bug_id}.md"));
|
||||
let archive_dir = item_archive_dir(project_root, bug_id);
|
||||
@@ -405,8 +405,8 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let backlog = root.join(".storkit/work/1_backlog");
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let backlog = root.join(".huskies/work/1_backlog");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(&backlog).unwrap();
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(backlog.join("10_story_foo.md"), "test").unwrap();
|
||||
@@ -422,7 +422,7 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("11_story_foo.md"), "test").unwrap();
|
||||
|
||||
@@ -441,8 +441,8 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let backlog = root.join(".storkit/work/1_backlog");
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let backlog = root.join(".huskies/work/1_backlog");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(&backlog).unwrap();
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(backlog.join("1_bug_test.md"), "# Bug 1\n").unwrap();
|
||||
@@ -460,14 +460,14 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("2_bug_test.md"), "# Bug 2\n").unwrap();
|
||||
|
||||
close_bug_to_archive(root, "2_bug_test").unwrap();
|
||||
|
||||
assert!(!current.join("2_bug_test.md").exists());
|
||||
assert!(root.join(".storkit/work/5_done/2_bug_test.md").exists());
|
||||
assert!(root.join(".huskies/work/5_done/2_bug_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -475,14 +475,14 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let backlog = root.join(".storkit/work/1_backlog");
|
||||
let backlog = root.join(".huskies/work/1_backlog");
|
||||
fs::create_dir_all(&backlog).unwrap();
|
||||
fs::write(backlog.join("3_bug_test.md"), "# Bug 3\n").unwrap();
|
||||
|
||||
close_bug_to_archive(root, "3_bug_test").unwrap();
|
||||
|
||||
assert!(!backlog.join("3_bug_test.md").exists());
|
||||
assert!(root.join(".storkit/work/5_done/3_bug_test.md").exists());
|
||||
assert!(root.join(".huskies/work/5_done/3_bug_test.md").exists());
|
||||
}
|
||||
|
||||
// ── item_type_from_id tests ────────────────────────────────────────────────
|
||||
@@ -502,14 +502,14 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("20_story_foo.md"), "test").unwrap();
|
||||
|
||||
move_story_to_merge(root, "20_story_foo").unwrap();
|
||||
|
||||
assert!(!current.join("20_story_foo.md").exists());
|
||||
assert!(root.join(".storkit/work/4_merge/20_story_foo.md").exists());
|
||||
assert!(root.join(".huskies/work/4_merge/20_story_foo.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -517,14 +517,14 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let qa_dir = root.join(".storkit/work/3_qa");
|
||||
let qa_dir = root.join(".huskies/work/3_qa");
|
||||
fs::create_dir_all(&qa_dir).unwrap();
|
||||
fs::write(qa_dir.join("40_story_test.md"), "test").unwrap();
|
||||
|
||||
move_story_to_merge(root, "40_story_test").unwrap();
|
||||
|
||||
assert!(!qa_dir.join("40_story_test.md").exists());
|
||||
assert!(root.join(".storkit/work/4_merge/40_story_test.md").exists());
|
||||
assert!(root.join(".huskies/work/4_merge/40_story_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -532,7 +532,7 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let merge_dir = root.join(".storkit/work/4_merge");
|
||||
let merge_dir = root.join(".huskies/work/4_merge");
|
||||
fs::create_dir_all(&merge_dir).unwrap();
|
||||
fs::write(merge_dir.join("21_story_test.md"), "test").unwrap();
|
||||
|
||||
@@ -554,14 +554,14 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("30_story_qa.md"), "test").unwrap();
|
||||
|
||||
move_story_to_qa(root, "30_story_qa").unwrap();
|
||||
|
||||
assert!(!current.join("30_story_qa.md").exists());
|
||||
assert!(root.join(".storkit/work/3_qa/30_story_qa.md").exists());
|
||||
assert!(root.join(".huskies/work/3_qa/30_story_qa.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -569,7 +569,7 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let qa_dir = root.join(".storkit/work/3_qa");
|
||||
let qa_dir = root.join(".huskies/work/3_qa");
|
||||
fs::create_dir_all(&qa_dir).unwrap();
|
||||
fs::write(qa_dir.join("31_story_test.md"), "test").unwrap();
|
||||
|
||||
@@ -591,14 +591,14 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let merge_dir = root.join(".storkit/work/4_merge");
|
||||
let merge_dir = root.join(".huskies/work/4_merge");
|
||||
fs::create_dir_all(&merge_dir).unwrap();
|
||||
fs::write(merge_dir.join("22_story_test.md"), "test").unwrap();
|
||||
|
||||
move_story_to_done(root, "22_story_test").unwrap();
|
||||
|
||||
assert!(!merge_dir.join("22_story_test.md").exists());
|
||||
assert!(root.join(".storkit/work/5_done/22_story_test.md").exists());
|
||||
assert!(root.join(".huskies/work/5_done/22_story_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -696,8 +696,8 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let qa_dir = root.join(".storkit/work/3_qa");
|
||||
let current_dir = root.join(".storkit/work/2_current");
|
||||
let qa_dir = root.join(".huskies/work/3_qa");
|
||||
let current_dir = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(&qa_dir).unwrap();
|
||||
fs::create_dir_all(¤t_dir).unwrap();
|
||||
fs::write(
|
||||
@@ -728,7 +728,7 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current_dir = root.join(".storkit/work/2_current");
|
||||
let current_dir = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t_dir).unwrap();
|
||||
fs::write(current_dir.join("51_story_test.md"), "---\nname: Test\n---\n# Story\n").unwrap();
|
||||
|
||||
@@ -743,8 +743,8 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let backlog = root.join(".storkit/work/1_backlog");
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let backlog = root.join(".huskies/work/1_backlog");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(&backlog).unwrap();
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(backlog.join("60_story_move.md"), "test").unwrap();
|
||||
@@ -762,8 +762,8 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let backlog = root.join(".storkit/work/1_backlog");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
let backlog = root.join(".huskies/work/1_backlog");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::create_dir_all(&backlog).unwrap();
|
||||
fs::write(current.join("61_story_back.md"), "test").unwrap();
|
||||
@@ -781,7 +781,7 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("62_story_idem.md"), "test").unwrap();
|
||||
|
||||
@@ -813,8 +813,8 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let qa_dir = root.join(".storkit/work/3_qa");
|
||||
let backlog = root.join(".storkit/work/1_backlog");
|
||||
let qa_dir = root.join(".huskies/work/3_qa");
|
||||
let backlog = root.join(".huskies/work/1_backlog");
|
||||
fs::create_dir_all(&qa_dir).unwrap();
|
||||
fs::create_dir_all(&backlog).unwrap();
|
||||
fs::write(qa_dir.join("63_story_qa.md"), "test").unwrap();
|
||||
|
||||
+28
-28
@@ -10,7 +10,7 @@ use super::gates::run_project_tests;
|
||||
|
||||
/// Global lock ensuring only one squash-merge runs at a time.
|
||||
///
|
||||
/// The merge pipeline uses a shared `.storkit/merge_workspace` directory and
|
||||
/// The merge pipeline uses a shared `.huskies/merge_workspace` directory and
|
||||
/// temporary `merge-queue/{story_id}` branches. If two merges run concurrently,
|
||||
/// the second call's initial cleanup destroys the first call's branch mid-flight,
|
||||
/// causing `git cherry-pick merge-queue/…` to fail with "bad revision".
|
||||
@@ -88,7 +88,7 @@ pub(crate) fn run_squash_merge(
|
||||
|
||||
let mut all_output = String::new();
|
||||
let merge_branch = format!("merge-queue/{story_id}");
|
||||
let merge_wt_path = project_root.join(".storkit").join("merge_workspace");
|
||||
let merge_wt_path = project_root.join(".huskies").join("merge_workspace");
|
||||
|
||||
// Ensure we start clean: remove any leftover merge workspace.
|
||||
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
||||
@@ -189,7 +189,7 @@ pub(crate) fn run_squash_merge(
|
||||
|
||||
// ── Commit in the temporary worktree ──────────────────────────
|
||||
all_output.push_str("=== git commit ===\n");
|
||||
let commit_msg = format!("storkit: merge {story_id}");
|
||||
let commit_msg = format!("huskies: merge {story_id}");
|
||||
let commit = Command::new("git")
|
||||
.args(["commit", "-m", &commit_msg])
|
||||
.current_dir(&merge_wt_path)
|
||||
@@ -238,7 +238,7 @@ pub(crate) fn run_squash_merge(
|
||||
}
|
||||
|
||||
// ── Bug 226: Verify the commit contains real code changes ─────
|
||||
// If the merge only brought in .storkit/ files (pipeline file moves),
|
||||
// If the merge only brought in .huskies/ files (pipeline file moves),
|
||||
// there are no actual code changes to land on master. Abort.
|
||||
{
|
||||
let diff_check = Command::new("git")
|
||||
@@ -247,10 +247,10 @@ pub(crate) fn run_squash_merge(
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to check merge diff: {e}"))?;
|
||||
let changed_files = String::from_utf8_lossy(&diff_check.stdout);
|
||||
let has_code_changes = changed_files.lines().any(|f| !f.starts_with(".storkit/work/"));
|
||||
let has_code_changes = changed_files.lines().any(|f| !f.starts_with(".huskies/work/"));
|
||||
if !has_code_changes {
|
||||
all_output.push_str(
|
||||
"=== Merge commit contains only .storkit/ file moves, no code changes ===\n",
|
||||
"=== Merge commit contains only .huskies/ file moves, no code changes ===\n",
|
||||
);
|
||||
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
||||
return Ok(SquashMergeResult {
|
||||
@@ -258,7 +258,7 @@ pub(crate) fn run_squash_merge(
|
||||
had_conflicts,
|
||||
conflicts_resolved,
|
||||
conflict_details: Some(
|
||||
"Feature branch has no code changes outside .storkit/ — only \
|
||||
"Feature branch has no code changes outside .huskies/ — only \
|
||||
pipeline file moves were found."
|
||||
.to_string(),
|
||||
),
|
||||
@@ -419,10 +419,10 @@ pub(crate) fn run_squash_merge(
|
||||
}
|
||||
|
||||
// Verify HEAD commit has actual code changes (not an empty cherry-pick).
|
||||
// Exclude .storkit/work/ (pipeline file moves) but keep .storkit/project.toml
|
||||
// Exclude .huskies/work/ (pipeline file moves) but keep .huskies/project.toml
|
||||
// and other config files which are legitimate deliverables.
|
||||
let diff_stat = Command::new("git")
|
||||
.args(["diff", "--stat", "HEAD~1..HEAD", "--", ".", ":(exclude).storkit/work"])
|
||||
.args(["diff", "--stat", "HEAD~1..HEAD", "--", ".", ":(exclude).huskies/work"])
|
||||
.current_dir(project_root)
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
@@ -1047,7 +1047,7 @@ after\n";
|
||||
|
||||
// Verify no leftover merge workspace directory.
|
||||
assert!(
|
||||
!repo.join(".storkit/merge_workspace").exists(),
|
||||
!repo.join(".huskies/merge_workspace").exists(),
|
||||
"merge workspace should be cleaned up"
|
||||
);
|
||||
}
|
||||
@@ -1167,7 +1167,7 @@ after\n";
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
let sk_dir = repo.join(".storkit/work/4_merge");
|
||||
let sk_dir = repo.join(".huskies/work/4_merge");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(sk_dir.join("diverge_test.md"), "---\nname: test\n---\n").unwrap();
|
||||
Command::new("git")
|
||||
@@ -1176,7 +1176,7 @@ after\n";
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("git")
|
||||
.args(["commit", "-m", "storkit: queue diverge_test for merge"])
|
||||
.args(["commit", "-m", "huskies: queue diverge_test for merge"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
@@ -1218,7 +1218,7 @@ after\n";
|
||||
"merge-queue branch should be cleaned up, got: {branch_list}"
|
||||
);
|
||||
assert!(
|
||||
!repo.join(".storkit/merge_workspace").exists(),
|
||||
!repo.join(".huskies/merge_workspace").exists(),
|
||||
"merge workspace should be cleaned up"
|
||||
);
|
||||
}
|
||||
@@ -1270,13 +1270,13 @@ after\n";
|
||||
|
||||
// Cleanup should still happen.
|
||||
assert!(
|
||||
!repo.join(".storkit/merge_workspace").exists(),
|
||||
!repo.join(".huskies/merge_workspace").exists(),
|
||||
"merge workspace should be cleaned up"
|
||||
);
|
||||
}
|
||||
|
||||
/// Bug 226: Verifies that `run_squash_merge` fails when the feature branch
|
||||
/// only contains .storkit/ file moves with no real code changes.
|
||||
/// only contains .huskies/ file moves with no real code changes.
|
||||
#[tokio::test]
|
||||
async fn squash_merge_md_only_changes_fails() {
|
||||
use std::fs;
|
||||
@@ -1286,13 +1286,13 @@ after\n";
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
// Create a feature branch that only moves a .storkit/ file.
|
||||
// Create a feature branch that only moves a .huskies/ file.
|
||||
Command::new("git")
|
||||
.args(["checkout", "-b", "feature/story-md_only_test"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
let sk_dir = repo.join(".storkit/work/2_current");
|
||||
let sk_dir = repo.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(sk_dir.join("md_only_test.md"), "---\nname: Test\n---\n").unwrap();
|
||||
Command::new("git")
|
||||
@@ -1313,17 +1313,17 @@ after\n";
|
||||
|
||||
let result = run_squash_merge(repo, "feature/story-md_only_test", "md_only_test").unwrap();
|
||||
|
||||
// The squash merge will commit the .storkit/ file, but should fail because
|
||||
// there are no code changes outside .storkit/.
|
||||
// The squash merge will commit the .huskies/ file, but should fail because
|
||||
// there are no code changes outside .huskies/.
|
||||
assert!(
|
||||
!result.success,
|
||||
"merge with only .storkit/ changes must fail: {}",
|
||||
"merge with only .huskies/ changes must fail: {}",
|
||||
result.output
|
||||
);
|
||||
|
||||
// Cleanup should still happen.
|
||||
assert!(
|
||||
!repo.join(".storkit/merge_workspace").exists(),
|
||||
!repo.join(".huskies/merge_workspace").exists(),
|
||||
"merge workspace should be cleaned up"
|
||||
);
|
||||
}
|
||||
@@ -1444,7 +1444,7 @@ after\n";
|
||||
"merge-queue branch must be cleaned up"
|
||||
);
|
||||
assert!(
|
||||
!repo.join(".storkit/merge_workspace").exists(),
|
||||
!repo.join(".huskies/merge_workspace").exists(),
|
||||
"merge workspace must be cleaned up"
|
||||
);
|
||||
}
|
||||
@@ -1560,7 +1560,7 @@ after\n";
|
||||
|
||||
// Cleanup must still happen.
|
||||
assert!(
|
||||
!repo.join(".storkit/merge_workspace").exists(),
|
||||
!repo.join(".huskies/merge_workspace").exists(),
|
||||
"merge workspace must be cleaned up even on gate failure"
|
||||
);
|
||||
}
|
||||
@@ -1600,7 +1600,7 @@ after\n";
|
||||
.unwrap();
|
||||
|
||||
// Simulate a stale merge workspace left from a previous failed merge.
|
||||
let stale_ws = repo.join(".storkit/merge_workspace");
|
||||
let stale_ws = repo.join(".huskies/merge_workspace");
|
||||
fs::create_dir_all(&stale_ws).unwrap();
|
||||
fs::write(stale_ws.join("leftover.txt"), "stale").unwrap();
|
||||
|
||||
@@ -1620,7 +1620,7 @@ after\n";
|
||||
|
||||
// ── story 216: merge worktree uses project.toml component setup ───────────
|
||||
|
||||
/// When the project has `[[component]]` entries in `.storkit/project.toml`,
|
||||
/// When the project has `[[component]]` entries in `.huskies/project.toml`,
|
||||
/// `run_squash_merge` must run their setup commands in the merge worktree
|
||||
/// before quality gates — matching the behaviour of `create_worktree`.
|
||||
#[cfg(unix)]
|
||||
@@ -1633,9 +1633,9 @@ after\n";
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
// Add a .storkit/project.toml with a component whose setup writes a
|
||||
// Add a .huskies/project.toml with a component whose setup writes a
|
||||
// sentinel file so we can confirm the command ran.
|
||||
let sk_dir = repo.join(".storkit");
|
||||
let sk_dir = repo.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1708,7 +1708,7 @@ after\n";
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
// No .storkit/project.toml — no component setup.
|
||||
// No .huskies/project.toml — no component setup.
|
||||
fs::write(repo.join("file.txt"), "initial").unwrap();
|
||||
Command::new("git")
|
||||
.args(["add", "."])
|
||||
|
||||
@@ -73,7 +73,7 @@ impl AgentPool {
|
||||
on feature branch. Writing merge_failure and blocking."
|
||||
);
|
||||
let story_path = project_root
|
||||
.join(".storkit/work")
|
||||
.join(".huskies/work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{story_id}.md"));
|
||||
let empty_diff_reason = "Feature branch has no code changes — the coder agent \
|
||||
@@ -221,7 +221,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn auto_assign_picks_up_story_queued_in_current() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let current = sk.join("work/2_current");
|
||||
std::fs::create_dir_all(¤t).unwrap();
|
||||
std::fs::write(
|
||||
@@ -260,7 +260,7 @@ mod tests {
|
||||
let root = tmp.path();
|
||||
|
||||
// Create project.toml with a QA agent.
|
||||
let sk = root.join(".storkit");
|
||||
let sk = root.join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
std::fs::write(
|
||||
sk.join("project.toml"),
|
||||
@@ -269,7 +269,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
// Put a spike in 3_qa/ with review_hold: true.
|
||||
let qa_dir = root.join(".storkit/work/3_qa");
|
||||
let qa_dir = root.join(".huskies/work/3_qa");
|
||||
std::fs::create_dir_all(&qa_dir).unwrap();
|
||||
std::fs::write(
|
||||
qa_dir.join("20_spike_test.md"),
|
||||
@@ -298,7 +298,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn auto_assign_ignores_coder_preference_when_story_is_in_qa_stage() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let qa_dir = sk.join("work/3_qa");
|
||||
std::fs::create_dir_all(&qa_dir).unwrap();
|
||||
std::fs::write(
|
||||
@@ -345,7 +345,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn auto_assign_respects_coder_preference_when_story_is_in_current_stage() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let current_dir = sk.join("work/2_current");
|
||||
std::fs::create_dir_all(¤t_dir).unwrap();
|
||||
std::fs::write(
|
||||
@@ -392,7 +392,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn auto_assign_stage_mismatch_with_no_fallback_starts_no_agent() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let qa_dir = sk.join("work/3_qa");
|
||||
std::fs::create_dir_all(&qa_dir).unwrap();
|
||||
// Only a coder agent is configured — no QA agent exists.
|
||||
@@ -431,7 +431,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path().to_path_buf();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
// Two stories waiting in 2_current, one coder agent.
|
||||
fs::create_dir_all(sk_dir.join("work/2_current")).unwrap();
|
||||
fs::write(
|
||||
|
||||
@@ -18,7 +18,7 @@ impl AgentPool {
|
||||
/// (called immediately after) picks up the right next-stage agents.
|
||||
///
|
||||
/// Algorithm:
|
||||
/// 1. List all worktree directories under `{project_root}/.storkit/worktrees/`.
|
||||
/// 1. List all worktree directories under `{project_root}/.huskies/worktrees/`.
|
||||
/// 2. For each worktree, check whether its feature branch has commits ahead of the
|
||||
/// base branch (`master` / `main`).
|
||||
/// 3. If committed work is found AND the story is in `2_current/` or `3_qa/`:
|
||||
@@ -153,7 +153,7 @@ impl AgentPool {
|
||||
.unwrap_or_default()
|
||||
.default_qa_mode();
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/2_current")
|
||||
.join(".huskies/work/2_current")
|
||||
.join(format!("{story_id}.md"));
|
||||
crate::io::story_metadata::resolve_qa_mode(&story_path, default_qa)
|
||||
}
|
||||
@@ -210,7 +210,7 @@ impl AgentPool {
|
||||
});
|
||||
} else {
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/3_qa")
|
||||
.join(".huskies/work/3_qa")
|
||||
.join(format!("{story_id}.md"));
|
||||
if let Err(e) =
|
||||
crate::io::story_metadata::write_review_hold(&story_path)
|
||||
@@ -268,7 +268,7 @@ impl AgentPool {
|
||||
true
|
||||
} else {
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/3_qa")
|
||||
.join(".huskies/work/3_qa")
|
||||
.join(format!("{story_id}.md"));
|
||||
let default_qa = crate::config::ProjectConfig::load(project_root)
|
||||
.unwrap_or_default()
|
||||
@@ -282,7 +282,7 @@ impl AgentPool {
|
||||
|
||||
if needs_human_review {
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/3_qa")
|
||||
.join(".huskies/work/3_qa")
|
||||
.join(format!("{story_id}.md"));
|
||||
if let Err(e) =
|
||||
crate::io::story_metadata::write_review_hold(&story_path)
|
||||
@@ -416,13 +416,13 @@ mod tests {
|
||||
let root = tmp.path();
|
||||
|
||||
// Set up story in 2_current/.
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("60_story_test.md"), "test").unwrap();
|
||||
|
||||
// Create a worktree directory that is a fresh git repo with no commits
|
||||
// ahead of its own base branch (simulates a worktree where no work was done).
|
||||
let wt_dir = root.join(".storkit/worktrees/60_story_test");
|
||||
let wt_dir = root.join(".huskies/worktrees/60_story_test");
|
||||
fs::create_dir_all(&wt_dir).unwrap();
|
||||
init_git_repo(&wt_dir);
|
||||
|
||||
@@ -447,7 +447,7 @@ mod tests {
|
||||
init_git_repo(root);
|
||||
|
||||
// Set up story in 2_current/ and commit it so the project root is clean.
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("61_story_test.md"), "test").unwrap();
|
||||
Command::new("git")
|
||||
@@ -470,7 +470,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
// Create a real git worktree for the story.
|
||||
let wt_dir = root.join(".storkit/worktrees/61_story_test");
|
||||
let wt_dir = root.join(".huskies/worktrees/61_story_test");
|
||||
fs::create_dir_all(wt_dir.parent().unwrap()).unwrap();
|
||||
Command::new("git")
|
||||
.args([
|
||||
@@ -518,7 +518,7 @@ mod tests {
|
||||
// and the story stays in 2_current/. The important assertion is that
|
||||
// reconcile ran without panicking and the story is in a consistent state.
|
||||
let in_current = current.join("61_story_test.md").exists();
|
||||
let in_qa = root.join(".storkit/work/3_qa/61_story_test.md").exists();
|
||||
let in_qa = root.join(".huskies/work/3_qa/61_story_test.md").exists();
|
||||
assert!(
|
||||
in_current || in_qa,
|
||||
"story should be in 2_current/ or 3_qa/ after reconciliation"
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(in crate::agents::pool) fn is_agent_free(
|
||||
}
|
||||
|
||||
pub(super) fn scan_stage_items(project_root: &Path, stage_dir: &str) -> Vec<String> {
|
||||
let dir = project_root.join(".storkit").join("work").join(stage_dir);
|
||||
let dir = project_root.join(".huskies").join("work").join(stage_dir);
|
||||
if !dir.is_dir() {
|
||||
return Vec::new();
|
||||
}
|
||||
@@ -169,7 +169,7 @@ mod tests {
|
||||
fn scan_stage_items_returns_sorted_story_ids() {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit").join("work").join("2_current");
|
||||
let stage_dir = tmp.path().join(".huskies").join("work").join("2_current");
|
||||
fs::create_dir_all(&stage_dir).unwrap();
|
||||
fs::write(stage_dir.join("42_story_foo.md"), "---\nname: foo\n---").unwrap();
|
||||
fs::write(stage_dir.join("10_story_bar.md"), "---\nname: bar\n---").unwrap();
|
||||
|
||||
@@ -13,7 +13,7 @@ pub(super) fn read_story_front_matter_agent(
|
||||
) -> Option<String> {
|
||||
use crate::io::story_metadata::parse_front_matter;
|
||||
let path = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{story_id}.md"));
|
||||
@@ -25,7 +25,7 @@ pub(super) fn read_story_front_matter_agent(
|
||||
pub(super) fn has_review_hold(project_root: &Path, stage_dir: &str, story_id: &str) -> bool {
|
||||
use crate::io::story_metadata::parse_front_matter;
|
||||
let path = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{story_id}.md"));
|
||||
@@ -43,7 +43,7 @@ pub(super) fn has_review_hold(project_root: &Path, stage_dir: &str, story_id: &s
|
||||
pub(super) fn is_story_blocked(project_root: &Path, stage_dir: &str, story_id: &str) -> bool {
|
||||
use crate::io::story_metadata::parse_front_matter;
|
||||
let path = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{story_id}.md"));
|
||||
@@ -61,7 +61,7 @@ pub(super) fn is_story_blocked(project_root: &Path, stage_dir: &str, story_id: &
|
||||
pub(super) fn has_merge_failure(project_root: &Path, stage_dir: &str, story_id: &str) -> bool {
|
||||
use crate::io::story_metadata::parse_front_matter;
|
||||
let path = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{story_id}.md"));
|
||||
@@ -84,7 +84,7 @@ mod tests {
|
||||
#[test]
|
||||
fn has_review_hold_returns_true_when_set() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let qa_dir = tmp.path().join(".storkit/work/3_qa");
|
||||
let qa_dir = tmp.path().join(".huskies/work/3_qa");
|
||||
std::fs::create_dir_all(&qa_dir).unwrap();
|
||||
let spike_path = qa_dir.join("10_spike_research.md");
|
||||
std::fs::write(
|
||||
@@ -98,7 +98,7 @@ mod tests {
|
||||
#[test]
|
||||
fn has_review_hold_returns_false_when_not_set() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let qa_dir = tmp.path().join(".storkit/work/3_qa");
|
||||
let qa_dir = tmp.path().join(".huskies/work/3_qa");
|
||||
std::fs::create_dir_all(&qa_dir).unwrap();
|
||||
let spike_path = qa_dir.join("10_spike_research.md");
|
||||
std::fs::write(&spike_path, "---\nname: Research spike\n---\n# Spike\n").unwrap();
|
||||
|
||||
@@ -87,7 +87,7 @@ impl AgentPool {
|
||||
let front_matter_agent: Option<String> = if agent_name.is_none() {
|
||||
find_active_story_stage(project_root, story_id).and_then(|stage_dir| {
|
||||
let path = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{story_id}.md"));
|
||||
@@ -873,7 +873,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn start_agent_auto_selects_second_coder_when_first_busy() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
std::fs::write(
|
||||
sk.join("project.toml"),
|
||||
@@ -919,7 +919,7 @@ stage = "coder"
|
||||
#[tokio::test]
|
||||
async fn start_agent_returns_busy_when_all_coders_occupied() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
std::fs::write(
|
||||
sk.join("project.toml"),
|
||||
@@ -951,7 +951,7 @@ stage = "coder"
|
||||
#[tokio::test]
|
||||
async fn start_agent_moves_story_to_current_when_coders_busy() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let backlog = sk.join("work/1_backlog");
|
||||
std::fs::create_dir_all(&backlog).unwrap();
|
||||
std::fs::write(
|
||||
@@ -996,7 +996,7 @@ stage = "coder"
|
||||
#[tokio::test]
|
||||
async fn start_agent_story_already_in_current_is_noop() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let current = sk.join("work/2_current");
|
||||
std::fs::create_dir_all(¤t).unwrap();
|
||||
std::fs::write(
|
||||
@@ -1023,7 +1023,7 @@ stage = "coder"
|
||||
#[tokio::test]
|
||||
async fn start_agent_explicit_name_unchanged_when_busy() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
std::fs::write(
|
||||
sk.join("project.toml"),
|
||||
@@ -1062,7 +1062,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(sk_dir.join("project.toml"), "[[agent]]\nname = \"qa\"\n").unwrap();
|
||||
|
||||
@@ -1089,7 +1089,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(sk_dir.join("project.toml"), "[[agent]]\nname = \"qa\"\n").unwrap();
|
||||
|
||||
@@ -1115,7 +1115,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1123,7 +1123,7 @@ stage = "coder"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let upcoming = root.join(".storkit/work/1_backlog");
|
||||
let upcoming = root.join(".huskies/work/1_backlog");
|
||||
fs::create_dir_all(&upcoming).unwrap();
|
||||
fs::write(upcoming.join("50_story_test.md"), "---\nname: Test\n---\n").unwrap();
|
||||
|
||||
@@ -1181,7 +1181,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(sk_dir.join("project.toml"), "[[agent]]\nname = \"qa\"\n").unwrap();
|
||||
|
||||
@@ -1207,7 +1207,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1238,20 +1238,20 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path().to_path_buf();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(sk_dir.join("work/1_backlog")).unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/project.toml"),
|
||||
root.join(".huskies/project.toml"),
|
||||
"[[agent]]\nname = \"coder-1\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/work/1_backlog/86_story_foo.md"),
|
||||
root.join(".huskies/work/1_backlog/86_story_foo.md"),
|
||||
"---\nname: Foo\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/work/1_backlog/130_story_bar.md"),
|
||||
root.join(".huskies/work/1_backlog/130_story_bar.md"),
|
||||
"---\nname: Bar\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1303,7 +1303,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1340,7 +1340,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(&sk_dir).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1372,15 +1372,15 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path().to_path_buf();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(sk_dir.join("work/2_current")).unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/project.toml"),
|
||||
root.join(".huskies/project.toml"),
|
||||
"[[agent]]\nname = \"coder-1\"\n\n[[agent]]\nname = \"coder-2\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/work/2_current/42_story_foo.md"),
|
||||
root.join(".huskies/work/2_current/42_story_foo.md"),
|
||||
"---\nname: Foo\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1426,15 +1426,15 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(sk_dir.join("work/1_backlog")).unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/project.toml"),
|
||||
root.join(".huskies/project.toml"),
|
||||
"[[agent]]\nname = \"coder-1\"\n\n[[agent]]\nname = \"coder-2\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/work/1_backlog/99_story_baz.md"),
|
||||
root.join(".huskies/work/1_backlog/99_story_baz.md"),
|
||||
"---\nname: Baz\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1464,7 +1464,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(sk_dir.join("work/2_current")).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1501,7 +1501,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(sk_dir.join("work/3_qa")).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1538,7 +1538,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(sk_dir.join("work/4_merge")).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1575,7 +1575,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(sk_dir.join("work/2_current")).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1611,7 +1611,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk_dir = root.join(".storkit");
|
||||
let sk_dir = root.join(".huskies");
|
||||
fs::create_dir_all(sk_dir.join("work/4_merge")).unwrap();
|
||||
fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1674,7 +1674,7 @@ stage = "coder"
|
||||
#[tokio::test]
|
||||
async fn start_agent_honours_front_matter_agent_when_idle() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let backlog = sk.join("work/1_backlog");
|
||||
std::fs::create_dir_all(&backlog).unwrap();
|
||||
std::fs::write(
|
||||
@@ -1730,7 +1730,7 @@ stage = "coder"
|
||||
#[tokio::test]
|
||||
async fn start_agent_returns_error_when_front_matter_agent_busy() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let backlog = sk.join("work/1_backlog");
|
||||
std::fs::create_dir_all(&backlog).unwrap();
|
||||
std::fs::write(
|
||||
@@ -1781,7 +1781,7 @@ stage = "coder"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("60_story_cleanup.md"), "test").unwrap();
|
||||
|
||||
@@ -1804,7 +1804,7 @@ stage = "coder"
|
||||
assert_eq!(remaining[0].story_id, "61_story_other");
|
||||
|
||||
assert!(
|
||||
root.join(".storkit/work/5_done/60_story_cleanup.md")
|
||||
root.join(".huskies/work/5_done/60_story_cleanup.md")
|
||||
.exists()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ impl AgentPool {
|
||||
let default_qa = config.default_qa_mode();
|
||||
// Story is in 2_current/ when a coder completes.
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/2_current")
|
||||
.join(".huskies/work/2_current")
|
||||
.join(format!("{story_id}.md"));
|
||||
crate::io::story_metadata::resolve_qa_mode(&story_path, default_qa)
|
||||
}
|
||||
@@ -104,7 +104,7 @@ impl AgentPool {
|
||||
if let Err(e) = crate::agents::lifecycle::move_story_to_qa(&project_root, story_id) {
|
||||
slog_error!("[pipeline] Failed to move '{story_id}' to 3_qa/: {e}");
|
||||
} else {
|
||||
let qa_dir = project_root.join(".storkit/work/3_qa");
|
||||
let qa_dir = project_root.join(".huskies/work/3_qa");
|
||||
let story_path = qa_dir.join(format!("{story_id}.md"));
|
||||
if let Err(e) =
|
||||
crate::io::story_metadata::write_review_hold(&story_path)
|
||||
@@ -119,7 +119,7 @@ impl AgentPool {
|
||||
} else {
|
||||
// Increment retry count and check if blocked.
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/2_current")
|
||||
.join(".huskies/work/2_current")
|
||||
.join(format!("{story_id}.md"));
|
||||
if let Some(reason) = should_block_story(&story_path, config.max_retries, story_id, "coder") {
|
||||
// Story has exceeded retry limit — do not restart.
|
||||
@@ -174,7 +174,7 @@ impl AgentPool {
|
||||
if item_type == "spike" {
|
||||
true // Spikes always need human review.
|
||||
} else {
|
||||
let qa_dir = project_root.join(".storkit/work/3_qa");
|
||||
let qa_dir = project_root.join(".huskies/work/3_qa");
|
||||
let story_path = qa_dir.join(format!("{story_id}.md"));
|
||||
let default_qa = config.default_qa_mode();
|
||||
matches!(
|
||||
@@ -186,7 +186,7 @@ impl AgentPool {
|
||||
|
||||
if needs_human_review {
|
||||
// Hold in 3_qa/ for human review.
|
||||
let qa_dir = project_root.join(".storkit/work/3_qa");
|
||||
let qa_dir = project_root.join(".huskies/work/3_qa");
|
||||
let story_path = qa_dir.join(format!("{story_id}.md"));
|
||||
if let Err(e) =
|
||||
crate::io::story_metadata::write_review_hold(&story_path)
|
||||
@@ -222,7 +222,7 @@ impl AgentPool {
|
||||
}
|
||||
} else {
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/3_qa")
|
||||
.join(".huskies/work/3_qa")
|
||||
.join(format!("{story_id}.md"));
|
||||
if let Some(reason) = should_block_story(&story_path, config.max_retries, story_id, "qa-coverage") {
|
||||
// Story has exceeded retry limit — do not restart.
|
||||
@@ -250,7 +250,7 @@ impl AgentPool {
|
||||
}
|
||||
} else {
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/3_qa")
|
||||
.join(".huskies/work/3_qa")
|
||||
.join(format!("{story_id}.md"));
|
||||
if let Some(reason) = should_block_story(&story_path, config.max_retries, story_id, "qa") {
|
||||
// Story has exceeded retry limit — do not restart.
|
||||
@@ -330,7 +330,7 @@ impl AgentPool {
|
||||
);
|
||||
} else {
|
||||
let story_path = project_root
|
||||
.join(".storkit/work/4_merge")
|
||||
.join(".huskies/work/4_merge")
|
||||
.join(format!("{story_id}.md"));
|
||||
if let Some(reason) = should_block_story(&story_path, config.max_retries, story_id, "mergemaster") {
|
||||
// Story has exceeded retry limit — do not restart.
|
||||
@@ -469,7 +469,7 @@ mod tests {
|
||||
let root = tmp.path();
|
||||
|
||||
// Set up story in 2_current/ (no qa frontmatter → uses project default "server")
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("50_story_test.md"), "test").unwrap();
|
||||
|
||||
@@ -490,7 +490,7 @@ mod tests {
|
||||
|
||||
// With default qa: server, story skips QA and goes straight to 4_merge/
|
||||
assert!(
|
||||
root.join(".storkit/work/4_merge/50_story_test.md")
|
||||
root.join(".huskies/work/4_merge/50_story_test.md")
|
||||
.exists(),
|
||||
"story should be in 4_merge/"
|
||||
);
|
||||
@@ -507,7 +507,7 @@ mod tests {
|
||||
let root = tmp.path();
|
||||
|
||||
// Set up story in 2_current/ with qa: agent frontmatter
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(
|
||||
current.join("50_story_test.md"),
|
||||
@@ -532,7 +532,7 @@ mod tests {
|
||||
|
||||
// With qa: agent, story should move to 3_qa/
|
||||
assert!(
|
||||
root.join(".storkit/work/3_qa/50_story_test.md").exists(),
|
||||
root.join(".huskies/work/3_qa/50_story_test.md").exists(),
|
||||
"story should be in 3_qa/"
|
||||
);
|
||||
assert!(
|
||||
@@ -548,7 +548,7 @@ mod tests {
|
||||
let root = tmp.path();
|
||||
|
||||
// Set up story in 3_qa/
|
||||
let qa_dir = root.join(".storkit/work/3_qa");
|
||||
let qa_dir = root.join(".huskies/work/3_qa");
|
||||
fs::create_dir_all(&qa_dir).unwrap();
|
||||
// qa: server so the story skips human review and goes straight to merge.
|
||||
fs::write(
|
||||
@@ -574,7 +574,7 @@ mod tests {
|
||||
|
||||
// Story should have moved to 4_merge/
|
||||
assert!(
|
||||
root.join(".storkit/work/4_merge/51_story_test.md")
|
||||
root.join(".huskies/work/4_merge/51_story_test.md")
|
||||
.exists(),
|
||||
"story should be in 4_merge/"
|
||||
);
|
||||
@@ -590,7 +590,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("52_story_test.md"), "test").unwrap();
|
||||
|
||||
@@ -624,18 +624,18 @@ mod tests {
|
||||
let root = tmp.path();
|
||||
|
||||
// Set up story in 2_current/
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("173_story_test.md"), "test").unwrap();
|
||||
// Ensure 3_qa/ exists for the move target
|
||||
fs::create_dir_all(root.join(".storkit/work/3_qa")).unwrap();
|
||||
fs::create_dir_all(root.join(".huskies/work/3_qa")).unwrap();
|
||||
// Ensure 1_backlog/ exists (start_agent calls move_story_to_current)
|
||||
fs::create_dir_all(root.join(".storkit/work/1_backlog")).unwrap();
|
||||
fs::create_dir_all(root.join(".huskies/work/1_backlog")).unwrap();
|
||||
|
||||
// Write a project.toml with a qa agent so start_agent can resolve it.
|
||||
fs::create_dir_all(root.join(".storkit")).unwrap();
|
||||
fs::create_dir_all(root.join(".huskies")).unwrap();
|
||||
fs::write(
|
||||
root.join(".storkit/project.toml"),
|
||||
root.join(".huskies/project.toml"),
|
||||
r#"
|
||||
default_qa = "agent"
|
||||
|
||||
@@ -703,7 +703,7 @@ stage = "qa"
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
|
||||
let sk = root.join(".storkit");
|
||||
let sk = root.join(".huskies");
|
||||
let qa_dir = sk.join("work/3_qa");
|
||||
fs::create_dir_all(&qa_dir).unwrap();
|
||||
|
||||
|
||||
@@ -563,7 +563,7 @@ mod tests {
|
||||
fs::set_permissions(&script_test, perms).unwrap();
|
||||
|
||||
// Story in 4_merge/ — must NOT be moved to 5_done/.
|
||||
let merge_dir = root.join(".storkit/work/4_merge");
|
||||
let merge_dir = root.join(".huskies/work/4_merge");
|
||||
fs::create_dir_all(&merge_dir).unwrap();
|
||||
let story_path = merge_dir.join("99_story_merge445.md");
|
||||
fs::write(&story_path, "---\nname: Merge 445 Test\n---\n").unwrap();
|
||||
@@ -590,7 +590,7 @@ mod tests {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
|
||||
|
||||
// Story must remain in 4_merge/ — not moved to 5_done/.
|
||||
let done_path = root.join(".storkit/work/5_done/99_story_merge445.md");
|
||||
let done_path = root.join(".huskies/work/5_done/99_story_merge445.md");
|
||||
assert!(
|
||||
!done_path.exists(),
|
||||
"Story must NOT be moved to 5_done/ when run_server_owned_completion \
|
||||
|
||||
@@ -289,7 +289,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
// Create the story file in 4_merge/ so we can test archival
|
||||
let merge_dir = repo.join(".storkit/work/4_merge");
|
||||
let merge_dir = repo.join(".huskies/work/4_merge");
|
||||
fs::create_dir_all(&merge_dir).unwrap();
|
||||
let story_file = merge_dir.join("23_test.md");
|
||||
fs::write(&story_file, "---\nname: Test\n---\n").unwrap();
|
||||
@@ -317,7 +317,7 @@ mod tests {
|
||||
"report should be coherent: {report:?}"
|
||||
);
|
||||
if report.story_archived {
|
||||
let done = repo.join(".storkit/work/5_done/23_test.md");
|
||||
let done = repo.join(".huskies/work/5_done/23_test.md");
|
||||
assert!(done.exists(), "done file should exist");
|
||||
}
|
||||
}
|
||||
@@ -502,7 +502,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
// Create story file in 4_merge.
|
||||
let merge_dir = repo.join(".storkit/work/4_merge");
|
||||
let merge_dir = repo.join(".huskies/work/4_merge");
|
||||
fs::create_dir_all(&merge_dir).unwrap();
|
||||
fs::write(merge_dir.join("42_story_foo.md"), "---\nname: Test\n---\n").unwrap();
|
||||
Command::new("git")
|
||||
|
||||
@@ -26,7 +26,7 @@ pub(super) fn find_active_story_stage(project_root: &Path, story_id: &str) -> Op
|
||||
const STAGES: [&str; 3] = ["2_current", "3_qa", "4_merge"];
|
||||
for stage in &STAGES {
|
||||
let path = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage)
|
||||
.join(format!("{story_id}.md"));
|
||||
@@ -46,7 +46,7 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("10_story_test.md"), "test").unwrap();
|
||||
|
||||
@@ -61,7 +61,7 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let qa = root.join(".storkit/work/3_qa");
|
||||
let qa = root.join(".huskies/work/3_qa");
|
||||
fs::create_dir_all(&qa).unwrap();
|
||||
fs::write(qa.join("11_story_test.md"), "test").unwrap();
|
||||
|
||||
@@ -73,7 +73,7 @@ mod tests {
|
||||
use std::fs;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let merge = root.join(".storkit/work/4_merge");
|
||||
let merge = root.join(".huskies/work/4_merge");
|
||||
fs::create_dir_all(&merge).unwrap();
|
||||
fs::write(merge.join("12_story_test.md"), "test").unwrap();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use super::{AgentRuntime, RuntimeContext, RuntimeResult, RuntimeStatus};
|
||||
/// `generateContent` REST API.
|
||||
///
|
||||
/// The runtime:
|
||||
/// 1. Fetches MCP tool definitions from storkit's MCP server.
|
||||
/// 1. Fetches MCP tool definitions from huskies' MCP server.
|
||||
/// 2. Converts them to Gemini function-calling format.
|
||||
/// 3. Sends the agent prompt + tools to the Gemini API.
|
||||
/// 4. Executes any requested function calls via MCP `tools/call`.
|
||||
@@ -401,7 +401,7 @@ fn build_generate_content_request(
|
||||
body
|
||||
}
|
||||
|
||||
/// Fetch MCP tool definitions from storkit's MCP server and convert
|
||||
/// Fetch MCP tool definitions from huskies' MCP server and convert
|
||||
/// them to Gemini function declaration format.
|
||||
async fn fetch_and_convert_mcp_tools(
|
||||
client: &Client,
|
||||
@@ -522,7 +522,7 @@ fn clean_schema_properties(properties: &Value) -> Value {
|
||||
Value::Object(cleaned)
|
||||
}
|
||||
|
||||
/// Call an MCP tool via storkit's MCP server.
|
||||
/// Call an MCP tool via huskies' MCP server.
|
||||
async fn call_mcp_tool(
|
||||
client: &Client,
|
||||
mcp_base: &str,
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct RuntimeContext {
|
||||
pub prompt: String,
|
||||
pub cwd: String,
|
||||
pub inactivity_timeout_secs: u64,
|
||||
/// Port of the storkit MCP server, used by API-based runtimes (Gemini, OpenAI)
|
||||
/// Port of the huskies MCP server, used by API-based runtimes (Gemini, OpenAI)
|
||||
/// to call back for tool execution.
|
||||
pub mcp_port: u16,
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use super::{AgentRuntime, RuntimeContext, RuntimeResult, RuntimeStatus};
|
||||
/// the OpenAI Chat Completions API.
|
||||
///
|
||||
/// The runtime:
|
||||
/// 1. Fetches MCP tool definitions from storkit's MCP server.
|
||||
/// 1. Fetches MCP tool definitions from huskies' MCP server.
|
||||
/// 2. Converts them to OpenAI function-calling format.
|
||||
/// 3. Sends the agent prompt + tools to the Chat Completions API.
|
||||
/// 4. Executes any requested tool calls via MCP `tools/call`.
|
||||
@@ -311,7 +311,7 @@ fn build_system_text(ctx: &RuntimeContext) -> String {
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch MCP tool definitions from storkit's MCP server and convert
|
||||
/// Fetch MCP tool definitions from huskies' MCP server and convert
|
||||
/// them to OpenAI function-calling format.
|
||||
async fn fetch_and_convert_mcp_tools(
|
||||
client: &Client,
|
||||
@@ -433,7 +433,7 @@ fn clean_schema_properties(properties: &Value) -> Value {
|
||||
Value::Object(cleaned)
|
||||
}
|
||||
|
||||
/// Call an MCP tool via storkit's MCP server.
|
||||
/// Call an MCP tool via huskies' MCP server.
|
||||
async fn call_mcp_tool(
|
||||
client: &Client,
|
||||
mcp_base: &str,
|
||||
|
||||
@@ -20,7 +20,7 @@ pub struct TokenUsageRecord {
|
||||
/// Append a token usage record to the persistent JSONL file.
|
||||
///
|
||||
/// Each line is a self-contained JSON object, making appends atomic and
|
||||
/// reads simple. The file lives at `.storkit/token_usage.jsonl`.
|
||||
/// reads simple. The file lives at `.huskies/token_usage.jsonl`.
|
||||
pub fn append_record(project_root: &Path, record: &TokenUsageRecord) -> Result<(), String> {
|
||||
let path = token_usage_path(project_root);
|
||||
if let Some(parent) = path.parent() {
|
||||
@@ -87,7 +87,7 @@ pub fn build_record(
|
||||
}
|
||||
|
||||
fn token_usage_path(project_root: &Path) -> std::path::PathBuf {
|
||||
project_root.join(".storkit").join("token_usage.jsonl")
|
||||
project_root.join(".huskies").join("token_usage.jsonl")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -147,7 +147,7 @@ mod tests {
|
||||
fn malformed_lines_are_skipped() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path();
|
||||
let path = root.join(".storkit").join("token_usage.jsonl");
|
||||
let path = root.join(".huskies").join("token_usage.jsonl");
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
fs::write(&path, "not json\n{\"bad\":true}\n").unwrap();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user