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>
This commit is contained in:
2026-02-18 12:41:19 +01:00
parent 776b8a618e
commit c6d425be63
6 changed files with 1838 additions and 309 deletions
+409 -309
View File
@@ -1,365 +1,465 @@
---
name: gitea
description: |
AI agent skill for interacting with Gitea. Manages users, organizations,
repositories, issues, pull requests, and project boards via Gitea API.
Supports both REST API and Gitea admin CLI operations.
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 administration and automation. Your role is to help manage users, organizations, repositories, issues, and projects using the Gitea API and CLI tools.
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 Instance:** https://git.terraphim.cloud (Gitea 1.22.6)
**Access Methods:**
1. **REST API** - Preferred for automation and scripting
2. **Admin CLI** - For user creation and administrative tasks inside container
**Default Task Repository:** `terraphim/agent-tasks`
**Required Environment Variables:**
- `GITEA_URL` - Gitea instance URL (default: https://git.terraphim.cloud)
- `GITEA_TOKEN` - API token for authentication
**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
### Get API Token
1. Login to Gitea web UI
2. Go to Settings → Applications
3. Create new token with appropriate scopes
### 1Password Token Retrieval
### Using the Token
```bash
export GITEA_TOKEN="your-token-here"
# 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")
```
## Core Operations
**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
#### Create Admin User (requires admin CLI)
```bash
# Inside Gitea container
# Create admin (CLI inside container)
docker exec gitea gitea admin user create \
--username <username> \
--password <password> \
--email <email> \
--admin \
--access-token
```
--username <user> --password <pass> --email <email> --admin --access-token
#### List Users (API)
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/admin/users"
```
# List users
gitea_api GET "/admin/users"
#### Get User Info
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/users/{username}"
# Get user
gitea_api GET "/users/{username}"
```
### Organizations
#### Create Organization
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/orgs" \
-d '{
"username": "myorg",
"full_name": "My Organization",
"description": "Organization description",
"visibility": "private"
}'
```
# Create org
gitea_api POST "/orgs" '{"username":"myorg","full_name":"My Org","visibility":"private"}'
#### List Organization Repos
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/orgs/{org}/repos"
# List org repos
gitea_api GET "/orgs/{org}/repos"
```
### Repositories
#### Create Repository
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/user/repos" \
-d '{
"name": "my-repo",
"description": "Repository description",
"private": true,
"auto_init": true,
"gitignores": "Rust",
"license": "MIT"
}'
```
# Create user repo
gitea_api POST "/user/repos" '{"name":"my-repo","auto_init":true,"private":true}'
#### Create Repository in Organization
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/orgs/{org}/repos" \
-d '{
"name": "my-repo",
"description": "Repository description",
"private": false
}'
```
# Create org repo
gitea_api POST "/orgs/{org}/repos" '{"name":"my-repo","private":false}'
#### Delete Repository
```bash
curl -s -X DELETE -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}"
```
# Delete repo
gitea_api DELETE "/repos/{owner}/{repo}"
#### List User Repositories
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/user/repos"
```
### Issues
#### Create Issue
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues" \
-d '{
"title": "Issue title",
"body": "Issue description",
"assignees": ["username"]
}'
```
#### List Issues
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues?state=all"
```
#### Get Issue
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}"
```
#### Update Issue
```bash
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}" \
-d '{
"title": "New title",
"body": "Updated body",
"state": "open"
}'
```
#### Close/Reopen Issue
```bash
# Close issue
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}" \
-d '{"state": "closed"}'
# Reopen issue
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}" \
-d '{"state": "open"}'
```
#### Add Issue Comment
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}/comments" \
-d '{"body": "Comment text"}'
```
#### Add/Remove Issue Labels
```bash
# Add labels
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}/labels" \
-d '{"labels": [1, 2, 3]}'
# List available labels
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/labels"
# List user repos
gitea_api GET "/user/repos"
```
### Pull Requests
#### List Pull Requests
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/pulls?state=all"
# 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"}'
```
#### Get Pull Request
### Releases
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/pulls/{index}"
```
#### Merge Pull Request
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/pulls/{index}/merge" \
-d '{"do": "merge", "merge_message": "Merge PR #1"}'
```
### Projects
#### Create Project
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/user/projects" \
-d '{
"name": "Project Name",
"description": "Project description",
"visibility": "private"
}'
```
#### Create Organization Project
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/orgs/{org}/projects" \
-d '{
"name": "Project Name",
"description": "Project description"
}'
```
#### List Projects
```bash
# User projects
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/user/projects"
# Organization projects
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/orgs/{org}/projects"
```
#### Add Issue to Project
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/projects/{project_id}/issues" \
-d '{"issue_id": 123}'
```
### Labels
#### Create Label
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/labels" \
-d '{
"name": "bug",
"color": "fc2929",
"description": "Bug report"
}'
```
### Release Notes
#### Create Release
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/{owner}/{repo}/releases" \
-d '{
"tag_name": "v1.0.0",
"name": "Release v1.0.0",
"body": "Release notes",
"draft": false,
"prerelease": false
}'
```
## Helper Functions
### Bash Alias for Common Operations
Add to your shell profile:
```bash
gitea() {
local cmd="$1"
shift
case "$cmd" in
repos)
curl -s -H "Authorization: token $GITEA_TOKEN" "$GITEA_URL/api/v1/user/repos" | jq -r '.[].full_name'
;;
issues)
local owner="$1" repo="$2"
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/$owner/$repo/issues?state=all" | \
jq -r '.[] | "#\(.number) [\(.state)] \(.title)"'
;;
create-issue)
local owner="$1" repo="$2" title="$3" body="${4:-}"
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_URL/api/v1/repos/$owner/$repo/issues" \
-d "{\"title\": \"$title\", \"body\": \"$body\"}"
;;
*)
echo "Usage: gitea {repos|issues|create-issue}"
;;
esac
}
gitea_api POST "/repos/{owner}/{repo}/releases" \
'{"tag_name":"v1.0.0","name":"Release v1.0.0","body":"Release notes"}'
```
## Error Handling
- Check HTTP status codes (200 = success, 201 = created, 404 = not found, 401 = unauthorized)
- Use `jq` to parse JSON responses
- Rate limiting: Gitea doesn't enforce strict rate limits but be reasonable
- Token scopes: Ensure token has required permissions (repo, read:user, write:user, etc.)
## Common Use Cases
### Automated Issue Tracking
1. Create issues for task tracking
2. Add labels for categorization
3. Assign to team members
4. Link to projects for board view
5. Update status via comments
### Project Management
1. Create organization for team
2. Create project board
3. Add repositories to project
4. Add issues to project columns
5. Track progress via project API
### Repository Automation
1. Initialize repositories with templates
2. Configure branch protection
3. Add deploy keys
4. Set up webhooks
5. Mirror from GitHub
| 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 in production
- Always use HTTPS
- Never commit tokens to version control
- Use environment variables for secrets
- Use `op read` for secrets at runtime
- Prefer API over CLI for idempotent operations
- Handle rate limiting gracefully
- Clean up temporary resources
- 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