Writing skills for AI assistants: where to put them, five complete examples

What a skill is

A skill is a reusable packet of instructions that teaches an AI assistant how to handle one class of tasks: which tools to call, which files to read first, what output format to produce. Unlike a one-off prompt, a skill is stored, versioned and loaded automatically whenever its trigger matches.

Think of it as onboarding documentation for a colleague who forgets everything between sessions: precise, example-driven, and short enough to actually be read. In OpenCode the agent sees only id, name and description at each step, then loads the full body on demand with the skill tool — or you load it directly with @skill-id. Behavior that must hold across sessions belongs in skills, one-off wishes belong in the prompt. Identities with their own model and permissions are agents, not skills.

Where to put skills

Skills are model-independent: the free OpenCode model, GPT 6 Luna and Claude Opus 5.5 all read from the same places. What differs is how strictly each model follows a long skill — see Skills per model. Put team skills in the project so they are committed, personal helpers in the global dir.

Two file shapes, same registry. Either a flat file at the source root or a directory with an exact SKILL.md anywhere below it. In OpenCode the path gives the id and the frontmatter name is only a label; Claude Code and the Agent Skills standard expect name to be that same kebab-case id, so write name: git-release and the skill works in both:

skills/review.md -> id: review skills/git-release/SKILL.md -> id: git-release skills/teams/release/SKILL.md -> id: release

Extra entries in opencode.jsonc, resolved from the active working dir, home expansion allowed, arrays merge across configs:

vi opencode.jsonc

{ "$schema": "https://opencode.ai/config.json", "skills": ["./team-skills", "~/shared/opencode-skills"] }

Precedence is by id, later source wins: built-ins, then .claude, then .agents, then global opencode/skills, then project .opencode/skills from root toward the current dir, then explicit skills entries. Avoid duplicate ids unless you mean to override. Full layout with links is under Real project skeleton, gating per agent under Gating skills per agent.

Anatomy of a good skill

Every skill needs four parts. A name and one-line description so the assistant can decide relevance. Trigger conditions: when to load it and, equally important, when not to. The procedure itself as numbered steps, with exact commands rather than vague advice. And two or three examples showing input, actions and expected output.

OpenCode frontmatter is small and optional, but description decides discovery — no description means never advertised. Set opencode/autoinvoke: false only to hide a helper that other skills include by hand. Quote any value that contains a colon followed by a space: YAML reads an unquoted description: Aredel: decide as a nested key and rejects the whole frontmatter, so the skill silently disappears. Example file .claude/skills/git-release/SKILL.md:

mkdir -p .claude/skills/git-release

vi .claude/skills/git-release/SKILL.md

--- name: git-release description: Prepare release notes, version bumps, and GitHub releases --- ## Workflow 1. Read references/release-policy.md. 2. Summarize merged changes since the previous tag. 3. Propose the version bump before changing files.

Paths inside the skill are relative to the dir with SKILL.md, so keep scripts and references beside it. The advertised list shows only id, name and description plus up to ten supporting paths, never the full body. What kills skills: generic wisdom, missing negative cases, and length. If it does not fit on one screen, split it into two skills.

Five complete examples

Copy these as starting points. Each one is a full file body, not a fragment. All five live in the tree under Real project skeleton.

Example 1 — flat commit style. File .opencode/skills/commit-style.md, id commit-style. Teaches message format for the whole repo in 12 lines:

mkdir -p .opencode/skills

vi .opencode/skills/commit-style.md

--- name: commit-style description: Write repo commit messages in Area: change form under 70 chars --- ## Use when User asks to commit, stage, or push the changes. ## Do not use for Code review, unstaged debug logs, release notes. ## Workflow 1. Run git status --short and git diff --stat. Never commit unrelated files. 2. Message form: Area: specific change, under 70 chars. Example: Auth: reject expired tokens with 401. 3. Stage only the agreed paths, show the staged summary, wait for approval. 4. Commit with the approved message, no push unless asked.

