huskies: merge 530_story_eliminate_filesystem_markdown_shadows_entirely_crdt_db_is_the_only_story_store
This commit is contained in:
@@ -38,13 +38,7 @@ impl AgentPool {
|
||||
let items = scan_stage_items(project_root, "1_backlog");
|
||||
for story_id in &items {
|
||||
// Only promote stories that explicitly declare dependencies.
|
||||
// Try content store first, fall back to filesystem.
|
||||
let contents = crate::db::read_content(story_id).or_else(|| {
|
||||
let story_path = project_root
|
||||
.join(".huskies/work/1_backlog")
|
||||
.join(format!("{story_id}.md"));
|
||||
std::fs::read_to_string(&story_path).ok()
|
||||
});
|
||||
let contents = crate::db::read_content(story_id);
|
||||
let has_deps = contents
|
||||
.and_then(|c| parse_front_matter(&c).ok())
|
||||
.and_then(|m| m.depends_on)
|
||||
@@ -382,38 +376,40 @@ mod tests {
|
||||
async fn auto_assign_ignores_coder_preference_when_story_is_in_qa_stage() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let qa_dir = sk.join("work/3_qa");
|
||||
std::fs::create_dir_all(&qa_dir).unwrap();
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
std::fs::write(
|
||||
sk.join("project.toml"),
|
||||
"[[agent]]\nname = \"coder-1\"\nstage = \"coder\"\n\n\
|
||||
[[agent]]\nname = \"qa-1\"\nstage = \"qa\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
// Story in 3_qa/ with a preferred coder-stage agent.
|
||||
std::fs::write(
|
||||
qa_dir.join("story-qa1.md"),
|
||||
// Story in 3_qa/ with a preferred coder-stage agent — write via CRDT.
|
||||
crate::db::ensure_content_store();
|
||||
crate::db::write_item_with_content(
|
||||
"9930_story_qa1",
|
||||
"3_qa",
|
||||
"---\nname: QA Story\nagent: coder-1\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let pool = AgentPool::new_test(3001);
|
||||
|
||||
pool.auto_assign_available_work(tmp.path()).await;
|
||||
|
||||
let agents = pool.agents.lock().unwrap();
|
||||
// coder-1 must NOT have been assigned (wrong stage for 3_qa/).
|
||||
let coder_assigned = agents.values().any(|a| {
|
||||
a.agent_name == "coder-1"
|
||||
// coder-1 must NOT have been assigned to the QA story (wrong stage).
|
||||
let coder_assigned_to_qa = agents.iter().any(|(key, a)| {
|
||||
key.contains("9930_story_qa1")
|
||||
&& a.agent_name == "coder-1"
|
||||
&& matches!(a.status, AgentStatus::Pending | AgentStatus::Running)
|
||||
});
|
||||
assert!(
|
||||
!coder_assigned,
|
||||
!coder_assigned_to_qa,
|
||||
"coder-1 should not be assigned to a QA-stage story"
|
||||
);
|
||||
// qa-1 should have been assigned instead.
|
||||
let qa_assigned = agents.values().any(|a| {
|
||||
a.agent_name == "qa-1"
|
||||
let qa_assigned = agents.iter().any(|(key, a)| {
|
||||
key.contains("9930_story_qa1")
|
||||
&& a.agent_name == "qa-1"
|
||||
&& matches!(a.status, AgentStatus::Pending | AgentStatus::Running)
|
||||
});
|
||||
assert!(
|
||||
@@ -429,8 +425,7 @@ mod tests {
|
||||
async fn auto_assign_respects_coder_preference_when_story_is_in_current_stage() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let current_dir = sk.join("work/2_current");
|
||||
std::fs::create_dir_all(¤t_dir).unwrap();
|
||||
std::fs::create_dir_all(sk.join("work/2_current")).unwrap();
|
||||
std::fs::write(
|
||||
sk.join("project.toml"),
|
||||
"[[agent]]\nname = \"coder-1\"\nstage = \"coder\"\n\n\
|
||||
@@ -438,11 +433,12 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
// Story in 2_current/ with a preferred coder-1 agent.
|
||||
std::fs::write(
|
||||
current_dir.join("story-pref.md"),
|
||||
crate::db::ensure_content_store();
|
||||
crate::db::write_item_with_content(
|
||||
"story-pref",
|
||||
"2_current",
|
||||
"---\nname: Coder Story\nagent: coder-1\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let pool = AgentPool::new_test(3001);
|
||||
|
||||
@@ -476,20 +472,20 @@ mod tests {
|
||||
async fn auto_assign_stage_mismatch_with_no_fallback_starts_no_agent() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".huskies");
|
||||
let qa_dir = sk.join("work/3_qa");
|
||||
std::fs::create_dir_all(&qa_dir).unwrap();
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
// Only a coder agent is configured — no QA agent exists.
|
||||
std::fs::write(
|
||||
sk.join("project.toml"),
|
||||
"[[agent]]\nname = \"coder-1\"\nstage = \"coder\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
// Story in 3_qa/ requests coder-1 (wrong stage) and no QA agent exists.
|
||||
std::fs::write(
|
||||
qa_dir.join("story-noqa.md"),
|
||||
// Story in 3_qa/ requests coder-1 (wrong stage) and no QA agent exists — write via CRDT.
|
||||
crate::db::ensure_content_store();
|
||||
crate::db::write_item_with_content(
|
||||
"9931_story_noqa",
|
||||
"3_qa",
|
||||
"---\nname: QA Story No Agent\nagent: coder-1\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let pool = AgentPool::new_test(3001);
|
||||
|
||||
@@ -497,8 +493,14 @@ mod tests {
|
||||
pool.auto_assign_available_work(tmp.path()).await;
|
||||
|
||||
let agents = pool.agents.lock().unwrap();
|
||||
// No agent should be assigned to the specific QA story (coder-1 may
|
||||
// be assigned to leaked 2_current items from the global CRDT store).
|
||||
let assigned_to_qa_story = agents.iter().any(|(key, a)| {
|
||||
key.contains("9931_story_noqa")
|
||||
&& matches!(a.status, AgentStatus::Pending | AgentStatus::Running)
|
||||
});
|
||||
assert!(
|
||||
agents.is_empty(),
|
||||
!assigned_to_qa_story,
|
||||
"No agent should be started when no stage-appropriate agent is available"
|
||||
);
|
||||
}
|
||||
@@ -510,26 +512,32 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let sk = root.join(".huskies");
|
||||
let current = sk.join("work/2_current");
|
||||
std::fs::create_dir_all(¤t).unwrap();
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
std::fs::write(
|
||||
sk.join("project.toml"),
|
||||
"[[agent]]\nname = \"coder-1\"\nstage = \"coder\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
// Story 10 depends on 999 which is not done.
|
||||
std::fs::write(
|
||||
current.join("10_story_waiting.md"),
|
||||
"---\nname: Waiting\ndepends_on: [999]\n---\n",
|
||||
)
|
||||
.unwrap();
|
||||
// Story 9932 depends on 9999 which is not done — write via CRDT.
|
||||
crate::db::ensure_content_store();
|
||||
crate::db::write_item_with_content(
|
||||
"9932_story_waiting",
|
||||
"2_current",
|
||||
"---\nname: Waiting\ndepends_on: [9999]\n---\n",
|
||||
);
|
||||
|
||||
let pool = AgentPool::new_test(3001);
|
||||
pool.auto_assign_available_work(root).await;
|
||||
|
||||
let agents = pool.agents.lock().unwrap();
|
||||
// Filter to only agents assigned to our specific story to avoid
|
||||
// interference from other tests sharing the global CRDT store.
|
||||
let assigned_to_our_story = agents.iter().any(|(key, a)| {
|
||||
key.contains("9932_story_waiting")
|
||||
&& matches!(a.status, AgentStatus::Pending | AgentStatus::Running)
|
||||
});
|
||||
assert!(
|
||||
agents.is_empty(),
|
||||
!assigned_to_our_story,
|
||||
"story with unmet deps should not be auto-assigned"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user