wip(929): stage 1 — migrate chat/commands/* off yaml_legacy

Each chat command that previously read parse_front_matter for story
metadata (name, agent, depends_on, blocked, retry_count, merge_failure,
qa_mode) now reads from the typed CRDT API:

- WorkItem (via crdt_state::read_item) for pipeline-item registers.
- MergeJobView (via crdt_state::read_merge_job) for the merge failure
  detail text, which has its own LWW-map CRDT entry.

Files migrated: depends.rs, freeze.rs, move_story.rs, overview.rs,
status/render.rs, triage.rs, unblock.rs, unreleased.rs.

unblock.rs: also removes the legacy front-matter cleanup branch that
fired when the typed Blocked→Coding transition failed. Post-929 there
is no YAML on disk to clean; the fallback now just resets retry_count
in the CRDT.

triage.rs: drops the YAML-only `review_hold` and `coverage_baseline`
fields from the dump. These have no CRDT register and were never
load-bearing on the triage output; if needed later, add a CRDT register
and surface it back.

Tests:
- The three status/render merge-failure rendering tests now seed a
  MergeJob CRDT entry via write_merge_job instead of writing YAML.
- The unblock test that asserted YAML cleanup on disk is now an assertion
  on the CRDT registers (blocked=false, retry_count=0). Also re-seeded
  in `2_blocked` stage so the typed Blocked → Coding transition actually
  fires (not the fallback path).

All 2855 tests pass; fmt clean; clippy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-05-12 18:41:43 +01:00
parent b940b95ec3
commit a49a1cf7cb
9 changed files with 88 additions and 180 deletions
+16 -51
View File
@@ -5,7 +5,6 @@
//! and returns a confirmation.
use super::CommandContext;
use crate::db::yaml_legacy::clear_front_matter_field_in_content;
use std::path::Path;
/// Handle the `unblock` command.
@@ -80,37 +79,14 @@ fn unblock_by_story_id(story_id: &str) -> String {
// the CRDT.
if let Err(e) = crate::agents::lifecycle::transition_to_unblocked(story_id) {
// If the typed transition fails (e.g. a legacy Archived item with no
// valid `Unblock` transition out of its current stage), fall back to
// a direct CRDT/content cleanup. The legacy front-matter cleanup is
// gated on content actually still containing YAML, so post-865
// CRDT-only stories don't hit a parse error.
// valid `Unblock` transition out of its current stage), at least
// reset retry_count directly in the CRDT so the agent doesn't stay
// tagged with a stale fail counter. Post-929 there's no FS shadow
// to clean up alongside.
crate::slog_warn!(
"[unblock] State-machine transition failed for '{story_id}': {e}. \
Falling back to direct CRDT cleanup."
Falling back to direct CRDT retry_count reset."
);
if let Some(content) = crate::db::read_content(story_id) {
// Only run legacy front-matter cleanup if the stored content still
// begins with a `---` YAML block. Post-865 content has been
// stripped and would no-op here anyway.
if content.trim_start().starts_with("---") {
let mut updated = content;
updated = clear_front_matter_field_in_content(&updated, "blocked");
updated = clear_front_matter_field_in_content(&updated, "merge_failure");
updated = clear_front_matter_field_in_content(&updated, "retry_count");
crate::db::write_content(story_id, &updated);
let stage = typed_item
.as_ref()
.map(|i| i.stage.dir_name().to_string())
.unwrap_or_else(|| "2_current".to_string());
crate::db::write_item_with_content(
story_id,
&stage,
&updated,
crate::db::ItemMeta::from_yaml(&updated),
);
}
}
crate::crdt_state::set_retry_count(story_id, 0);
}
@@ -221,14 +197,15 @@ mod tests {
// global content store.
write_story_file(
tmp.path(),
"2_current",
"2_blocked",
"9903_story_stuck.md",
"---\nname: Stuck Story\nblocked: true\nretry_count: 5\n---\n# Story\n",
);
// Seed the story in the CRDT with retry_count=5 so set_retry_count can reset it.
// Seed the story in the CRDT in 2_blocked stage so the typed
// Blocked → Coding transition fires and clears `blocked` properly.
crate::crdt_state::write_item(
"9903_story_stuck",
"2_current",
"2_blocked",
Some("Stuck Story"),
None,
Some(5),
@@ -249,25 +226,9 @@ mod tests {
"should include story_id in response: {output}"
);
// The unblock command writes back via the content store; blocked field should be gone.
let contents = crate::db::read_content("9903_story_stuck")
.or_else(|| {
std::fs::read_to_string(
tmp.path()
.join(".huskies/work/2_current/9903_story_stuck.md"),
)
.ok()
})
.expect("story content should be readable after unblock");
assert!(
!contents.contains("blocked:"),
"blocked field should be removed: {contents}"
);
// retry_count is now in the CRDT, not in front-matter.
assert!(
!contents.contains("retry_count:"),
"retry_count should be cleared from front-matter after unblock: {contents}"
);
// Post-929: the CRDT is the sole source of truth; we no longer clean
// YAML front matter from on-disk content. Verify the CRDT registers
// were updated correctly.
let item = crate::crdt_state::read_item("9903_story_stuck")
.expect("story should be in CRDT after unblock");
assert_eq!(
@@ -275,6 +236,10 @@ mod tests {
0,
"retry_count should be reset to 0 in CRDT after unblock"
);
assert!(
!item.blocked(),
"blocked flag should be cleared in CRDT after unblock"
);
}
#[test]