Invoke with @commit-style Write a commit for the auth fix. Happy path: two changed files produce one staged commit. Edge case: mixed unrelated files produce two separate proposals. Lookalike where it must stay silent: user asks to review a diff.

Example 2 — directory migration skill with helpers. File .opencode/skills/db-migrate/SKILL.md, id db-migrate:

mkdir -p .opencode/skills/db-migrate

vi .opencode/skills/db-migrate/SKILL.md

--- name: db-migrate description: Safe database migration with review and rollback plan --- ## Use when User mentions migration, migrates the DB, edits files under db/. ## Do not use for Plain SELECT questions, schema explainers, seed data design. ## Workflow 1. Read references/policy.md for naming and reversibility rules. 2. Run git diff --stat and show the planned SQL diff. Stop and wait for approval. 3. Run scripts/rollback.sh --dry-run and paste the output. 4. Apply the migration, then run npm test -- --tag migration. 5. Output: applied files, rollback command, test result. ## Example Input: Add unique index on users.email. Actions: propose db/014_unique_email.sql, show rollback DROP INDEX, dry run first. Output: three bullet lines with file, rollback, tests green.

Beside it: scripts/rollback.sh prints the reverse SQL without applying it, references/policy.md holds naming rules in 20 lines:

# policy.md: every migration has a down file, no raw DROP TABLE, # index names like idx_users_email, all SQL reviewed before apply.

The agent reads helpers only when the skill tells it to. Flat skills get no neighbor file list, so this directory shape is the right choice as soon as a skill needs more than one file.

Example 3 — flat release skill. File .opencode/skills/git-release.md, id git-release:

vi .opencode/skills/git-release.md

--- name: git-release description: Prepare changelog, version bump, tag, and release notes --- ## Use when User asks for release, changelog, version bump, tag the release. ## Workflow 1. Run git log --oneline v1.4.0..HEAD and group into Features, Fixes, Docs. 2. Propose the semver bump with reasons. Wait for approval. 3. Update CHANGELOG.md and package.json, run npm test. 4. Tag v1.5.0 and draft notes with highlights first. ## Example Input: Prepare 1.5.0. Output: bump minor because of new export API, changelog draft, tag command ready.

Example 4 — test writer for cheap models. File .opencode/skills/test-writer/SKILL.md, id test-writer. Written for the free OpenCode model and GPT 6 Luna: short, imperative, one exact test command:

mkdir -p .opencode/skills/test-writer

vi .opencode/skills/test-writer/SKILL.md

--- name: test-writer description: Add focused unit tests for one module with repo test runner --- ## Use when User asks to add tests, cover a module, fix missing coverage. ## Do not use for E2E suites, load tests, manual QA plans. ## Workflow 1. Read the target module and glob sibling tests under tests/ for style. 2. Write one test file per module: happy path, one edge, one error. 3. Run npm test -- --run tests/auth.test.ts and paste failures. 4. Fix until green. Output: new file path, cases added, command result. ## Example Input: Cover src/auth.ts. Actions: read src/auth.ts, mirror tests/auth.test.ts style, three cases. Output: file created, 3 passed in output block.

Example 5 — strict review skill for the top model. File .opencode/skills/api-review/SKILL.md, id api-review. Longer rubric, only the Opus reviewer agent is allowed to load it (see Gating):

mkdir -p .opencode/skills/api-review

vi .opencode/skills/api-review/SKILL.md

--- name: api-review description: Review API diffs for bugs, security, and missing tests in severity order --- ## Use when User asks to review a diff, audit an endpoint, check auth handling. ## Workflow 1. Run git diff --cached and list touched endpoints. 2. Check in order: auth bypass, injection, broken status codes, missing tests. 3. For each finding give file, line, severity, and a minimal fix. 4. Verdict: ship, fix first, or block. Never edit files, report only. ## Example Input: Review staged auth diff. Output: 1. High: missing 401 on expired token in src/auth.ts:42. 2. Medium: no test for refresh replay. Verdict: fix first.

