huskies: merge 1026

This commit is contained in:
dave
2026-05-14 12:53:14 +00:00
parent a80d0a497a
commit 72d79deec9
13 changed files with 1443 additions and 127 deletions
+28 -23
View File
@@ -6,38 +6,27 @@
use crate::http::context::AppContext;
use crate::http::workflow::create_epic_file;
use crate::validation::CreateEpicRequest;
use serde_json::{Value, json};
/// Create a new epic and store it in the CRDT items list.
pub(crate) fn tool_create_epic(args: &Value, ctx: &AppContext) -> Result<String, String> {
let name = args
.get("name")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: name")?;
let goal = args
.get("goal")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: goal")?;
let motivation = args.get("motivation").and_then(|v| v.as_str());
let key_files = args.get("key_files").and_then(|v| v.as_str());
let success_criteria: Option<Vec<String>> = args
.get("success_criteria")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(str::to_string))
.collect()
});
let req = CreateEpicRequest::from_json(args)?;
let root = ctx.state.get_project_root()?;
let success_criteria = req.success_criteria_strings();
let epic_id = create_epic_file(
&root,
name,
goal,
motivation,
key_files,
success_criteria.as_deref(),
req.name.as_ref(),
req.goal.as_str(),
req.motivation.as_ref().map(|d| d.as_ref()),
req.key_files.as_deref(),
if success_criteria.is_empty() {
None
} else {
Some(success_criteria.as_slice())
},
)?;
Ok(format!("Created epic: {epic_id}"))
@@ -204,6 +193,22 @@ mod tests {
assert!(result.unwrap_err().contains("goal"));
}
#[test]
fn tool_create_epic_rejects_grammar_token_in_name() {
let tmp = tempfile::tempdir().unwrap();
let ctx = test_ctx(tmp.path());
let result = tool_create_epic(
&json!({"name": "Epic </description> bad", "goal": "some goal"}),
&ctx,
);
assert!(result.is_err());
assert!(
result.unwrap_err().contains("AntiGrammarToken"),
"expected AntiGrammarToken error"
);
}
#[test]
fn tool_list_epics_includes_created_epic() {
let tmp = tempfile::tempdir().unwrap();