diff --git a/src/compile/common.rs b/src/compile/common.rs index 71e1fa6..6304b47 100644 --- a/src/compile/common.rs +++ b/src/compile/common.rs @@ -1176,13 +1176,13 @@ pub fn validate_update_pr_votes(front_matter: &FrontMatter) -> Result<()> { Ok(()) } -/// Validate that resolve-pr-review-thread has a required `allowed-statuses` field when configured. +/// Validate that resolve-pr-thread has a required `allowed-statuses` field when configured. /// /// An empty or missing `allowed-statuses` list would let agents set any thread status, /// including "fixed" or "wontFix" on security-critical review threads. Operators must /// explicitly opt in to each allowed status transition. pub fn validate_resolve_pr_thread_statuses(front_matter: &FrontMatter) -> Result<()> { - if let Some(config_value) = front_matter.safe_outputs.get("resolve-pr-review-thread") { + if let Some(config_value) = front_matter.safe_outputs.get("resolve-pr-thread") { if let Some(obj) = config_value.as_object() { let allowed_statuses = obj.get("allowed-statuses"); let is_empty = match allowed_statuses { @@ -1191,19 +1191,19 @@ pub fn validate_resolve_pr_thread_statuses(front_matter: &FrontMatter) -> Result }; if is_empty { anyhow::bail!( - "safe-outputs.resolve-pr-review-thread requires a non-empty \ + "safe-outputs.resolve-pr-thread requires a non-empty \ 'allowed-statuses' list to prevent agents from manipulating thread \ statuses without explicit operator consent. Example:\n\n \ - safe-outputs:\n resolve-pr-review-thread:\n allowed-statuses:\n\ + safe-outputs:\n resolve-pr-thread:\n allowed-statuses:\n\ \x20 - fixed\n\n\ Valid statuses: active, fixed, wont-fix, closed, by-design\n" ); } } else { anyhow::bail!( - "safe-outputs.resolve-pr-review-thread must be a configuration object \ + "safe-outputs.resolve-pr-thread must be a configuration object \ with an 'allowed-statuses' list. Example:\n\n \ - safe-outputs:\n resolve-pr-review-thread:\n allowed-statuses:\n\ + safe-outputs:\n resolve-pr-thread:\n allowed-statuses:\n\ \x20 - fixed\n" ); } @@ -2854,7 +2854,7 @@ mod tests { #[test] fn test_resolve_pr_thread_fails_when_allowed_statuses_missing() { let (fm, _) = parse_markdown( - "---\nname: test\ndescription: test\nsafe-outputs:\n resolve-pr-review-thread:\n allowed-repositories:\n - self\n---\n" + "---\nname: test\ndescription: test\nsafe-outputs:\n resolve-pr-thread:\n allowed-repositories:\n - self\n---\n" ).unwrap(); let result = validate_resolve_pr_thread_statuses(&fm); assert!(result.is_err()); @@ -2865,7 +2865,7 @@ mod tests { #[test] fn test_resolve_pr_thread_fails_when_allowed_statuses_empty() { let (fm, _) = parse_markdown( - "---\nname: test\ndescription: test\nsafe-outputs:\n resolve-pr-review-thread:\n allowed-statuses: []\n---\n" + "---\nname: test\ndescription: test\nsafe-outputs:\n resolve-pr-thread:\n allowed-statuses: []\n---\n" ).unwrap(); let result = validate_resolve_pr_thread_statuses(&fm); assert!(result.is_err()); @@ -2876,7 +2876,7 @@ mod tests { #[test] fn test_resolve_pr_thread_fails_when_value_is_scalar() { let (fm, _) = parse_markdown( - "---\nname: test\ndescription: test\nsafe-outputs:\n resolve-pr-review-thread: true\n---\n" + "---\nname: test\ndescription: test\nsafe-outputs:\n resolve-pr-thread: true\n---\n" ).unwrap(); let result = validate_resolve_pr_thread_statuses(&fm); assert!(result.is_err()); @@ -2885,7 +2885,7 @@ mod tests { #[test] fn test_resolve_pr_thread_passes_when_statuses_provided() { let (fm, _) = parse_markdown( - "---\nname: test\ndescription: test\nsafe-outputs:\n resolve-pr-review-thread:\n allowed-statuses:\n - fixed\n - wont-fix\n---\n" + "---\nname: test\ndescription: test\nsafe-outputs:\n resolve-pr-thread:\n allowed-statuses:\n - fixed\n - wont-fix\n---\n" ).unwrap(); assert!(validate_resolve_pr_thread_statuses(&fm).is_ok()); } diff --git a/src/execute.rs b/src/execute.rs index 80a8fc9..e615b97 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -378,12 +378,12 @@ pub async fn execute_safe_output( ); output.execute_sanitized(ctx).await? } - "resolve-pr-review-thread" => { - debug!("Parsing resolve-pr-review-thread payload"); + "resolve-pr-thread" => { + debug!("Parsing resolve-pr-thread payload"); let mut output: ResolvePrThreadResult = serde_json::from_value(entry.clone()) - .map_err(|e| anyhow::anyhow!("Failed to parse resolve-pr-review-thread: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to parse resolve-pr-thread: {}", e))?; debug!( - "resolve-pr-review-thread: pr_id={}, thread_id={}, status='{}'", + "resolve-pr-thread: pr_id={}, thread_id={}, status='{}'", output.pull_request_id, output.thread_id, output.status ); output.execute_sanitized(ctx).await? diff --git a/src/mcp.rs b/src/mcp.rs index 4af854b..2778451 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -1033,17 +1033,17 @@ Provide the PR ID, thread ID, and reply content. The reply will be posted during } #[tool( - name = "resolve-pr-review-thread", + name = "resolve-pr-thread", description = "Resolve or change the status of a review thread on an Azure DevOps pull request. \ Valid statuses: fixed, wont-fix, closed, by-design, active. \ The status change will be applied during safe output processing." )] - async fn resolve_pr_review_thread( + async fn resolve_pr_thread( &self, params: Parameters, ) -> Result { info!( - "Tool called: resolve-pr-review-thread - PR #{} thread #{} → '{}'", + "Tool called: resolve-pr-thread - PR #{} thread #{} → '{}'", params.0.pull_request_id, params.0.thread_id, params.0.status ); let result: ResolvePrThreadResult = params.0.try_into()?; diff --git a/src/safeoutputs/resolve_pr_thread.rs b/src/safeoutputs/resolve_pr_thread.rs index c6f215c..107c9ad 100644 --- a/src/safeoutputs/resolve_pr_thread.rs +++ b/src/safeoutputs/resolve_pr_thread.rs @@ -75,7 +75,7 @@ impl Validate for ResolvePrThreadParams { } tool_result! { - name = "resolve-pr-review-thread", + name = "resolve-pr-thread", write = true, params = ResolvePrThreadParams, /// Result of resolving or reactivating a PR review thread @@ -96,12 +96,12 @@ impl SanitizeContent for ResolvePrThreadResult { } } -/// Configuration for the resolve-pr-review-thread tool (specified in front matter) +/// Configuration for the resolve-pr-thread tool (specified in front matter) /// /// Example front matter: /// ```yaml /// safe-outputs: -/// resolve-pr-review-thread: +/// resolve-pr-thread: /// allowed-repositories: /// - self /// - other-repo @@ -153,7 +153,7 @@ impl Executor for ResolvePrThreadResult { .context("No access token available (SYSTEM_ACCESSTOKEN or AZURE_DEVOPS_EXT_PAT)")?; debug!("ADO org: {}, project: {}", org_url, project); - let config: ResolvePrThreadConfig = ctx.get_tool_config("resolve-pr-review-thread"); + let config: ResolvePrThreadConfig = ctx.get_tool_config("resolve-pr-thread"); debug!("Config: {:?}", config); // Validate status against allowed-statuses — REQUIRED. @@ -162,10 +162,10 @@ impl Executor for ResolvePrThreadResult { // concerns as "fixed") without explicit operator consent. if config.allowed_statuses.is_empty() { return Ok(ExecutionResult::failure( - "resolve-pr-review-thread requires 'allowed-statuses' to be configured in \ - safe-outputs.resolve-pr-review-thread. This prevents agents from \ + "resolve-pr-thread requires 'allowed-statuses' to be configured in \ + safe-outputs.resolve-pr-thread. This prevents agents from \ manipulating thread statuses without explicit operator consent. Example:\n \ - safe-outputs:\n resolve-pr-review-thread:\n allowed-statuses:\n \ + safe-outputs:\n resolve-pr-thread:\n allowed-statuses:\n \ - fixed\n\nValid statuses: active, fixed, wont-fix, closed, by-design" .to_string(), )); @@ -291,7 +291,7 @@ mod tests { #[test] fn test_result_has_correct_name() { - assert_eq!(ResolvePrThreadResult::NAME, "resolve-pr-review-thread"); + assert_eq!(ResolvePrThreadResult::NAME, "resolve-pr-thread"); } #[test] @@ -314,7 +314,7 @@ mod tests { repository: Some("self".to_string()), }; let result: ResolvePrThreadResult = params.try_into().unwrap(); - assert_eq!(result.name, "resolve-pr-review-thread"); + assert_eq!(result.name, "resolve-pr-thread"); assert_eq!(result.pull_request_id, 42); assert_eq!(result.thread_id, 7); assert_eq!(result.status, "fixed"); @@ -367,7 +367,7 @@ mod tests { let result: ResolvePrThreadResult = params.try_into().unwrap(); let json = serde_json::to_string(&result).unwrap(); - assert!(json.contains(r#""name":"resolve-pr-review-thread""#)); + assert!(json.contains(r#""name":"resolve-pr-thread""#)); assert!(json.contains(r#""pull_request_id":42"#)); assert!(json.contains(r#""thread_id":7"#)); }