Initial import: 10 Radix skills across 3 categories

Kategoriserer danske regnskabs-/ERP-skills, ERP-data-afstemnings-
skill og to devops-workflows (gitea-issue-agent, kanban-workflows)
som Radix-medlemmers AI-agenter kan dele.

Struktur:
  skills/<kategori>/<skill-navn>/SKILL.md
  skills/<kategori>/<skill-navn>/{references,templates,scripts}/

Indekseret af index.json (genereret af scripts/rebuild_index.py).
Kør `python3 scripts/rebuild_index.py --check` i CI for at fange
synkroniseringsfejl.
This commit is contained in:
2026-06-15 14:41:04 +02:00
commit 236053cd7f
30 changed files with 4460 additions and 0 deletions
+248
View File
@@ -0,0 +1,248 @@
---
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=<the user's Gitea login, e.g. "dennis">
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/<owner>/<repo>/<index>/`.
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 `<!-- hermes:gitea-issue-agent v1 status=proposal -->`
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-<n>-<slug>`.
3. Implement, run tests, commit, push (with token in URL or via
`~/.netrc`).
4. `gitea_create_pull_request` with `head=hermes/issue-<n>-<slug>`,
`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 <user> --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.
@@ -0,0 +1,101 @@
# Identity and safety in the Gitea issue agent
## Two identities, one Gitea server
The setup has two Gitea users that should remain separate:
| Identity | Token stored in | Used for | What it posts |
| --- | --- | --- | --- |
| **Service-bruger** (e.g. `hermes-issues`) | `GITEA_TOKEN` in `~/.hermes/.env` | All plugin tool calls: scan, comment, create PR | Comments and PRs attributed to the bot |
| **Personal user** (e.g. `dennis`) | `tea` login + `~/.netrc` | Local `git push` of branches the agent created | Commits and merges attributed to you |
The plugin's `gitea_agent_status` reports `authenticated_user`. If it
reports your personal login, the bot will post comments as you — fix
the `GITEA_TOKEN` before scheduling any cron job.
## Why the Hermes safety-guards exist (and why the agent can't bypass them)
Hermes has three layers of "agent cannot edit" protection, all by
design:
1. **`~/.hermes/config.yaml`** — gates plugin load, model selection,
toolsets, gateway config. An agent that could rewrite this could
give itself new tools, change providers, or open outbound network
channels. The `patch` tool explicitly refuses:
`Refusing to write to Hermes config file: ... Agent cannot modify
security-sensitive configuration.`
2. **`~/.hermes/.env`** — contains API keys, tokens, passwords. The
`write_file`, `patch`, and `execute_code` tools all refuse to
modify it.
3. **`execute_code` with file writes** — when a script is going to
write files under `~/.hermes/`, the safety guard blocks it
unconditionally: `BLOCKED: execute_code script timed out without
user response.`
**The right move when blocked:** give the user the exact one-line
edit (or `cat > file <<EOF ... EOF` block) and stop. Do not try
alternative tools, do not retry, do not work around it. The user
must run the edit themselves.
This is not a bug. It is the threat model.
## Approval-gate design rationale
The agent should never implement, push, or open a PR for an issue
that hasn't been approved. This protects against three classes of
mistake:
1. **Misread issue** — the user wanted "fix the X button", the agent
rewrote `X()` in 14 files. Approval forces a human to look at the
proposal first.
2. **Out-of-scope issue** — the user wanted to push back, not fix.
Approval makes "no" cheap (just don't reply).
3. **Bad agent day** — the LLM hallucinated, the issue got a wrong
proposal, the agent is in a weird state. Approval makes the loop
recoverable without reverting commits.
The two state markers (`proposal`, `pr_ready`) survive Hermes
restarts, plugin reloads, and Gitea restarts, because they live in
the issue's comment history. The agent never has to remember
anything to resume — it can re-scan and pick up where it left off.
## Approval matching — why it requires a known login by default
The default approval regex is permissive enough to handle Danish
("godkend", "godkender") and English ("approve", "approved", "ok",
"go") in either order with the agent mention. But without a
known-login check, **any user** with write access to the repo could
say "looks good, agent" in a comment and trigger a push. That's a
prompt-injection vector — a malicious screenshot, or a co-worker
joking, could authorize code execution.
Two safe modes:
- **Label-only mode** (the default until `GITEA_APPROVER_LOGIN` is
set): a maintainer applies one of three labels. Labels can only
be applied by users with write access, so the trust surface is
the maintainer set, not "anyone who can comment".
- **Login-gated mode** (once `GITEA_APPROVER_LOGIN` is set): a
comment from that specific user, matching the regex, AND
after the latest proposal comment. This is the strict mode.
If the user wants both, both work — labels OR login-comment. If they
want neither (auto-implement everything), they should set both
`GITEA_APPROVER_LOGIN` and approve a *meta-issue* saying "always
implement" — but that's not the default and not recommended.
## The first run is always manual
Before scheduling a cron, the user must:
1. Run a manual `gitea_issue_agent_scan` and confirm issues show up.
2. Pick a real issue and walk it through: scan → fetch images →
analyze → propose → wait → approve → implement → test → push →
PR.
3. Confirm the PR body links to the issue, the commit message is
reasonable, and `git log` shows the expected author/email.
4. **Then** schedule a cron. The cron prompt itself should be
minimal — "scan, propose for new issues, implement approved" —
with a clear hand-off to the skill for the details.
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# Verify the Gitea issue agent is wired up end to end.
#
# Exits 0 on success. Prints OK / FAIL lines for each step.
# Run after every env or plugin change.
#
# Does NOT need a real token in the script — reads from ~/.hermes/.env.
set -uo pipefail
ok() { echo " OK $*"; }
bad() { echo " FAIL $*"; exit 1; }
echo "=== 1. Plugin files present ==="
PLUGIN_DIR="$HOME/.hermes/plugins/gitea-issue-agent"
for f in plugin.yaml __init__.py README.md; do
[ -f "$PLUGIN_DIR/$f" ] && ok "$f" || bad "$PLUGIN_DIR/$f missing"
done
echo
echo "=== 2. Plugin listed in config.yaml ==="
grep -q "gitea-issue-agent" "$HOME/.hermes/config.yaml" \
&& ok "config.yaml references gitea-issue-agent" \
|| bad "gitea-issue-agent not in plugins.enabled — add it under plugins.enabled in ~/.hermes/config.yaml"
echo
echo "=== 3. Plugin imports without error ==="
python3 - <<'PY' && ok "import gitea-issue-agent OK" || bad "import failed"
import importlib.util, sys
spec = importlib.util.spec_from_file_location(
"gitea_issue_agent",
f"{__import__('os').path.expanduser('~')}/.hermes/plugins/gitea-issue-agent/__init__.py",
)
mod = importlib.util.module_from_spec(spec)
sys.modules["gitea_issue_agent"] = mod
spec.loader.exec_module(mod)
assert len(mod.SCHEMAS) >= 5, f"expected at least 5 tools, got {len(mod.SCHEMAS)}"
PY
echo
echo "=== 4. Env vars present ==="
set +e
. "$HOME/.hermes/.env" 2>/dev/null
set -e
[ -n "${GITEA_BASE_URL:-}" ] && ok "GITEA_BASE_URL=$GITEA_BASE_URL" || bad "GITEA_BASE_URL missing in ~/.hermes/.env"
[ -n "${GITEA_TOKEN:-}" ] && ok "GITEA_TOKEN set (${#GITEA_TOKEN} chars)" || bad "GITEA_TOKEN missing in ~/.hermes/.env"
echo
echo "=== 5. API connectivity ==="
HTTP=$(curl -4 -s -o /dev/null -m 10 -w "%{http_code}" \
-H "Authorization: token $GITEA_TOKEN" \
"$GITEA_BASE_URL/api/v1/user")
if [ "$HTTP" = "200" ]; then
USER=$(curl -4 -s -m 10 -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_BASE_URL/api/v1/user" | python3 -c "import sys,json; print(json.load(sys.stdin).get('login','?'))")
ok "API reachable, authenticated as: $USER"
else
bad "API $GITEA_BASE_URL/api/v1/user returned HTTP $HTTP"
fi
echo
echo "=== 6. At least one repo visible ==="
N=$(curl -4 -s -m 10 -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_BASE_URL/api/v1/user/repos?limit=100" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))")
[ "$N" -gt 0 ] && ok "$N repos visible to token" || bad "no repos visible — token scope may be too narrow"
echo
echo "=== 7. local tea login (optional but recommended) ==="
if command -v tea >/dev/null 2>&1; then
TUSER=$(tea whoami 2>/dev/null | grep -E "^[a-zA-Z0-9_-]+$" | head -1)
[ -n "$TUSER" ] && ok "tea whoami -> $TUSER" || echo " INFO tea installed but no login yet"
else
echo " INFO tea not on PATH — see gitea-tea-macos-setup"
fi
echo
echo "=== 8. ~/.netrc (optional but recommended) ==="
if [ -f "$HOME/.netrc" ] && grep -q "git.radixadm.dk\|$GITEA_BASE_URL" "$HOME/.netrc"; then
ok "~/.netrc has an entry for $GITEA_BASE_URL"
else
echo " INFO ~/.netrc missing — git push will use osxkeychain (may be stale)"
fi
echo
echo "All checks passed. The agent is ready to use."
@@ -0,0 +1,40 @@
<!-- hermes:gitea-issue-agent v1 status=proposal -->
## Analyse
<kort resumé af hvad issuet beder om — i dansk hvis brugeren er dansk>
## Screenshots / visuelle referencer
<bullet-liste over hvad hver screenshot viser og hvad vi har udledt af den>
## Rod-hypotese
<1-3 bullets der peger på den underliggende årsag, ikke symptomet>
## Foreslået ændring
- **Repo:** `<owner>/<repo>`
- **Branch:** `hermes/issue-<index>-<kort-slug>`
- **Filer der forventes ændret:**
- `<sti/til/fil.py>` — <hvad og hvorfor>
- `<sti/til/test_x.py>` — ny test der dækker regressionen
- **Risiko:** lav / medium / høj — <begrundelse>
## Test-plan
- `docker compose exec web python manage.py test <app>` skal køre grønt
- Manuel verify: <trin brugeren kan tjekke i UI>
- Hvis relevant: <evt. playwright/screenshot-verifikation>
## Spørgsmål til Dennis
- <evt. valg brugeren skal tage før implementering — f.eks. "skal eksisterende data migreres, eller kun nye records?">
## Godkend
Hvis du vil have mig til at implementere det her, så svar med:
> @hermes godkend
(eller sæt label `hermes-approved` på issuet)
+56
View File
@@ -0,0 +1,56 @@
---
name: kanban-workflows
description: "Use when operating Hermes Kanban workflows: decomposing work as an orchestrator, executing as a worker, managing lanes, status, blockers, and reconciliation."
version: 1.0.0
author: Hermes Agent
license: MIT
metadata:
hermes:
tags: [kanban, orchestration, workers, task-management, hermes]
related_skills: [autonomous-coding-agents]
---
# Kanban Workflows Umbrella
## Overview
Kanban work has two sides: orchestrators decompose and route work without doing it themselves; workers execute a scoped card, keep status current, and return verifiable evidence. Use this umbrella for both roles.
## When to Use
- Acting as a Hermes Kanban orchestrator.
- Acting as a Hermes Kanban worker.
- Deciding whether to split, assign, block, or reconcile cards.
- Handling worker pitfalls, examples, and edge cases.
## Role Playbooks
### Orchestrator
Decompose outcomes into cards small enough for one worker, include acceptance criteria, and avoid the temptation to implement the work directly. Route work, unblock workers, and reconcile results.
### Worker
Own exactly the assigned card. Update status when starting, blocked, or done. Run the requested checks and return concise evidence: files changed, commands run, output, and remaining risks.
### Lanes and external agents
If a worker uses an external coding agent lane, keep Kanban as the source of truth and verify the lane's output before closing the card.
## Common Pitfalls
1. Orchestrators doing implementation work themselves.
2. Workers broadening scope beyond the card.
3. Closing cards from self-reports without independent verification.
4. Losing blocker state instead of surfacing it promptly.
## Package Notes
Archived source skills retain the deeper role-specific text: `kanban-orchestrator` and `kanban-worker`.
## Verification Checklist
- [ ] Card scope and acceptance criteria are explicit.
- [ ] Exactly one owner is responsible for each action.
- [ ] Status reflects reality: pending, in progress, blocked, done.
- [ ] Completion includes verifiable evidence.