--- name: gitea-issue-agent description: Build a Hermes-managed autonomous issue handler for a self-hosted Gitea instance — scan issues, analyze text + screenshot attachments, post a proposal for human approval, implement, test, push a branch, and open a Gitea PR. Use when the user wants Hermes to act on Gitea issues end-to-end with a human-in-the-loop approval gate, especially with image/visual context in the issue text. version: 0.1.0 author: Hermes Agent license: MIT metadata: hermes: tags: [gitea, issues, automation, hermes-plugin, pr-workflow, agent-loop, approval-gate] related_skills: [gitea-tea-macos-setup, software-engineering-workflows] --- # Gitea Issue Agent End-to-end workflow for an autonomous Gitea issue handler that lives inside Hermes. Combines: - A **Hermes plugin** exposing Gitea REST API tools (status, scan, get, fetch images, comment, create PR). - A **skill** that orchestrates the human-in-the-loop loop: propose → wait for approval → implement → test → push → open PR. - An optional **cron job** that triggers the loop on a schedule. ## When to use - User wants Hermes to monitor issues across one or more Gitea repos and propose fixes autonomously. - Issue text often includes screenshots or design mock-ups (image support is a first-class requirement). - A human (the user) must approve each fix before code is written. - Push and PR creation should target a working branch the user can review and merge manually. Do **not** use for: GitHub (use the `gitea`-equivalent catalog MCP for that), issues that are just questions/discussions (no code work expected), or workflows where the user wants fully autonomous implementation with no human gate. ## Architecture ``` ┌────────────────────────────────────────┐ │ ~/.hermes/plugins/gitea-issue-agent/ │ Cron tick → │ ├ plugin.yaml │ │ ├ __init__.py (registers 7 tools) │ │ └ README.md │ └─────────────┬──────────────────────────┘ │ exposes ▼ gitea_agent_status gitea_issue_agent_scan gitea_list_repositories gitea_get_issue gitea_fetch_issue_images gitea_comment_issue gitea_create_pull_request │ consumed by ▼ ┌────────────────────────────────────────┐ │ Hermes skill gitea-issue-agent-loop │ │ (orchestrates: scan → propose → wait │ │ → implement → test → push → PR) │ └─────────────┬──────────────────────────┘ │ triggers ▼ ┌────────────────────────────────────────┐ │ Cron job (optional) "every 30m" │ │ "Scan Gitea issues, propose solutions │ │ for new ones, implement approved." │ └────────────────────────────────────────┘ ``` The plugin does **not** implement the loop — it only exposes the API surface. The loop lives in the skill and the cron prompt. This separation keeps the plugin small and lets the loop evolve without code changes to the plugin. ## Required environment variables Add to `~/.hermes/.env` and restart the gateway: ```bash GITEA_BASE_URL=https://git.example.dk GITEA_TOKEN=*** service-bruger PAT, scope: read+write issues/PRs/attachments> GITEA_APPROVER_LOGIN= GITEA_DEFAULT_BASE_BRANCH=main # Optional: comma-separated allowlist. If empty, /user/repos is scanned. GITEA_REPOS=owner/repo1,owner/repo2 GITEA_AGENT_REPO_LIMIT=200 GITEA_AGENT_ISSUE_LIMIT_PER_REPO=50 ``` Two Gitea identities, kept separate: - `GITEA_TOKEN` → service-bruger account (e.g. `hermes-issues`). Posts comments and PRs as a bot. - `tea` CLI → the **user's personal** token, e.g. `dennis`. Used for local git push of branches the user owns. Mixing them causes comments to look like they came from the human personally. See `references/identity-and-safety.md`. ## Tool set (the plugin registers these) | Tool | Purpose | | --- | --- | | `gitea_agent_status` | Verify config + API connectivity; reports `authenticated_user`. | | `gitea_list_repositories` | List repos visible to the token (or `GITEA_REPOS` allowlist). | | `gitea_issue_agent_scan` | Scan open issues, classify each as `new` / `awaiting_approval` / `approved` / `pr_ready`. Returns short summary + image count. | | `gitea_get_issue` | Fetch one issue + comments + image links + classification status. | | `gitea_fetch_issue_images` | Download all screenshots/attachments from the issue + comments to a local cache, so `vision_analyze` can inspect them. | | `gitea_comment_issue` | Post a comment; optional `marker` arg (`proposal` or `pr_ready`) prepends an HTML marker the scanner uses to track state. | | `gitea_create_pull_request` | Open a PR from an already-pushed branch. The branch must be created and pushed by the agent via `git`/`terminal` first. | Every tool returns JSON of the shape `{"success": true, ...}` or `{"success": false, "error": "..."}`. JSON is the only contract; the agent's loop should branch on `success` and surface `error` verbatim. ## The workflow (the loop the skill/cron implements) 1. **Scan**: `gitea_issue_agent_scan` to find `status == "new"` issues. 2. **For each new issue**: 1. `gitea_get_issue` for full text + comments. 2. `gitea_fetch_issue_images` to download any screenshots to `~/.hermes/cache/gitea-issue-agent////`. 3. `vision_analyze` on each image (loaded into context) so the proposal is grounded in what the screenshot actually shows. 4. Draft a proposal (in Danish if the user is Danish-speaking) covering: - summary of the bug/feature - root-cause hypothesis - proposed change (files, tests, branch name) - explicit approval request 5. `gitea_comment_issue` with `marker="proposal"`. The plugin prepends the `` marker so the next scan classifies this issue as `awaiting_approval`. 3. **Stop and wait for approval.** Do not implement. The skill exits cleanly; the cron re-scans later and the human will have either: - Posted an approval comment (matched by `APPROVAL_RE` against `GITEA_APPROVER_LOGIN`), or - Applied one of the approval labels (`hermes-approved`, `approved-by-dennis`, `dennis-approved`). 4. **On the next scan**, issues that satisfy the approval rule are classified as `approved`. The skill then: 1. `cd` into the local clone of the repo (clone it first via `git clone` if not present — `tea clone` will fail on non-standard SSH ports, see `gitea-tea-macos-setup`). 2. Create a branch `hermes/issue--`. 3. Implement, run tests, commit, push (with token in URL or via `~/.netrc`). 4. `gitea_create_pull_request` with `head=hermes/issue--`, `base=main`, `body` linking to the issue, `draft=true`. 5. `gitea_comment_issue` with `marker="pr_ready"` and the PR URL. 5. **Stop.** The user reviews the PR in Gitea and merges by hand. The two state markers (`proposal`, `pr_ready`) are the only state machine. They survive Gitea restarts, plugin reloads, and Hermes restarts, because they live in the issue's comment history. ## Approval semantics (default) - A comment from `GITEA_APPROVER_LOGIN` matching `(?is)(@?hermes|agent|bot).{0,80}(godkend|approved|ok|go)` counts as approval. Danish is first-class because the user is Danish-speaking. - Any of these labels applied by a maintainer also counts: `hermes-approved`, `approved-by-dennis`, `dennis-approved`. - Approval is only counted if it is **after** the latest `proposal` comment. Re-approving an old proposal does nothing. - If `GITEA_APPROVER_LOGIN` is not set, free-form approval comments are ignored — only labels count. This is the safe default until the user has decided which login should be allowed to approve. For a longer rationale, see `references/identity-and-safety.md`. ## Install checklist (do these in order) 1. Decide on a service-bruger name (e.g. `hermes-issues`) and create a Gitea PAT for it with `repository` (read+write) + `issue` (write) scope. 2. Tell the user the env vars they need to set in `~/.hermes/.env` (see above). The agent **cannot** edit `.env` itself — Hermes blocks it at the tool layer. See `references/identity-and-safety.md` for why. 3. Drop the plugin files into `~/.hermes/plugins/gitea-issue-agent/` (`plugin.yaml` + `__init__.py` + `README.md`). 4. Tell the user to add `- gitea-issue-agent` under `plugins.enabled` in `~/.hermes/config.yaml`. The agent **cannot** edit `config.yaml` either — same reason. 5. Restart the gateway: `hermes gateway restart`. 6. Verify with `hermes chat -q "Kald gitea_agent_status" -t gitea_issue_agent` — `api_ok: true`, `authenticated_user: hermes-issues` (or whatever the service-bruger is named). 7. Verify the local CLI path: `tea login add --name --url ...`, `tea whoami`, then `git clone` any visible repo (don't rely on `tea clone` — see `gitea-tea-macos-setup` for the SSH-port-2224 trap). 8. Only then create the cron job, and only after the user has approved running it unattended. ## Verification Use the bundled script `scripts/verify-plugin-and-gitea.sh` to confirm the plugin is loaded, the API is reachable, and at least one repo is visible. It should print `OK` lines and exit 0. ## Pitfalls (learned) - **Hermes blocks the agent from writing to `~/.hermes/config.yaml` and `~/.hermes/.env`.** The right move when blocked is to give the user the exact one-line edit and stop. Do not try `execute_code`, `patch`, or `write_file` a different way — the guard fires on all of them. See `references/identity-and-safety.md`. - **`tea clone` fails opaquely when SSH is on a non-standard port (e.g. 2224).** Use `git clone` with the HTTPS `clone_url` from the Gitea API. Strip the token from `origin` immediately. - **Stale `osxkeychain` credentials can sabotage `git push` even when the token is correct.** Switch to `~/.netrc` with `chmod 600`. See the troubleshooting reference in `gitea-tea-macos-setup`. - **Service-bruger identity matters.** The plugin's `authenticated_user` field should NOT be your personal login. If it is, create a separate service-bruger or you'll get bot comments attributed to you. - **Free-form approval comments must be gated by a known login** (or by maintainer-applied labels) — otherwise a co-worker saying "looks good!" could trigger an autonomous push. - **First run: verify, don't cron.** Always run a manual scan + a manually-approved test issue before scheduling cron. A cron job firing off PRs in the user's name is the failure mode to avoid. - **Image analysis is mandatory for screenshot issues.** The plugin extracts image URLs and downloads them locally; the skill MUST call `vision_analyze` on each before writing the proposal, otherwise the proposal will be blind to what the screenshot shows. - **Plugin state is in issue comments, not in plugin memory.** The proposal/PR markers make the workflow restart-safe. ## Related skills - `gitea-tea-macos-setup` — the local `tea` + `git` + `osxkeychain` setup that the agent relies on. Includes the SSH-port-2224 trap and the `getlogin(2)` gotcha. - `software-engineering-workflows` — the plan / spike / TDD / debugging patterns the implement-and-test phase of the loop should follow. - `software-development/simplify-code` — useful to invoke on the agent's own code changes before opening the PR. ## Files in this skill - `scripts/verify-plugin-and-gitea.sh` — one-shot verification of the full setup (plugin loaded + API reachable + repos visible + clone works). Run it after every change to the env or plugin. - `references/identity-and-safety.md` — service-bruger vs personal identity split, Hermes safety-guard reality, approval-gate design rationale. - `templates/proposal-comment.md` — starter template for the `gitea_comment_issue` body when posting a proposal. Copy, fill in the issue-specific bits, send.