Merge branch 'feature/story-28-ui-show-test-todos'

This commit is contained in:
Dave
2026-02-19 15:36:04 +00:00
13 changed files with 606 additions and 17 deletions

View File

@@ -1,5 +1,5 @@
use crate::http::context::{AppContext, OpenApiResult, bad_request};
use crate::io::story_metadata::{StoryMetadata, parse_front_matter};
use crate::io::story_metadata::{StoryMetadata, parse_front_matter, parse_unchecked_todos};
use crate::workflow::{
CoverageReport, StoryTestResults, TestCaseResult, TestStatus,
evaluate_acceptance_with_coverage, parse_coverage_json, summarize_results,
@@ -87,6 +87,18 @@ struct ReviewListResponse {
pub stories: Vec<ReviewStory>,
}
#[derive(Object)]
struct StoryTodosResponse {
pub story_id: String,
pub story_name: Option<String>,
pub todos: Vec<String>,
}
#[derive(Object)]
struct TodoListResponse {
pub stories: Vec<StoryTodosResponse>,
}
fn load_current_story_metadata(ctx: &AppContext) -> Result<Vec<(String, StoryMetadata)>, String> {
let root = ctx.state.get_project_root()?;
let current_dir = root.join(".story_kit").join("stories").join("current");
@@ -403,6 +415,51 @@ impl WorkflowApi {
}))
}
/// List unchecked acceptance criteria (TODOs) for all current stories.
#[oai(path = "/workflow/todos", method = "get")]
async fn story_todos(&self) -> OpenApiResult<Json<TodoListResponse>> {
let root = self.ctx.state.get_project_root().map_err(bad_request)?;
let current_dir = root.join(".story_kit").join("stories").join("current");
if !current_dir.exists() {
return Ok(Json(TodoListResponse {
stories: Vec::new(),
}));
}
let mut stories = Vec::new();
let mut entries: Vec<_> = fs::read_dir(&current_dir)
.map_err(|e| bad_request(format!("Failed to read current stories: {e}")))?
.filter_map(|e| e.ok())
.collect();
entries.sort_by_key(|e| e.file_name());
for entry in entries {
let path = entry.path();
if path.extension().and_then(|ext| ext.to_str()) != Some("md") {
continue;
}
let story_id = path
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or_default()
.to_string();
let contents = fs::read_to_string(&path)
.map_err(|e| bad_request(format!("Failed to read {}: {e}", path.display())))?;
let story_name = parse_front_matter(&contents)
.ok()
.and_then(|m| m.name);
let todos = parse_unchecked_todos(&contents);
stories.push(StoryTodosResponse {
story_id,
story_name,
todos,
});
}
Ok(Json(TodoListResponse { stories }))
}
/// Ensure a story can be accepted; returns an error when gates fail.
#[oai(path = "/workflow/acceptance/ensure", method = "post")]
async fn ensure_acceptance(