diff --git a/gitea-client.zsh b/gitea-client.zsh new file mode 100755 index 0000000..0fd4c45 --- /dev/null +++ b/gitea-client.zsh @@ -0,0 +1,213 @@ +#!/usr/bin/env zsh + +# ============================================================================== +# Gitea Client Wrapper (gitea-client.zsh) - FULLY FEATURED +# +# PURPOSE: +# Высокоуровневая, отказоустойчивая обертка над 'tea' для +# использования AI-агентами. Абстрагирует сложность управления +# контекстом, предоставляет атомарные операции и прозрачный вывод. +# +# VERSION: 1.4 +# CHANGE LOG: +# - v1.4: Добавлены критически важные функции 'create-task' и 'create-pr', +# чтобы полностью соответствовать архитектуре системы +# 'AI_Dev_System_Ivanov_Gemini_v1.1'. +# - v1.3: Исправлена критическая ошибка парсинга remote URL, +# когда в SSH адресе присутствует номер порта. +# Логика `initialize` полностью переработана для надежности. +# ============================================================================== + +# --- ОБРАБОТКА ОШИБОК И ВЫХОД --- +set -e +set -u +set -o pipefail + +# --- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ И НАСТРОЙКИ --- +ROLE_NAME="" +REPO_SLUG="" +VERBOSE="true" + +# --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ --- +log_info() { + if [[ "$VERBOSE" == "true" ]]; then + echo -e "\033[34m[INFO]\033[0m $1" >&2 + fi +} + +error_exit() { + echo -e "\033[31m[ОШИБКА ЛОГИКИ]\033[0m $1" >&2 + exit 1 +} + +# --- ЦЕНТРАЛИЗОВАННЫЙ ИСПОЛНИТЕЛЬ КОМАНД --- +run_command() { + log_info "Выполняется команда:" + echo -e "\033[33m[CMD]\033[0m $@" >&2 + + local output + local exit_code + + output=$("$@" 2>&1) + exit_code=$? + + if [[ $exit_code -ne 0 ]]; then + echo -e "\033[31m[ОШИБКА ВЫПОЛНЕНИЯ]\033[0m Команда завершилась с кодом $exit_code." >&2 + echo "------------------------- ВЫВОД КОМАНДЫ -------------------------" >&2 + echo "$output" >&2 + echo "-----------------------------------------------------------------" >&2 + exit $exit_code + fi + + echo "$output" +} + +# --- ИНИЦИАЛИЗАЦИЯ И ПРОВЕРКИ --- +initialize() { + log_info "Инициализация клиента..." + for cmd in git tea jq; do + command -v "$cmd" >/dev/null 2>&1 || error_exit "Зависимость не найдена: $cmd. Пожалуйста, установите ее." + done + + local git_remote_url + git_remote_url=$(run_command git remote get-url origin) || error_exit "Не удалось определить remote URL." + + # Надежная логика парсинга для SSH и HTTPS URL + local path_part=$(echo "$git_remote_url" | sed -n 's|.*//[^/]*/\(.*\)|\1|p') + if [[ -z "$path_part" ]]; then + path_part=$(echo "$git_remote_url" | sed -n 's|.*:\(.*\)| + \1|p') + fi + REPO_SLUG=$(echo "$path_part" | sed 's/\.git$//') + + [[ -z "$REPO_SLUG" ]] && error_exit "Не удалось извлечь 'owner/repo' из URL: $git_remote_url" + log_info "Репозиторий определен как: $REPO_SLUG" + + [[ -z "$ROLE_NAME" ]] && error_exit "Имя роли (ROLE_NAME) не было предоставлено." + log_info "Роль установлена: $ROLE_NAME" +} + +# --- ФУНКЦИИ КОМАНД --- + +create_task() { + local title="" body="" assignee="" labels="" + while [[ $# -gt 0 ]]; do case $1 in + --title) title="$2"; shift 2;; + --body) body="$2"; shift 2;; + --assignee) assignee="$2"; shift 2;; + --labels) labels="$2"; shift 2;; + *) shift;; + esac; done + [[ -z "$title" || -z "$body" || -z "$assignee" || -z "$labels" ]] && error_exit "Для 'create-task' требуются флаги --title, --body, --assignee, --labels." + + log_info "Создание задачи для '$assignee'..." + run_command tea issues create --repo "$REPO_SLUG" \ + --title "$title" \ + --description "$body" \ + --assignees "$assignee" \ + --labels "$labels" + log_info "Задача успешно создана." +} + +create_pr() { + local title="" body="" head="" base="main" + while [[ $# -gt 0 ]]; do case $1 in + --title) title="$2"; shift 2;; + --body) body="$2"; shift 2;; + --head) head="$2"; shift 2;; + --base) base="$2"; shift 2;; + *) shift;; + esac; done + [[ -z "$title" || -z "$body" || -z "$head" ]] && error_exit "Для 'create-pr' требуются флаги --title, --body, --head." + + log_info "Создание Pull Request из ветки '$head' в '$base' ભા" + run_command tea pr create --repo "$REPO_SLUG" \ + --title "$title" \ + --description "$body" \ + --head "$head" \ + --base "$base" + log_info "Pull Request успешно создан." +} + +find_tasks() { + local task_type="" + while [[ $# -gt 0 ]]; do case $1 in --type) task_type="$2"; shift 2;; *) shift;; esac; done + [[ -z "$task_type" ]] && error_exit "Для 'find-tasks' требуется флаг --type." + + local issues_json + issues_json=$(run_command tea issues list --output json --repo "$REPO_SLUG" --labels "$task_type" --state "open") + + if [[ -z "$issues_json" || "$issues_json" == "[]" ]]; then + echo "[]" + else + echo "$issues_json" | jq -c '.[] | select(.labels[]?.name == "status::pending")' + fi +} + +update_task_status() { + local issue_id="" old_status="" new_status="" + while [[ $# -gt 0 ]]; do case $1 in --issue-id) issue_id="$2"; shift 2;; --old) old_status="$2"; shift 2;; --new) new_status="$2"; shift 2;; *) shift;; esac; done + [[ -z "$issue_id" || -z "$old_status" || -z "$new_status" ]] && error_exit "Для 'update-task-status' требуются флаги --issue-id, --old, --new." + + run_command tea issues edit "$issue_id" --repo "$REPO_SLUG" --remove-labels "$old_status" --add-labels "$new_status" + log_info "Статус задачи #$issue_id успешно обновлен на '$new_status'." +} + +merge_and_complete() { + local issue_id="" pr_id="" branch_name="" + while [[ $# -gt 0 ]]; do case $1 in --issue-id) issue_id="$2"; shift 2;; --pr-id) pr_id="$2"; shift 2;; --branch) branch_name="$2"; shift 2;; *) shift;; esac; done + [[ -z "$issue_id" || -z "$pr_id" || -z "$branch_name" ]] && error_exit "Для 'merge-and-complete' требуются флаги --issue-id, --pr-id, --branch." + + log_info "Слияние PR #$pr_id..." + run_command tea pr merge "$pr_id" --repo "$REPO_SLUG" + + log_info "Удаление ветки '$branch_name'..." + run_command git push origin --delete "$branch_name" + + log_info "Закрытие задачи #$issue_id..." + run_command tea issues close "$issue_id" --repo "$REPO_SLUG" + log_info "Процесс успешно завершен." +} + +return_to_dev() { + local issue_id="" pr_id="" report="" + while [[ $# -gt 0 ]]; do case $1 in --issue-id) issue_id="$2"; shift 2;; --pr-id) pr_id="$2"; shift 2;; --report) report="$2"; shift 2;; *) shift;; esac; done + [[ -z "$issue_id" || -z "$pr_id" || -z "$report" ]] && error_exit "Для 'return-to-dev' требуются флаги --issue-id, --pr-id, --report." + + log_info "Отклонение PR #$pr_id..." + run_command tea pr close "$pr_id" --repo "$REPO_SLUG" + + log_info "Добавление отчета о дефектах в задачу #$issue_id..." + run_command tea issues comment create "$issue_id" --repo "$REPO_SLUG" --comment "$report" + + log_info "Возврат задачи #$issue_id разработчику..." + run_command tea issues edit "$issue_id" --repo "$REPO_SLUG" \ + --title "[QA -> DEV] FAILED: Fix Defects in PR #$pr_id" \ + --assignee "agent-developer" \ + --remove-labels "status::in-progress,type::quality-assurance" \ + --add-labels "status::failed,type::development" + log_info "Задача возвращена." +} + +# --- ГЛАВНЫЙ ДИСПЕТЧЕР КОМАНД --- +main() { + [[ $# -lt 2 ]] && error_exit "Неверное использование. Требуется: ./gitea-client.zsh [OPTIONS]" + + ROLE_NAME="$1"; shift + local COMMAND="$1"; shift + + initialize + + case "$COMMAND" in + create-task) create_task "$@";; + create-pr) create_pr "$@";; + find-tasks) find_tasks "$@";; + update-task-status) update_task_status "$@";; + merge-and-complete) merge_and_complete "$@";; + return-to-dev) return_to_dev "$@";; + *) error_exit "Неизвестная команда: '$COMMAND'";; + esac +} + +# --- ТОЧКА ВХОДА --- +main "$@" \ No newline at end of file