From c088aaa6368137ece22493d8bc8405f31d845cb9 Mon Sep 17 00:00:00 2001 From: MS Date: Wed, 12 Aug 2026 11:40:19 +0530 Subject: [PATCH] The installable unit is a plugin, and a skill PR is a security review THE LAYOUT. A plugin is the thing you install, and a skill is one of the things a plugin contains. Verified against a real installation of the reference implementation rather than assumed: a repository containing only skills still becomes marketplace, then plugin, then version. The unit is uniform even when the payload is one skill. Adopting it now is not only future-proofing. Consent is a fingerprint over the whole plugin directory INCLUDING files the loader does not read, so that teaching the loader to read more later cannot widen what was agreed to without re-asking. A plugin directory makes that hold for free when MCP servers and hooks arrive. Leaving it flat would mean a cache migration and a forced re-consent for everyone who ever installed anything, because the fingerprint boundary itself moves. skills//SKILL.md -> plugins//skills//SKILL.md WHY OUR OWN BUILT-INS ARE NOT PUBLISHED HERE, since teach and the ML family are the obvious omissions and the reason is structural rather than editorial. An installed skill claiming a bundled id is not applied at all. The registry reserves bundled ids against origin `installed` and reports "was not applied: that name belongs to a built-in skill". Rejected, not shadowed. Publishing the ML family here would therefore have shipped a plugin that installs nothing and emits seven notes, which is a worse first experience than shipping two plugins that install cleanly. That reservation is correct and deliberate: an installed file taking a bundled id would replace the built-in AND strip the finalOverride slot it relies on. Teach is the same boundary from the other side, needing that slot for its length rule and so unable to be published faithfully at all. The corollary is the useful half, and it is now written into the design spec: dogfooding the install path always needs content that does not already ship. CI, AND WHY IT COMES FIRST. A contributor whose first pull request is rejected by a check learns the rule in thirty seconds. One whose skill merges and then silently does nothing learns that we do not review, and that cannot be taken back. So the checks land in the same change as the invitation: no override block, no model or provider named, every plugin has a manifest that parses, every plugin is listed and every listing exists, every SKILL.md has frontmatter carrying name and description. --- .github/workflows/skills.yml | 87 ++++++++++++++++ .oxcode-plugin/marketplace.json | 8 +- CONTRIBUTING.md | 98 ++++++++++++++----- .../code-review/.oxcode-plugin/plugin.json | 13 +++ .../code-review/skills}/code-review/SKILL.md | 0 plugins/ui-review/.oxcode-plugin/plugin.json | 13 +++ .../ui-review/skills}/ui-review/SKILL.md | 0 7 files changed, 188 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/skills.yml create mode 100644 plugins/code-review/.oxcode-plugin/plugin.json rename {skills => plugins/code-review/skills}/code-review/SKILL.md (100%) create mode 100644 plugins/ui-review/.oxcode-plugin/plugin.json rename {skills => plugins/ui-review/skills}/ui-review/SKILL.md (100%) diff --git a/.github/workflows/skills.yml b/.github/workflows/skills.yml new file mode 100644 index 0000000..694cd85 --- /dev/null +++ b/.github/workflows/skills.yml @@ -0,0 +1,87 @@ +name: skills + +on: + pull_request: + push: + branches: [main] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # An installed skill never receives the final-override slot: it is the one + # position from which a prompt can overrule a shared safety rule, and a + # downloaded file does not get it. So a contributor who writes one has + # written something that silently does nothing. Better to learn that from + # a failing check than from a skill that quietly has no effect. + - name: No override blocks, which an installed skill never receives + run: | + if grep -rl "oxcode:final-override" plugins/ ; then + echo "::error::An installed skill cannot use the final-override slot." + echo "::error::It is ignored at load, so this block would do nothing. Remove it." + exit 1 + fi + + # Model ids change under us and a skill that names one goes stale for + # everybody at once. capability is the field to use; OxCode routes. + - name: No model or provider named + run: | + if grep -rniE "^model:|\bkimi\b|\bdeepseek\b|\bgpt-[0-9]|\bclaude-|\bgemini\b" plugins/ --include=SKILL.md ; then + echo "::error::A skill must not name a model or provider. Use capability." + exit 1 + fi + + - name: Every plugin declares a manifest, and it parses + run: | + fail=0 + for d in plugins/*/ ; do + m="$d.oxcode-plugin/plugin.json" + if [ ! -f "$m" ]; then + echo "::error::$d has no .oxcode-plugin/plugin.json"; fail=1; continue + fi + python3 -c "import json,sys; json.load(open('$m'))" || { echo "::error::$m is not valid JSON"; fail=1; } + done + python3 -c "import json,sys; json.load(open('.oxcode-plugin/marketplace.json'))" \ + || { echo "::error::marketplace.json is not valid JSON"; fail=1; } + exit $fail + + # The manifest lists what exists, so a plugin nobody can find is a plugin + # nobody installs, and a listing that points at nothing is worse. + - name: Every plugin is listed, and every listing exists + run: | + python3 - <<'PY' + import json, os, sys + mk = json.load(open('.oxcode-plugin/marketplace.json')) + listed = {p['name']: p['source'] for p in mk['plugins']} + on_disk = sorted(d for d in os.listdir('plugins') if os.path.isdir(f'plugins/{d}')) + fail = False + for d in on_disk: + if d not in listed: + print(f"::error::plugins/{d} exists but is not in marketplace.json"); fail = True + for name, src in listed.items(): + if not os.path.isdir(src): + print(f"::error::marketplace.json lists {name} at {src}, which does not exist"); fail = True + elif not os.path.isdir(os.path.join(src, 'skills')): + print(f"::error::{src} has no skills/ directory"); fail = True + sys.exit(1 if fail else 0) + PY + + # A skill that will not parse cannot be merged. Two core keys, and a + # frontmatter block to carry them. + - name: Every SKILL.md has frontmatter with name and description + run: | + python3 - <<'PY' + import glob, sys + fail = False + for f in glob.glob('plugins/**/SKILL.md', recursive=True): + t = open(f, encoding='utf-8').read() + if not t.lstrip().startswith('---'): + print(f"::error::{f} has no frontmatter block"); fail = True; continue + head = t.split('---', 2)[1] + for key in ('name:', 'description:'): + if key not in head: + print(f"::error::{f} frontmatter is missing {key}"); fail = True + sys.exit(1 if fail else 0) + PY diff --git a/.oxcode-plugin/marketplace.json b/.oxcode-plugin/marketplace.json index d6123a2..1be8043 100644 --- a/.oxcode-plugin/marketplace.json +++ b/.oxcode-plugin/marketplace.json @@ -8,17 +8,17 @@ "plugins": [ { "name": "code-review", + "source": "./plugins/code-review", "description": "Review a change for defects that would reach a user, with a reproducing input for every one.", "category": "development", - "author": "Oxlo.ai", - "source": "./skills/code-review" + "author": "Oxlo.ai" }, { "name": "ui-review", + "source": "./plugins/ui-review", "description": "Review an interface for what a user would actually hit: contrast, focus, overflow, empty and error states.", "category": "design", - "author": "Oxlo.ai", - "source": "./skills/ui-review" + "author": "Oxlo.ai" } ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c45d8a0..f8ed874 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,31 @@ # Contributing a skill -One pull request, one skill folder. That keeps review fast and lets us take -yours without waiting on anything else in the same branch. +One pull request, one plugin. That keeps review fast and lets us take yours +without waiting on anything else in the same branch. + +## What review means here + +Read this part first, because it is what makes this repository different from a +docs repository. + +A skill is a system prompt that runs on someone else's machine with file and +command tools. So **a pull request here is a security review, not a docs +review**, and these are what a reviewer checks: + +- **The body instructs rather than describes.** A skill that restates the docs + changes no answer and costs tokens on every request. +- **Nothing needs a secret.** If it only works with your API key, it is not a + skill. +- **No model or provider is named anywhere.** Model ids change under us and a + skill naming one goes stale for everybody at once. Use `capability` and let + OxCode route. CI rejects this, so you will see it before we do. +- **The skill does not try to widen its own tools.** `tools` is a request that + gets clamped to whatever the session already allows. It is never a grant. +- **No `oxcode:final-override` block.** An installed skill never receives that + slot, so writing one ships something that silently does nothing. CI rejects it. + +Expect questions. We would rather ask than merge something that adds tokens to +every request and nothing to any answer. ## What we are looking for @@ -24,26 +48,50 @@ payments work. - **Generic advice.** If it reads like it could apply to any task, it will not change any answer. "Follow best practices", "write clean code", "consider edge cases" cost tokens on every request and buy nothing. -- **A model or provider named anywhere.** Model ids change under us and a skill - that names one goes stale for everybody at once. Use `capability` and let - OxCode route. A `model:` key is rejected by the parser, not ignored. - **A restatement of the docs.** Link the docs. Put the things the docs get wrong or bury in the skill. -- **Anything that needs a secret.** Skills are prompts. If it only works with - your API key, it is not a skill. +- Anything failing the review checks above. -## Writing it +## The layout -Start from [`template/SKILL.md`](template/SKILL.md). +The installable unit is a **plugin**. A plugin is a directory that may carry +several skills, which is why the ML pipeline ships as one plugin with seven. ``` -skills/ - your-skill/ - SKILL.md +plugins/ + your-plugin/ + .oxcode-plugin/ + plugin.json + skills/ + your-skill/ + SKILL.md ``` -The folder name is the skill's id and should match `name` in the frontmatter. -Use lowercase with hyphens. +Start from [`template/SKILL.md`](template/SKILL.md). + +The skill's directory name is its id and should match `name` in the frontmatter. +Lowercase with hyphens. A plugin carrying one skill usually gives them the same +name. + +`plugin.json`: + +```json +{ + "name": "your-plugin", + "version": "1.0.0", + "description": "Same one line as the marketplace entry.", + "author": { "name": "Your name or handle" }, + "license": "MIT", + "requires": { "oxcode": ">=0.4.0" } +} +``` + +`requires.oxcode` is the lowest version your plugin works on. Leave it at +`>=0.4.0` unless you use something newer, and raise it if you do: a user on an +older extension is then told what they need instead of installing something that +half works. + +## Writing it **Write the description as a trigger.** It is what the model reads to know your skill exists, and it is the one line a person reads in the picker. Say what it @@ -64,11 +112,11 @@ following it. ## Testing it before you open the PR -You do not need to install anything or wait for us. Copy your folder into your -own skills directory: +You do not need to install anything or wait for us. Copy the skill into your own +skills directory: ```bash -cp -r skills/your-skill ~/.oxcode/skills/ +cp -r plugins/your-plugin/skills/your-skill ~/.oxcode/skills/ ``` OxCode picks it up on save. Type `/your-skill` and give it a real task. If the @@ -85,24 +133,20 @@ Add your entry to `.oxcode-plugin/marketplace.json`: ```json { - "name": "your-skill", - "description": "Same one line as the frontmatter", + "name": "your-plugin", + "description": "Same one line as the manifest", "category": "development", "author": "Your name or handle", - "source": "./skills/your-skill" + "source": "./plugins/your-plugin" } ``` +CI checks that every plugin on disk is listed and every listing exists, so a +missing entry fails before a human looks at it. + In the description, tell us: - What task you used it on, and what changed in the answer. - What you deliberately left out, and why. That second one is the part we read first. It tells us you drew a boundary. - -## Review - -We review for whether the skill changes an answer, and for whether its -instructions are specific enough to follow. Expect questions about anything -that reads as general advice. We would rather ask than merge something that -adds tokens to every request and nothing to any answer. diff --git a/plugins/code-review/.oxcode-plugin/plugin.json b/plugins/code-review/.oxcode-plugin/plugin.json new file mode 100644 index 0000000..1a7076e --- /dev/null +++ b/plugins/code-review/.oxcode-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "code-review", + "version": "1.0.0", + "description": "Review a change for defects that would reach a user, with a reproducing input for every one.", + "author": { + "name": "Oxlo.ai" + }, + "license": "MIT", + "homepage": "https://github.com/Cyborg-Network/oxcode-skills", + "requires": { + "oxcode": ">=0.4.0" + } +} diff --git a/skills/code-review/SKILL.md b/plugins/code-review/skills/code-review/SKILL.md similarity index 100% rename from skills/code-review/SKILL.md rename to plugins/code-review/skills/code-review/SKILL.md diff --git a/plugins/ui-review/.oxcode-plugin/plugin.json b/plugins/ui-review/.oxcode-plugin/plugin.json new file mode 100644 index 0000000..5fe7f78 --- /dev/null +++ b/plugins/ui-review/.oxcode-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "ui-review", + "version": "1.0.0", + "description": "Review an interface for what a user would actually hit: contrast, focus, overflow, empty and error states.", + "author": { + "name": "Oxlo.ai" + }, + "license": "MIT", + "homepage": "https://github.com/Cyborg-Network/oxcode-skills", + "requires": { + "oxcode": ">=0.4.0" + } +} diff --git a/skills/ui-review/SKILL.md b/plugins/ui-review/skills/ui-review/SKILL.md similarity index 100% rename from skills/ui-review/SKILL.md rename to plugins/ui-review/skills/ui-review/SKILL.md