Files
reliquary/docs/workpad/designs/2026-05-11-17-gitea-skill-port.md
T
g4borg 1b5a7638d7 🧠 Add brain-dump docs for skill adaptation
Add exploratory and design docs for porting dirigent skills to
reliquary, including:
- Explore port options for the gitea skill (in-plugin vs vendored)
- Document activation mechanics across Claude Code and cross-tools
- Include an orchestration comparison between dirigent and reliquary
  approaches
- Add research on glob negation and flag-file activation patterns
- Create uv-detection goals and skill-engines cross-tool reports
- Provide a research plan for future porting work and risk notes
2026-05-11 23:01:30 +02:00

8.1 KiB

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.<module>. 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

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.):
    # /// 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

  1. Replace hardcoded docs/workpad/tickets/ with a CLI flag --workpad <path> defaulting to env var WORKPAD_FOLDER, defaulting to ./docs/workpad. Honor the workpad skill's resolution order.
  2. 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.
  3. Audit any other dirigent-isms: references to "dirigent" in error messages, the README mentioning dirigent's workpad, etc.

Phase 3 — rewrite the skill markdown

  1. 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.
  2. Replace every uv run python -m scripts.gitea.X example with uv run ${CLAUDE_PLUGIN_ROOT}/skills/gitea/scripts/X.py.
  3. Connectivity check stays — just relocate the inline python -c snippet's imports to whatever the new module layout is.
  4. Error-recovery table stays as-is. It's good.

Phase 4 — documentation

  1. README at plugin root: install instructions + Gitea token setup. Lift most of scripts/gitea/README.md, drop the dirigent-specific bits.
  2. 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.