Pair it with the reviewer agent in Agents examples that denies edit so review never mutates code.

Real project skeleton

Full layout of a project with all five skills plus shared team skills. Output of tree, every element links to the chapter where it is explained:

my-app/ ├── opencode.jsonc ├── .opencode/skills/ │ ├── commit-style.md │ ├── db-migrate/ │ │ ├── SKILL.md │ │ ├── scripts/ │ │ │ └── rollback.sh │ │ └── references/ │ │ └── policy.md │ ├── git-release.md │ ├── test-writer/ │ │ └── SKILL.md │ └── api-review/ │ └── SKILL.md ├── team-skills/ │ └── deploy/ │ └── SKILL.md └── .opencode/agents/ ├── reviewer.md └── builder.md

Read it top down: opencode.jsonc registers extra dirs and gates ids per agent, commit-style is the flat starter, db-migrate is the directory pattern with helpers, test-writer is tuned for cheap models, api-review is the strict Opus-only skill. The sibling agents dir holds the profiles that load these ids.

Aredel project map

The aredel repo is a large PHP site with over 7000 PHP files where URL maps 1:1 to file path. Repo root and docroot:

/home/andrei/github/aredel

container/src/

The repo is worked on with OpenCode and Claude Code, so every skill lives once in .claude/skills/ at the repo root: OpenCode reads it as a compatibility location, Claude Code natively. No global skills for aredel work — global entries widen the advertised list on every step and cost tokens on every model. This layout replaced an .opencode/skills/ set plus a nested override inside the docroot; why, and how each step was checked, is in Agent setup audit. Role wiring is in Aredel trio on one checkout.

aredel/ ├── AGENTS.md ├── opencode.jsonc └── .claude/skills/ ├── aredel-map/SKILL.md ├── aredel-page/ │ ├── SKILL.md │ └── references/page-checklist.md ├── aredel-blocks/ │ ├── SKILL.md │ └── references/block-classes.md ├── aredel-css/SKILL.md ├── aredel-tooling/SKILL.md ├── aredel-verify/SKILL.md └── ai-style/SKILL.md

The model sees seven short ids, all advertised. Heavy notes stay in references/ and load only when the skill body tells the agent to read them. Every description starts with Aredel: so filtering happens from the advertised list without loading bodies, and every description is quoted: YAML reads an unquoted Aredel: decide as a nested key and rejects the whole frontmatter.

Aredel skill 1 — map, tiny. File .claude/skills/aredel-map/SKILL.md. Distilled from the internal page versus include rules, under 20 lines so even a free OpenCode model follows it:

--- name: aredel-map description: "Aredel: decide page versus include before editing or creating any PHP file under container/src/" --- ## Use when User asks to edit, fix, or create any file under container/src/. ## Do not use for CSS-only edits (aredel-css), tooling under container/tools/ or tests (aredel-tooling). ## Workflow 1. Basename matches _top_path, _menu, _inc, _footer fragment, arj, copy dup, validate_, demo, or PascalCase helper like RelatedArticles.php: hands off, it is a non-page. 2. Else check footer path: page must render bottomid via createFooter or terminal _footer include. If already covered, hands off. 3. Else it is a page candidate: URL equals src plus path, confirm in container/src/sitemap.xml before patching. ## Example Input: Fix src/ao/chessboard/game.php. Output: Non-page, game tooling, refuse footer patch, propose page-level fix instead. ## Deep reference Full pattern table and footer rules: _internal_ai_skill_for_files.md at the repo root.

Aredel skill 2 — page scaffold. File .claude/skills/aredel-page/SKILL.md. References hold the long class table so the body stays short:

