114 lines
3.9 KiB
Rust
114 lines
3.9 KiB
Rust
|
|
//! Front-matter checks for story files: review holds, blocked state, and merge failures.
|
||
|
|
|
||
|
|
use std::path::Path;
|
||
|
|
|
||
|
|
/// Read the optional `agent:` field from the front matter of a story file.
|
||
|
|
///
|
||
|
|
/// Returns `Some(agent_name)` if the front matter specifies an agent, or `None`
|
||
|
|
/// if the field is absent or the file cannot be read / parsed.
|
||
|
|
pub(super) fn read_story_front_matter_agent(
|
||
|
|
project_root: &Path,
|
||
|
|
stage_dir: &str,
|
||
|
|
story_id: &str,
|
||
|
|
) -> Option<String> {
|
||
|
|
use crate::io::story_metadata::parse_front_matter;
|
||
|
|
let path = project_root
|
||
|
|
.join(".storkit")
|
||
|
|
.join("work")
|
||
|
|
.join(stage_dir)
|
||
|
|
.join(format!("{story_id}.md"));
|
||
|
|
let contents = std::fs::read_to_string(path).ok()?;
|
||
|
|
parse_front_matter(&contents).ok()?.agent
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return `true` if the story file in the given stage has `review_hold: true` in its front matter.
|
||
|
|
pub(super) fn has_review_hold(project_root: &Path, stage_dir: &str, story_id: &str) -> bool {
|
||
|
|
use crate::io::story_metadata::parse_front_matter;
|
||
|
|
let path = project_root
|
||
|
|
.join(".storkit")
|
||
|
|
.join("work")
|
||
|
|
.join(stage_dir)
|
||
|
|
.join(format!("{story_id}.md"));
|
||
|
|
let contents = match std::fs::read_to_string(path) {
|
||
|
|
Ok(c) => c,
|
||
|
|
Err(_) => return false,
|
||
|
|
};
|
||
|
|
parse_front_matter(&contents)
|
||
|
|
.ok()
|
||
|
|
.and_then(|m| m.review_hold)
|
||
|
|
.unwrap_or(false)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return `true` if the story file has `blocked: true` in its front matter.
|
||
|
|
pub(super) fn is_story_blocked(project_root: &Path, stage_dir: &str, story_id: &str) -> bool {
|
||
|
|
use crate::io::story_metadata::parse_front_matter;
|
||
|
|
let path = project_root
|
||
|
|
.join(".storkit")
|
||
|
|
.join("work")
|
||
|
|
.join(stage_dir)
|
||
|
|
.join(format!("{story_id}.md"));
|
||
|
|
let contents = match std::fs::read_to_string(path) {
|
||
|
|
Ok(c) => c,
|
||
|
|
Err(_) => return false,
|
||
|
|
};
|
||
|
|
parse_front_matter(&contents)
|
||
|
|
.ok()
|
||
|
|
.and_then(|m| m.blocked)
|
||
|
|
.unwrap_or(false)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return `true` if the story file has a `merge_failure` field in its front matter.
|
||
|
|
pub(super) fn has_merge_failure(project_root: &Path, stage_dir: &str, story_id: &str) -> bool {
|
||
|
|
use crate::io::story_metadata::parse_front_matter;
|
||
|
|
let path = project_root
|
||
|
|
.join(".storkit")
|
||
|
|
.join("work")
|
||
|
|
.join(stage_dir)
|
||
|
|
.join(format!("{story_id}.md"));
|
||
|
|
let contents = match std::fs::read_to_string(path) {
|
||
|
|
Ok(c) => c,
|
||
|
|
Err(_) => return false,
|
||
|
|
};
|
||
|
|
parse_front_matter(&contents)
|
||
|
|
.ok()
|
||
|
|
.and_then(|m| m.merge_failure)
|
||
|
|
.is_some()
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Tests ──────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn has_review_hold_returns_true_when_set() {
|
||
|
|
let tmp = tempfile::tempdir().unwrap();
|
||
|
|
let qa_dir = tmp.path().join(".storkit/work/3_qa");
|
||
|
|
std::fs::create_dir_all(&qa_dir).unwrap();
|
||
|
|
let spike_path = qa_dir.join("10_spike_research.md");
|
||
|
|
std::fs::write(
|
||
|
|
&spike_path,
|
||
|
|
"---\nname: Research spike\nreview_hold: true\n---\n# Spike\n",
|
||
|
|
)
|
||
|
|
.unwrap();
|
||
|
|
assert!(has_review_hold(tmp.path(), "3_qa", "10_spike_research"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn has_review_hold_returns_false_when_not_set() {
|
||
|
|
let tmp = tempfile::tempdir().unwrap();
|
||
|
|
let qa_dir = tmp.path().join(".storkit/work/3_qa");
|
||
|
|
std::fs::create_dir_all(&qa_dir).unwrap();
|
||
|
|
let spike_path = qa_dir.join("10_spike_research.md");
|
||
|
|
std::fs::write(&spike_path, "---\nname: Research spike\n---\n# Spike\n").unwrap();
|
||
|
|
assert!(!has_review_hold(tmp.path(), "3_qa", "10_spike_research"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn has_review_hold_returns_false_when_file_missing() {
|
||
|
|
let tmp = tempfile::tempdir().unwrap();
|
||
|
|
assert!(!has_review_hold(tmp.path(), "3_qa", "99_spike_missing"));
|
||
|
|
}
|
||
|
|
}
|