wip(929): stage 10 sweep — production callsites move to CRDT, yaml_legacy shrinks

After 932 (review_hold register) and 933 (item_type + epic registers), the
remaining production yaml_legacy callers all had typed CRDT equivalents.
Migrated:

- agents/lifecycle.rs:
  - transition_to_merge_failure writes to MergeJob.error CRDT entry instead
    of YAML body. The legacy `merge_failure: "..."` front-matter write is gone.
  - reject_story_from_qa inlines the QA-rejection notes append; no longer
    needs yaml_legacy::write_rejection_notes_to_content.
  - fields_to_clear_transform helper deleted along with all five callers —
    blocked/retry_count/merge_failure are typed CRDT fields now, so clearing
    the equivalent YAML keys is redundant.

- http/workflow/pipeline.rs:
  - load_pipeline_state reads merge_failure from MergeJob.error (mirrors
    status_tools.rs).
  - validate_story_dirs checks the typed CRDT `name` register instead of
    parsing YAML front matter.

- http/mcp/status_tools.rs: review_hold reads the typed CRDT register
  (yaml_residue wrap was the last one in this file).
- http/mcp/story_tools/criteria.rs: story_name reads from CRDT.
- service/agents/mod.rs::get_work_item_content: name/agent come from CRDT.
- service/notifications/io/mod.rs::read_story_name: same.
- http/workflow/bug_ops/{bug,refactor}.rs: name-fallback paths drop YAML
  parsing in favour of the CRDT-derived item.name.

Dead helpers removed from db/yaml_legacy.rs:
  yaml_residue, write_merge_failure_in_content, write_rejection_notes_to_content,
  clear_front_matter_field_in_content, write_review_hold_in_content,
  clear_front_matter_field, write_review_hold (the last four shipped in 932).
Remaining surface: FrontMatter / StoryMetadata structs, parse_front_matter,
set_front_matter_field — kept for `coverage_baseline` writes via
test_results.rs and the generic update_story front_matter escape hatch.

Test fixtures rewritten to seed the CRDT register instead of relying on
YAML parsing during write_item_with_content:
- has_review_hold_returns_* tests
- item_type_from_id_uses_crdt_register_for_numeric_ids
- tool_list_epics_shows_member_rollup
- get_work_item_content (both copies — http/agents + service/agents)
- validate_story_dirs_missing_name_in_crdt
- server_side_merge_*_sets_merge_failure (assert MergeJob.error, not YAML)

cargo fmt --check, clippy --all-targets -- -D warnings, and the
2856-test suite all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-05-12 20:13:17 +01:00
parent 7d7ab85994
commit 4888f051c3
14 changed files with 132 additions and 206 deletions
+26 -6
View File
@@ -183,14 +183,21 @@ pub fn get_work_item_content(
let work_dir = project_root.join(".huskies").join("work");
let filename = format!("{story_id}.md");
let crdt_view = crate::crdt_state::read_item(story_id);
let crdt_name = crdt_view
.as_ref()
.and_then(|v| v.name().map(str::to_string));
let crdt_agent = crdt_view
.as_ref()
.and_then(|v| v.agent().map(str::to_string));
for (stage_dir, stage_name) in &stages {
if let Some(content) = io::read_work_item_from_stage(&work_dir, stage_dir, &filename)? {
let metadata = crate::db::yaml_legacy::parse_front_matter(&content).ok();
return Ok(WorkItemContent {
content,
stage: stage_name.to_string(),
name: metadata.as_ref().and_then(|m| m.name.clone()),
agent: metadata.and_then(|m| m.agent),
name: crdt_name.clone(),
agent: crdt_agent.clone(),
});
}
}
@@ -215,12 +222,11 @@ pub fn get_work_item_content(
})
.unwrap_or("unknown")
.to_string();
let metadata = crate::db::yaml_legacy::parse_front_matter(&content).ok();
return Ok(WorkItemContent {
content,
stage,
name: metadata.as_ref().and_then(|m| m.name.clone()),
agent: metadata.and_then(|m| m.agent),
name: crdt_name,
agent: crdt_agent,
});
}
@@ -329,6 +335,7 @@ max_budget_usd = 5.0
#[test]
fn get_work_item_content_reads_from_backlog() {
crate::crdt_state::init_for_test();
let tmp = TempDir::new().unwrap();
make_stage_dirs(&tmp);
write_story_file(
@@ -336,6 +343,19 @@ max_budget_usd = 5.0
".huskies/work/1_backlog/42_story_foo.md",
"---\nname: \"Foo Story\"\n---\n\nSome content.",
);
// Story 929: name lives in the CRDT register.
crate::crdt_state::write_item(
"42_story_foo",
"1_backlog",
Some("Foo Story"),
None,
None,
None,
None,
None,
None,
None,
);
let item = get_work_item_content(tmp.path(), "42_story_foo").unwrap();
assert!(item.content.contains("Some content."));
assert_eq!(item.stage, "backlog");
+3 -6
View File
@@ -4,7 +4,6 @@
//! side effects: reading from the CRDT content store, loading configuration,
//! and spawning the background listener task.
use crate::db::yaml_legacy::parse_front_matter;
use std::path::Path;
mod listener;
@@ -17,13 +16,11 @@ mod tests_notifications;
#[cfg(test)]
mod tests_stage;
/// Read the story name from the CRDT content store's YAML front matter.
/// Read the story name from the typed CRDT register (story 929).
///
/// Returns `None` if the item is not in the content store or has no parseable name.
/// Returns `None` if the item is not in the CRDT or has no name set.
pub fn read_story_name(_project_root: &Path, _stage: &str, item_id: &str) -> Option<String> {
let contents = crate::db::read_content(item_id)?;
let meta = parse_front_matter(&contents).ok()?;
meta.name
crate::crdt_state::read_item(item_id).and_then(|v| v.name().map(str::to_string))
}
/// Look up a story name from the CRDT content store regardless of stage.