--- name: aredel-page description: "Aredel: scaffold a new page under container/src: ArticlePage, BasePage, RelatedArticles, tcont and lesson nav" --- ## Use when User asks for a new article, lesson, hub, or course index. ## Do not use for Edits to article body text (aredel-blocks), CSS (aredel-css), tooling, or redirects. ## Workflow 1. Read references/page-checklist.md for the class choice: ArticlePage for lessons, BasePage for hubs, DictPage for dictionary entries. 2. Create sibling RelatedArticles.php with articles plus breadcrumbs, wire tcont_urls, wire prev and next lesson on both neighbours. 3. Never use legacy 010_header, 020_header, hand TOC tables, or new Page. 4. Run php -l and CLI render with DOCUMENT_ROOT shim, then regenerate the sitemap (a page was added). ## Example Input: Add dev/ruby/course lesson 51. Output: ArticlePage file plus RelatedArticles entry plus tcont plus sitemap run. ## Deep reference Working notes on page classes and migration: skill.md at the repo root, sections 2 and 6.

Aredel skill 3 — blocks and trees. File .claude/skills/aredel-blocks/SKILL.md:

--- name: aredel-blocks description: "Aredel: article body HTML between createHead and createFooter: code and command blocks, trees, paths, external links" --- ## Use when User edits article body HTML between createHead and createFooter. ## Do not use for New pages (aredel-page), CSS (aredel-css), tooling, or test files. ## Workflow 1. One shell command per cmd1, narrative in p2, output in cout1, Python in pycode, generic code in somecode. Never use pre code. 2. Tree output uses cout1 wspw with each element as link to its chapter anchor, same as ai-style pages. 3. Standalone filesystem path on its own line uses path p1 (tight) or path p2 (section break); inline mentions stay tt spans. 4. External URLs are always real links with class exturl and rel nofollow, never bare https text. 5. First-mention Linux, Windows, Unix links only in prose p2, never in code or paths. 6. A snippet the reader must put in a file (config, agent, skill) names that file and is preceded by the cmd1 that opens it, e.g. `vi opencode.jsonc`; add `mkdir -p <dir>` first when the directory may not exist. 7. A reference to another part of the same page (test 3, step 2, the table above) is a link to an anchor placed right before that part: `<div id="test-3" class="anchor"></div>` there, `<a href="#test-3">test 3</a>` here. 8. Keep tag balance: php -l plus render must stay exit 0. ## Example Input: Add project tree to a lesson. Output: cout1 wspw block with five linked elements plus render check. ## Deep reference Full block class list: references/block-classes.md; highlight rules: skill.md at the repo root, code-block section.

Aredel skill 4 — CSS. File .claude/skills/aredel-css/SKILL.md. Advertised like the others: it used to be hidden with opencode/autoinvoke: false, and a hidden skill is one the role editing CSS never finds:

--- name: aredel-css description: "Aredel: CSS changes: code-block classes in source/_codeblocks.css, site overrides, building the served sheets with build-css.sh" --- ## Use when User asks for a new code block class or a change to site CSS under container/src/.css/. ## Do not use for Page markup (aredel-blocks), page scaffolds (aredel-page), tooling. ## Workflow 1. New code class: clone the nearest sibling language block in `container/src/.css/source/_codeblocks.css` (`.phpcode`, `.pycode`, `.somecode`, …), rename the class, label and header colors. Keep the HTML class casing (`.incFr13`, not `.incfr13`). 2. Add the class to the table in `container/docs/articles.md` and to `CODE_CLASSES` in `container/tests/test_css_sources.py`. 3. Build from `container/`: `tools/css/build-css.sh`; gate with `tools/css/build-css.sh --check`. Never edit `.css/output/*.css`; language classes live in `source/_codeblocks.css`, which is a core input, so do not copy them into the layout partials or a site file. 4. Verify the class is present in all four built sheets and render one page using it (header label, dark background, line numbers). 5. Fixing an existing code-block rule: edit `_codeblocks.css`; use the site file only for a genuinely site-specific override, never rewrite shared selectors in place. ## Deep reference Full CSS pipeline and override precedent: skill_css.md at the repo root, section 5.

