story-kit: merge 306_story_replace_manual_qa_boolean_with_configurable_qa_mode_field

This commit is contained in:
Dave
2026-03-19 11:56:39 +00:00
parent a058fa5f19
commit 2067abb2e5
12 changed files with 418 additions and 125 deletions

View File

@@ -639,7 +639,7 @@ fn handle_tools_list(id: Option<Value>) -> JsonRpcResponse {
},
{
"name": "update_story",
"description": "Update an existing story file. Can replace the '## User Story' and/or '## Description' section content, and/or set YAML front matter fields (e.g. agent, manual_qa). Auto-commits via the filesystem watcher.",
"description": "Update an existing story file. Can replace the '## User Story' and/or '## Description' section content, and/or set YAML front matter fields (e.g. agent, qa). Auto-commits via the filesystem watcher.",
"inputSchema": {
"type": "object",
"properties": {

View File

@@ -27,9 +27,9 @@ pub struct UpcomingStory {
/// True when the item is held in QA for human review.
#[serde(skip_serializing_if = "Option::is_none")]
pub review_hold: Option<bool>,
/// Whether the item requires manual QA (defaults to true when absent).
/// QA mode for this item: "human", "server", or "agent".
#[serde(skip_serializing_if = "Option::is_none")]
pub manual_qa: Option<bool>,
pub qa: Option<String>,
}
pub struct StoryValidationResult {
@@ -123,12 +123,12 @@ fn load_stage_items(
.to_string();
let contents = fs::read_to_string(&path)
.map_err(|e| format!("Failed to read story file {}: {e}", path.display()))?;
let (name, error, merge_failure, review_hold, manual_qa) = match parse_front_matter(&contents) {
Ok(meta) => (meta.name, None, meta.merge_failure, meta.review_hold, meta.manual_qa),
let (name, error, merge_failure, review_hold, qa) = match parse_front_matter(&contents) {
Ok(meta) => (meta.name, None, meta.merge_failure, meta.review_hold, meta.qa.map(|m| m.as_str().to_string())),
Err(e) => (None, Some(e.to_string()), None, None, None),
};
let agent = agent_map.get(&story_id).cloned();
stories.push(UpcomingStory { story_id, name, error, merge_failure, agent, review_hold, manual_qa });
stories.push(UpcomingStory { story_id, name, error, merge_failure, agent, review_hold, qa });
}
stories.sort_by(|a, b| a.story_id.cmp(&b.story_id));
@@ -1691,12 +1691,12 @@ mod tests {
fs::write(&filepath, "---\nname: T\n---\n\n## User Story\n\nSome story\n").unwrap();
let mut fields = HashMap::new();
fields.insert("manual_qa".to_string(), "true".to_string());
fields.insert("qa".to_string(), "human".to_string());
fields.insert("priority".to_string(), "high".to_string());
update_story_in_file(tmp.path(), "25_test", None, None, Some(&fields)).unwrap();
let result = fs::read_to_string(&filepath).unwrap();
assert!(result.contains("manual_qa: \"true\""), "manual_qa field should be set");
assert!(result.contains("qa: \"human\""), "qa field should be set");
assert!(result.contains("priority: \"high\""), "priority field should be set");
assert!(result.contains("name: T"), "name field preserved");
}

View File

@@ -738,7 +738,7 @@ mod tests {
merge_failure: None,
agent: None,
review_hold: None,
manual_qa: None,
qa: None,
};
let resp = WsResponse::PipelineState {
backlog: vec![story],
@@ -877,7 +877,7 @@ mod tests {
merge_failure: None,
agent: None,
review_hold: None,
manual_qa: None,
qa: None,
}],
current: vec![UpcomingStory {
story_id: "2_story_b".to_string(),
@@ -886,7 +886,7 @@ mod tests {
merge_failure: None,
agent: None,
review_hold: None,
manual_qa: None,
qa: None,
}],
qa: vec![],
merge: vec![],
@@ -897,7 +897,7 @@ mod tests {
merge_failure: None,
agent: None,
review_hold: None,
manual_qa: None,
qa: None,
}],
};
let resp: WsResponse = state.into();
@@ -1055,7 +1055,7 @@ mod tests {
status: "running".to_string(),
}),
review_hold: None,
manual_qa: None,
qa: None,
}],
qa: vec![],
merge: vec![],