huskies: merge 492_story_remove_filesystem_pipeline_state_and_store_story_content_in_database

This commit is contained in:
dave
2026-04-08 03:03:59 +00:00
parent f43d30bdae
commit 8fd49d563e
27 changed files with 1663 additions and 1295 deletions
+46 -26
View File
@@ -102,32 +102,51 @@ pub async fn handle_assign(
agents: &AgentPool,
) -> String {
// Find the story file across all pipeline stages.
// Try the content store / CRDT state first, then fall back to filesystem.
let mut found: Option<(std::path::PathBuf, String)> = None;
'outer: for stage in STAGES {
let dir = project_root.join(".huskies").join("work").join(stage);
if !dir.exists() {
continue;
// --- DB-first lookup ---
for id in crate::db::all_content_ids() {
let file_num = id.split('_').next().unwrap_or("");
if file_num == story_number && let Some(item) = crate::crdt_state::read_item(&id) {
let path = project_root
.join(".huskies")
.join("work")
.join(&item.stage)
.join(format!("{id}.md"));
found = Some((path, id));
break;
}
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("md") {
continue;
}
if let Some(stem) = path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
{
let file_num = stem
.split('_')
.next()
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or("")
.to_string();
if file_num == story_number {
found = Some((path, stem));
break 'outer;
}
// --- Filesystem fallback ---
if found.is_none() {
'outer: for stage in STAGES {
let dir = project_root.join(".huskies").join("work").join(stage);
if !dir.exists() {
continue;
}
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("md") {
continue;
}
if let Some(stem) = path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
{
let file_num = stem
.split('_')
.next()
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or("")
.to_string();
if file_num == story_number {
found = Some((path, stem));
break 'outer;
}
}
}
}
@@ -144,8 +163,9 @@ pub async fn handle_assign(
};
// Read the human-readable name from front matter for the response.
let story_name = std::fs::read_to_string(&path)
.ok()
// Try the content store first, then fall back to reading from disk.
let story_name = crate::db::read_content(&story_id)
.or_else(|| std::fs::read_to_string(&path).ok())
.and_then(|contents| {
parse_front_matter(&contents)
.ok()
+48 -28
View File
@@ -70,32 +70,51 @@ pub async fn handle_delete(
];
// Find the story file across all pipeline stages.
let mut found: Option<(std::path::PathBuf, &str, String)> = None; // (path, stage, story_id)
'outer: for stage in STAGES {
let dir = project_root.join(".huskies").join("work").join(stage);
if !dir.exists() {
continue;
// Try the content store / CRDT state first, then fall back to filesystem.
let mut found: Option<(std::path::PathBuf, String, String)> = None; // (path, stage, story_id)
// --- DB-first lookup ---
for id in crate::db::all_content_ids() {
let file_num = id.split('_').next().unwrap_or("");
if file_num == story_number && let Some(item) = crate::crdt_state::read_item(&id) {
let path = project_root
.join(".huskies")
.join("work")
.join(&item.stage)
.join(format!("{id}.md"));
found = Some((path, item.stage, id));
break;
}
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("md") {
continue;
}
if let Some(stem) = path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
{
let file_num = stem
.split('_')
.next()
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or("")
.to_string();
if file_num == story_number {
found = Some((path, stage, stem));
break 'outer;
}
// --- Filesystem fallback ---
if found.is_none() {
'outer: for stage in STAGES {
let dir = project_root.join(".huskies").join("work").join(stage);
if !dir.exists() {
continue;
}
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("md") {
continue;
}
if let Some(stem) = path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
{
let file_num = stem
.split('_')
.next()
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or("")
.to_string();
if file_num == story_number {
found = Some((path, stage.to_string(), stem));
break 'outer;
}
}
}
}
@@ -110,8 +129,9 @@ pub async fn handle_delete(
};
// Read the human-readable name from front matter for the confirmation message.
let story_name = std::fs::read_to_string(&path)
.ok()
// Try the content store first, then fall back to reading from disk.
let story_name = crate::db::read_content(&story_id)
.or_else(|| std::fs::read_to_string(&path).ok())
.and_then(|contents| {
crate::io::story_metadata::parse_front_matter(&contents)
.ok()
@@ -161,7 +181,7 @@ pub async fn handle_delete(
.output();
// Build the response.
let stage_label = stage_display_name(stage);
let stage_label = stage_display_name(&stage);
let mut response = format!("Deleted **{story_name}** from **{stage_label}**.");
if !stopped_agents.is_empty() {
let agent_list = stopped_agents.join(", ");
+46 -26
View File
@@ -89,32 +89,51 @@ pub async fn handle_start(
];
// Find the story file across all pipeline stages.
// Try the content store / CRDT state first, then fall back to filesystem.
let mut found: Option<(std::path::PathBuf, String)> = None; // (path, story_id)
'outer: for stage in STAGES {
let dir = project_root.join(".huskies").join("work").join(stage);
if !dir.exists() {
continue;
// --- DB-first lookup ---
for id in crate::db::all_content_ids() {
let file_num = id.split('_').next().unwrap_or("");
if file_num == story_number && let Some(item) = crate::crdt_state::read_item(&id) {
let path = project_root
.join(".huskies")
.join("work")
.join(&item.stage)
.join(format!("{id}.md"));
found = Some((path, id));
break;
}
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("md") {
continue;
}
if let Some(stem) = path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
{
let file_num = stem
.split('_')
.next()
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or("")
.to_string();
if file_num == story_number {
found = Some((path, stem));
break 'outer;
}
// --- Filesystem fallback ---
if found.is_none() {
'outer: for stage in STAGES {
let dir = project_root.join(".huskies").join("work").join(stage);
if !dir.exists() {
continue;
}
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("md") {
continue;
}
if let Some(stem) = path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
{
let file_num = stem
.split('_')
.next()
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or("")
.to_string();
if file_num == story_number {
found = Some((path, stem));
break 'outer;
}
}
}
}
@@ -131,8 +150,9 @@ pub async fn handle_start(
};
// Read the human-readable name from front matter for the response.
let story_name = std::fs::read_to_string(&path)
.ok()
// Try the content store first, then fall back to reading from disk.
let story_name = crate::db::read_content(&story_id)
.or_else(|| std::fs::read_to_string(&path).ok())
.and_then(|contents| {
crate::io::story_metadata::parse_front_matter(&contents)
.ok()