Aredel skill 5 — tooling. File .claude/skills/aredel-tooling/SKILL.md. Scripts, tests and the backoffice follow different rules from pages, so they get their own short skill:

--- name: aredel-tooling description: "Aredel: Python and shell tooling in container/tools, tests in container/tests, the backoffice in backoffice-container" --- ## Use when User changes a script, a test, or backoffice code. ## Do not use for Pages and CSS under container/src (aredel-map, aredel-page, aredel-css). ## Workflow 1. Shell scripts: bump the `# version X.Y.Z` header on line 2 on every edit (pre-commit check-version); shellcheck runs in pre-commit. 2. Python targets 3.12. Gate with `pre-commit run --files <paths>` (ruff, ruff format, mypy, pylint with container/pyproject.toml). 3. Dependencies: add to the owning pyproject.toml, then `uv lock --project <dir>` and `uv sync --project <dir>`. Never pip install into a .venv; the next uv sync wipes it. 4. Tests: `cd container && ./tools/run-tests-venv.sh -- <tests> -q`; backoffice: `cd backoffice-container && .venv/bin/python -m pytest -q`. 5. A script that rewrites a file other code reads writes a temp file in the same directory and renames it (container/docs/atomic-file-writes.md). 6. Tree-wide rewrites read and write bytes so CRLF survives where HEAD has it. 7. Backoffice: blocking work goes in plain `def` endpoints (FastAPI runs them in a thread pool), never in `async def`. Restart backoffice-container/start.sh to rebuild the UI. ## Example Input: Add a --dry-run flag to container/tools/generate-sitemap.sh. Output: flag plus usage line, version header bumped, shellcheck and a dry run shown. ## Deep reference skill.md at the repo root, sections 1 and 4; container/tests/README.md; backoffice-container/README.md.

Aredel skill 6 — verify before commit. File .claude/skills/aredel-verify/SKILL.md. Only the trigger and workflow are shown; the file also holds commit approval, the commit-plan lifecycle and the pre-commit failure audit:

--- name: aredel-verify description: "Aredel: finish or commit work: php -l, CLI render, venv tests, pre-commit failure audit, CRLF-safe commit" --- ## Use when User asks to commit, verify, or finish aredel work. ## Do not use for Content planning, new page scaffolds, CSS design. ## Workflow 1. Run php -l on touched files. Render touched pages via CLI shim with DOCUMENT_ROOT set to container/src, expect exit 0 and only REMOTE_ADDR noise. 2. Run container tests via `container/tools/run-tests-venv.sh -- <tests>` (from `container/`: `./tools/run-tests-venv.sh`), never pip install into managed venv. 3. For tree-wide edits preserve CRLF where HEAD has CR, run check-reflow gate without SKIP on final state. 4. Regenerate sitemap after page add or remove and show grep -c loc count. 5. On a failed pre-commit run: name the hook, audit its finding, retry with the narrowest fix. `SKIP=<hook-id>` disables one hook only after the audit below; every other gate must still run. Never bypass the whole suite with `git commit --no-verify`, `git -c core.hooksPath=/dev/null`, or an equivalent. If a hook environment is broken, repair it and rerun. 6. Python tooling must stay on 3.12. Run `python3 container/tools/linters/check_python_consistency.py --require-precommit` (the `check-python-consistency` pre-commit hook does this automatically) and the matching pytest guard before finishing.

Area skill — AI articles only. File .claude/skills/ai-style/SKILL.md. It lives with the others and limits itself to container/src/ai/ through its description. The older nested copy under container/src/ai/.opencode/ sat inside the docroot and was uploaded with the site:

