Story 26: Establish TDD workflow and quality gates
Add workflow engine with acceptance gates, test recording, and review queue. Frontend displays gate status (blocked/ready), test summaries, failing badges, and warnings. Proceed action is disabled when gates are not met. Includes 13 unit tests (Vitest) and 9 E2E tests (Playwright) covering all five acceptance criteria. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use crate::io::story_metadata::{TestPlanStatus, parse_front_matter};
|
||||
use crate::state::SessionState;
|
||||
use serde::Serialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
@@ -8,6 +10,30 @@ fn get_project_root(state: &SessionState) -> Result<PathBuf, String> {
|
||||
state.get_project_root()
|
||||
}
|
||||
|
||||
async fn ensure_test_plan_approved(root: PathBuf) -> Result<(), String> {
|
||||
let approved = tokio::task::spawn_blocking(move || {
|
||||
let story_path = root
|
||||
.join(".story_kit")
|
||||
.join("stories")
|
||||
.join("current")
|
||||
.join("26_establish_tdd_workflow_and_gates.md");
|
||||
let contents = fs::read_to_string(&story_path)
|
||||
.map_err(|e| format!("Failed to read story file for test plan approval: {e}"))?;
|
||||
let metadata = parse_front_matter(&contents)
|
||||
.map_err(|e| format!("Failed to parse story front matter: {e:?}"))?;
|
||||
|
||||
Ok::<bool, String>(matches!(metadata.test_plan, Some(TestPlanStatus::Approved)))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task failed: {e}"))??;
|
||||
|
||||
if approved {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Test plan is not approved for the current story.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, poem_openapi::Object)]
|
||||
pub struct CommandOutput {
|
||||
pub stdout: String,
|
||||
@@ -54,5 +80,30 @@ pub async fn exec_shell(
|
||||
state: &SessionState,
|
||||
) -> Result<CommandOutput, String> {
|
||||
let root = get_project_root(state)?;
|
||||
ensure_test_plan_approved(root.clone()).await?;
|
||||
exec_shell_impl(command, args, root).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn exec_shell_requires_approved_test_plan() {
|
||||
let dir = tempdir().expect("tempdir");
|
||||
let state = SessionState::default();
|
||||
|
||||
{
|
||||
let mut root = state.project_root.lock().expect("lock project root");
|
||||
*root = Some(dir.path().to_path_buf());
|
||||
}
|
||||
|
||||
let result = exec_shell("ls".to_string(), Vec::new(), &state).await;
|
||||
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"expected shell execution to be blocked when test plan is not approved"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user