huskies: merge 512_story_migrate_chat_commands_from_filesystem_lookup_to_crdt_db
This commit is contained in:
@@ -13,16 +13,6 @@ use crate::chat::util::strip_bot_mention;
|
||||
use crate::io::story_metadata::{parse_front_matter, set_front_matter_field};
|
||||
use std::path::Path;
|
||||
|
||||
/// All pipeline stage directories to search when finding a work item by number.
|
||||
const STAGES: &[&str] = &[
|
||||
"1_backlog",
|
||||
"2_current",
|
||||
"3_qa",
|
||||
"4_merge",
|
||||
"5_done",
|
||||
"6_archived",
|
||||
];
|
||||
|
||||
/// A parsed assign command from a Matrix message body.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AssignCommand {
|
||||
@@ -101,76 +91,20 @@ pub async fn handle_assign(
|
||||
project_root: &Path,
|
||||
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;
|
||||
|
||||
// --- 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 Ok(Some(item)) = crate::pipeline_state::read_typed(&id) {
|
||||
let path = project_root
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(item.stage.dir_name())
|
||||
.join(format!("{id}.md"));
|
||||
found = Some((path, id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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;
|
||||
// Find the story by numeric prefix: CRDT → content store → filesystem.
|
||||
let (story_id, _stage_dir, path, content) =
|
||||
match crate::chat::lookup::find_story_by_number(project_root, story_number) {
|
||||
Some(found) => found,
|
||||
None => {
|
||||
return format!(
|
||||
"No story, bug, or spike with number **{story_number}** found."
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (path, story_id) = match found {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
return format!(
|
||||
"No story, bug, or spike with number **{story_number}** found."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Read the human-readable name from front matter for the response.
|
||||
// Try the content store first, then fall back to reading from disk.
|
||||
let story_name = crate::db::read_content(&story_id)
|
||||
let story_name = content
|
||||
.or_else(|| std::fs::read_to_string(&path).ok())
|
||||
.and_then(|contents| {
|
||||
parse_front_matter(&contents)
|
||||
.ok()
|
||||
.and_then(|m| m.name)
|
||||
})
|
||||
.and_then(|contents| parse_front_matter(&contents).ok().and_then(|m| m.name))
|
||||
.unwrap_or_else(|| story_id.clone());
|
||||
|
||||
let agent_name = resolve_agent_name(model_str);
|
||||
@@ -370,6 +304,7 @@ mod tests {
|
||||
|
||||
// -- handle_assign (no running coder) ------------------------------------
|
||||
|
||||
use crate::chat::lookup::STAGES;
|
||||
use crate::chat::test_helpers::write_story_file;
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -60,77 +60,18 @@ pub async fn handle_delete(
|
||||
project_root: &Path,
|
||||
agents: &AgentPool,
|
||||
) -> String {
|
||||
const STAGES: &[&str] = &[
|
||||
"1_backlog",
|
||||
"2_current",
|
||||
"3_qa",
|
||||
"4_merge",
|
||||
"5_done",
|
||||
"6_archived",
|
||||
];
|
||||
|
||||
// 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, 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 Ok(Some(item)) = crate::pipeline_state::read_typed(&id) {
|
||||
let path = project_root
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(item.stage.dir_name())
|
||||
.join(format!("{id}.md"));
|
||||
found = Some((path, item.stage.dir_name().to_string(), id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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;
|
||||
// Find the story by numeric prefix: CRDT → content store → filesystem.
|
||||
let (story_id, stage, path, content) =
|
||||
match crate::chat::lookup::find_story_by_number(project_root, story_number) {
|
||||
Some(found) => found,
|
||||
None => {
|
||||
return format!(
|
||||
"No story, bug, or spike with number **{story_number}** found."
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (path, stage, story_id) = match found {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
return format!("No story, bug, or spike with number **{story_number}** found.");
|
||||
}
|
||||
};
|
||||
|
||||
// Read the human-readable name from front matter for the confirmation message.
|
||||
// Try the content store first, then fall back to reading from disk.
|
||||
let story_name = crate::db::read_content(&story_id)
|
||||
let story_name = content
|
||||
.or_else(|| std::fs::read_to_string(&path).ok())
|
||||
.and_then(|contents| {
|
||||
crate::io::story_metadata::parse_front_matter(&contents)
|
||||
|
||||
@@ -79,79 +79,18 @@ pub async fn handle_start(
|
||||
project_root: &Path,
|
||||
agents: &AgentPool,
|
||||
) -> String {
|
||||
const STAGES: &[&str] = &[
|
||||
"1_backlog",
|
||||
"2_current",
|
||||
"3_qa",
|
||||
"4_merge",
|
||||
"5_done",
|
||||
"6_archived",
|
||||
];
|
||||
|
||||
// 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)
|
||||
|
||||
// --- 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 Ok(Some(item)) = crate::pipeline_state::read_typed(&id) {
|
||||
let path = project_root
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(item.stage.dir_name())
|
||||
.join(format!("{id}.md"));
|
||||
found = Some((path, id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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;
|
||||
// Find the story by numeric prefix: CRDT → content store → filesystem.
|
||||
let (story_id, _stage_dir, path, content) =
|
||||
match crate::chat::lookup::find_story_by_number(project_root, story_number) {
|
||||
Some(found) => found,
|
||||
None => {
|
||||
return format!(
|
||||
"No story, bug, or spike with number **{story_number}** found."
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (path, story_id) = match found {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
return format!(
|
||||
"No story, bug, or spike with number **{story_number}** found."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Read the human-readable name from front matter for the response.
|
||||
// Try the content store first, then fall back to reading from disk.
|
||||
let story_name = crate::db::read_content(&story_id)
|
||||
let story_name = content
|
||||
.or_else(|| std::fs::read_to_string(&path).ok())
|
||||
.and_then(|contents| {
|
||||
crate::io::story_metadata::parse_front_matter(&contents)
|
||||
|
||||
Reference in New Issue
Block a user