--- name: ai-style description: "Aredel: AI articles under container/src/ai/ only: model names and prices, free-model links, cross-links between the agent guides" --- ## Use when The task edits or creates a page under container/src/ai/. ## Do not use for Any page outside container/src/ai/. ## Workflow 1. Model pricing tables carry a link to /ai/opencode/#verify-prices; never quote prices as eternal. 2. Prose never names a free model id: write "free OpenCode model" and link it to /ai/opencode/free_model/, the one page that names the current id and its fallbacks. Config examples use the current id from that page. 3. First mention per page or chapter links GPT 6 Luna to /ai/models/luna/, Claude Opus 5.5 to /ai/models/claude5_5/, Muse Spark 1.3 (paid) to /ai/models/muse_spark/. 4. OpenCode agent examples live on /ai/opencode/agents/ (#ex-triage, #ex-builder, #ex-reviewer, #ex-orchestrator); /ai/agents/ is the tool-agnostic overview. 5. External URLs use class exturl with rel nofollow. 6. No secrets, customer data, or private code to a free tier; route to a paid zero-retention model or a local model.

Root wiring. Root AGENTS.md routes each task to its skills and keeps the few rules every task needs; the skills carry the procedures:

# Project instructions aredel is a PHP site: `container/src` is the docroot and a URL maps 1:1 to a file. Tools live in `container/tools`, tests in `container/tests`, the local dashboard in `backoffice-container`. Skills live in `.claude/skills/<name>/SKILL.md`; OpenCode and Claude Code both read them. ## Route the task | Task | Load these skills first | |---|---| | Edit or create anything under `container/src` | `aredel-map` | | New page, lesson, hub or course index | `aredel-page` + `aredel-blocks` | | Article body HTML | `aredel-blocks` | | CSS under `container/src/.css` | `aredel-css` | | Pages under `container/src/ai` | `ai-style` too | | `container/tools`, `container/tests`, `backoffice-container` | `aredel-tooling` | | Finish, verify or commit | `aredel-verify` | ## Always - Never commit without asking: finish verification, then ask whether to commit or hand over explicit-path `git add` / `git commit` commands. Never `git add -A`, never `--no-verify` or another hook bypass, never edit ignore files to hide untracked work (details: `aredel-verify`). - Bump the `# version X.Y.Z` header of every changed versioned file. - Python stays on 3.12; declare dependencies in `pyproject.toml` and lock with uv, never `pip install` into a uv-managed `.venv`. - Keep CRLF line endings where HEAD has them. - Regenerate the sitemap (`cd container && ./tools/generate-sitemap.sh`) only after adding or removing pages. - Articles written by an assistant credit `author: ['Arthur Isaev', '/about/arthur_isaev/']`. ## Agents - OpenCode: start it with `container/tools/ai/opencode.sh`, which checks that every pinned model id still exists first. Roles in `.opencode/agents/`: `triage` (read-only, free model), `builder` (edits, GPT 6 Luna), `reviewer` (read-only, Claude Opus 5.5; `/review`). - Claude Code: the same three roles in `.claude/agents/`.

Gate by role only where the job allows it: triage sees only the map, builder and the Opus reviewer see every skill, because the role that edits or judges PHP must see the rules for it. The full role config is in Aredel trio.

Skills per model

One location, three reading styles. Tune length and strictness to the model in Models that matter:

Practical split from OpenCode workflows: the free OpenCode model does drafts with @test-writer, Luna runs routine migrations with @db-migrate, Opus does the final pass with @api-review. Never send secrets to the free contributor tier — switch to paid or local for sensitive code. How the three share one checkout with per-agent visibility is shown in Three models, one project.

Gating skills per agent

Advertise every skill globally, then restrict per agent. Last matching rule wins. Example: everyone sees commit and tests, only the reviewer sees the strict review skill, the explore agent sees nothing to load. All of it goes in opencode.jsonc:

vi opencode.jsonc

{ "$schema": "https://opencode.ai/config.json", "permissions": [ { "action": "skill", "resource": "*", "effect": "allow" } ], "agents": { "reviewer": { "permissions": [ { "action": "skill", "resource": "*", "effect": "deny" }, { "action": "skill", "resource": "api-review", "effect": "allow" } ] }, "explore": { "permissions": [ { "action": "skill", "resource": "*", "effect": "deny" } ] } } }

