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
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:
- The Python scripts live in the project being worked on, importable as
scripts.gitea.*from project root. - The project has uv set up and a
.envwithGITEA_*vars. - The user is in dirigent itself — the skill says
dirigent workpad, referencesdocs/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
.envin the current working directory (the project the user is in), not the plugin dir.python-dotenvalready supports this. - Workpad path comes from the
workpadskill'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 +uvcache 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
- Copy
scripts/gitea/contents →plugins/gitea/skills/gitea/scripts/. - Drop
__init__.pyif scripts are run as files; keep it if we wantpython -m. - Add PEP 723 inline-dep blocks to each CLI entrypoint (
fetch_ticket.py,create_ticket.py, etc.):This makes each script runnable in isolation via# /// script # requires-python = ">=3.11" # dependencies = ["httpx", "pydantic>=2", "python-dotenv"] # ///uv run script.pywithout a project context. - Rewrite imports:
from scripts.gitea.client import ...→from client import ...(relative within the scripts/ dir). Or run aspython -mwithPYTHONPATHset.
Phase 2 — parameterize project-specific behavior
- Replace hardcoded
docs/workpad/tickets/with a CLI flag--workpad <path>defaulting to env varWORKPAD_FOLDER, defaulting to./docs/workpad. Honor theworkpadskill's resolution order. - Move the hardcoded label-emoji list out of
setup_labels.pyinto a JSON/YAML file shipped with the plugin (labels.default.json) so users can override per-project. - Audit any other dirigent-isms: references to "dirigent" in error messages, the README mentioning dirigent's workpad, etc.
Phase 3 — rewrite the skill markdown
Step 0: Read Current Script Capabilitiescurrently points atscripts/gitea/CLAUDE.md. Replace with inline capability summary in the SKILL itself — the skill is the reference now; the scripts are an implementation detail.- Replace every
uv run python -m scripts.gitea.Xexample withuv run ${CLAUDE_PLUGIN_ROOT}/skills/gitea/scripts/X.py. - Connectivity check stays — just relocate the inline
python -csnippet's imports to whatever the new module layout is. - Error-recovery table stays as-is. It's good.
Phase 4 — documentation
- README at plugin root: install instructions + Gitea token setup. Lift most of
scripts/gitea/README.md, drop the dirigent-specific bits. - 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.mdandSKILL.mdfor 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 #Nkeywords — 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-flowskill that ties together: "fetch a ticket → write plan in workpad → post plan back as comment → branch viastart_ticket". This is workflow glue, not API access. - A
gitea-bulkskill for grooming: relabel many issues, close stale ones, etc. - A
gitea-prskill 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.