Ajoute le paramètre de tri dans TicketListQuery et l'expose via ticket_list (surface app-tauri + MCP). Ports et adaptateur mock frontend alignés sur le contrat de tri. La partie UI (contrôle de tri dans TicketFacetsBar) suivra dans un commit frontend dédié. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
267 lines
9.6 KiB
Rust
267 lines
9.6 KiB
Rust
//! Public MCP ticket tools.
|
|
//!
|
|
//! The public surface says `ticket`, while the provider behind this trait maps
|
|
//! to the application/domain `Issue` use cases.
|
|
|
|
use async_trait::async_trait;
|
|
use domain::Project;
|
|
use serde_json::{json, Value};
|
|
|
|
use super::tools::ToolDef;
|
|
|
|
/// Error returned by an MCP ticket tool provider.
|
|
#[derive(Debug, Clone, thiserror::Error)]
|
|
#[error("{code}: {message}")]
|
|
pub struct TicketToolError {
|
|
/// Stable machine-readable code.
|
|
pub code: &'static str,
|
|
/// Human-readable message.
|
|
pub message: String,
|
|
}
|
|
|
|
impl TicketToolError {
|
|
/// Builds a typed ticket-tool error.
|
|
#[must_use]
|
|
pub fn new(code: &'static str, message: impl Into<String>) -> Self {
|
|
Self {
|
|
code,
|
|
message: message.into(),
|
|
}
|
|
}
|
|
|
|
/// Serialises the error as a camelCase JSON object.
|
|
#[must_use]
|
|
pub fn to_value(&self) -> Value {
|
|
json!({ "code": self.code, "message": self.message })
|
|
}
|
|
}
|
|
|
|
/// Provider injected by the composition root to execute public ticket tools.
|
|
#[async_trait]
|
|
pub trait TicketToolProvider: Send + Sync {
|
|
/// Executes one public ticket/sprint planning tool for `project`.
|
|
async fn handle_ticket_tool(
|
|
&self,
|
|
project: &Project,
|
|
requester: &str,
|
|
name: &str,
|
|
arguments: Value,
|
|
) -> Result<Value, TicketToolError>;
|
|
}
|
|
|
|
/// Returns true when `name` is one of the public ticket tools.
|
|
#[must_use]
|
|
pub fn is_ticket_tool(name: &str) -> bool {
|
|
matches!(
|
|
name,
|
|
"idea_ticket_create"
|
|
| "idea_ticket_read"
|
|
| "idea_ticket_list"
|
|
| "idea_ticket_update"
|
|
| "idea_ticket_update_status"
|
|
| "idea_ticket_update_priority"
|
|
| "idea_ticket_read_carnet"
|
|
| "idea_ticket_update_carnet"
|
|
| "idea_ticket_link"
|
|
| "idea_ticket_unlink"
|
|
| "idea_sprint_list"
|
|
)
|
|
}
|
|
|
|
/// Public ticket tool definitions advertised by `tools/list`.
|
|
#[must_use]
|
|
pub fn catalogue() -> Vec<ToolDef> {
|
|
let ticket_ref = json!({ "type": "string", "pattern": "^#[1-9][0-9]*$" });
|
|
let status = json!({ "type": "string", "enum": ["open", "inProgress", "QA", "closed"] });
|
|
let priority = json!({ "type": "string", "enum": ["low", "medium", "high", "critical"] });
|
|
let sort = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"field": { "type": "string", "enum": ["number", "priority", "status", "title"] },
|
|
"direction": { "type": "string", "enum": ["asc", "desc"] }
|
|
},
|
|
"required": ["field", "direction"],
|
|
"additionalProperties": false
|
|
});
|
|
let link_kind = json!({
|
|
"type": "string",
|
|
"enum": ["relatesTo", "blocks", "blockedBy", "duplicates", "dependsOn"]
|
|
});
|
|
let link = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"targetRef": ticket_ref.clone(),
|
|
"kind": link_kind.clone()
|
|
},
|
|
"required": ["targetRef", "kind"],
|
|
"additionalProperties": false
|
|
});
|
|
|
|
vec![
|
|
ToolDef {
|
|
name: "idea_ticket_create",
|
|
description: "Create an IdeA ticket in the current project and return it as JSON.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"title": { "type": "string" },
|
|
"description": { "type": "string" },
|
|
"priority": priority.clone(),
|
|
"status": status.clone(),
|
|
"assignedAgentIds": { "type": "array", "items": { "type": "string", "format": "uuid" } },
|
|
"links": { "type": "array", "items": link.clone() }
|
|
},
|
|
"required": ["title"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_read",
|
|
description: "Read one IdeA ticket by public reference (#N).",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"ref": ticket_ref.clone(),
|
|
"includeCarnet": { "type": "boolean" }
|
|
},
|
|
"required": ["ref"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_list",
|
|
description: "List IdeA tickets in the current project.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"statuses": {
|
|
"type": "array",
|
|
"items": status.clone(),
|
|
"uniqueItems": true
|
|
},
|
|
"priorities": {
|
|
"type": "array",
|
|
"items": priority.clone(),
|
|
"uniqueItems": true
|
|
},
|
|
"assignedAgentId": { "type": "string", "format": "uuid" },
|
|
"text": { "type": "string" },
|
|
"sort": sort.clone(),
|
|
"limit": { "type": "integer", "minimum": 1 },
|
|
"cursor": { "type": "string" }
|
|
},
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_update",
|
|
description: "Update ticket fields using optimistic concurrency.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"ref": ticket_ref.clone(),
|
|
"title": { "type": "string" },
|
|
"description": { "type": "string" },
|
|
"status": status.clone(),
|
|
"priority": priority.clone(),
|
|
"assignedAgentIds": { "type": "array", "items": { "type": "string", "format": "uuid" } },
|
|
"expectedVersion": { "type": "integer", "minimum": 1 }
|
|
},
|
|
"required": ["ref", "expectedVersion"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_update_status",
|
|
description: "Update a ticket status using optimistic concurrency.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"ref": ticket_ref.clone(),
|
|
"status": status.clone(),
|
|
"expectedVersion": { "type": "integer", "minimum": 1 }
|
|
},
|
|
"required": ["ref", "status", "expectedVersion"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_update_priority",
|
|
description: "Update a ticket priority using optimistic concurrency.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"ref": ticket_ref.clone(),
|
|
"priority": priority.clone(),
|
|
"expectedVersion": { "type": "integer", "minimum": 1 }
|
|
},
|
|
"required": ["ref", "priority", "expectedVersion"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_read_carnet",
|
|
description: "Read the editable carnet attached to a ticket.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": { "ref": ticket_ref.clone() },
|
|
"required": ["ref"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_update_carnet",
|
|
description: "Update a ticket carnet using optimistic concurrency.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"ref": ticket_ref.clone(),
|
|
"carnet": { "type": "string" },
|
|
"expectedVersion": { "type": "integer", "minimum": 1 }
|
|
},
|
|
"required": ["ref", "carnet", "expectedVersion"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_link",
|
|
description: "Add a link from one ticket to another using optimistic concurrency.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"ref": ticket_ref.clone(),
|
|
"targetRef": ticket_ref.clone(),
|
|
"kind": link_kind.clone(),
|
|
"expectedVersion": { "type": "integer", "minimum": 1 }
|
|
},
|
|
"required": ["ref", "targetRef", "kind", "expectedVersion"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_ticket_unlink",
|
|
description: "Remove a link from one ticket to another using optimistic concurrency.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"ref": ticket_ref.clone(),
|
|
"targetRef": ticket_ref,
|
|
"kind": link_kind,
|
|
"expectedVersion": { "type": "integer", "minimum": 1 }
|
|
},
|
|
"required": ["ref", "targetRef", "expectedVersion"],
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
ToolDef {
|
|
name: "idea_sprint_list",
|
|
description: "List IdeA sprints in execution order. Read-only.",
|
|
input_schema: json!({
|
|
"type": "object",
|
|
"properties": {},
|
|
"additionalProperties": false
|
|
}),
|
|
},
|
|
]
|
|
}
|