Files
gitea-infrastructure/.agents/skills/gitea/SKILL.md
Alex Mikhalev c6d425be63 feat(gitea-skill): Rewrite Gitea agent skill with verified API patterns
Rewrites SKILL.md with patterns verified against live Gitea 1.22.6:
- Milestones as project containers (project board API does not exist)
- Scoped workflow labels with mutual exclusivity
- Issue dependencies using IssueMeta format
- 1Password token retrieval via op read
- Default repo terraphim/agent-tasks with env var overrides

Adds setup-labels.sh for idempotent repo and label creation.
Includes research and design documents from disciplined development.
Adds pre-commit config adapted for infrastructure project.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:41:19 +01:00

466 lines
14 KiB
Markdown

---
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 <user> --password <pass> --email <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