fix: read agent pin from CRDT register, not just YAML front matter
After story 871 the `agent` pin lives in the typed CRDT register (`PipelineItemView.agent`), not the YAML front matter — the YAML mutation was removed at the same time. Both spawn-resolution paths (`auto_assign::story_checks::read_story_front_matter_agent` and `start::validation::read_front_matter_agent`) still read only YAML via parse_front_matter, which returns None for any story whose pin was set via the post-871 typed setter. The spawn then falls back to "first available coder," silently downgrading opus-pinned stories to the first available sonnet — which is why 855/864/866 kept hitting the 80-turn watchdog limit despite the user's explicit opus pin. Now: both paths consult `crdt_state::read_item()` first and use `view.agent` if non-empty. YAML parsing remains as a fallback so older stories whose CRDT entry doesn't yet have the field still resolve. Adds a regression test that seeds an item with empty YAML, sets the typed CRDT register via `set_agent`, and asserts `read_story_front_matter_agent` returns the CRDT value.
This commit is contained in:
@@ -7,15 +7,23 @@ fn read_story_contents(_project_root: &Path, story_id: &str) -> Option<String> {
|
||||
crate::db::read_content(story_id)
|
||||
}
|
||||
|
||||
/// Read the optional `agent:` field from the front matter of a story file.
|
||||
/// Read the optional `agent:` pin for a story.
|
||||
///
|
||||
/// 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.
|
||||
/// After story 871 the agent assignment lives in the CRDT typed register
|
||||
/// (`PipelineItemView.agent`), not the YAML front matter. We check the CRDT
|
||||
/// first; falling back to legacy YAML parsing keeps behaviour intact for any
|
||||
/// stories whose CRDT entry doesn't yet have the field set.
|
||||
pub(super) fn read_story_front_matter_agent(
|
||||
project_root: &Path,
|
||||
_stage_dir: &str,
|
||||
story_id: &str,
|
||||
) -> Option<String> {
|
||||
if let Some(view) = crate::crdt_state::read_item(story_id)
|
||||
&& let Some(agent) = view.agent.as_ref()
|
||||
&& !agent.is_empty()
|
||||
{
|
||||
return Some(agent.clone());
|
||||
}
|
||||
use crate::io::story_metadata::parse_front_matter;
|
||||
let contents = read_story_contents(project_root, story_id)?;
|
||||
parse_front_matter(&contents).ok()?.agent
|
||||
@@ -293,4 +301,29 @@ mod tests {
|
||||
check_archived_dependencies(tmp.path(), "1_backlog", "503_story_waiting");
|
||||
assert!(archived_deps.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_story_front_matter_agent_prefers_crdt_typed_register_over_yaml() {
|
||||
// Regression: after story 871 the agent pin lives in the CRDT typed
|
||||
// register, not the YAML front matter. read_story_front_matter_agent
|
||||
// must consult the register first so auto-assign honours the pin.
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
crate::db::ensure_content_store();
|
||||
|
||||
// Seed a story whose YAML has NO agent field.
|
||||
crate::db::write_item_with_content(
|
||||
"9971_story_pin_in_crdt",
|
||||
"2_current",
|
||||
"---\nname: Pin In CRDT\n---\n",
|
||||
);
|
||||
|
||||
// Set the typed CRDT register (this is the path 871's migration uses).
|
||||
let written =
|
||||
crate::crdt_state::set_agent("9971_story_pin_in_crdt", Some("coder-opus"));
|
||||
assert!(written, "set_agent should succeed for an existing item");
|
||||
|
||||
// The reader must return the CRDT register value, not None.
|
||||
let agent = read_story_front_matter_agent(tmp.path(), "2_current", "9971_story_pin_in_crdt");
|
||||
assert_eq!(agent.as_deref(), Some("coder-opus"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user