story-kit: merge 287_story_rename_upcoming_pipeline_stage_to_backlog

This commit is contained in:
Dave
2026-03-18 14:31:12 +00:00
parent 967ebd7a84
commit df6f792214
26 changed files with 250 additions and 228 deletions

View File

@@ -16,9 +16,9 @@ pub(super) fn item_type_from_id(item_id: &str) -> &'static str {
}
}
/// Return the source directory path for a work item (always work/1_upcoming/).
/// 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(".story_kit").join("work").join("1_upcoming")
project_root.join(".story_kit").join("work").join("1_backlog")
}
/// Return the done directory path for a work item (always work/5_done/).
@@ -26,10 +26,10 @@ fn item_archive_dir(project_root: &Path, _item_id: &str) -> PathBuf {
project_root.join(".story_kit").join("work").join("5_done")
}
/// Move a work item (story, bug, or spike) from `work/1_upcoming/` to `work/2_current/`.
/// Move a work item (story, bug, or spike) from `work/1_backlog/` to `work/2_current/`.
///
/// Idempotent: if the item is already in `2_current/`, returns Ok without committing.
/// If the item is not found in `1_upcoming/`, logs a warning and returns Ok.
/// 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(".story_kit").join("work");
let current_dir = sk.join("2_current");
@@ -219,16 +219,16 @@ pub fn move_story_to_qa(project_root: &Path, story_id: &str) -> Result<(), Strin
Ok(())
}
/// Move a bug from `work/2_current/` or `work/1_upcoming/` to `work/5_done/` and auto-commit.
/// Move a bug from `work/2_current/` or `work/1_backlog/` to `work/5_done/` and auto-commit.
///
/// * If the bug is in `2_current/`, it is moved to `5_done/` and committed.
/// * If the bug is still in `1_upcoming/` (never started), it is moved directly to `5_done/`.
/// * If the bug is still in `1_backlog/` (never started), it is moved directly to `5_done/`.
/// * 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(".story_kit").join("work");
let current_path = sk.join("2_current").join(format!("{bug_id}.md"));
let upcoming_path = sk.join("1_upcoming").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);
let archive_path = archive_dir.join(format!("{bug_id}.md"));
@@ -238,11 +238,11 @@ pub fn close_bug_to_archive(project_root: &Path, bug_id: &str) -> Result<(), Str
let source_path = if current_path.exists() {
current_path.clone()
} else if upcoming_path.exists() {
upcoming_path.clone()
} else if backlog_path.exists() {
backlog_path.clone()
} else {
return Err(format!(
"Bug '{bug_id}' not found in work/2_current/ or work/1_upcoming/. Cannot close bug."
"Bug '{bug_id}' not found in work/2_current/ or work/1_backlog/. Cannot close bug."
));
};
@@ -269,15 +269,15 @@ mod tests {
use std::fs;
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let upcoming = root.join(".story_kit/work/1_upcoming");
let backlog = root.join(".story_kit/work/1_backlog");
let current = root.join(".story_kit/work/2_current");
fs::create_dir_all(&upcoming).unwrap();
fs::create_dir_all(&backlog).unwrap();
fs::create_dir_all(&current).unwrap();
fs::write(upcoming.join("10_story_foo.md"), "test").unwrap();
fs::write(backlog.join("10_story_foo.md"), "test").unwrap();
move_story_to_current(root, "10_story_foo").unwrap();
assert!(!upcoming.join("10_story_foo.md").exists());
assert!(!backlog.join("10_story_foo.md").exists());
assert!(current.join("10_story_foo.md").exists());
}
@@ -295,25 +295,25 @@ mod tests {
}
#[test]
fn move_story_to_current_noop_when_not_in_upcoming() {
fn move_story_to_current_noop_when_not_in_backlog() {
let tmp = tempfile::tempdir().unwrap();
assert!(move_story_to_current(tmp.path(), "99_missing").is_ok());
}
#[test]
fn move_bug_to_current_moves_from_upcoming() {
fn move_bug_to_current_moves_from_backlog() {
use std::fs;
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let upcoming = root.join(".story_kit/work/1_upcoming");
let backlog = root.join(".story_kit/work/1_backlog");
let current = root.join(".story_kit/work/2_current");
fs::create_dir_all(&upcoming).unwrap();
fs::create_dir_all(&backlog).unwrap();
fs::create_dir_all(&current).unwrap();
fs::write(upcoming.join("1_bug_test.md"), "# Bug 1\n").unwrap();
fs::write(backlog.join("1_bug_test.md"), "# Bug 1\n").unwrap();
move_story_to_current(root, "1_bug_test").unwrap();
assert!(!upcoming.join("1_bug_test.md").exists());
assert!(!backlog.join("1_bug_test.md").exists());
assert!(current.join("1_bug_test.md").exists());
}
@@ -335,17 +335,17 @@ mod tests {
}
#[test]
fn close_bug_moves_from_upcoming_when_not_started() {
fn close_bug_moves_from_backlog_when_not_started() {
use std::fs;
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 backlog = root.join(".story_kit/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!(!upcoming.join("3_bug_test.md").exists());
assert!(!backlog.join("3_bug_test.md").exists());
assert!(root.join(".story_kit/work/5_done/3_bug_test.md").exists());
}

View File

@@ -212,7 +212,7 @@ impl AgentPool {
let event_log: Arc<Mutex<Vec<AgentEvent>>> = Arc::new(Mutex::new(Vec::new()));
let log_session_id = uuid::Uuid::new_v4().to_string();
// Move story from upcoming/ to current/ before checking agent
// Move story from backlog/ to current/ before checking agent
// availability so that auto_assign_available_work can pick it up even
// when all coders are currently busy (story 203). This is idempotent:
// if the story is already in 2_current/ or a later stage, the call is
@@ -1430,7 +1430,7 @@ impl AgentPool {
///
/// Scans `work/2_current/`, `work/3_qa/`, and `work/4_merge/` for items that have no
/// active agent and assigns the first free agent of the appropriate role. Items in
/// `work/1_upcoming/` are never auto-started.
/// `work/1_backlog/` are never auto-started.
///
/// Respects the configured agent roster: the maximum number of concurrently active agents
/// per role is bounded by the count of agents of that role defined in `project.toml`.
@@ -1603,7 +1603,7 @@ impl AgentPool {
// Determine which active stage the story is in.
let stage_dir = match find_active_story_stage(project_root, story_id) {
Some(s) => s,
None => continue, // Not in any active stage (upcoming/archived or unknown).
None => continue, // Not in any active stage (backlog/archived or unknown).
};
// 4_merge/ is left for auto_assign to handle with a fresh mergemaster.
@@ -2728,8 +2728,8 @@ mod tests {
fs::write(current.join("173_story_test.md"), "test").unwrap();
// Ensure 3_qa/ exists for the move target
fs::create_dir_all(root.join(".story_kit/work/3_qa")).unwrap();
// Ensure 1_upcoming/ exists (start_agent calls move_story_to_current)
fs::create_dir_all(root.join(".story_kit/work/1_upcoming")).unwrap();
// Ensure 1_backlog/ exists (start_agent calls move_story_to_current)
fs::create_dir_all(root.join(".story_kit/work/1_backlog")).unwrap();
// Write a project.toml with a qa agent so start_agent can resolve it.
fs::create_dir_all(root.join(".story_kit")).unwrap();
@@ -3498,14 +3498,14 @@ stage = "coder"
}
/// Story 203: when all coders are busy the story file must be moved from
/// 1_upcoming/ to 2_current/ so that auto_assign_available_work can pick
/// 1_backlog/ to 2_current/ so that auto_assign_available_work can pick
/// it up once a coder finishes.
#[tokio::test]
async fn start_agent_moves_story_to_current_when_coders_busy() {
let tmp = tempfile::tempdir().unwrap();
let sk = tmp.path().join(".story_kit");
let upcoming = sk.join("work/1_upcoming");
std::fs::create_dir_all(&upcoming).unwrap();
let backlog = sk.join("work/1_backlog");
std::fs::create_dir_all(&backlog).unwrap();
std::fs::write(
sk.join("project.toml"),
r#"
@@ -3515,9 +3515,9 @@ stage = "coder"
"#,
)
.unwrap();
// Place the story in 1_upcoming/.
// Place the story in 1_backlog/.
std::fs::write(
upcoming.join("story-3.md"),
backlog.join("story-3.md"),
"---\nname: Story 3\n---\n",
)
.unwrap();
@@ -3547,10 +3547,10 @@ stage = "coder"
current_path.exists(),
"story should be in 2_current/ after busy error, but was not"
);
let upcoming_path = upcoming.join("story-3.md");
let backlog_path = backlog.join("story-3.md");
assert!(
!upcoming_path.exists(),
"story should no longer be in 1_upcoming/"
!backlog_path.exists(),
"story should no longer be in 1_backlog/"
);
}
@@ -3774,7 +3774,7 @@ stage = "coder"
// Create the story in upcoming so `move_story_to_current` succeeds,
// but do NOT init a git repo — `create_worktree` will fail in the spawn.
let upcoming = root.join(".story_kit/work/1_upcoming");
let upcoming = root.join(".story_kit/work/1_backlog");
fs::create_dir_all(&upcoming).unwrap();
fs::write(
upcoming.join("50_story_test.md"),
@@ -3924,7 +3924,7 @@ stage = "coder"
let root = tmp.path().to_path_buf();
let sk_dir = root.join(".story_kit");
fs::create_dir_all(sk_dir.join("work/1_upcoming")).unwrap();
fs::create_dir_all(sk_dir.join("work/1_backlog")).unwrap();
fs::write(
root.join(".story_kit/project.toml"),
"[[agent]]\nname = \"coder-1\"\n",
@@ -3933,12 +3933,12 @@ stage = "coder"
// Both stories must exist in upcoming so move_story_to_current can run
// (only the winner reaches that point, but we set both up defensively).
fs::write(
root.join(".story_kit/work/1_upcoming/86_story_foo.md"),
root.join(".story_kit/work/1_backlog/86_story_foo.md"),
"---\nname: Foo\n---\n",
)
.unwrap();
fs::write(
root.join(".story_kit/work/1_upcoming/130_story_bar.md"),
root.join(".story_kit/work/1_backlog/130_story_bar.md"),
"---\nname: Bar\n---\n",
)
.unwrap();
@@ -4138,14 +4138,14 @@ stage = "coder"
let root = tmp.path();
let sk_dir = root.join(".story_kit");
fs::create_dir_all(sk_dir.join("work/1_upcoming")).unwrap();
fs::create_dir_all(sk_dir.join("work/1_backlog")).unwrap();
fs::write(
root.join(".story_kit/project.toml"),
"[[agent]]\nname = \"coder-1\"\n\n[[agent]]\nname = \"coder-2\"\n",
)
.unwrap();
fs::write(
root.join(".story_kit/work/1_upcoming/99_story_baz.md"),
root.join(".story_kit/work/1_backlog/99_story_baz.md"),
"---\nname: Baz\n---\n",
)
.unwrap();