# Porting the `gitea` skill + scripts into reliquary Hands-on design for moving dirigent's `gitea` skill (which is a thin wrapper around `scripts/gitea/*.py` in the dirigent repo) into a reliquary plugin where the scripts and the skill ship as one unit. ## Current state in dirigent ``` dirigent/ ├── .claude/skills/gitea/SKILL.md # 123 LOC — thin wrapper, points at scripts └── scripts/gitea/ ├── CLAUDE.md # agent reference for scripts (~127 LOC) ├── README.md # human setup guide (~178 LOC) ├── __init__.py ├── client.py # 186 LOC — GiteaClient REST wrapper ├── close_ticket.py # 39 LOC ├── config.py # 68 LOC — env loading ├── create_ticket.py # 115 LOC ├── fetch_ticket.py # 113 LOC ├── list_tickets.py # 56 LOC ├── models.py # 66 LOC — Pydantic models ├── post_comment.py # 69 LOC ├── setup_labels.py # 43 LOC └── start_ticket.py # 163 LOC ``` Total: ~919 LOC of Python + ~430 LOC of docs. The skill itself does almost nothing — it tells the agent to *read* `scripts/gitea/CLAUDE.md` and *run* `uv run python -m scripts.gitea.`. Everything load-bearing lives in the dirigent project root: the venv, `uv.lock`, `pyproject.toml`, and `.env`. ## The fundamental tension The dirigent skill assumes: 1. **The Python scripts live in the project being worked on**, importable as `scripts.gitea.*` from project root. 2. **The project has uv set up and a `.env` with `GITEA_*` vars.** 3. **The user is in dirigent itself** — the skill says `dirigent workpad`, references `docs/workpad/tickets/`, and the labels list (🐛 bug, ✨ feature, 📋 workpad…) is curated for dirigent. To turn this into a **reusable plugin skill**, none of those assumptions hold. The scripts must live with the plugin, work from anywhere, and adapt to any project's workpad layout. ## Two viable port shapes ### Option A — Scripts ship inside the plugin (recommended) ``` plugins/gitea/ ├── .claude-plugin/plugin.json ├── README.md └── skills/gitea/ ├── SKILL.md └── scripts/ ├── __init__.py ├── client.py ├── config.py ├── models.py ├── fetch_ticket.py ├── create_ticket.py ├── post_comment.py ├── start_ticket.py ├── list_tickets.py ├── close_ticket.py └── setup_labels.py ``` Invocation pattern changes: - Skill expands `${CLAUDE_PLUGIN_ROOT}` to locate scripts. - Each call becomes `uv run --no-project --with httpx --with pydantic --with python-dotenv python ${CLAUDE_PLUGIN_ROOT}/skills/gitea/scripts/fetch_ticket.py 42` — or, better, an inline-deps PEP 723 header at the top of each script so they're self-installing. - Env vars come from `.env` in the *current working directory* (the project the user is in), not the plugin dir. `python-dotenv` already supports this. - Workpad path comes from the `workpad` skill's resolution rules (env var → just recipe → `/docs/workpad`), not hardcoded. **Pros:** - Plugin is fully self-contained — install it once, use it in any repo. - Versioned together with the skill markdown. - No requirement that the host project has uv configured. **Cons:** - `uv run --no-project --with httpx ...` is slow on first run (env build). Mitigation: PEP 723 headers + `uv` cache make second-run startup fast. - The scripts must be reworked to take a workpad root as an arg (or env var) rather than hardcoding `docs/workpad/`. ### Option B — Scripts vendored into each project (rejected) Keep the dirigent shape: ship a "scaffolder" that copies scripts into the user's repo. This is what `superpowers`-style skills sometimes do. **Why reject:** updates don't propagate, every project carries duplicate code, and the scripts become "the user's code now" which makes version skew permanent. ## Concrete port checklist If we go with Option A: ### Phase 1 — copy + decouple from dirigent 1. Copy `scripts/gitea/` contents → `plugins/gitea/skills/gitea/scripts/`. 2. Drop `__init__.py` if scripts are run as files; keep it if we want `python -m`. 3. Add PEP 723 inline-dep blocks to each CLI entrypoint (`fetch_ticket.py`, `create_ticket.py`, etc.): ```python # /// script # requires-python = ">=3.11" # dependencies = ["httpx", "pydantic>=2", "python-dotenv"] # /// ``` This makes each script runnable in isolation via `uv run script.py` without a project context. 4. Rewrite imports: `from scripts.gitea.client import ...` → `from client import ...` (relative within the scripts/ dir). Or run as `python -m` with `PYTHONPATH` set. ### Phase 2 — parameterize project-specific behavior 5. Replace hardcoded `docs/workpad/tickets/` with a CLI flag `--workpad ` defaulting to env var `WORKPAD_FOLDER`, defaulting to `./docs/workpad`. Honor the `workpad` skill's resolution order. 6. Move the hardcoded label-emoji list out of `setup_labels.py` into a JSON/YAML file shipped with the plugin (`labels.default.json`) so users can override per-project. 7. Audit any other dirigent-isms: references to "dirigent" in error messages, the README mentioning dirigent's workpad, etc. ### Phase 3 — rewrite the skill markdown 8. `Step 0: Read Current Script Capabilities` currently points at `scripts/gitea/CLAUDE.md`. Replace with **inline** capability summary in the SKILL itself — the skill *is* the reference now; the scripts are an implementation detail. 9. Replace every `uv run python -m scripts.gitea.X` example with `uv run ${CLAUDE_PLUGIN_ROOT}/skills/gitea/scripts/X.py`. 10. Connectivity check stays — just relocate the inline `python -c` snippet's imports to whatever the new module layout is. 11. Error-recovery table stays as-is. It's good. ### Phase 4 — documentation 12. README at plugin root: install instructions + Gitea token setup. Lift most of `scripts/gitea/README.md`, drop the dirigent-specific bits. 13. The skill markdown should be self-contained — don't make the agent open a second file unless absolutely necessary. ## Things to NOT do in the port - **Don't keep both `CLAUDE.md` and `SKILL.md` for the scripts.** That split made sense in dirigent because the scripts had a life of their own outside the skill. Inside a plugin they don't. One file: `SKILL.md`. - **Don't ship Pydantic v2 + httpx as hard plugin dependencies installed in the user's project venv.** Use PEP 723 inline deps so uv builds an isolated env per invocation. - **Don't hardcode `closes #N` / `fixes #N` keywords** — those are Gitea behavior, not script behavior. They should stay in the skill markdown as guidance, not in code. - **Don't copy `setup_labels.py`'s default label set verbatim.** It's tuned for one user's taste. Externalize. ## Open question — sibling skill in a "mini plugin" The user mentioned wanting **another skill in a custom mini plugin** that builds on this. That's not designed yet — likely candidates given the workpad ecosystem: - A `gitea-workpad-flow` skill that ties together: "fetch a ticket → write plan in workpad → post plan back as comment → branch via `start_ticket`". This is workflow glue, not API access. - A `gitea-bulk` skill for grooming: relabel many issues, close stale ones, etc. - A `gitea-pr` skill if/when Gitea's PR API gets used (the current scripts only touch issues). These should be a separate plugin so the core `gitea` plugin stays purely a thin API wrapper. The workflow-glue skill *depends on* the API skill but is opinionated about flow — exactly the kind of thing that should be swappable. ## Estimated work | Phase | Effort | Risk | |---|---|---| | Copy + PEP 723 conversion | ~1h | Low — mechanical | | Parameterize workpad path + labels | ~1-2h | Medium — touches 4-5 files | | Skill markdown rewrite | ~30min | Low | | README + plugin manifest | ~20min | Low | | Manual end-to-end test (real Gitea + new project) | ~1h | This is where the bugs are | Total: **half a day of focused work** for a complete port. The `start_ticket` flow is the most likely to surface issues because it touches git state, the workpad, and the API in one call.