diff --git a/agent_promts/AI_ARCHITECT_ANALYST_PROTOCOL.xml b/agent_promts/AI_AGENT_ARCHITECT_PROTOCOL.xml similarity index 100% rename from agent_promts/AI_ARCHITECT_ANALYST_PROTOCOL.xml rename to agent_promts/AI_AGENT_ARCHITECT_PROTOCOL.xml diff --git a/gitea-client-mock.zsh b/gitea-client-mock.zsh new file mode 100644 index 0000000..5d68801 --- /dev/null +++ b/gitea-client-mock.zsh @@ -0,0 +1,504 @@ +#!/usr/bin/env zsh + +# Mock curl function +function curl() { + echo "MOCK_CURL_CALL: $*" >&2 + # Simulate a successful response for GET requests, especially for issue data + if [[ "$1" == "-s" && "$3" == "GET" ]]; then + if [[ "$6" == *"issues/"* ]]; then + # Simulate issue data for update_task_status + echo '{"labels": [{"name": "status::pending"}, {"name": "type::development"}], "id": 123}' + else + echo '[]' # Empty array for find_tasks + fi + elif [[ "$1" == "-s" && "$3" == "POST" && "$6" == *"pulls/"* ]]; then + echo '{"merged": true}' # Simulate successful PR merge + else + echo '{}' # Generic successful response for other POST/PATCH/DELETE + fi +} + +#!/usr/bin/env zsh +# [PACKAGE: 'homebox_lens'] +# [FILE: 'gitea-client.zsh'] +# [SEMANTICS] +# [ENTITY: 'File'('gitea-client.zsh')] +# [ENTITY: 'Function'('api_request')] +# [ENTITY: 'Function'('find_tasks')] +# [ENTITY: 'Function'('update_task_status')] +# [ENTITY: 'Function'('create_pr')] +# [ENTITY: 'Function'('create_task')] +# [ENTITY: 'Function'('add_comment')] +# [ENTITY: 'Function'('merge_and_complete')] +# [ENTITY: 'Function'('return_to_dev')] +# [ENTITY: 'EntryPoint'('main_dispatch')] +# [ENTITY: 'Configuration'('GITEA_URL')] +# [ENTITY: 'Configuration'('GITEA_TOKEN')] +# [ENTITY: 'Configuration'('GITEA_OWNER')] +# [ENTITY: 'Configuration'('GITEA_REPO')] +# [ENTITY: 'ExternalCommand'('jq')] +# [ENTITY: 'ExternalCommand'('curl')] +# [RELATION: 'File'('gitea-client.zsh')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'File'('gitea-client.zsh')] -> [DEPENDS_ON] -> ['ExternalCommand'('curl')] +# [RELATION: 'Function'('api_request')] -> [DEPENDS_ON] -> ['ExternalCommand'('curl')] +# [RELATION: 'Function'('api_request')] -> [READS_FROM] -> ['Configuration'('GITEA_URL')] +# [RELATION: 'Function'('api_request')] -> [READS_FROM] -> ['Configuration'('GITEA_TOKEN')] +# [RELATION: 'Function'('find_tasks')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('update_task_status')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('update_task_status')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('create_pr')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('create_pr')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('create_task')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('create_task')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('add_comment')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('add_comment')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('merge_and_complete')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('merge_and_complete')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('return_to_dev')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('return_to_dev')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('find_tasks')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('update_task_status')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('create_pr')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('create_task')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('add_comment')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('merge_and_complete')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('return_to_dev')] +# [END_SEMANTICS] + +set -x + +# [DEPENDENCIES] +# Gitea Client Script +# Version: 1.0 +if ! command -v jq &> /dev/null; + then + echo "jq could not be found. Please install jq to use this script." + exit 1 +fi +# [END_DEPENDENCIES] + +# [CONFIGURATION] +# IMPORTANT: Replace with your Gitea URL, API Token, repository owner and repository name. +# You can also set these as environment variables: GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO +: ${GITEA_URL:="https://gitea.bebesh.ru"} +: ${GITEA_TOKEN:="c6fb6d73a18b2b4ddf94b67f2da6b6bb832164ce"} +: ${GITEA_OWNER:="busya"} +: ${GITEA_REPO:="homebox_lens"} +# [END_CONFIGURATION] + + +# [HELPERS] + +# [ENTITY: 'Function'('api_request')] +# [CONTRACT] +# Generic function to make requests to the Gitea API. +# This is the central communication point with the Gitea instance. +# +# @param $1: method - The HTTP method (GET, POST, PATCH). +# @param $2: endpoint - The API endpoint (e.g., "repos/owner/repo/issues"). +# @param $3: json_data - The JSON payload for POST/PATCH requests. +# +# @stdout The body of the API response on success. +# @stderr Error messages on failure. +# +# @returns 0 on success, 1 on unsupported method. Curl exit code on curl failure. +# [/CONTRACT] +function api_request() { + local method="$1" + local endpoint="$2" + local data="$3" + local url="$GITEA_URL/api/v1/$endpoint" + + local -a curl_opts + curl_opts=("-s" "-H" "Authorization: token $GITEA_TOKEN" "-H" "Content-Type: application/json") + + case "$method" in + GET) + curl "${curl_opts[@]}" "$url" + ;; + POST|PATCH) + curl "${curl_opts[@]}" -X "$method" -d @- "$url" <<< "$data" + ;; *) + echo "Unsupported HTTP method: $method" >&2 + return 1 + ;; + esac +} +# [END_ENTITY: 'Function'('api_request')] + +# [END_HELPERS] + + +# [COMMANDS] + +# [ENTITY: 'Function'('find_tasks')] +# [CONTRACT] +# Finds open issues with a specific type and 'status::pending' label. +# +# @param --type: The label to filter issues by (e.g., "type::development"). +# +# @stdout A JSON array of Gitea issues matching the criteria. +# [/CONTRACT] +function find_tasks() { + local type="" + # Parsing arguments like --type "type::development" + while [[ $# -gt 0 ]]; do + case "$1" in + --type) type="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + # In Gitea, we can filter issues by labels. + # The protocol uses "type::development" and "status::pending" + # We will treat these as labels. + local labels="type::development,status::pending" + if [[ -n "$type" ]]; then + labels="status::pending,${type}" + fi + + api_request "GET" "repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=$labels&state=open" +} +# [END_ENTITY: 'Function'('find_tasks')] + +# [ENTITY: 'Function'('update_task_status')] +# [CONTRACT] +# Atomically changes the status of a task by removing an old status label and adding a new one. +# +# @param --issue-id: The ID of the issue to update. +# @param --old: The old status label to remove (e.g., "status::pending"). +# @param --new: The new status label to add (e.g., "status::in-progress"). +# +# @stdout The JSON representation of the updated issue. +# [/CONTRACT] +function update_task_status() { + local issue_id="" + local old_status="" + local new_status="" + + # Parsing arguments like --issue-id 123 + 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 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$issue_id" || -z "$old_status" || -z "$new_status" ]]; then + echo "Usage: update-task-status --issue-id --old --new " >&2 + return 1 + fi + + # In Gitea, we manage status with labels. + # This function will remove the old status label and add the new one. + # First, get existing labels for the issue. + local issue_data=$(api_request "GET" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id") + if [[ -z "$issue_data" ]]; then + echo "Error: Could not retrieve issue data for issue ID $issue_id. The issue may not exist or there might be a problem with the Gitea API or your token." >&2 + return 1 + fi + local existing_labels=$(echo "$issue_data" | jq -r '.labels | .[].name') + + local -a new_labels + for label in ${=existing_labels}; + do + if [[ "$label" != "$old_status" ]]; then + new_labels+=($label) + fi + done + new_labels+=($new_status) + + local new_labels_json=$(printf '%s\n' "${new_labels[@]}" | jq -R . | jq -s .) + + local data=$(jq -n --argjson labels "$new_labels_json" '{labels: $labels}') + + api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$data" +} +# [END_ENTITY: 'Function'('update_task_status')] + +# [ENTITY: 'Function'('create_pr')] +# [CONTRACT] +# Creates a new Pull Request in the repository. +# +# @param --title: The title of the pull request. +# @param --head: The source branch for the pull request. +# @param --body: (Optional) The body/description of the pull request. +# @param --base: (Optional) The target branch. Defaults to 'main'. +# +# @stdout The JSON representation of the newly created pull request. +# [/CONTRACT] +function create_pr() { + local title="" + local body="" + local head_branch="" + local base_branch="main" # Assuming 'main' is the default base + + while [[ $# -gt 0 ]]; do + case "$1" in + --title) title="$2"; shift 2 ;; + --body) body="$2"; shift 2 ;; + --head) head_branch="$2"; shift 2 ;; + --base) base_branch="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$title" || -z "$head_branch" ]]; then + echo "Usage: create-pr --title --head <head_branch> [--body <body>] [--base <base_branch>]" >&2 + return 1 + fi + + local data=$(jq -n \ + --arg title "$title" \ + --arg body "$body" \ + --arg head "$head_branch" \ + --arg base "$base_branch" \ + '{title: $title, body: $body, head: $head, base: $base}') + + api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/pulls" "$data" +} +# [END_ENTITY: 'Function'('create_pr')] + +# [ENTITY: 'Function'('create_task')] +# [CONTRACT] +# Creates a new issue (task) in the repository. +# +# @param --title: The title of the issue. +# @param --body: (Optional) The body/description of the issue. +# @param --assignee: (Optional) Comma-separated list of usernames to assign. +# @param --labels: (Optional) Comma-separated list of labels to add. +# +# @stdout The JSON representation of the newly created issue. +# [/CONTRACT] +function create_task() { + local title="" + local body="" + local assignee="" + local 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 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$title" ]]; then + echo "Usage: create-task --title <title> [--body <body>] [--assignee <assignee>] [--labels <labels>]" >&2 + return 1 + fi + + local labels_json="[]" + if [[ -n "$labels" ]]; then + # Split by comma + local -a labels_arr + IFS=',' read -rA labels_arr <<< "$labels" + labels_json=$(printf '%s\n' "${labels_arr[@]}" | jq -R . | jq -s .) + + fi + + local assignees_json="[]" + if [[ -n "$assignee" ]]; then + # Split by comma + local -a assignees_arr + IFS=',' read -rA assignees_arr <<< "$assignee" + assignees_json=$(printf '%s\n' "${assignees_arr[@]}" | jq -R . | jq -s .) + + fi + + local data=$(jq -n \ + --arg title "$title" \ + --arg body "$body" \ + --argjson assignees "$assignees_json" \ + --argjson labels "$labels_json" \ + '{title: $title, body: $body, assignees: $assignees, labels: $labels}') + + api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/issues" "$data" +} +# [END_ENTITY: 'Function'('create_task')] + +# [ENTITY: 'Function'('add_comment')] +# [CONTRACT] +# Adds a comment to an existing issue or pull request. +# +# @param --issue-id: The ID of the issue/PR to comment on. +# @param --body: The content of the comment. +# +# @stdout The JSON representation of the newly created comment. +# [/CONTRACT] +function add_comment() { + local issue_id="" + local comment_body="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --issue-id) issue_id="$2"; shift 2 ;; + --body) comment_body="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$issue_id" || -z "$comment_body" ]]; then + echo "Usage: add-comment --issue-id <id> --body <comment_body>" >&2 + return 1 + fi + + local data=$(jq -n --arg body "$comment_body" '{body: $body}') + api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id/comments" "$data" +} +# [END_ENTITY: 'Function'('add_comment')] + +# [ENTITY: 'Function'('merge_and_complete')] +# [CONTRACT] +# Atomic operation to merge a PR, delete its source branch, and close the associated issue. +# +# @param --issue-id: The ID of the issue to close. +# @param --pr-id: The ID of the pull request to merge. +# @param --branch: The name of the source branch to delete after merging. +# +# @stderr Log messages indicating the progress of each step. +# @returns 1 on failure to merge or close the issue. +# [/CONTRACT] +function merge_and_complete() { + local issue_id="" + local pr_id="" + local branch_to_delete="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --issue-id) issue_id="$2"; shift 2 ;; + --pr-id) pr_id="$2"; shift 2 ;; + --branch) branch_to_delete="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$issue_id" || -z "$pr_id" || -z "$branch_to_delete" ]]; then + echo "Usage: merge-and-complete --issue-id <issue_id> --pr-id <pr_id> --branch <branch_to_delete>" >&2 + return 1 + fi + + # 1. Merge the PR + echo "Attempting to merge PR #$pr_id..." + local merge_data=$(jq -n '{Do: "merge"} ) # Gitea API expects a MergePullRequestOption object + local merge_response=$(api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/pulls/$pr_id/merge" "$merge_data") + if echo "$merge_response" | jq -e '.merged' > /dev/null; then + echo "PR #$pr_id merged successfully." + else + echo "Error merging PR #$pr_id: $merge_response" >&2 + return 1 + } + + # 2. Delete the branch + echo "Attempting to delete branch $branch_to_delete..." + local delete_branch_response=$(api_request "DELETE" "repos/$GITEA_OWNER/$GITEA_REPO/branches/$branch_to_delete") + if [[ -z "$delete_branch_response" ]]; then # Gitea API returns empty on successful delete + echo "Branch $branch_to_delete deleted successfully." + else + echo "Error deleting branch $branch_to_delete: $delete_branch_response" >&2 + # Do not return 1 here, as PR might be merged even if branch deletion fails + } + + # 3. Close the associated issue + echo "Attempting to close issue #$issue_id..." + local close_issue_data=$(jq -n '{state: "closed"}') + api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$close_issue_data" +} +# [END_ENTITY: 'Function'('merge_and_complete')] + +# [ENTITY: 'Function'('return_to_dev')] +# [CONTRACT] +# Atomically changes the status of a task by removing an old status label and adding a new one. +# +# @param --issue-id: The ID of the issue to update. +# @param --pr-id: The ID of the pull request to update. +# @param --report: The defect report text. +# +# @stdout The JSON representation of the updated issue. +# [/CONTRACT] +function return_to_dev() { + local issue_id="" + local pr_id="" + local report_text="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --issue-id) issue_id="$2"; shift 2 ;; + --pr-id) pr_id="$2"; shift 2 ;; + --report) report_text="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$issue_id" || -z "$pr_id" || -z "$report_text" ]]; then + echo "Usage: return-to-dev --issue-id <issue_id> --pr-id <pr_id> --report <report_text>" >&2 + return 1 + fi + + echo "Attempting to return PR #$pr_id and issue #$issue_id to developer with report: $report_text" + + # 1. Add comment to PR/Issue + add_comment --issue-id "$pr_id" --body "Defect Report: $report_text" + add_comment --issue-id "$issue_id" --body "Defect Report: $report_text" + + # 2. Reopen issue and change status to 'in-progress' for developer + # First, get existing labels for the issue. + local issue_data=$(api_request "GET" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id") + if [[ -z "$issue_data" ]]; then + echo "Error: Could not retrieve issue data for issue ID $issue_id. The issue may not exist or there might be a problem with the Gitea API or your token." >&2 + return 1 + fi + local existing_labels=$(echo "$issue_data" | jq -r '.labels | .[].name') + + local -a new_labels + for label in ${=existing_labels}; + do + if [[ "$label" == "status::completed" || "$label" == "status::in-review" ]]; then + continue # Remove completed/in-review status + } + new_labels+=($label) + done + new_labels+=("status::in-progress") # Add in-progress status + + local new_labels_json=$(printf '%s\n' "${new_labels[@]}" | jq -R . | jq -s .) + + local data=$(jq -n --argjson labels "$new_labels_json" '{state: "open", labels: $labels}') + api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$data" + + # 3. Close PR (or leave open for developer to fix and re-push) - for now, just comment + # Gitea API doesn't have a direct "reject PR" or "return to dev" state. + # We'll just comment and update the issue. + echo "PR #$pr_id commented. Issue #$issue_id status updated to in-progress." +} +# [END_ENTITY: 'Function'('return_to_dev')] + +# Test calls for each function +echo "--- Testing find_tasks ---" +find_tasks --type "type::development" +find_tasks + +echo "--- Testing update_task_status ---" +update_task_status --issue-id 123 --old "status::pending" --new "status::in-progress" + +echo "--- Testing create_pr ---" +create_pr --title "Test PR" --head "feature/test-branch" --body "This is a test pull request." + +echo "--- Testing create_task ---" +create_task --title "Test Task" --body "This is a test task body." --assignee "busya" --labels "type::test,status::pending" +create_task --title "Another Test Task" + +echo "--- Testing add_comment ---" +add_comment --issue-id 456 --body "This is a test comment." + +echo "--- Testing merge_and_complete ---" +merge_and_complete --issue-id 123 --pr-id 789 --branch "feature/test-branch" + +echo "--- Testing return_to_dev ---" +return_to_dev --issue-id 123 --pr-id 789 --report "Found a bug in feature X." + +echo "--- All tests completed ---" diff --git a/gitea-client.zsh b/gitea-client.zsh new file mode 100755 index 0000000..b5ffbc2 --- /dev/null +++ b/gitea-client.zsh @@ -0,0 +1,485 @@ +#!/usr/bin/env zsh +# [PACKAGE: 'homebox_lens'] +# [FILE: 'gitea-client.zsh'] +# [SEMANTICS] +# [ENTITY: 'File'('gitea-client.zsh')] +# [ENTITY: 'Function'('api_request')] +# [ENTITY: 'Function'('find_tasks')] +# [ENTITY: 'Function'('update_task_status')] +# [ENTITY: 'Function'('create_pr')] +# [ENTITY: 'Function'('create_task')] +# [ENTITY: 'Function'('add_comment')] +# [ENTITY: 'Function'('merge_and_complete')] +# [ENTITY: 'Function'('return_to_dev')] +# [ENTITY: 'EntryPoint'('main_dispatch')] +# [ENTITY: 'Configuration'('GITEA_URL')] +# [ENTITY: 'Configuration'('GITEA_TOKEN')] +# [ENTITY: 'Configuration'('GITEA_OWNER')] +# [ENTITY: 'Configuration'('GITEA_REPO')] +# [ENTITY: 'ExternalCommand'('jq')] +# [ENTITY: 'ExternalCommand'('curl')] +# [RELATION: 'File'('gitea-client.zsh')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'File'('gitea-client.zsh')] -> [DEPENDS_ON] -> ['ExternalCommand'('curl')] +# [RELATION: 'Function'('api_request')] -> [DEPENDS_ON] -> ['ExternalCommand'('curl')] +# [RELATION: 'Function'('api_request')] -> [READS_FROM] -> ['Configuration'('GITEA_URL')] +# [RELATION: 'Function'('api_request')] -> [READS_FROM] -> ['Configuration'('GITEA_TOKEN')] +# [RELATION: 'Function'('find_tasks')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('update_task_status')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('update_task_status')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('create_pr')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('create_pr')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('create_task')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('create_task')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('add_comment')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('add_comment')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('merge_and_complete')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('merge_and_complete')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'Function'('return_to_dev')] -> [CALLS] -> ['Function'('api_request')] +# [RELATION: 'Function'('return_to_dev')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('find_tasks')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('update_task_status')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('create_pr')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('create_task')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('add_comment')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('merge_and_complete')] +# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('return_to_dev')] +# [END_SEMANTICS] + +set -x + +# [DEPENDENCIES] +# Gitea Client Script +# Version: 1.0 +if ! command -v jq &> /dev/null; + then + echo "jq could not be found. Please install jq to use this script." + exit 1 +fi +# [END_DEPENDENCIES] + +# [CONFIGURATION] +# IMPORTANT: Replace with your Gitea URL, API Token, repository owner and repository name. +# You can also set these as environment variables: GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO +: ${GITEA_URL:="https://gitea.bebesh.ru"} +: ${GITEA_TOKEN:="c6fb6d73a18b2b4ddf94b67f2da6b6bb832164ce"} +: ${GITEA_OWNER:="busya"} +: ${GITEA_REPO:="gitea-client-tests"} # <-- Убедитесь, что здесь тестовый репозиторий +# [END_CONFIGURATION] + + +# [HELPERS] + +# [ENTITY: 'Function'('api_request')] +# [CONTRACT] +# Generic function to make requests to the Gitea API. +# This is the central communication point with the Gitea instance. +# +# @param $1: method - The HTTP method (GET, POST, PATCH, DELETE). +# @param $2: endpoint - The API endpoint (e.g., "repos/owner/repo/issues"). +# @param $3: json_data - The JSON payload for POST/PATCH requests. +# +# @stdout The body of the API response on success. +# @stderr Error messages on failure. +# +# @returns 0 on success, 1 on unsupported method. Curl exit code on curl failure. +# [/CONTRACT] +# ЗАМЕНИТЕ ВСЮ ФУНКЦИЮ api_request НА ЭТУ ВЕРСИЮ + +function api_request() { + local method="$1" + local endpoint="$2" + local data="$3" + local url="$GITEA_URL/api/v1/$endpoint" + + local http_code + local response_body + + # Создаем временный файл для хранения тела ответа + local body_file=$(mktemp) + + local -a curl_opts + # -s: silent + # -w '%{http_code}': записать http-код в stdout ПОСЛЕ ответа + # -o "$body_file": записать тело ответа в файл + curl_opts=("-s" "-w" "%{http_code}" "-o" "$body_file" \ + "-H" "Authorization: token $GITEA_TOKEN" \ + "-H" "Content-Type: application/json") + + case "$method" in + GET|DELETE) + http_code=$(curl "${curl_opts[@]}" -X "$method" "$url") + ;; + POST|PATCH) + http_code=$(curl "${curl_opts[@]}" -X "$method" -d @- "$url" <<< "$data") + ;; + *) + echo "Unsupported HTTP method: $method" >&2 + rm -f "$body_file" # Очистка перед выходом + return 1 + ;; + esac + + response_body=$(<"$body_file") + rm -f "$body_file" # Очистка после использования + + if [[ "$http_code" -ge 200 && "$http_code" -lt 300 ]]; then + if [[ -z "$response_body" ]]; then + echo "{\"http_status\": $http_code, \"body\": \"empty\"}" + else + echo "$response_body" + fi + return 0 + else + echo "API Error: Received HTTP status $http_code. Body: $response_body" >&2 + return 1 + fi +} +# [END_ENTITY: 'Function'('api_request')] + +# [END_HELPERS] + + +# [COMMANDS] + +# [ENTITY: 'Function'('find_tasks')] +# [CONTRACT] +# Finds open issues with a specific type and 'status::pending' label. +# +# @param --type: The label to filter issues by (e.g., "type::development"). +# +# @stdout A JSON array of Gitea issues matching the criteria. +# [/CONTRACT] +function find_tasks() { + local type="" + # Parsing arguments like --type "type::development" + while [[ $# -gt 0 ]]; do + case "$1" in + --type) type="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + local labels="type::development,status::pending" + if [[ -n "$type" ]]; then + labels="status::pending,${type}" + fi + + api_request "GET" "repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=$labels&state=open" +} +# [END_ENTITY: 'Function'('find_tasks')] + +# [ENTITY: 'Function'('update_task_status')] +# [CONTRACT] +# Atomically changes the status of a task by removing an old status label and adding a new one. +# +# @param --issue-id: The ID of the issue to update. +# @param --old: The old status label to remove (e.g., "status::pending"). +# @param --new: The new status label to add (e.g., "status::in-progress"). +# +# @stdout The JSON representation of the updated issue. +# [/CONTRACT] +function update_task_status() { + local issue_id="" + local old_status="" + local 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 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$issue_id" || -z "$old_status" || -z "$new_status" ]]; then + echo "Usage: update-task-status --issue-id <id> --old <old_status> --new <new_status>" >&2 + return 1 + fi + + local issue_data=$(api_request "GET" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id") + if [[ -z "$issue_data" ]]; then + echo "Error: Could not retrieve issue data for issue ID $issue_id." >&2 + return 1 + fi + local existing_labels=$(echo "$issue_data" | jq -r '.labels | .[].name') + + local -a new_labels + for label in ${=existing_labels}; + do + if [[ "$label" != "$old_status" ]]; then + new_labels+=($label) + fi + done + new_labels+=($new_status) + + local new_labels_json=$(printf '%s\n' "${new_labels[@]}" | jq -R . | jq -s .) + local data=$(jq -n --argjson labels "$new_labels_json" '{labels: $labels}') + + api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$data" +} +# [END_ENTITY: 'Function'('update_task_status')] + +# [ENTITY: 'Function'('create_pr')] +# [CONTRACT] +# Creates a new Pull Request in the repository. +# +# @param --title: The title of the pull request. +# @param --head: The source branch for the pull request. +# @param --body: (Optional) The body/description of the pull request. +# @param --base: (Optional) The target branch. Defaults to 'main'. +# +# @stdout The JSON representation of the newly created pull request. +# [/CONTRACT] +function create_pr() { + local title="" + local body="" + local head_branch="" + local base_branch="main" + + while [[ $# -gt 0 ]]; do + case "$1" in + --title) title="$2"; shift 2 ;; + --body) body="$2"; shift 2 ;; + --head) head_branch="$2"; shift 2 ;; + --base) base_branch="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$title" || -z "$head_branch" ]]; then + echo "Usage: create-pr --title <title> --head <head_branch> [--body <body>] [--base <base_branch>]" >&2 + return 1 + fi + + local data=$(jq -n \ + --arg title "$title" \ + --arg body "$body" \ + --arg head "$head_branch" \ + --arg base "$base_branch" \ + '{title: $title, body: $body, head: $head, base: $base}') + + api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/pulls" "$data" +} +# [END_ENTITY: 'Function'('create_pr')] + +# [ENTITY: 'Function'('create_task')] +# [CONTRACT] +# Creates a new issue (task) in the repository. +# +# @param --title: The title of the issue. +# @param --body: (Optional) The body/description of the issue. +# @param --assignee: (Optional) Comma-separated list of usernames to assign. +# @param --labels: (Optional) Comma-separated list of labels to add. +# +# @stdout The JSON representation of the newly created issue. +# [/CONTRACT] +function create_task() { + local title="" + local body="" + local assignee="" + local 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 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$title" ]]; then + echo "Usage: create-task --title <title> [--body <body>] [--assignee <assignee>] [--labels <labels>]" >&2 + return 1 + fi + + local labels_json="[]" + if [[ -n "$labels" ]]; then + local -a labels_arr + IFS=',' read -rA labels_arr <<< "$labels" + labels_json=$(printf '%s\n' "${labels_arr[@]}" | jq -R . | jq -s .) + fi + + local assignees_json="[]" + if [[ -n "$assignee" ]]; then + local -a assignees_arr + IFS=',' read -rA assignees_arr <<< "$assignee" + assignees_json=$(printf '%s\n' "${assignees_arr[@]}" | jq -R . | jq -s .) + fi + + local data=$(jq -n \ + --arg title "$title" \ + --arg body "$body" \ + --argjson assignees "$assignees_json" \ + --argjson labels "$labels_json" \ + '{title: $title, body: $body, assignees: $assignees, labels: $labels}') + + api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/issues" "$data" +} +# [END_ENTITY: 'Function'('create_task')] + +# [ENTITY: 'Function'('add_comment')] +# [CONTRACT] +# Adds a comment to an existing issue or pull request. +# +# @param --issue-id: The ID of the issue/PR to comment on. +# @param --body: The content of the comment. +# +# @stdout The JSON representation of the newly created comment. +# [/CONTRACT] +function add_comment() { + local issue_id="" + local comment_body="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --issue-id) issue_id="$2"; shift 2 ;; + --body) comment_body="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$issue_id" || -z "$comment_body" ]]; then + echo "Usage: add-comment --issue-id <id> --body <comment_body>" >&2 + return 1 + fi + + local data=$(jq -n --arg body "$comment_body" '{body: $body}') + api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id/comments" "$data" +} +# [END_ENTITY: 'Function'('add_comment')] + +# [ENTITY: 'Function'('merge_and_complete')] +# [CONTRACT] +# Atomic operation to merge a PR, delete its source branch, and close the associated issue. +# +# @param --issue-id: The ID of the issue to close. +# @param --pr-id: The ID of the pull request to merge. +# @param --branch: The name of the source branch to delete after merging. +# +# @stderr Log messages indicating the progress of each step. +# @returns 1 on failure to merge or close the issue. +# [/CONTRACT] +function merge_and_complete() { + local issue_id="" + local pr_id="" + local branch_to_delete="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --issue-id) issue_id="$2"; shift 2 ;; + --pr-id) pr_id="$2"; shift 2 ;; + --branch) branch_to_delete="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$issue_id" || -z "$pr_id" || -z "$branch_to_delete" ]]; then + echo "Usage: merge-and-complete --issue-id <issue_id> --pr-id <pr_id> --branch <branch_to_delete>" >&2 + return 1 + fi + + # 1. Merge the PR + echo "Attempting to merge PR #$pr_id..." + local merge_data=$(jq -n '{Do: "merge"}' ) + # Запускаем в подоболочке, чтобы обработать возможную ошибку, если api_request вернет 1 + local merge_response + if ! merge_response=$(api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/pulls/$pr_id/merge" "$merge_data"); then + echo "Error merging PR #$pr_id: API request failed." >&2 + echo "Response: $merge_response" >&2 + return 1 + fi + + # API на успешный мерж возвращает ПУСТОЕ тело и код 200/204. + # Наша новая api_request вернет JSON-маркер. Проверяем это. + if echo "$merge_response" | jq -e '.body == "empty"' > /dev/null; then + echo "PR #$pr_id merged successfully." + else + # Если тело не пустое, это может быть тоже успех (старые версии Gitea) или ошибка + if echo "$merge_response" | jq -e '.merged' > /dev/null; then + echo "PR #$pr_id merged successfully (with response body)." + else + echo "Error merging PR #$pr_id: Unexpected API response: $merge_response" >&2 + return 1 + fi + fi + + # 2. Delete the branch + echo "Attempting to delete branch $branch_to_delete..." + if api_request "DELETE" "repos/$GITEA_OWNER/$GITEA_REPO/branches/$branch_to_delete" > /dev/null; then + echo "Branch $branch_to_delete deleted successfully." + else + echo "Warning: Failed to delete branch $branch_to_delete. It might have already been deleted or protected." >&2 + fi + + # 3. Close the associated issue + echo "Attempting to close issue #$issue_id..." + local close_issue_data=$(jq -n '{state: "closed"}') + local close_response + if ! close_response=$(api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$close_issue_data"); then + echo "Error closing issue #$issue_id: API request failed." >&2 + return 1 + fi + + if echo "$close_response" | jq -e '.state == "closed"' > /dev/null; then + echo "Issue #$issue_id closed successfully." + else + echo "Error closing issue #$issue_id: Unexpected API response: $close_response" >&2 + return 1 + fi +} +# [END_ENTITY: 'Function'('merge_and_complete')] + +# [ENTITY: 'Function'('return_to_dev')] +# [CONTRACT] +# Returns an issue to development by adding a comment and changing its status. +# It specifically changes the status from 'status::in-review' to 'status::in-progress'. +# +# @param --issue-id: The ID of the issue to update. +# @param --comment: The comment explaining why the issue is being returned. +# +# @stderr Log messages indicating the progress of each step. +# @returns 1 on failure to add comment or update status. +# [/CONTRACT] +function return_to_dev() { + local issue_id="" + local comment_body="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --issue-id) issue_id="$2"; shift 2 ;; + --comment) comment_body="$2"; shift 2 ;; + *) echo "Unknown parameter: $1"; return 1 ;; + esac + done + + if [[ -z "$issue_id" || -z "$comment_body" ]]; then + echo "Usage: return-to-dev --issue-id <id> --comment <comment_body>" >&2 + return 1 + fi + + # 1. Add the comment + echo "Adding comment to issue #$issue_id..." + local add_comment_response + add_comment_response=$(add_comment --issue-id "$issue_id" --body "$comment_body") + if ! echo "$add_comment_response" | jq -e '.id' > /dev/null; then + echo "Error: Failed to add comment to issue #$issue_id. Response: $add_comment_response" >&2 + return 1 + fi + + # 2. Update the status + echo "Updating status for issue #$issue_id..." + local update_status_response + update_status_response=$(update_task_status --issue-id "$issue_id" --old "status::in-review" --new "status::in-progress") + if ! echo "$update_status_response" | jq -e '.id' > /dev/null; then + echo "Error: Failed to update status for issue #$issue_id. Response: $update_status_response" >&2 + return 1 + fi + + echo "Issue #$issue_id returned to development." +} +# [END_ENTITY: 'Function'('return_to_dev')] + +# Здесь может быть функция main_dispatch, если она вам нужна diff --git a/tasks/current_work_order.xml b/tasks/current_work_order.xml new file mode 100644 index 0000000..99dab6a --- /dev/null +++ b/tasks/current_work_order.xml @@ -0,0 +1,35 @@ +<WORK_ORDERS> + <WORK_ORDER id="1" title="Implement Inventory List Screen"> + <INTENT_SPECIFICATION> + Implement the UI for the inventory list screen in `InventoryListScreen.kt` to display a list of items using Jetpack Compose. + Implement the `InventoryListViewModel.kt` to fetch a paginated list of items from the `ItemRepository` and expose it to the UI. + The screen should show a loading indicator while data is being fetched and handle empty or error states. + </INTENT_SPECIFICATION> + <TARGET_FILES> + <FILE>app/src/main/java/com/homebox/lens/ui/screen/inventorylist/InventoryListScreen.kt</FILE> + <FILE>app/src/main/java/com/homebox/lens/ui/screen/inventorylist/InventoryListViewModel.kt</FILE> + </TARGET_FILES> + </WORK_ORDER> + <WORK_ORDER id="2" title="Implement Item Details Screen"> + <INTENT_SPECIFICATION> + Implement the UI for the item details screen in `ItemDetailsScreen.kt`. It should display all the information about a specific item. + Implement the `ItemDetailsViewModel.kt` to fetch the details of a single item from the `ItemRepository` using its ID. + The screen should handle cases where the item is not found. + </INTENT_SPECIFICATION> + <TARGET_FILES> + <FILE>app/src/main/java/com/homebox/lens/ui/screen/itemdetails/ItemDetailsScreen.kt</FILE> + <FILE>app/src/main/java/com/homebox/lens/ui/screen/itemdetails/ItemDetailsViewModel.kt</FILE> + </TARGET_FILES> + </WORK_ORDER> + <WORK_ORDER id="3" title="Implement Search Screen"> + <INTENT_SPECIFICATION> + Implement the UI for the search screen in `SearchScreen.kt`. It should contain a search bar and a list to display search results. + Implement the `SearchViewModel.kt` to take a search query, call the `SearchItemsUseCase`, and expose the results to the UI. + The search should be triggered as the user types, with debouncing to avoid excessive API calls. + </INTENT_SPECIFICATION> + <TARGET_FILES> + <FILE>app/src/main/java/com/homebox/lens/ui/screen/search/SearchScreen.kt</FILE> + <FILE>app/src/main/java/com/homebox/lens/ui/screen/search/SearchViewModel.kt</FILE> + </TARGET_FILES> + </WORK_ORDER> +</WORK_ORDERS> \ No newline at end of file