From b8945654bfd6e7860c107c2724e74204832242a8 Mon Sep 17 00:00:00 2001 From: Timmy Date: Tue, 12 May 2026 18:54:32 +0100 Subject: [PATCH] =?UTF-8?q?wip(929):=20stage=203=20=E2=80=94=20migrate=20h?= =?UTF-8?q?ttp/mcp/*=20off=20yaml=5Flegacy=20+=20introduce=20yaml=5Fresidu?= =?UTF-8?q?e=20marker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three MCP files touched: - status_tools.rs (story-status JSON dump): every field with a CRDT equivalent now reads from WorkItem (name, agent, blocked, qa_mode, retry_count, depends_on, claimed_by, claimed_at) or MergeJob.error (merge_failure detail). One field — review_hold — has no CRDT register yet (sub-story 932) and is wrapped in `yaml_residue(parse_front_matter(...))` so the gap is visible at every code-search. - qa_tools.rs: • tool_approve_qa wraps the legacy `clear_front_matter_field("review_hold")` write in `yaml_residue(...)` pending sub-story 932. • tool_reject_qa now reads the agent name from the CRDT WorkItem instead of parsing front matter on disk. - story_tools/epic.rs: the entire epic feature (item_type, epic link) has no CRDT analog — sub-story 933. Every parse_front_matter call here is wrapped in `yaml_residue(...)`. Also: new identity wrapper `db::yaml_legacy::yaml_residue(v: T) -> T` that marks a yaml_legacy callsite blocked on a CRDT-register gap. Pure identity at runtime; the distinctive name makes the residue grep-findable (`grep -rn yaml_residue`). Sub-stories 932 and 933 enumerate the gaps. Filed: - 932: Add CRDT register for review_hold - 933: Add CRDT registers for the epic mechanism All 2854 tests pass; fmt + clippy clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- server/src/db/yaml_legacy.rs | 17 ++++++++ server/src/http/mcp/qa_tools.rs | 24 +++++------ server/src/http/mcp/status_tools.rs | 56 ++++++++++++------------- server/src/http/mcp/story_tools/epic.rs | 11 +++-- 4 files changed, 61 insertions(+), 47 deletions(-) diff --git a/server/src/db/yaml_legacy.rs b/server/src/db/yaml_legacy.rs index a9588645..3fccfd89 100644 --- a/server/src/db/yaml_legacy.rs +++ b/server/src/db/yaml_legacy.rs @@ -12,6 +12,23 @@ use serde::Deserialize; use std::fs; use std::path::Path; +/// Identity wrapper that flags a yaml_legacy callsite blocked on adding a +/// CRDT register (story 929 residue). Every wrap is a grep-findable marker — +/// `grep -rn yaml_residue` enumerates every remaining gap — so it stays +/// visible in every code review. +/// +/// When the CRDT register lands and the caller is migrated, delete the wrap. +/// Once every wrap is gone, delete this function and `db::yaml_legacy` +/// entirely (929 stage 10). +/// +/// Filed sub-stories enumerate each gap: +/// - 932: `review_hold` flag (write-side in qa_tools, read-side in +/// auto_assign). +/// - 933: epic mechanism — `item_type` and `epic` link fields. +pub fn yaml_residue(v: T) -> T { + v +} + /// Front-matter fields used by the legacy `parse_front_matter` API. Mirrors /// the original `io::story_metadata::FrontMatter`. #[derive(Debug, Default, Deserialize)] diff --git a/server/src/http/mcp/qa_tools.rs b/server/src/http/mcp/qa_tools.rs index e69cd709..beec1fc0 100644 --- a/server/src/http/mcp/qa_tools.rs +++ b/server/src/http/mcp/qa_tools.rs @@ -54,12 +54,16 @@ pub(super) async fn tool_approve_qa(args: &Value, ctx: &AppContext) -> Result Result Result 0 - { + let rc = view.retry_count(); + if rc > 0 { front_matter.insert("retry_count".to_string(), json!(rc)); } - if let Some(mf) = &meta.merge_failure { - front_matter.insert("merge_failure".to_string(), json!(mf)); - } - if let Some(rh) = meta.review_hold - && rh - { - front_matter.insert("review_hold".to_string(), json!(rh)); - } - if let Some(deps) = &meta.depends_on - && !deps.is_empty() - { + let deps = view.depends_on(); + if !deps.is_empty() { front_matter.insert("depends_on".to_string(), json!(deps)); } - } - - // --- CRDT view fields (claimed_by, claimed_at, is_deleted) --- - // read_item uses the visible index, so is_deleted is always false here; - // we include it only when true (which cannot happen for stories that - // pass the read_typed / 2_current check above, but the code is present - // for completeness and future-proofing). - if let Some(view) = crate::crdt_state::read_item(story_id) { if let Some(cb) = view.claimed_by() && !cb.is_empty() { @@ -227,6 +209,22 @@ pub(super) async fn tool_status(args: &Value, ctx: &AppContext) -> Result = parse_ac_items(&contents) .into_iter() diff --git a/server/src/http/mcp/story_tools/epic.rs b/server/src/http/mcp/story_tools/epic.rs index 3843dcf0..a76b68fb 100644 --- a/server/src/http/mcp/story_tools/epic.rs +++ b/server/src/http/mcp/story_tools/epic.rs @@ -4,7 +4,10 @@ //! and refactors. They are not pipeline-driven but provide authoritative context //! injected into agent prompts for all member work items. -use crate::db::yaml_legacy::parse_front_matter; +// Epic mechanism (item_type, epic link) has no CRDT register yet — story 933. +// parse_front_matter calls here are wrapped in `yaml_residue` so they're +// grep-findable until 933 lands. +use crate::db::yaml_legacy::{parse_front_matter, yaml_residue}; use crate::http::context::AppContext; use crate::http::workflow::create_epic_file; use serde_json::{Value, json}; @@ -62,7 +65,7 @@ pub(crate) fn tool_list_epics(_ctx: &AppContext) -> Result { Some(c) => c, None => continue, }; - let meta = match parse_front_matter(&content) { + let meta = match yaml_residue(parse_front_matter(&content)) { Ok(m) => m, Err(_) => continue, }; @@ -113,7 +116,7 @@ pub(crate) fn tool_show_epic(args: &Value, _ctx: &AppContext) -> Result Result c, None => continue, }; - let member_meta = match parse_front_matter(&member_content) { + let member_meta = match yaml_residue(parse_front_matter(&member_content)) { Ok(m) => m, Err(_) => continue, };