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>
22 KiB
Implementation Plan: Gitea Agent Skill for Project/Task Management
Phase: 2 - Disciplined Design Date: 2026-02-18 Status: DRAFT - Awaiting Approval Research Document: .docs/RESEARCH-gitea-agent-skill.md
1. Overview
Summary
Enhance the existing Gitea AI agent skill to support project and task management workflows for AI agents. The core insight from Phase 1 research is that Gitea 1.22.6 has NO project board REST API, so milestones serve as the API-backed project container. Labels implement a workflow state machine inspired by the br (beads_rust) pattern: ready -> in_progress -> done.
Approach
Option C from the research document: Dual-mode architecture combining gitea-mcp (official MCP server with 75+ tools) for standard operations with curl/jq shell helpers in the skill document for milestone management, issue dependencies, time tracking, and workflow orchestration -- areas where gitea-mcp has gaps.
Scope (5/25 Rule Applied)
Top 5 (Will Do):
- Rewrite SKILL.md with correct API endpoints (remove fake project board endpoints)
- Add milestone-based project management (create, list, progress tracking)
- Add label-based workflow state machine (ready/in_progress/blocked/done)
- Add shell helper functions with JSON output for agent consumption
- Add integration test script validating all operations against live Gitea
Next 20 (Will NOT Do):
- Custom Gitea plugin for project boards
- Gitea Actions workflow templates
- Webhook-based event processing
- Dashboard or web UI
- Multi-instance Gitea federation
- Custom MCP server wrapping Gitea API
- Kanban board simulation via API
- Automated code review integration
- Release management automation
- CI/CD pipeline integration
2. Architecture
Component Diagram
+--------------------------------------------------+
| AI Agent |
| (Claude Code / MCP Client) |
+--------+------------------+----------------------+
| |
v v
+----------------+ +------------------+
| gitea-mcp | | SKILL.md |
| (MCP Server) | | (Shell Helpers) |
| - repos | | - milestones |
| - issues CRUD | | - dependencies |
| - PRs | | - workflow labels |
| - users/orgs | | - progress track |
| - branches | | - time tracking |
| - files | | - setup-labels |
+-------+--------+ +--------+---------+
| |
v v
+--------------------------------------------------+
| Gitea REST API v1 |
| https://git.terraphim.cloud/api/v1 |
+--------------------------------------------------+
| Gitea 1.22.6 Instance |
+--------------------------------------------------+
Data Flow
- Agent reads SKILL.md for available operations and patterns
- For standard CRUD (repos, issues, PRs): agent uses gitea-mcp tools
- For milestones, dependencies, workflow: agent uses curl/jq helpers from SKILL.md
- All helpers use GITEA_TOKEN from 1Password via op read
- All helpers output JSON for machine-readable agent consumption
- Labels encode workflow state; milestones encode project containers
Key Design Decisions
| Decision | Rationale |
|---|---|
| Milestones as projects | Only API-backed grouping in Gitea 1.22.6; project board API does not exist |
| Labels for workflow state | Lightweight, API-accessible, visible in UI, no custom code needed |
| curl/jq helpers (not scripts) | Copy-paste into agent context; no installation; works everywhere |
| JSON output from all helpers | Machine-readable for agent pipelines; parseable with jq |
| 1Password op read for tokens | Consistent with existing infrastructure; no plaintext secrets |
| Dual MCP + skill approach | gitea-mcp covers 80% of operations; skill fills the 20% gap |
Eliminated Options
| Option | Why Eliminated |
|---|---|
| Option A: Skill-only | Ignores existing gitea-mcp with 75+ tools; duplicates work |
| Option B: MCP-only | gitea-mcp lacks milestones, dependencies, workflow labels |
| Custom Gitea plugin | Over-engineered; requires Go development and Gitea rebuilds |
| GitHub Projects API shim | Wrong abstraction; Gitea is not GitHub |
| Database-backed state | External dependency; Gitea already stores state in issues/labels |
Simplicity Check
- Could be explained in one paragraph? YES: "Use milestones as projects, labels as workflow states, curl/jq for API calls, gitea-mcp for standard ops"
- Junior developer could implement from this plan? YES: all operations are curl commands with documented endpoints
- No speculative features? YES: every feature maps to a concrete user request from the design brief
- Every component has a clear owner? YES: SKILL.md owned by this repo; gitea-mcp is external dependency
3. File Changes
File 1: .agents/skills/gitea/SKILL.md (MODIFY)
What changes: Complete rewrite. Remove incorrect project board endpoints (lines 222-263). Restructure into: Configuration, Authentication, Core Operations (repos, issues, PRs, milestones, labels, releases), Agent Workflow (state machine, helpers), MCP Integration, Error Handling.
Why: Current file has non-existent API endpoints. Need milestone-based project management and workflow state machine for agent task tracking.
Size estimate: ~500 lines (currently ~366 lines)
File 2: .agents/skills/gitea/setup-labels.sh (NEW)
What changes: Shell script that creates the 12 workflow labels on a Gitea repository. Idempotent (checks for existing labels before creating). Uses GITEA_TOKEN and GITEA_URL environment variables.
Why: Labels are the foundation of the workflow state machine. Need automated setup rather than manual UI clicks.
Size estimate: ~80 lines
File 3: .agents/skills/gitea/test-gitea-skill.sh (NEW)
What changes: Integration test script that validates all skill operations against a live Gitea instance. Creates a test repo, runs all operations, validates JSON output, cleans up. Uses real API calls (no mocks).
Why: User instructions mandate no mocks. Need to verify all documented endpoints actually work with Gitea 1.22.6.
Size estimate: ~200 lines
4. API Design
Environment Variables
GITEA_URL="https://git.terraphim.cloud" # Gitea instance URL
GITEA_TOKEN="$(op read op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential)" # API token from 1Password
GITEA_OWNER="${GITEA_OWNER:-terraphim}" # Org or user owning the repo
GITEA_REPO="${GITEA_REPO:-agent-tasks}" # Default repo for task tracking (user-overridable)
Shell Helper Functions
All functions output JSON. All functions use these conventions:
- Exit 0 on success, non-zero on failure
- Print JSON to stdout
- Print errors to stderr
- Accept positional arguments (no flags)
4.1 gitea_api - Base API caller
# Usage: gitea_api METHOD ENDPOINT [DATA]
# Returns: JSON response from Gitea API
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"
}
4.2 gitea_create_project - Create milestone as project
# Usage: gitea_create_project TITLE [DESCRIPTION] [DUE_DATE] [OWNER] [REPO]
# Returns: JSON milestone object with id, title, state, open_issues, closed_issues
# DUE_DATE format: 2026-03-01T00:00:00Z
# OWNER/REPO default to GITEA_OWNER/GITEA_REPO (terraphim/agent-tasks)
gitea_create_project() {
local title="$1"
local 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"
}
4.3 gitea_create_task - Create issue with milestone and labels
# Usage: gitea_create_task TITLE BODY MILESTONE_ID [LABEL_IDS] [OWNER] [REPO]
# LABEL_IDS: comma-separated label IDs (e.g., "1,2,3")
# Returns: JSON issue object
gitea_create_task() {
local title="$1" body="$2" milestone="$3"
local labels="${4:-}"
local 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"
}
4.4 gitea_set_labels - Replace all labels on an issue
# Usage: gitea_set_labels ISSUE_INDEX LABEL_IDS [OWNER] [REPO]
# LABEL_IDS: comma-separated label IDs
# Returns: JSON array of label objects
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]}"
}
4.5 gitea_ready - Mark task as ready for work
# Usage: gitea_ready ISSUE_INDEX [OWNER] [REPO]
# Adds "workflow:ready" label, removes other workflow labels
gitea_ready() {
local index="$1"
local 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_set_labels "$owner" "$repo" "$index" "$ready_id"
}
4.6 gitea_claim - Claim task (mark in-progress, assign self)
# Usage: gitea_claim ISSUE_INDEX [USERNAME] [OWNER] [REPO]
# Sets "workflow:in-progress" label, assigns user
gitea_claim() {
local index="$1" user="${2:-}"
local 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_set_labels "$owner" "$repo" "$index" "$ip_id"
if [ -n "$user" ]; then
gitea_api POST "/repos/$owner/$repo/issues/$index/assignees" "{\"assignees\": [\"$user\"]}"
fi
}
4.7 gitea_close_task - Complete task
# Usage: gitea_close_task ISSUE_INDEX [COMMENT] [OWNER] [REPO]
# Sets "workflow:done" label, closes issue, optionally adds comment
gitea_close_task() {
local index="$1" comment="${2:-}"
local 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_set_labels "$owner" "$repo" "$index" "$done_id"
gitea_api PATCH "/repos/$owner/$repo/issues/$index" '{"state": "closed"}'
if [ -n "$comment" ]; then
local cdata
cdata=$(jq -n --arg b "$comment" '{body: $b}')
gitea_api POST "/repos/$owner/$repo/issues/$index/comments" "$cdata"
fi
}
4.8 gitea_progress - Show project progress
# Usage: gitea_progress [MILESTONE_ID] [OWNER] [REPO]
# Returns: JSON with open/closed counts, percentage, task list
gitea_progress() {
local milestone="${1:-}"
local owner="${2:-$GITEA_OWNER}" repo="${3:-$GITEA_REPO}"
if [ -n "$milestone" ]; then
local ms
ms=$(gitea_api GET "/repos/$owner/$repo/milestones/$milestone")
echo "$ms" | 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)
}'
else
gitea_api GET "/repos/$owner/$repo/milestones?state=open"
fi
}
5. Label Scheme
Workflow Labels (mutually exclusive)
| Label | Color | Description |
|---|---|---|
workflow:ready |
#0e8a16 (green) |
Task is ready for an agent to pick up |
workflow:in-progress |
#fbca04 (yellow) |
Task is actively being worked on |
workflow:blocked |
#d93f0b (red) |
Task is blocked by a dependency |
workflow:done |
#6f42c1 (purple) |
Task is complete |
Priority Labels
| Label | Color | Description |
|---|---|---|
priority:critical |
#b60205 (dark red) |
Must be done immediately |
priority:high |
#d93f0b (red) |
Should be done this cycle |
priority:medium |
#fbca04 (yellow) |
Normal priority |
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/spike |
6. Agent Workflow State Machine
create_task
|
v
+---------------+
| workflow:ready |<--------+
+-------+-------+ |
| |
gitea_claim (unblock)
| |
v |
+--------------------+ +------------------+
| workflow:in-progress|->| workflow:blocked |
+---------+----------+ +------------------+
|
gitea_close_task
|
v
+---------------+
| workflow:done |
| (issue closed) |
+---------------+
Workflow Commands (br-inspired)
| br Command | Gitea Equivalent | Description |
|---|---|---|
br ready |
gitea_ready 1 |
Mark task #1 ready |
br update ID --status in_progress |
gitea_claim 1 myuser |
Claim and start task #1 |
br close ID |
gitea_close_task 1 "Done" |
Complete task #1 |
br list --status ready |
gitea_api GET /repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=workflow:ready |
List ready tasks |
br status |
gitea_progress 1 |
Show milestone #1 progress |
All helpers default to GITEA_OWNER=terraphim and GITEA_REPO=agent-tasks. Override with env vars or trailing arguments.
7. Test Strategy
Approach
Integration tests against the live Gitea instance at https://git.terraphim.cloud. No mocks. Each test creates its own resources and cleans up afterward. Tests run sequentially to avoid race conditions.
Test Cases
| # | Test | Validates | Expected Result |
|---|---|---|---|
| 1 | Create test repository | POST /repos |
201, JSON with repo name |
| 2 | Create labels via setup-labels.sh | POST /repos/{owner}/{repo}/labels |
12 labels created |
| 3 | Create milestone (project) | gitea_create_project |
201, JSON with milestone id |
| 4 | Create issue (task) with milestone | gitea_create_task |
201, JSON with issue number |
| 5 | Mark task ready | gitea_ready |
Label set to workflow:ready |
| 6 | Claim task | gitea_claim |
Label set to workflow:in-progress, assignee set |
| 7 | Check project progress | gitea_progress |
JSON with 0% complete (1 open, 0 closed) |
| 8 | Close task | gitea_close_task |
Issue state=closed, label=workflow:done |
| 9 | Check progress after close | gitea_progress |
JSON with 100% complete |
| 10 | Delete test repository | DELETE /repos/{owner}/{repo} |
204, cleanup successful |
Test Runner
# Run all tests
./test-gitea-skill.sh
# Requirements:
# - GITEA_TOKEN set (or op read available)
# - GITEA_URL set (defaults to https://git.terraphim.cloud)
# - curl, jq installed
Validation Criteria
- All 10 tests pass against live Gitea 1.22.6
- All helper functions return valid JSON (validated with jq)
- Cleanup succeeds (no orphaned test repos)
- No hardcoded secrets in any file
8. Implementation Steps
Step 1: Create setup-labels.sh (30 min)
Files: .agents/skills/gitea/setup-labels.sh
Actions:
- Create
terraphim/agent-tasksrepo if it doesn't exist (idempotent) - Write script that creates all 12 labels
- Make idempotent (check existing labels first)
- Use GITEA_TOKEN, GITEA_URL, GITEA_OWNER, GITEA_REPO environment variables
- Output JSON summary of created/existing labels
Commit message: "Add label setup script for Gitea agent workflow"
Step 2: Rewrite SKILL.md (2 hours)
Files: .agents/skills/gitea/SKILL.md
Actions:
- Keep frontmatter and system prompt
- Update Configuration section with 1Password integration
- Keep correct existing sections (users, orgs, repos, issues, PRs, releases)
- Remove incorrect project board endpoints (lines 222-263)
- Add Milestones section (create, list, update, delete)
- Add Agent Workflow section (state machine, all 8 helper functions)
- Add MCP Integration section (gitea-mcp setup and usage)
- Add Dependencies section (issue blocking API)
- Update Error Handling section
Commit message: "Rewrite Gitea skill with milestone-based project management"
Step 3: Create test script (1 hour)
Files: .agents/skills/gitea/test-gitea-skill.sh
Actions:
- Write integration test runner
- Implement all 10 test cases
- Add setup/teardown (create/delete test repo)
- Add JSON validation for all outputs
- Add color-coded pass/fail output
Commit message: "Add integration test script for Gitea skill"
Step 4: Verify 1Password integration (30 min)
Actions (no file changes):
- Verify gitea-token item exists in TerraphimPlatform vault (or create it)
- Test
op read op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential - Verify token has required scopes (repo, admin:org, write:user)
Commit message: None (verification only; document findings in test output)
Step 5: Run tests and iterate (1 hour)
Actions:
- Run setup-labels.sh against a test repository
- Run test-gitea-skill.sh
- Fix any failing tests
- Verify cleanup succeeded
Commit message: "Fix issues found during integration testing" (if needed)
Total estimated time: ~5 hours
9. Risk Mitigation
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Gitea API changes in future version | Low | Medium | Pin to 1.22.6 in docs; version check in test script |
| gitea-mcp adds milestone support | Low | Low | Reduces need for curl helpers; update skill to prefer MCP |
| 1Password token not configured | Medium | High | Test script checks for token before running; clear error message |
| Label name collision with existing labels | Low | Low | setup-labels.sh checks for existing labels; skips if present |
| Test repo cleanup fails | Low | Medium | Test script has explicit cleanup step; documents manual cleanup |
| Rate limiting on bulk label creation | Low | Low | Sequential creation with small delay if needed |
Rollback Plan
All changes are in .agents/skills/gitea/ directory. Rollback = git revert the commits.
No infrastructure changes. No database migrations. No service restarts needed.
10. Dependencies
| Dependency | Type | Status |
|---|---|---|
| Gitea 1.22.6 running at git.terraphim.cloud | Runtime | Available |
| GITEA_TOKEN in 1Password TerraphimPlatform vault | Secret | Needs verification |
| curl | Tool | Available on bigbox |
| jq | Tool | Available on bigbox |
| gitea-mcp (optional) | MCP Server | Not yet installed; documented for agent use |
11. Open Items (Updated After Live Verification)
-
GITEA_TOKEN in 1Password: RESOLVED. Token exists at
op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential. Authenticated user isroot(admin). Update SKILL.md to use this path. -
gitea-mcp installation: The skill documents gitea-mcp setup but does not install it. Installation is a separate task (requires Go 1.24+ or pre-compiled binary).
-
Issue dependencies API: RESOLVED. Works with
IssueMetabody format:{"owner": "...", "repo": "...", "index": N}. Supports cross-repo dependencies. Update design helper functions to use correct format. -
Scoped labels: RESOLVED. Mutual exclusivity confirmed. Using
exclusive: trueon label creation + scope prefix (e.g.,status/) enforces one-label-per-scope. -
Default tracking repo: RESOLVED. Default is
terraphim/agent-tasksviaGITEA_OWNER=terraphimandGITEA_REPO=agent-tasks. User can override both env vars to point at any repo. Theagent-tasksrepo will be created during setup if it doesn't exist.
12. Approval Checklist
Before proceeding to Phase 3 (Implementation), confirm:
- Architecture (milestones as projects, labels as workflow) is acceptable
- File change list is complete and correct
- Shell helper function signatures are acceptable
- Label scheme (12 labels, 3 categories) is acceptable
- Test strategy (10 integration tests, no mocks) is acceptable
- Implementation sequence (5 steps, ~5 hours) is reasonable
Appendix A: Gitea 1.22.6 Milestone API Reference
POST /repos/{owner}/{repo}/milestones - Create milestone
GET /repos/{owner}/{repo}/milestones - List milestones
GET /repos/{owner}/{repo}/milestones/{id} - Get milestone
PATCH /repos/{owner}/{repo}/milestones/{id} - Update milestone
DELETE /repos/{owner}/{repo}/milestones/{id} - Delete milestone
Response fields: id, title, description, state (open/closed),
open_issues, closed_issues, due_on, created_at, updated_at.
Appendix B: Gitea 1.22.6 Label API Reference
POST /repos/{owner}/{repo}/labels - Create label
GET /repos/{owner}/{repo}/labels - List labels
PATCH /repos/{owner}/{repo}/labels/{id} - Update label
DELETE /repos/{owner}/{repo}/labels/{id} - Delete label
POST /repos/{owner}/{repo}/issues/{index}/labels - Add labels to issue
PUT /repos/{owner}/{repo}/issues/{index}/labels - Replace issue labels
DELETE /repos/{owner}/{repo}/issues/{index}/labels - Clear issue labels
DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id} - Remove label from issue
Appendix C: br Workflow Mapping Reference
| br Concept | Gitea Mapping |
|---|---|
| Bead | Issue |
| Bead status | Label (workflow:*) |
| Bead priority | Label (priority:*) |
| Bead type | Label (type:*) |
| Project/Sprint | Milestone |
| Assignment | Issue assignee |
| Dependencies | Issue dependencies API |
| Progress | Milestone open/closed ratio |
| Comments | Issue comments |
| Close | Issue state = closed + workflow:done label |