huskies: merge 492_story_remove_filesystem_pipeline_state_and_store_story_content_in_database

This commit is contained in:
dave
2026-04-08 03:03:59 +00:00
parent f43d30bdae
commit 8fd49d563e
27 changed files with 1663 additions and 1295 deletions
+55 -62
View File
@@ -131,19 +131,6 @@ fn build_metadata(front: FrontMatter) -> StoryMetadata {
}
}
/// Write or update a `coverage_baseline:` field in the YAML front matter of a story file.
///
/// If front matter is present, adds or replaces `coverage_baseline:` before the closing `---`.
/// If no front matter is present, this is a no-op (returns Ok).
pub fn write_coverage_baseline(path: &Path, coverage_pct: f64) -> Result<(), String> {
let contents =
fs::read_to_string(path).map_err(|e| format!("Failed to read story file: {e}"))?;
let updated = set_front_matter_field(&contents, "coverage_baseline", &format!("{coverage_pct:.1}%"));
fs::write(path, &updated).map_err(|e| format!("Failed to write story file: {e}"))?;
Ok(())
}
/// Write or update a `merge_failure:` field in the YAML front matter of a story file.
///
/// The reason is stored as a quoted YAML string so that colons, hashes, and newlines
@@ -254,25 +241,6 @@ pub fn set_front_matter_field(contents: &str, key: &str, value: &str) -> String
result
}
/// Increment the `retry_count` field in the story file's front matter.
///
/// Reads the current value (defaulting to 0), increments by 1, and writes back.
/// Returns the new retry count.
pub fn increment_retry_count(path: &Path) -> Result<u32, String> {
let contents =
fs::read_to_string(path).map_err(|e| format!("Failed to read story file: {e}"))?;
let current = parse_front_matter(&contents)
.ok()
.and_then(|m| m.retry_count)
.unwrap_or(0);
let new_count = current + 1;
let updated = set_front_matter_field(&contents, "retry_count", &new_count.to_string());
fs::write(path, &updated).map_err(|e| format!("Failed to write story file: {e}"))?;
Ok(new_count)
}
/// Write `blocked: true` to the YAML front matter of a story file.
///
/// Used to mark stories that have exceeded the retry limit and should not
@@ -351,18 +319,63 @@ fn dep_is_done(project_root: &Path, dep_number: u32) -> bool {
false
}
/// Append rejection notes to a story file body.
///
/// Adds a `## QA Rejection Notes` section at the end of the file so the coder
/// agent can see what needs fixing.
pub fn write_rejection_notes(path: &Path, notes: &str) -> Result<(), String> {
let contents =
fs::read_to_string(path).map_err(|e| format!("Failed to read story file: {e}"))?;
// ── In-memory content variants (no filesystem access) ───────────────
/// Remove a key from the YAML front matter of a markdown string (pure function).
///
/// Returns the updated content. If no front matter or key is not found,
/// returns the original content unchanged.
pub fn clear_front_matter_field_in_content(contents: &str, key: &str) -> String {
remove_front_matter_field(contents, key)
}
/// Append rejection notes to a markdown string (pure function).
///
/// Returns the updated content with a `## QA Rejection Notes` section appended.
pub fn write_rejection_notes_to_content(contents: &str, notes: &str) -> String {
let section = format!("\n\n## QA Rejection Notes\n\n{notes}\n");
let updated = format!("{contents}{section}");
fs::write(path, &updated).map_err(|e| format!("Failed to write story file: {e}"))?;
Ok(())
format!("{contents}{section}")
}
/// Resolve the effective QA mode from story content (no filesystem access).
///
/// Parses front matter from `contents` and returns the `qa` field if present,
/// otherwise returns `default`.
pub fn resolve_qa_mode_from_content(contents: &str, default: QaMode) -> QaMode {
match parse_front_matter(contents) {
Ok(meta) => meta.qa.unwrap_or(default),
Err(_) => default,
}
}
/// Increment the `retry_count` field in story content (pure function).
///
/// Returns `(updated_content, new_count)`.
pub fn increment_retry_count_in_content(contents: &str) -> (String, u32) {
let current = parse_front_matter(contents)
.ok()
.and_then(|m| m.retry_count)
.unwrap_or(0);
let new_count = current + 1;
let updated = set_front_matter_field(contents, "retry_count", &new_count.to_string());
(updated, new_count)
}
/// Write `blocked: true` to story content (pure function).
pub fn write_blocked_in_content(contents: &str) -> String {
set_front_matter_field(contents, "blocked", "true")
}
/// Write or update `merge_failure` in story content (pure function).
pub fn write_merge_failure_in_content(contents: &str, reason: &str) -> String {
let escaped = reason.replace('"', "\\\"").replace('\n', " ").replace('\r', "");
let yaml_value = format!("\"{escaped}\"");
set_front_matter_field(contents, "merge_failure", &yaml_value)
}
/// Write `review_hold: true` to story content (pure function).
pub fn write_review_hold_in_content(contents: &str) -> String {
set_front_matter_field(contents, "review_hold", "true")
}
/// Resolve the effective QA mode for a story file.
@@ -442,16 +455,6 @@ workflow: tdd
assert_eq!(output, input);
}
#[test]
fn write_coverage_baseline_updates_file() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("story.md");
std::fs::write(&path, "---\nname: Test\n---\n# Story\n").unwrap();
write_coverage_baseline(&path, 82.3).unwrap();
let contents = std::fs::read_to_string(&path).unwrap();
assert!(contents.contains("coverage_baseline: 82.3%"));
}
#[test]
fn rejects_missing_front_matter() {
let input = "# Story 26\n";
@@ -691,14 +694,4 @@ workflow: tdd
assert!(!dep_is_done(tmp.path(), 101));
}
#[test]
fn write_rejection_notes_appends_section() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("story.md");
std::fs::write(&path, "---\nname: Test\n---\n# Story\n").unwrap();
write_rejection_notes(&path, "Button color is wrong").unwrap();
let contents = std::fs::read_to_string(&path).unwrap();
assert!(contents.contains("## QA Rejection Notes"));
assert!(contents.contains("Button color is wrong"));
}
}