Spike 61: filesystem watcher and UI simplification
Add notify-based filesystem watcher for .story_kit/work/ that auto-commits changes with deterministic messages and broadcasts events over WebSocket. Push full pipeline state (Upcoming, Current, QA, To Merge) to frontend on connect and after every watcher event. Strip dead UI: remove ReviewPanel, GatePanel, TodoPanel, UpcomingPanel and all associated REST polling. Replace with 4 generic StagePanel components driven by WebSocket. Simplify AgentPanel to roster-only. Delete all 11 workflow HTTP endpoints and 16 request/response types from the server. Clean dead code from workflow module. MCP tools call Rust functions directly and need none of the HTTP layer. Net: ~4,100 lines deleted, ~400 added. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -753,42 +753,6 @@ pub struct MergeReport {
|
||||
pub story_archived: bool,
|
||||
}
|
||||
|
||||
/// Stage one or more file paths and create a deterministic commit in the given git root.
|
||||
///
|
||||
/// Pass deleted paths too so git stages their removal alongside any new files.
|
||||
pub fn git_stage_and_commit(
|
||||
git_root: &Path,
|
||||
paths: &[&Path],
|
||||
message: &str,
|
||||
) -> Result<(), String> {
|
||||
let mut add_cmd = Command::new("git");
|
||||
add_cmd.arg("add").current_dir(git_root);
|
||||
for path in paths {
|
||||
add_cmd.arg(path.to_string_lossy().as_ref());
|
||||
}
|
||||
let output = add_cmd.output().map_err(|e| format!("git add: {e}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
"git add failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
let output = Command::new("git")
|
||||
.args(["commit", "-m", message])
|
||||
.current_dir(git_root)
|
||||
.output()
|
||||
.map_err(|e| format!("git commit: {e}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
"git commit failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine the work item type from its ID (new naming: `{N}_{type}_{slug}`).
|
||||
/// Returns "bug", "spike", or "story".
|
||||
#[allow(dead_code)]
|
||||
@@ -850,12 +814,7 @@ pub fn move_story_to_current(project_root: &Path, story_id: &str) -> Result<(),
|
||||
source_dir.display()
|
||||
);
|
||||
|
||||
let msg = format!("story-kit: start {story_id}");
|
||||
git_stage_and_commit(
|
||||
project_root,
|
||||
&[current_path.as_path(), source_path.as_path()],
|
||||
&msg,
|
||||
)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move a story from `work/2_current/` to `work/5_archived/` and auto-commit.
|
||||
@@ -899,12 +858,7 @@ pub fn move_story_to_archived(project_root: &Path, story_id: &str) -> Result<(),
|
||||
};
|
||||
eprintln!("[lifecycle] Moved story '{story_id}' from {from_dir} to work/5_archived/");
|
||||
|
||||
let msg = format!("story-kit: accept story {story_id}");
|
||||
git_stage_and_commit(
|
||||
project_root,
|
||||
&[archived_path.as_path(), source_path.as_path()],
|
||||
&msg,
|
||||
)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move a story/bug from `work/2_current/` to `work/4_merge/` and auto-commit.
|
||||
@@ -935,12 +889,7 @@ pub fn move_story_to_merge(project_root: &Path, story_id: &str) -> Result<(), St
|
||||
|
||||
eprintln!("[lifecycle] Moved '{story_id}' from work/2_current/ to work/4_merge/");
|
||||
|
||||
let msg = format!("story-kit: queue {story_id} for merge");
|
||||
git_stage_and_commit(
|
||||
project_root,
|
||||
&[merge_path.as_path(), current_path.as_path()],
|
||||
&msg,
|
||||
)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move a story/bug from `work/2_current/` to `work/3_qa/` and auto-commit.
|
||||
@@ -971,12 +920,7 @@ pub fn move_story_to_qa(project_root: &Path, story_id: &str) -> Result<(), Strin
|
||||
|
||||
eprintln!("[lifecycle] Moved '{story_id}' from work/2_current/ to work/3_qa/");
|
||||
|
||||
let msg = format!("story-kit: queue {story_id} for QA");
|
||||
git_stage_and_commit(
|
||||
project_root,
|
||||
&[qa_path.as_path(), current_path.as_path()],
|
||||
&msg,
|
||||
)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move a bug from `work/2_current/` or `work/1_upcoming/` to `work/5_archived/` and auto-commit.
|
||||
@@ -1015,12 +959,7 @@ pub fn close_bug_to_archive(project_root: &Path, bug_id: &str) -> Result<(), Str
|
||||
"[lifecycle] Closed bug '{bug_id}' → work/5_archived/"
|
||||
);
|
||||
|
||||
let msg = format!("story-kit: close bug {bug_id}");
|
||||
git_stage_and_commit(
|
||||
project_root,
|
||||
&[archive_path.as_path(), source_path.as_path()],
|
||||
&msg,
|
||||
)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Acceptance-gate helpers ───────────────────────────────────────────────────
|
||||
@@ -1634,6 +1573,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// ── move_story_to_current tests ────────────────────────────────────────────
|
||||
// No git repo needed: the watcher handles commits asynchronously.
|
||||
|
||||
fn init_git_repo(repo: &std::path::Path) {
|
||||
Command::new("git")
|
||||
@@ -1659,179 +1599,86 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_story_to_current_moves_file_and_commits() {
|
||||
fn move_story_to_current_moves_file() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let upcoming = repo.join(".story_kit/work/1_upcoming");
|
||||
let current_dir = repo.join(".story_kit/work/2_current");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let upcoming = root.join(".story_kit/work/1_upcoming");
|
||||
let current = root.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(&upcoming).unwrap();
|
||||
fs::create_dir_all(¤t_dir).unwrap();
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(upcoming.join("10_story_foo.md"), "test").unwrap();
|
||||
|
||||
let story_file = upcoming.join("10_story_my_story.md");
|
||||
fs::write(&story_file, "---\nname: Test\ntest_plan: pending\n---\n").unwrap();
|
||||
move_story_to_current(root, "10_story_foo").unwrap();
|
||||
|
||||
Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("git")
|
||||
.args(["commit", "-m", "add story"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
move_story_to_current(repo, "10_story_my_story").unwrap();
|
||||
|
||||
assert!(!story_file.exists(), "upcoming file should be gone");
|
||||
assert!(
|
||||
current_dir.join("10_story_my_story.md").exists(),
|
||||
"current/ file should exist"
|
||||
);
|
||||
assert!(!upcoming.join("10_story_foo.md").exists());
|
||||
assert!(current.join("10_story_foo.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_story_to_current_is_idempotent_when_already_current() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("11_story_foo.md"), "test").unwrap();
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let current_dir = repo.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(¤t_dir).unwrap();
|
||||
fs::write(
|
||||
current_dir.join("11_story_my_story.md"),
|
||||
"---\nname: Test\ntest_plan: pending\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Should succeed without error even though there's nothing to move
|
||||
move_story_to_current(repo, "11_story_my_story").unwrap();
|
||||
|
||||
assert!(current_dir.join("11_story_my_story.md").exists());
|
||||
move_story_to_current(root, "11_story_foo").unwrap();
|
||||
assert!(current.join("11_story_foo.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_story_to_current_noop_when_not_in_upcoming() {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
// Story doesn't exist anywhere — should return Ok (lenient)
|
||||
let result = move_story_to_current(repo, "99_missing");
|
||||
assert!(result.is_ok(), "should return Ok when story is not found");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
assert!(move_story_to_current(tmp.path(), "99_missing").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_bug_to_current_moves_from_bugs_dir() {
|
||||
fn move_bug_to_current_moves_from_upcoming() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let upcoming = root.join(".story_kit/work/1_upcoming");
|
||||
let current = root.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(&upcoming).unwrap();
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(upcoming.join("1_bug_test.md"), "# Bug 1\n").unwrap();
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
move_story_to_current(root, "1_bug_test").unwrap();
|
||||
|
||||
let upcoming_dir = repo.join(".story_kit/work/1_upcoming");
|
||||
let current_dir = repo.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(&upcoming_dir).unwrap();
|
||||
fs::create_dir_all(¤t_dir).unwrap();
|
||||
|
||||
let bug_file = upcoming_dir.join("1_bug_test.md");
|
||||
fs::write(&bug_file, "# Bug 1\n").unwrap();
|
||||
|
||||
Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("git")
|
||||
.args(["commit", "-m", "add bug"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
move_story_to_current(repo, "1_bug_test").unwrap();
|
||||
|
||||
assert!(!bug_file.exists(), "upcoming/ file should be gone");
|
||||
assert!(
|
||||
current_dir.join("1_bug_test.md").exists(),
|
||||
"current/ file should exist"
|
||||
);
|
||||
assert!(!upcoming.join("1_bug_test.md").exists());
|
||||
assert!(current.join("1_bug_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_bug_moves_from_current_to_archive() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("2_bug_test.md"), "# Bug 2\n").unwrap();
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
close_bug_to_archive(root, "2_bug_test").unwrap();
|
||||
|
||||
let current_dir = repo.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(¤t_dir).unwrap();
|
||||
|
||||
let bug_in_current = current_dir.join("2_bug_test.md");
|
||||
fs::write(&bug_in_current, "# Bug 2\n").unwrap();
|
||||
|
||||
Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("git")
|
||||
.args(["commit", "-m", "add bug to current"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
close_bug_to_archive(repo, "2_bug_test").unwrap();
|
||||
|
||||
let archive_path = repo.join(".story_kit/work/5_archived/2_bug_test.md");
|
||||
assert!(!bug_in_current.exists(), "current/ file should be gone");
|
||||
assert!(archive_path.exists(), "archive file should exist");
|
||||
assert!(!current.join("2_bug_test.md").exists());
|
||||
assert!(root.join(".story_kit/work/5_archived/2_bug_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_bug_moves_from_bugs_dir_when_not_started() {
|
||||
fn close_bug_moves_from_upcoming_when_not_started() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let upcoming = root.join(".story_kit/work/1_upcoming");
|
||||
fs::create_dir_all(&upcoming).unwrap();
|
||||
fs::write(upcoming.join("3_bug_test.md"), "# Bug 3\n").unwrap();
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
close_bug_to_archive(root, "3_bug_test").unwrap();
|
||||
|
||||
let upcoming_dir = repo.join(".story_kit/work/1_upcoming");
|
||||
fs::create_dir_all(&upcoming_dir).unwrap();
|
||||
|
||||
let bug_file = upcoming_dir.join("3_bug_test.md");
|
||||
fs::write(&bug_file, "# Bug 3\n").unwrap();
|
||||
|
||||
Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("git")
|
||||
.args(["commit", "-m", "add bug"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
close_bug_to_archive(repo, "3_bug_test").unwrap();
|
||||
|
||||
let archive_path = repo.join(".story_kit/work/5_archived/3_bug_test.md");
|
||||
assert!(!bug_file.exists(), "upcoming/ file should be gone");
|
||||
assert!(archive_path.exists(), "archive file should exist");
|
||||
assert!(!upcoming.join("3_bug_test.md").exists());
|
||||
assert!(root.join(".story_kit/work/5_archived/3_bug_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1842,216 +1689,102 @@ mod tests {
|
||||
assert_eq!(item_type_from_id("1_story_simple"), "story");
|
||||
}
|
||||
|
||||
// ── git_stage_and_commit tests ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn git_stage_and_commit_creates_commit() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let file = repo.join("hello.txt");
|
||||
fs::write(&file, "hello").unwrap();
|
||||
|
||||
git_stage_and_commit(repo, &[file.as_path()], "story-kit: test commit").unwrap();
|
||||
|
||||
// Verify the commit exists
|
||||
let output = Command::new("git")
|
||||
.args(["log", "--oneline", "-1"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
let log = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(log.contains("story-kit: test commit"), "commit should appear in log: {log}");
|
||||
}
|
||||
|
||||
// ── move_story_to_merge tests ──────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn move_story_to_merge_moves_file_and_commits() {
|
||||
fn move_story_to_merge_moves_file() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("20_story_foo.md"), "test").unwrap();
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
move_story_to_merge(root, "20_story_foo").unwrap();
|
||||
|
||||
let current_dir = repo.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(¤t_dir).unwrap();
|
||||
let story_file = current_dir.join("20_story_my_story.md");
|
||||
fs::write(&story_file, "---\nname: Test\ntest_plan: approved\n---\n").unwrap();
|
||||
|
||||
Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("git")
|
||||
.args(["commit", "-m", "add story"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
move_story_to_merge(repo, "20_story_my_story").unwrap();
|
||||
|
||||
let merge_path = repo.join(".story_kit/work/4_merge/20_story_my_story.md");
|
||||
assert!(!story_file.exists(), "2_current file should be gone");
|
||||
assert!(merge_path.exists(), "4_merge file should exist");
|
||||
assert!(!current.join("20_story_foo.md").exists());
|
||||
assert!(root.join(".story_kit/work/4_merge/20_story_foo.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_story_to_merge_idempotent_when_already_in_merge() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let merge_dir = repo.join(".story_kit/work/4_merge");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let merge_dir = root.join(".story_kit/work/4_merge");
|
||||
fs::create_dir_all(&merge_dir).unwrap();
|
||||
fs::write(
|
||||
merge_dir.join("21_story_test.md"),
|
||||
"---\nname: Test\ntest_plan: approved\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(merge_dir.join("21_story_test.md"), "test").unwrap();
|
||||
|
||||
// Should succeed without error even though there's nothing to move
|
||||
move_story_to_merge(repo, "21_story_test").unwrap();
|
||||
move_story_to_merge(root, "21_story_test").unwrap();
|
||||
assert!(merge_dir.join("21_story_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_story_to_merge_errors_when_not_in_current() {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let result = move_story_to_merge(repo, "99_nonexistent");
|
||||
assert!(result.is_err());
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let result = move_story_to_merge(tmp.path(), "99_nonexistent");
|
||||
assert!(result.unwrap_err().contains("not found in work/2_current/"));
|
||||
}
|
||||
|
||||
// ── move_story_to_qa tests ────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn move_story_to_qa_moves_file_and_commits() {
|
||||
fn move_story_to_qa_moves_file() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let current = root.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(current.join("30_story_qa.md"), "test").unwrap();
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
move_story_to_qa(root, "30_story_qa").unwrap();
|
||||
|
||||
let current_dir = repo.join(".story_kit/work/2_current");
|
||||
fs::create_dir_all(¤t_dir).unwrap();
|
||||
let story_file = current_dir.join("30_story_qa_test.md");
|
||||
fs::write(&story_file, "---\nname: QA Test\ntest_plan: approved\n---\n").unwrap();
|
||||
|
||||
Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("git")
|
||||
.args(["commit", "-m", "add story"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
move_story_to_qa(repo, "30_story_qa_test").unwrap();
|
||||
|
||||
let qa_path = repo.join(".story_kit/work/3_qa/30_story_qa_test.md");
|
||||
assert!(!story_file.exists(), "2_current file should be gone");
|
||||
assert!(qa_path.exists(), "3_qa file should exist");
|
||||
assert!(!current.join("30_story_qa.md").exists());
|
||||
assert!(root.join(".story_kit/work/3_qa/30_story_qa.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_story_to_qa_idempotent_when_already_in_qa() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let qa_dir = repo.join(".story_kit/work/3_qa");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let qa_dir = root.join(".story_kit/work/3_qa");
|
||||
fs::create_dir_all(&qa_dir).unwrap();
|
||||
fs::write(
|
||||
qa_dir.join("31_story_test.md"),
|
||||
"---\nname: Test\ntest_plan: approved\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(qa_dir.join("31_story_test.md"), "test").unwrap();
|
||||
|
||||
// Should succeed without error even though there's nothing to move
|
||||
move_story_to_qa(repo, "31_story_test").unwrap();
|
||||
move_story_to_qa(root, "31_story_test").unwrap();
|
||||
assert!(qa_dir.join("31_story_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_story_to_qa_errors_when_not_in_current() {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let result = move_story_to_qa(repo, "99_nonexistent");
|
||||
assert!(result.is_err());
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let result = move_story_to_qa(tmp.path(), "99_nonexistent");
|
||||
assert!(result.unwrap_err().contains("not found in work/2_current/"));
|
||||
}
|
||||
|
||||
// ── move_story_to_archived with 4_merge source ────────────────────────────
|
||||
// ── move_story_to_archived tests ──────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn move_story_to_archived_finds_in_merge_dir() {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let merge_dir = repo.join(".story_kit/work/4_merge");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let merge_dir = root.join(".story_kit/work/4_merge");
|
||||
fs::create_dir_all(&merge_dir).unwrap();
|
||||
let story_file = merge_dir.join("22_story_test.md");
|
||||
fs::write(&story_file, "---\nname: Test\ntest_plan: approved\n---\n").unwrap();
|
||||
fs::write(merge_dir.join("22_story_test.md"), "test").unwrap();
|
||||
|
||||
Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("git")
|
||||
.args(["commit", "-m", "add story in merge"])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.unwrap();
|
||||
move_story_to_archived(root, "22_story_test").unwrap();
|
||||
|
||||
move_story_to_archived(repo, "22_story_test").unwrap();
|
||||
|
||||
let archived = repo.join(".story_kit/work/5_archived/22_story_test.md");
|
||||
assert!(!story_file.exists(), "4_merge file should be gone");
|
||||
assert!(archived.exists(), "5_archived file should exist");
|
||||
assert!(!merge_dir.join("22_story_test.md").exists());
|
||||
assert!(root.join(".story_kit/work/5_archived/22_story_test.md").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_story_to_archived_error_when_not_in_current_or_merge() {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo = tmp.path();
|
||||
init_git_repo(repo);
|
||||
|
||||
let result = move_story_to_archived(repo, "99_nonexistent");
|
||||
assert!(result.is_err());
|
||||
let msg = result.unwrap_err();
|
||||
assert!(msg.contains("4_merge"), "error should mention 4_merge: {msg}");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let result = move_story_to_archived(tmp.path(), "99_nonexistent");
|
||||
assert!(result.unwrap_err().contains("4_merge"));
|
||||
}
|
||||
|
||||
// ── merge_agent_work tests ────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user