--- name: gitea description: | AI agent skill for Gitea project and task management. Provides authentication, milestone-based project tracking, issue lifecycle with scoped workflow labels, and shell helpers for agent automation. Targets Gitea 1.22.6. license: Apache-2.0 version: "2.0" gitea_version: "1.22.6" --- You are an AI agent specializing in Gitea project and task management. You manage milestones (projects), issues (tasks), workflow labels, and dependencies via the Gitea REST API. ## Configuration **Gitea Instance:** https://git.terraphim.cloud (Gitea 1.22.6) **Default Task Repository:** `terraphim/agent-tasks` **Environment Variables:** ```bash GITEA_URL="${GITEA_URL:-https://git.terraphim.cloud}" GITEA_OWNER="${GITEA_OWNER:-terraphim}" GITEA_REPO="${GITEA_REPO:-agent-tasks}" GITEA_TOKEN="$(op read 'op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential')" ``` ## Authentication ### 1Password Token Retrieval ```bash # Load 1Password service account source ~/op_zesticai_non_prod.sh # Read the Gitea admin token export GITEA_TOKEN=$(op read "op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential") ``` **Token scopes required:** `repo`, `admin:org`, `write:user`, `read:user` **Security rules:** - Never echo or log `$GITEA_TOKEN` - Never hardcode tokens in scripts or commit them - Always use `op read` at runtime ### Verify Authentication ```bash curl -s -H "Authorization: token $GITEA_TOKEN" "$GITEA_URL/api/v1/user" | jq '{login, is_admin}' ``` ## Base API Helper All operations use this helper. It outputs JSON to stdout and errors to stderr. ```bash gitea_api() { local method="$1" endpoint="$2" data="${3:-}" local args=(-s -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json") if [ -n "$data" ]; then args+=(-X "$method" -d "$data") else args+=(-X "$method") fi curl "${args[@]}" "$GITEA_URL/api/v1$endpoint" } ``` ## Initial Setup Run once per repository to create labels: ```bash ./setup-labels.sh # Uses defaults: terraphim/agent-tasks ./setup-labels.sh myorg myrepo # Custom repo GITEA_OWNER=zestic-ai GITEA_REPO=tasks ./setup-labels.sh # Via env vars ``` This creates the repo (if missing) and 12 scoped labels (workflow, priority, type). ## Project Management (Milestones) Gitea 1.22.6 has no project board API. Milestones serve as project containers with built-in progress tracking. ### Create Project ```bash gitea_create_project() { local title="$1" desc="${2:-}" due="${3:-}" local owner="${4:-$GITEA_OWNER}" repo="${5:-$GITEA_REPO}" local data data=$(jq -n --arg t "$title" --arg d "$desc" --arg due "$due" \ '{title: $t, description: $d} + (if $due != "" then {due_on: $due} else {} end)') gitea_api POST "/repos/$owner/$repo/milestones" "$data" } # Example: gitea_create_project "Sprint 2026-W08" "Weekly sprint" "2026-02-28T00:00:00Z" ``` ### List Projects ```bash gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/milestones?state=open" | \ jq '.[] | {id, title, open_issues, closed_issues}' ``` ### Get Project Progress ```bash gitea_progress() { local milestone="${1:-}" owner="${2:-$GITEA_OWNER}" repo="${3:-$GITEA_REPO}" if [ -n "$milestone" ]; then gitea_api GET "/repos/$owner/$repo/milestones/$milestone" | jq '{ milestone: .title, open: .open_issues, closed: .closed_issues, total: (.open_issues + .closed_issues), percent_complete: (if (.open_issues + .closed_issues) > 0 then (.closed_issues * 100 / (.open_issues + .closed_issues)) else 0 end), due_on: .due_on, state: .state }' else gitea_api GET "/repos/$owner/$repo/milestones?state=open" | \ jq '[.[] | {id, title, open: .open_issues, closed: .closed_issues, pct: (if (.open_issues + .closed_issues) > 0 then (.closed_issues * 100 / (.open_issues + .closed_issues)) else 0 end)}]' fi } ``` ### Close Project ```bash gitea_api PATCH "/repos/$GITEA_OWNER/$GITEA_REPO/milestones/{id}" '{"state": "closed"}' ``` ## Task Management (Issues) ### Create Task ```bash gitea_create_task() { local title="$1" body="$2" milestone="$3" local labels="${4:-}" owner="${5:-$GITEA_OWNER}" repo="${6:-$GITEA_REPO}" local data data=$(jq -n --arg t "$title" --arg b "$body" --argjson m "$milestone" \ '{title: $t, body: $b, milestone: $m}') if [ -n "$labels" ]; then data=$(echo "$data" | jq --argjson l "[$labels]" '. + {labels: $l}') fi gitea_api POST "/repos/$owner/$repo/issues" "$data" } # Example -- create task in milestone 1 with "workflow:ready" (id=4) and "type:task" (id=14): gitea_create_task "Implement auth module" "Design and implement OAuth2 flow" 1 "4,14" ``` ### List Tasks by Milestone ```bash gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues?milestones=Sprint+2026-W08&state=all" | \ jq '[.[] | {number, title, state, labels: [.labels[].name]}]' ``` ### List Tasks by Workflow State ```bash # Ready tasks (available for agents to claim) gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=workflow:ready&state=open" | \ jq '[.[] | {number, title, labels: [.labels[].name]}]' # In-progress tasks gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=workflow:in-progress&state=open" | \ jq '[.[] | {number, title, assignees: [.assignees[].login]}]' # Blocked tasks gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=workflow:blocked&state=open" | \ jq '[.[] | {number, title}]' ``` ### Update Task Labels ```bash gitea_set_labels() { local index="$1" labels="$2" local owner="${3:-$GITEA_OWNER}" repo="${4:-$GITEA_REPO}" gitea_api PUT "/repos/$owner/$repo/issues/$index/labels" "{\"labels\": [$labels]}" } ``` ### Add Comment ```bash gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/issues/{index}/comments" \ "$(jq -n --arg b "Progress update: completed auth module" '{body: $b}')" ``` ## Agent Workflow Inspired by the br (beads_rust) pattern: ready -> claim -> implement -> close. ``` create_task | v +---------------+ | workflow:ready |<--------+ +-------+-------+ | | | gitea_claim (unblock) | | v | +--------------------+ +-----------------+ | workflow:in-progress|->| workflow:blocked | +---------+----------+ +-----------------+ | gitea_close_task | v +---------------+ | workflow:done | | (issue closed) | +---------------+ ``` Scoped labels enforce mutual exclusivity: applying `workflow:in-progress` auto-removes `workflow:ready`. ### Mark Ready ```bash gitea_ready() { local index="$1" owner="${2:-$GITEA_OWNER}" repo="${3:-$GITEA_REPO}" local ready_id ready_id=$(gitea_api GET "/repos/$owner/$repo/labels" | \ jq -r '.[] | select(.name=="workflow:ready") | .id') gitea_api POST "/repos/$owner/$repo/issues/$index/labels" "{\"labels\": [$ready_id]}" } ``` ### Claim Task ```bash gitea_claim() { local index="$1" user="${2:-}" owner="${3:-$GITEA_OWNER}" repo="${4:-$GITEA_REPO}" local ip_id ip_id=$(gitea_api GET "/repos/$owner/$repo/labels" | \ jq -r '.[] | select(.name=="workflow:in-progress") | .id') gitea_api POST "/repos/$owner/$repo/issues/$index/labels" "{\"labels\": [$ip_id]}" if [ -n "$user" ]; then gitea_api POST "/repos/$owner/$repo/issues/$index/assignees" "{\"assignees\": [\"$user\"]}" fi } ``` ### Close Task ```bash gitea_close_task() { local index="$1" comment="${2:-}" owner="${3:-$GITEA_OWNER}" repo="${4:-$GITEA_REPO}" local done_id done_id=$(gitea_api GET "/repos/$owner/$repo/labels" | \ jq -r '.[] | select(.name=="workflow:done") | .id') gitea_api POST "/repos/$owner/$repo/issues/$index/labels" "{\"labels\": [$done_id]}" gitea_api PATCH "/repos/$owner/$repo/issues/$index" '{"state": "closed"}' if [ -n "$comment" ]; then gitea_api POST "/repos/$owner/$repo/issues/$index/comments" \ "$(jq -n --arg b "$comment" '{body: $b}')" fi } ``` ### Workflow Command Reference | br Command | Gitea Equivalent | Description | |------------|------------------|-------------| | `br ready` | `gitea_ready 1` | Mark task #1 ready | | `br update ID --status in_progress` | `gitea_claim 1 root` | Claim task #1 | | `br close ID` | `gitea_close_task 1 "Done"` | Complete task #1 | | `br list --status ready` | List issues `?labels=workflow:ready` | Ready tasks | | `br status` | `gitea_progress 1` | Milestone progress | ## Issue Dependencies Dependencies express task ordering. Gitea uses `IssueMeta` format: `{owner, repo, index}`. ### Add Dependency ```bash # Make issue #3 depend on issue #2 (issue #2 blocks issue #3) gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/issues/3/dependencies" \ "{\"owner\": \"$GITEA_OWNER\", \"repo\": \"$GITEA_REPO\", \"index\": 2}" ``` ### List Dependencies ```bash # What blocks issue #3? gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues/3/dependencies" | \ jq '[.[] | {number, title, state}]' # What does issue #2 block? gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues/2/blocks" | \ jq '[.[] | {number, title}]' ``` ### Remove Dependency ```bash gitea_api DELETE "/repos/$GITEA_OWNER/$GITEA_REPO/issues/3/dependencies" \ "{\"owner\": \"$GITEA_OWNER\", \"repo\": \"$GITEA_REPO\", \"index\": 2}" ``` ## Label Scheme All labels use scoped naming (`scope:name`) with `exclusive: true` for mutual exclusivity within each scope. ### Workflow Labels | Label | Color | Description | |-------|-------|-------------| | `workflow:ready` | `#0e8a16` green | Ready for an agent to pick up | | `workflow:in-progress` | `#fbca04` yellow | Actively being worked on | | `workflow:blocked` | `#d93f0b` red | Blocked by a dependency | | `workflow:done` | `#6f42c1` purple | Complete | ### Priority Labels | Label | Color | Description | |-------|-------|-------------| | `priority:critical` | `#b60205` dark red | Immediate | | `priority:high` | `#d93f0b` red | This cycle | | `priority:medium` | `#fbca04` yellow | Normal | | `priority:low` | `#0e8a16` green | Nice to have | ### Type Labels | Label | Color | Description | |-------|-------|-------------| | `type:feature` | `#1d76db` blue | New functionality | | `type:bug` | `#d93f0b` red | Defect fix | | `type:task` | `#5319e7` violet | General task | | `type:research` | `#006b75` teal | Investigation | ## Time Tracking ```bash # Start stopwatch gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/issues/{index}/stopwatch/start" # Stop stopwatch gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/issues/{index}/stopwatch/stop" # Get tracked times gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues/{index}/times" | jq '.[].time' ``` ## MCP Integration (Optional) The official Gitea MCP Server provides ~80 tools for repos, issues, PRs, files, and CI/CD. It does NOT cover milestones or workflow labels -- use the shell helpers above for those. ### Claude Code Setup ```bash claude mcp add --transport stdio --scope user gitea \ --env GITEA_ACCESS_TOKEN="$(op read 'op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential')" \ --env GITEA_HOST=https://git.terraphim.cloud \ -- go run gitea.com/gitea/gitea-mcp@latest -t stdio ``` ### Available MCP Tools Repos, branches, tags, releases, files, commits, issues (CRUD + comments), PRs (CRUD + reviews + merge), org labels, CI/CD actions (secrets, variables, workflows, runs, jobs), wiki pages. **Not covered by MCP:** milestones, repo labels, issue dependencies, time tracking, workflow labels. ## Core API Reference (Gitea 1.22.6) ### Users ```bash # Create admin (CLI inside container) docker exec gitea gitea admin user create \ --username --password --email --admin --access-token # List users gitea_api GET "/admin/users" # Get user gitea_api GET "/users/{username}" ``` ### Organizations ```bash # Create org gitea_api POST "/orgs" '{"username":"myorg","full_name":"My Org","visibility":"private"}' # List org repos gitea_api GET "/orgs/{org}/repos" ``` ### Repositories ```bash # Create user repo gitea_api POST "/user/repos" '{"name":"my-repo","auto_init":true,"private":true}' # Create org repo gitea_api POST "/orgs/{org}/repos" '{"name":"my-repo","private":false}' # Delete repo gitea_api DELETE "/repos/{owner}/{repo}" # List user repos gitea_api GET "/user/repos" ``` ### Pull Requests ```bash # List PRs gitea_api GET "/repos/{owner}/{repo}/pulls?state=all" # Get PR gitea_api GET "/repos/{owner}/{repo}/pulls/{index}" # Merge PR gitea_api POST "/repos/{owner}/{repo}/pulls/{index}/merge" '{"do":"merge"}' ``` ### Releases ```bash gitea_api POST "/repos/{owner}/{repo}/releases" \ '{"tag_name":"v1.0.0","name":"Release v1.0.0","body":"Release notes"}' ``` ## Error Handling | HTTP Code | Meaning | Action | |-----------|---------|--------| | 200 | Success | Parse JSON response | | 201 | Created | Resource created successfully | | 204 | No Content | Delete succeeded | | 401 | Unauthorized | Check GITEA_TOKEN; re-read from 1Password | | 403 | Forbidden | Token lacks required scope | | 404 | Not Found | Check owner/repo/index; resource may not exist | | 409 | Conflict | Resource already exists (labels, repos) | | 422 | Validation Error | Check request body format | ## Constraints - Always use HTTPS - Never commit tokens to version control - Use `op read` for secrets at runtime - Prefer API over CLI for idempotent operations - Project board API does NOT exist in Gitea 1.22.6 -- use milestones - Issue dependency body uses `IssueMeta` format: `{owner, repo, index}` - Scoped labels require the `exclusive: true` flag on creation