validate(['ticket_id' => ['required', 'integer']]); $user = $this->kanbanGitMcp->user($request); $ticket = $this->kanbanGitMcp->ticketForWrite($user, (int) $selector['ticket_id']); $validated = $this->ticketUpdateInput->validate($request, $ticket, $user); $updated = $this->updateProjectTicket->handle($ticket, $user, $validated, true); return Response::structured([ 'data' => $this->kanbanGitMcp->ticketPayload($ticket->fresh()), 'meta' => ['updated' => $updated], ]); } /** @return array */ public function schema(JsonSchema $schema): array { return [ 'ticket_id' => $schema->integer()->description('Existing ticket ID.')->required(), 'title' => $schema->string()->max(255)->description('Nonblank title; omit to preserve.'), 'description' => $schema->string()->nullable()->description('Description; null clears it. '.TicketDescriptionFormat::instructions()), 'ticket_type_id' => $schema->integer()->description('Type ID from get_project_configuration.'), 'ticket_priority_id' => $schema->integer()->description('Priority ID from get_project_configuration.'), 'parent_ticket_id' => $schema->integer()->nullable()->description('Parent-enabled ticket in this project; null removes the parent. Cycles are forbidden.'), 'assignee_user_id' => $schema->integer()->nullable()->description('Allowed person ID; null unassigns.'), 'author_user_id' => $schema->integer()->nullable()->description('Author ID; null clears. Changes require project admin permission.'), 'story_points' => $schema->integer()->min(0)->nullable()->description('Nonnegative estimate; null clears.'), 'is_blocked' => $schema->boolean()->description('Blocked state; true requires a nonblank blocker comment. Omit to preserve.'), 'blocked_comment' => $schema->string()->nullable()->description('Blocker reason; null clears only when the ticket is unblocked.'), 'acceptance_criteria' => $schema->array()->items($schema->object([ 'id' => $schema->integer()->min(1)->description('Existing criterion ID belonging to this ticket. Omit for a new item. IDs must be unique.'), 'text' => $schema->string()->description('Nonblank criterion text.')->required(), 'checked' => $schema->boolean()->description('Checked state, defaults to false for supplied items.'), 'indent' => $schema->integer()->min(0)->max(8)->description('Indent level, defaults to 0; cannot skip a level.'), ])->withoutAdditionalProperties())->description('Ordered replacement checklist. Omitted items are removed. [] clears; omit field to preserve; null is invalid. '.TicketDescriptionFormat::criteriaInstructions()), 'field_values' => $schema->array()->items($schema->object([ 'ticket_field_definition_id' => $schema->integer()->description('Project field ID, supplied once.')->required(), 'value_number' => $schema->number()->nullable()->description('Value for a numeric field; null clears.'), 'value_user_id' => $schema->integer()->nullable()->description('Allowed person ID for a user field; null clears.'), ])->withoutAdditionalProperties())->description('Replacement custom-field list. Omitted definitions are cleared. [] clears all; omit field to preserve; null is invalid.'), ]; } }