Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ You can add a plugin for a library or tool that isn't covered yet, or improve on

1. Copy `templates/plugin/` to `plugins/<name>/`. Plugin names start with `rf-` and describe the technology, for example `rf-database`.
2. Fill in `catalog.yaml`: a one-line summary, `tier: community`, `status: active`, yourself under `maintainers`, the libraries with the version ranges you checked, the Robot Framework range and the agents you tried. The schema is `schemas/catalog.schema.json`.

For each library, list under `skills` the skills of your plugin that cover it, so a tool that detects the library in a project can pick the right skill (for example `skills: [rf-browser]` for Browser in rf-web). Leave it out only when every skill of the plugin applies. When you add, rename or remove a skill, update `skills` too; the catalogue check fails on a skill that doesn't exist.
3. Rename the example skill to `rf-<topic>` (the folder name and the `name` in `SKILL.md` must match) and write it. Skill and subagent names must be unique across every plugin.
4. Add a line for your plugin to `.github/CODEOWNERS` with the same people as `maintainers`.
5. Regenerate the catalogue and the marketplace: `python scripts/build-catalog.py`.
Expand Down
12 changes: 6 additions & 6 deletions docs/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ RequestsLibrary and RESTinstance skills for API tests.
- **Version:** 1.0.0
- **Robot Framework:** `>=7.0`
- **Libraries:**
- RequestsLibrary (`robotframework-requests` `>=0.9.7`)
- RESTinstance (`RESTinstance` `>=1.8,<2`)
- RequestsLibrary (`robotframework-requests` `>=0.9.7`): `rf-requests`
- RESTinstance (`RESTinstance` `>=1.8,<2`): `rf-restinstance`
- **Tested with:** claude-code, copilot-cli, vscode-copilot, codex, cursor, opencode, goose, claude-desktop
- **Maintainers:** [@manykarim](https://github.com/manykarim)
- **Install (Claude Code):** `/plugin install rf-api@robotframework-plugins`
Expand All @@ -72,7 +72,7 @@ PlatynUI skill for desktop UI tests.
- **Version:** 1.0.0
- **Robot Framework:** `>=7.0`
- **Libraries:**
- PlatynUI (`robotframework-PlatynUI` `>=0.12.0.dev0`)
- PlatynUI (`robotframework-PlatynUI` `>=0.12.0.dev0`): `rf-platynui`
- **Tested with:** claude-code, copilot-cli, vscode-copilot, codex, cursor, opencode, goose, claude-desktop
- **Maintainers:** [@manykarim](https://github.com/manykarim)
- **Install (Claude Code):** `/plugin install rf-desktop@robotframework-plugins`
Expand All @@ -86,7 +86,7 @@ AppiumLibrary skill for Android and iOS tests.
- **Version:** 1.0.0
- **Robot Framework:** `>=7.0`
- **Libraries:**
- AppiumLibrary (`robotframework-appiumlibrary` `>=3,<4`)
- AppiumLibrary (`robotframework-appiumlibrary` `>=3,<4`): `rf-appium`
- **Tested with:** claude-code, copilot-cli, vscode-copilot, codex, cursor, opencode, goose, claude-desktop
- **Maintainers:** [@manykarim](https://github.com/manykarim)
- **Install (Claude Code):** `/plugin install rf-mobile@robotframework-plugins`
Expand All @@ -100,8 +100,8 @@ Browser Library and SeleniumLibrary skills for web tests.
- **Version:** 1.0.0
- **Robot Framework:** `>=7.0`
- **Libraries:**
- Browser (`robotframework-browser` `>=20,<21`)
- SeleniumLibrary (`robotframework-seleniumlibrary` `>=6.9,<7`)
- Browser (`robotframework-browser` `>=20,<21`): `rf-browser`
- SeleniumLibrary (`robotframework-seleniumlibrary` `>=6.9,<7`): `rf-selenium`
- **Tested with:** claude-code, copilot-cli, vscode-copilot, codex, cursor, opencode, goose, claude-desktop
- **Maintainers:** [@manykarim](https://github.com/manykarim)
- **Install (Claude Code):** `/plugin install rf-web@robotframework-plugins`
1 change: 1 addition & 0 deletions installer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The installer has calendar versions (`YYYY.M.PATCH`), independent of the plugins
### Added
- **A public Python API, `robotframework_plugins.bundle`** ([#5](https://github.com/MarketSquare/robotframework-plugins/issues/5)), so other tools such as rf-mcp can depend on this package and serve the bundled skills: `catalog()` (the bundled plugins with their libraries and supported Robot Framework range), `skills_dirs()` (each active plugin's `skills/` folder, also when the package is imported from a zip) and `CATALOG_FORMAT` (`1`). `fetch(ref="stable")` returns the same for a branch, tag or commit of the repository, downloaded once into the user cache and checked before use; it is the only part of the package that uses the network, and only when called. See "Python API" in the README for the compatibility promise.
- **`catalog.json` carries `"format": 1`**, and each entry its `robotframework` range.
- **Each library names the skills that cover it** (`libraries[].skills`, [#7](https://github.com/MarketSquare/robotframework-plugins/issues/7)), for example Browser → `rf-browser`, so a tool that detects one library in a project can pick its skill rather than every skill of the plugin.
- **Several plugins.** The installer bundles every listed plugin (`rf-core`, `rf-web`, `rf-api`, `rf-mobile`, `rf-desktop` and the `rf-agentskills` bundle). By default it installs `rf-core` plus the plugins for the libraries the project declares in `pyproject.toml` or `requirements*.txt`, or every verified plugin when it declares none. `--plugins <csv>|all` chooses them; an unknown or removed plugin is an error, a deprecated one a warning.
- **`robotframework-plugins catalog [--json]`** lists the bundled plugins with version, tier and status. `version` prints every bundled plugin's version.
- **Upgrades keep what is installed.** On an upgrade, `install` covers the plugins already installed and adds the ones the project now declares. The summary names the skills and plugins an install removed, including a plugin the catalogue no longer has; files you changed are kept and reported.
Expand Down
5 changes: 3 additions & 2 deletions installer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Other tools can depend on `robotframework-plugins` and read the plugins it bundl
| Name | What it gives you |
|---|---|
| `CATALOG_FORMAT` | The catalogue format, `1`. Check it at runtime. |
| `catalog()` | The bundled plugins (active and deprecated): `name`, `version`, `status`, `tier`, `summary`, `libraries` (`name`, `package`, `versions`), `robotframework`, `dependencies`, `bundle`, `bundled`. A new list on each call. |
| `catalog()` | The bundled plugins (active and deprecated): `name`, `version`, `status`, `tier`, `summary`, `libraries` (`name`, `package`, `versions`, and `skills`: the plugin's skills that cover the library; absent means all of them), `robotframework`, `dependencies`, `bundle`, `bundled`. A new list on each call. |
| `skills_dirs()` | `{plugin: Path}` of each active plugin's `skills/` folder, `rf-core` first; bundles such as `rf-agentskills` are left out. The folders exist for the life of the process. |
| `fetch(ref="stable")` | The same for a branch, tag or commit of this repository: a `Snapshot` with `ref`, `commit`, `catalog()` and `skills_dirs()`. Raises `FetchError` on any failure. |

Expand All @@ -112,7 +112,8 @@ installed = {"robotframework-browser": "20.1.0"}
for plugin in bundle.catalog():
for lib in plugin["libraries"]:
if lib["package"] in installed:
print(plugin["name"], lib["name"], installed[lib["package"]] in SpecifierSet(lib["versions"]))
print(plugin["name"], lib["name"], lib.get("skills", "all skills"),
installed[lib["package"]] in SpecifierSet(lib["versions"]))

# Skill folders, each holding <skill>/SKILL.md and references/
skills = bundle.skills_dirs()
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/catalog-library-skills/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-13
45 changes: 45 additions & 0 deletions openspec/changes/catalog-library-skills/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## Context

See proposal.md (Why). What the change builds on:

- `schemas/catalog.schema.json` describes `libraries` items with `additionalProperties: false` and the keys `name`, `package` and `versions`. `scripts/build-catalog.py` validates every `plugins/<name>/catalog.yaml` against it, runs its own checks (`name_problems`, `symlink_problems`, `bundle_problems`, lifecycle and renames), and writes the README table, `docs/catalog.md` and the marketplace.
- The installer's `_catalog.py` (#5) copies `libraries` as it is into `catalog.json`. `bundle.catalog()` and `bundle.fetch()` read that, so a new key in a library entry reaches them with no installer code change.
- `rf-agentskills` is a bundle with `libraries: []`. A removed plugin keeps its manifest but leaves the catalogue.

## Goals / Non-Goals

**Goals:**
- One authoritative mapping from a library to the skills that cover it, checked against the skills on disk.
- No change for readers and community plugins that don't use the field.

**Non-Goals:**
- Per-skill installs in the installer.
- Pointing a library at another plugin's skill. Skills are named within their own plugin, because the plugin is the unit a user installs.
- A `CATALOG_FORMAT` bump: the field is optional and new.

## Decisions

### D1. Optional, same-plugin skill names

`skills` is optional, has at least one item, holds unique kebab-case names, and names skill folders of the same plugin. Absent means "every skill of the plugin", the only assumption a reader can make today, so existing catalogues keep their meaning. An empty list is rejected by `minItems: 1`: "no skill covers this library" would mean the library doesn't belong in the plugin's `libraries`.

Alternative: a top-level `skills` map (`{skill: [library]}`). It would need a second lookup and would let a library go unmapped silently; per-library keeps the answer next to the package a tool detected.

### D2. The check lives with the other catalogue checks

A new `library_skill_problems(plugins)` in `build-catalog.py` runs for every manifest (active, deprecated and removed alike, so a stale entry is caught before a plugin is revived). For each library and each listed skill, it reports `<plugin>: library <name> lists skill <skill>, but plugins/<plugin>/skills/<skill>/SKILL.md doesn't exist`. It runs in both `build-catalog.py` and `--check`, like `name_problems`.

### D3. Where the field shows

- `docs/catalog.md`: the library line becomes `Browser (robotframework-browser >=20,<21): rf-browser`.
- The README table keeps library names only: it is a summary and has no room.
- The marketplace has no field for it.

### D4. Installer: tests and docs only

No code change. The tests assert the field in `bundle.catalog()` (from the dev bundle) and in a `fetch()` snapshot (from the fake GitHub's archive). The README's "Python API" table lists `skills` among the library fields, with the "absent means every skill" rule. The changelog gets a line in the unreleased 2026.11.0 entry, next to the catalogue's `format` and `robotframework` additions.

## Risks / Trade-offs

- [A plugin adds a skill for a library and forgets `skills`] → The library keeps its old, narrower list, and tools miss the new skill. The check can't know intent; CONTRIBUTING.md asks plugin authors to update `skills` when they add or rename a skill. A rename that removes the old folder fails the check.
- [Community plugins never fill it in] → Readers fall back to all of the plugin's skills, which is today's behaviour.
41 changes: 41 additions & 0 deletions openspec/changes/catalog-library-skills/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Why

The catalogue maps a library to a plugin, but a plugin can ship several skills for different libraries (rf-web: Browser and SeleniumLibrary; rf-api: RequestsLibrary and RESTinstance). A tool that detects one library can't tell which skill covers it ([issue #7](https://github.com/MarketSquare/robotframework-plugins/issues/7)).

rf-mcp reads the catalogue through `bundle.catalog()` (#5) to attach skills to an agent's first tool call. In its evaluation at `37562367`, a Browser project that also had RequestsLibrary installed got `rf-requests`, `rf-restinstance` and `rf-browser`, in that order. rf-mcp now guesses the skill from its name (Browser → `rf-browser`). That's a naming convention, not a contract: a skill such as `rf-api-auth` or a company plugin breaks it.

## What Changes

- **Schema:** `libraries[].skills`, an optional list of skill names of the same plugin that cover the library, in `schemas/catalog.schema.json`. Other unknown keys stay rejected.
- **Check:** `scripts/build-catalog.py --check` fails when a listed skill has no `plugins/<plugin>/skills/<skill>/SKILL.md`, naming the plugin, the library and the skill.
- **Data:** the four technology plugins name their skills per library:
- rf-web: Browser → `rf-browser`, SeleniumLibrary → `rf-selenium`;
- rf-api: RequestsLibrary → `rf-requests`, RESTinstance → `rf-restinstance`;
- rf-mobile: AppiumLibrary → `rf-appium`;
- rf-desktop: PlatynUI → `rf-platynui`.
- **Output:**
- `catalog.json` in the installer wheel, `bundle.catalog()` and `bundle.fetch()` snapshots carry the field unchanged;
- `docs/catalog.md` lists the skills next to each library.
- **Docs:** CONTRIBUTING.md (the `catalog.yaml` step) and the installer README's "Python API" table explain the field: when it's absent, every skill of the plugin covers the library.

The field is optional and new, so `CATALOG_FORMAT` stays `1`, and catalogues and readers without it keep working.

Out of scope: per-skill installs (`install` picking `rf-browser` without `rf-selenium`). The issue mentions that the installer could use the field; that would be a separate change.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `plugin-catalogue`: the programme manifest's `libraries` entries may name the skills that cover them, the catalogue check verifies those skills exist, and the generated catalogue and the installer's catalogue pass them on. (The capability is added by the unarchived change `launch-plugin-programme`; this change adds a requirement to it.)

## Impact

- **Schema and tooling:** `schemas/catalog.schema.json`, `scripts/build-catalog.py` (check and `docs/catalog.md`), `tests/test_catalog.py`.
- **Data:** `plugins/rf-web/catalog.yaml`, `plugins/rf-api/catalog.yaml`, `plugins/rf-mobile/catalog.yaml`, `plugins/rf-desktop/catalog.yaml`; regenerated `docs/catalog.md`.
- **Installer:** no code change, because `_catalog.py` already passes `libraries` through. Tests for `bundle.catalog()` and snapshots, the README "Python API" table, and a changelog line for 2026.11.0, which is still unreleased.
- **Docs:** `CONTRIBUTING.md`.
- **Consumers:** rf-mcp can map a detected package to its skill without guessing and fall back to all of a plugin's skills when `skills` is absent.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## ADDED Requirements

### Requirement: Libraries name the skills that cover them

Each entry of a manifest's `libraries` MAY have `skills`: a non-empty list of distinct skill names (kebab-case) of the same plugin that cover that library. When `skills` is absent, every skill of the plugin covers the library. The manifest schema SHALL accept `skills` and SHALL keep rejecting other unknown keys of a library entry. The catalogue check SHALL fail when a listed skill has no `plugins/<plugin>/skills/<skill>/SKILL.md`, naming the plugin, the library and the skill. The generated `docs/catalog.md` SHALL show the skills next to each library that names them, and the installer's bundled catalogue (`catalog.json`, `bundle.catalog()` and `bundle.fetch()` snapshots) SHALL carry `skills` unchanged. Every technology plugin of this repository SHALL name the skills of each of its libraries.

#### Scenario: Skills per library accepted
- **WHEN** `plugins/rf-web/catalog.yaml` lists Browser with `skills: [rf-browser]` and SeleniumLibrary with `skills: [rf-selenium]`
- **THEN** schema validation and the catalogue check pass

#### Scenario: Other unknown keys still rejected
- **WHEN** a library entry has a key `skill: rf-browser` (singular)
- **THEN** schema validation fails on that key

#### Scenario: Missing skill reported
- **WHEN** rf-api lists RequestsLibrary with `skills: [rf-requests-auth]` and `plugins/rf-api/skills/rf-requests-auth/SKILL.md` doesn't exist
- **THEN** the catalogue check fails with a message naming `rf-api`, `RequestsLibrary` and `rf-requests-auth`

#### Scenario: Passed through to tools
- **WHEN** a tool reads the `rf-web` entry of `bundle.catalog()`
- **THEN** its Browser library has `skills == ["rf-browser"]` and its SeleniumLibrary library has `skills == ["rf-selenium"]`

#### Scenario: Absent field keeps working
- **WHEN** a community plugin's manifest lists a library without `skills`
- **THEN** validation passes, and its catalogue entry has no `skills` key for that library
21 changes: 21 additions & 0 deletions openspec/changes/catalog-library-skills/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## 1. Schema and check (D1, D2)

- [x] 1.1 Add `skills` (array of unique kebab-case strings, `minItems: 1`) to `libraries.items.properties` in `schemas/catalog.schema.json`; verify tests in `tests/test_catalog.py` for "Skills per library accepted" and "Other unknown keys still rejected" (`skill:` singular), and an empty list rejected.
- [x] 1.2 Add `library_skill_problems()` to `scripts/build-catalog.py` and run it with the other checks; verify a test for "Missing skill reported" (message names the plugin, the library and the skill) and one for a removed plugin's manifest with a stale skill.

## 2. Data and generated catalogue (D3)

- [x] 2.1 Add `skills` to the libraries of rf-web, rf-api, rf-mobile and rf-desktop, as listed in the proposal; verify `scripts/build-catalog.py --check` passes after regenerating.
- [x] 2.2 Show the skills in `docs/catalog.md` (`Browser (robotframework-browser >=20,<21): rf-browser`) and regenerate; verify a test on the generated text and that `--check` fails when the committed `docs/catalog.md` is stale.
- [x] 2.3 Add a repository test that every library of every active technology plugin names its skills, and that together they cover all of the plugin's skills; verify it passes.

## 3. Installer (D4)

- [x] 3.1 Rebuild the bundle and add tests for "Passed through to tools" (`bundle.catalog()` from the dev bundle) and for a `fetch()` snapshot whose fake archive has `skills` in a manifest, plus "Absent field keeps working" (no `skills` key appears); verify `pytest tests/installer` passes.
- [x] 3.2 List `skills` in the README "Python API" table (absent means every skill of the plugin covers the library) and add a line to the 2026.11.0 changelog entry; verify `scripts/check-docs-voice.py installer/README.md installer/CHANGELOG.md` passes.

## 4. Docs, checks, PR

- [x] 4.1 Explain the field in CONTRIBUTING.md's `catalog.yaml` step, including that it must change when a skill is added or renamed; verify `scripts/check-docs-voice.py` passes.
- [x] 4.2 Run `pytest tests --ignore=tests/eval`, `scripts/build-catalog.py --check` and `scripts/check-drift.sh`; verify all pass. Done: `tests` 832 passed; `build-catalog.py --check`, `check-drift.sh` and `check-template.sh` pass, except `test_check_rf_environment_uses_project_venv`, which fails locally on a clean `main` too and passes in CI.
- [ ] 4.3 Open a PR against `main` that closes #7 (ask before posting); verify the required checks pass. Opened as #8; ticked once the required checks pass.
2 changes: 2 additions & 0 deletions plugins/rf-api/catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ libraries:
- name: RequestsLibrary
package: robotframework-requests
versions: ">=0.9.7"
skills: [rf-requests]
- name: RESTinstance
package: RESTinstance
versions: ">=1.8,<2"
skills: [rf-restinstance]
robotframework: ">=7.0"
agents:
- claude-code
Expand Down
1 change: 1 addition & 0 deletions plugins/rf-desktop/catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ libraries:
- name: PlatynUI
package: robotframework-PlatynUI
versions: ">=0.12.0.dev0"
skills: [rf-platynui]
robotframework: ">=7.0"
agents:
- claude-code
Expand Down
1 change: 1 addition & 0 deletions plugins/rf-mobile/catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ libraries:
- name: AppiumLibrary
package: robotframework-appiumlibrary
versions: ">=3,<4"
skills: [rf-appium]
robotframework: ">=7.0"
agents:
- claude-code
Expand Down
2 changes: 2 additions & 0 deletions plugins/rf-web/catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ libraries:
- name: Browser
package: robotframework-browser
versions: ">=20,<21"
skills: [rf-browser]
- name: SeleniumLibrary
package: robotframework-seleniumlibrary
versions: ">=6.9,<7"
skills: [rf-selenium]
robotframework: ">=7.0"
agents:
- claude-code
Expand Down
Loading
Loading