Effects: allow loads silently, ask shows the skill but asks before loading, deny hides it from the model entirely. Agent blocks append after global rules. Full agent side is in How to build an agent.

Two gates: discovery vs permission

Loading a skill needs two independent gates to pass, and they answer different questions. Discovery asks where: is this skill advertised for the current working dir. Permission asks who: may this agent load it. Both must pass; either one fails and the model never sees the skill:

Discovered hereAgent permissionWhat the model sees
yesallowAdvertised, loads silently
yesaskAdvertised, asks before loading
yesdenyInvisible, same as absent
noallowInvisible, permission never fires

There is also a third state: discovered and registered but hidden from the advertised list via opencode/autoinvoke: false. The skill stays loadable by hand with @skill-id. Use it only for helpers another skill pulls in: a hidden skill is never offered to the role that needs it. The aredel aredel-css skill was hidden this way and stayed unused during CSS work, so it is advertised again.

Example 1 — same permission, different dir. In a monorepo, builder is allowed web-style, which lives in packages/web/.opencode/skills/. Editing the web package, builder sees the checklist and loads it. Editing the API package, the skill is never advertised there, so the same allow-rule never fires. One permission config, location-dependent visibility. Keep such nested skills out of any directory that gets deployed: a skill inside a docroot is uploaded with the site.

Example 2 — same dir, different role. In the aredel repo root all seven skills are discovered, but triage is allowed only aredel-map, while builder and the Opus reviewer see every skill. Hide a skill from a role only when the role never needs it: an earlier aredel config hid aredel-map from the builder that edits PHP, although AGENTS.md told every agent to read it first. Same shelf, different visibility per role — the split from Three models, one project.

Example 3 — debugging a missing skill. The agent should know a skill but does not mention it: check discovery first (wrong dir, wrong filename — must be root-level *.md or exactly SKILL.md, frontmatter that does not parse as YAML), then permission (a deny later in the merged list wins), then autoinvoke: false. Nine times out of ten it is the first two.

Team catalog over HTTP

For skills shared across repos, ship a catalog dir over HTTP with index.json at the base URL. Each entry lists safe relative files, must include SKILL.md or name.md, bump version to refresh caches:

{ "skills": [ { "name": "deploy", "version": "3", "files": ["SKILL.md", "references/checklist.md"] } ] }

Register it alongside local dirs in opencode.jsonc:

{ "$schema": "https://opencode.ai/config.json", "skills": ["./team-skills", "https://example.com/skills/"] }

Downloaded dirs become source roots, so deploy/SKILL.md keeps id deploy. Keep ids lowercase kebab-case, unique across all sources, or later sources silently override earlier ones.

Testing and sharing

Test every new skill with three prompts before committing. Example for db-migrate:

Load manually with @skill-id to verify the body, then check the agent permission that gates it. Share as plain files next to the code they govern and link from team docs — the same single-entry principle as agents. Mention @db-migrate once instead of pasting the checklist into every prompt.

Trigger design

Triggers are half the skill. Bad trigger block, fires everywhere:

Use when: any database question.

Good trigger block, fires only on intent plus path:

## Use when User mentions migration, migrates the DB, edits files under db/. ## Do not use for Plain SELECT questions, schema explainers, seed data design.

Name the exact phrases, file paths and contexts that should load it, and just as explicitly list the near-misses where it must stay silent. Over-broad triggers teach the assistant to ignore the skill entirely. Good blocks name a dir, a verb, and two negatives. Test the negatives — they are the common failure.

Failure smells

Four smells say the skill, not the model, is broken, each with a fix:

Fix the skill first; a bigger model only makes bad instructions fail more eloquently. When the fix needs a different identity or tool set rather than better text, promote it to an agent instead.

Article author: Arthur Isaev