huskies: merge 950

This commit is contained in:
dave
2026-05-13 08:41:57 +00:00
parent 7491eec257
commit 4a8ed4348b
38 changed files with 354 additions and 4329 deletions
+3 -41
View File
@@ -17,7 +17,6 @@ use crate::agents::AgentPool;
use crate::agents::token_usage::TokenUsageRecord;
use crate::config::ProjectConfig;
use crate::workflow::StoryTestResults;
use crate::worktree::{WorktreeInfo, WorktreeListEntry};
use std::path::Path;
pub use io::is_archived;
@@ -35,8 +34,6 @@ pub enum Error {
AgentNotFound(String),
/// No work item found for the requested story ID.
WorkItemNotFound(String),
/// A worktree operation failed.
Worktree(String),
/// Project configuration could not be loaded.
Config(String),
/// A filesystem or I/O operation failed.
@@ -48,7 +45,6 @@ impl std::fmt::Display for Error {
match self {
Self::AgentNotFound(msg) => write!(f, "Agent not found: {msg}"),
Self::WorkItemNotFound(msg) => write!(f, "Work item not found: {msg}"),
Self::Worktree(msg) => write!(f, "Worktree error: {msg}"),
Self::Config(msg) => write!(f, "Config error: {msg}"),
Self::Io(msg) => write!(f, "I/O error: {msg}"),
}
@@ -62,8 +58,6 @@ impl std::fmt::Display for Error {
pub struct WorkItemContent {
pub content: String,
pub stage: crate::pipeline_state::Stage,
/// Whether the item is frozen — orthogonal to [`Self::stage`].
pub frozen: bool,
pub name: Option<String>,
pub agent: Option<String>,
}
@@ -117,41 +111,12 @@ pub async fn stop_agent(
.map_err(Error::AgentNotFound)
}
/// Create a git worktree for a story.
pub async fn create_worktree(
pool: &AgentPool,
project_root: &Path,
story_id: &str,
) -> Result<WorktreeInfo, Error> {
pool.create_worktree(project_root, story_id)
.await
.map_err(Error::Worktree)
}
/// List all worktrees under `.huskies/worktrees/`.
pub fn list_worktrees(project_root: &Path) -> Result<Vec<WorktreeListEntry>, Error> {
io::list_worktrees(project_root)
}
/// Remove the git worktree for a story.
pub async fn remove_worktree(project_root: &Path, story_id: &str) -> Result<(), Error> {
io::remove_worktree(project_root, story_id).await
}
/// Get the configured agent roster from `project.toml`.
pub fn get_agent_config(project_root: &Path) -> Result<Vec<AgentConfigEntry>, Error> {
let config = io::load_config(project_root)?;
Ok(config_to_entries(&config))
}
/// Reload and return the project's agent configuration.
///
/// Semantically identical to `get_agent_config`; provided as a distinct
/// function so callers can express intent (UI "Reload" button).
pub fn reload_config(project_root: &Path) -> Result<Vec<AgentConfigEntry>, Error> {
get_agent_config(project_root)
}
/// Get the concatenated output text for an agent's most recent session.
///
/// Returns an empty string when no log file exists yet.
@@ -207,7 +172,6 @@ pub fn get_work_item_content(
return Ok(WorkItemContent {
content,
stage: stage.clone(),
frozen: false,
name: crdt_name.clone(),
agent: crdt_agent.clone(),
});
@@ -218,14 +182,13 @@ pub fn get_work_item_content(
if let Some(content) = crate::db::read_content(story_id) {
let item = crate::pipeline_state::read_typed(story_id)
.map_err(|e| Error::Io(format!("Pipeline read error: {e}")))?;
let (stage, frozen) = match item.as_ref() {
Some(i) => (i.stage.clone(), i.is_frozen()),
None => (Stage::Upcoming, false),
let stage = match item.as_ref() {
Some(i) => i.stage.clone(),
None => Stage::Upcoming,
};
return Ok(WorkItemContent {
content,
stage,
frozen,
name: crdt_name,
agent: crdt_agent,
});
@@ -359,7 +322,6 @@ max_budget_usd = 5.0
let item = get_work_item_content(tmp.path(), "42_story_foo").unwrap();
assert!(item.content.contains("Some content."));
assert_eq!(item.stage, crate::pipeline_state::Stage::Backlog);
assert!(!item.frozen);
assert_eq!(item.name, Some("Foo Story".to_string()));
}