{"owner":"K-Dense-AI","repo":"scientific-agent-skills","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","CLAUDE.md"],"skills":{"AGENTS.md":"# Repository Guidance\n\nThis repository is a collection of Agent Skills for science and research. Every skill lives in its\nown directory under `skills/` and must conform to the open\n[Agent Skills specification](https://agentskills.io/specification).\n\nRead this file before creating or changing a skill. `CONTRIBUTING.md` covers the same ground at\nmore length, plus the pull-request process.\n\n## What belongs here\n\n**In scope:** a narrow skill for one scientific package, database, platform, or research workflow —\n`scanpy`, `depmap`, `benchling-integration`, `experimental-design`.\n\n**Out of scope**, and routinely declined:\n\n- General software-engineering or coding-judgment skills — they compete for selection on every task.\n- General infrastructure with a scientific example bolted on (a vector database, a cloud SDK) —\n  accepting one implies carrying every competitor.\n- Broad \"orchestrator\" skills that route to other skills — they overlap every specialist by design.\n- A second provider for a service an existing skill already reaches.\n\nThe general-purpose skills that do exist are narrow output-format helpers (`docx`, `pdf`, `pptx`,\n`generate-image`, `markdown-mermaid-writing`). They are not precedent for broadening scope.\n\n## Layout\n\nThe repository root is an [Agent Plugins](https://agent-plugins.org/) 1.0.0 package: `plugin.json`\nplus the portable `skills/` tree. Keep `plugin.json` valid against the Agent Plugins manifest\nschema, and keep its `version` identical to `pyproject.toml` `[project].version`. Do not add\nnon-portable top-level fields to `plugin.json` (no inline MCP, hooks, or client-only keys — use\n`mcp.json` or a reverse-domain `extensions` namespace if those are ever needed).\n\n```text\nplugin.json                 # Agent Plugins manifest (repo root)\nskills/<skill-name>/\n├── SKILL.md        # required\n├── references/     # optional: long documentation, loaded only when needed\n├── scripts/        # optional: executable helpers\n└── assets/         # optional: templates and static resources\n```\n\nOnly `SKILL.md` is required inside each skill. Reference other files with relative paths from the\nskill root, kept one level deep.\n\n**Tests never live under `skills/`.** A skill directory ships only what an agent loads. Checks for a\nskill's scripts and structure go in the repository-level suite instead:\n\n```text\ntests/<skill-name>/          # same name as the skill directory\n├── test_scripts.py\n└── fixtures/                # optional test data\n```\n\n**Diagrams never live under `skills/` either.** Every skill has one generated workflow diagram at\n`docs/images/<skill-name>.png`, produced by `scripts/generate_skill_image.py` and kept in step with\nthe skill's documentation — see [Skill diagrams](#skill-diagrams).\n\nTests reach their skill through an explicit anchor, never a relative walk:\n\n```python\nSKILL_ROOT = Path(__file__).resolve().parents[2] / \"skills\" / \"<skill-name>\"\n```\n\n## Creating a skill\n\n1. Create `skills/<name>/` — **the directory name is the skill name** and must equal frontmatter\n   `name`.\n2. Write `SKILL.md` from the template below. Start at `metadata.version: \"1.0\"`.\n3. Add `references/`, `scripts/`, or `assets/` only when they earn their place.\n4. Run the commands and code you document. Scope claims to the release you actually tested\n   (\"targets stable GeoPandas 1.1.4\"), and mark anything untested as illustrative.\n5. If the skill ships `scripts/`, put their tests in **`tests/<name>/`** — never in the skill\n   directory. Fixtures go in `tests/<name>/fixtures/`.\n6. Validate and scan (below).\n7. Generate the skill's diagram — a new skill without `docs/images/<name>.png` is incomplete:\n\n   ```bash\n   uv run python scripts/generate_skill_image.py --skill <name>\n   ```\n\n```markdown\n---\nname: skill-name\ndescription: What the skill does and when an agent should use it, including the terms that should trigger it.\nlicense: MIT\ncompatibility: Requires Python 3.12+ with <package> installed. Needs network access.\nmetadata:\n  version: \"1.0\"\n  skill-author: Your Name\n---\n\n# Skill Title\n\n## When to use\n\nUse this skill when...\n\n## Workflow\n\n1. ...\n\n## Examples\n\n...\n```\n\n## Updating a skill\n\n1. Read the current `SKILL.md` and its supporting files first.\n2. Check upstream docs — APIs move, and the skill may be pinned to an older release.\n3. Make the smallest useful change.\n4. **Bump `metadata.version` in the same change**: minor for normal improvements (`\"1.2\"` →\n   `\"1.3\"`), major only for a breaking change or substantial redesign (`\"1.9\"` → `\"2.0\"`).\n5. Re-run any example, command, or script you touched, plus `tests/<name>/` if that suite exists.\n   Suites check that `metadata.version` is present and quoted, not what it equals, so a version bump\n   never needs a matching test edit.\n6. **Regenerate the diagram in the same change** whenever the edit changes what the skill does or\n   how its workflow runs — the picture is generated from `SKILL.md` and `references/`, so it goes\n   stale silently. The command overwrites `docs/images/<name>.png` in place:\n\n   ```bash\n   uv run python scripts/generate_skill_image.py --skill <name>\n   ```\n\n   A typo fix, a link repair, or a version bump alone does not need a new image.\n\n## Frontmatter\n\n`SKILL.md` starts with YAML frontmatter. **Only these six fields are allowed** — the spec defines a\nclosed set, and any other top-level key is a validation error:\n\n| Field | Required | Constraints |\n| --- | --- | --- |\n| `name` | Yes | 1–64 chars, lowercase letters/digits/hyphens only, no leading, trailing, or consecutive hyphens, and **must equal the directory name**. |\n| `description` | Yes | 1–1024 chars. Say what the skill does *and* when to use it, with the keywords that should trigger it. Write it in third person. |\n| `license` | No | License name, or a reference to a bundled license file. |\n| `compatibility` | No | Max 500 chars. Environment requirements only — omit it if the skill has none. |\n| `allowed-tools` | No | A **space-separated string**, e.g. `Read Write Edit Bash`. Not a YAML list, not comma-separated. |\n| `metadata` | No | Mapping of string keys to **string** values, except the host manifest blocks below. Required here: `metadata.version`. |\n\nPut anything else — authorship, upstream versions, review dates, client-specific config — inside\n`metadata`, never at the top level. In particular, Hermes' top-level\n`required_environment_variables` cannot be used here: it fails the validator and, because\n`strictyaml` rejects the whole document, takes `name` and `description` down with it. Declare\ncredentials in `compatibility` and `metadata.openclaw.envVars` instead.\n\n### Write block-style YAML, not JSON flow style\n\nThe reference validator parses frontmatter with `strictyaml`, which **rejects JSON-style flow\nmappings and sequences**. A flow mapping does not merely fail one check: the whole frontmatter\nfails to parse, so `name` and `description` become unreadable and the skill will not register.\n\n```yaml\n# Wrong -- breaks the validator\nmetadata: {\"version\": \"1.1\", \"skill-author\": \"K-Dense Inc.\"}\n\n# Right\nmetadata:\n  version: \"1.1\"\n  skill-author: K-Dense Inc.\n```\n\n### Quote `metadata` scalars\n\nQuote values that would otherwise be parsed as a number, boolean, or date — `version: \"1.0\"`,\n`last-reviewed: \"2026-07-23\"` — so they stay strings as the spec requires.\n\n### Host manifest blocks stay nested mappings\n\n`metadata.openclaw` and `metadata.hermes` are the documented exception: keep them as **nested\nmappings**, not JSON strings. OpenClaw's `resolveOpenClawManifestBlock()` requires\n`typeof candidate === \"object\"`, so a JSON string silently disables its dependency gating and\ncredential injection. Nested mappings still pass `skills-ref validate`.\n\n```yaml\nmetadata:\n  version: \"1.1\"\n  skill-author: Exa\n  openclaw:\n    primaryEnv: EXA_API_KEY\n    envVars:\n      - name: EXA_API_KEY\n        required: true\n        description: Exa search API key.\n  hermes:\n    category: research\n```\n\nOnly skills with external requirements need these blocks; most omit them. A failed `requires` /\n`requires_toolsets` gate *hides* the skill from the agent, so gate only on something the skill\ngenuinely cannot run without.\n\n## Body and layout\n\n- Keep `SKILL.md` under 500 lines. CI warns above that. Move long reference material into\n  `references/` so agents load it only when needed.\n- A skill directory ships only what an agent loads. Tests, fixtures, scratch data, and generated\n  artifacts stay out of it; tests go in `tests/<name>/`.\n- Give concrete workflows, commands, and worked examples rather than background explanation.\n- Name the required packages, system dependencies, credentials, and network access.\n- Include the scientific caveats and validation checks that matter.\n- Put fragile or repetitive logic in `scripts/` instead of asking the agent to recreate it.\n- Never include secrets, API keys, private URLs, or unpublished data.\n\n## Validate and scan\n\n```bash\nuv sync\n\n# spec conformance for one skill\nuv run skills-ref validate skills/<name>\n\n# every skill, the way CI does\nfor d in skills/*/; do uv run skills-ref validate \"$d\"; done\n```\n\n`.github/workflows/skill-spec-validation.yml` runs that on every PR touching `skills/`, plus the\nrepo rules `skills-ref` does not check: `metadata.version` present, `allowed-tools` a\nspace-separated string, `metadata` scalars quoted, and a warning past 500 lines.\n\nSecurity-scan new or substantially changed skills. Scanning uses\n[Cisco AI Defense Skill Scanner](https://github.com/cisco-ai-defense/skill-scanner) — the\n`cisco-ai-skill-scanner` package pinned in `pyproject.toml`, which detects prompt injection, data\nexfiltration, and malicious code patterns in Agent Skills. Its README documents the rule IDs and\nCLI flags; consult it when a finding's rule is unfamiliar.\n\n`.github/workflows/pr-skill-scan.yml` runs the repo wrapper for changed skills on every PR and\nposts a sticky comment, failing on HIGH or above:\n\n```bash\n# needs SKILL_SCANNER_LLM_API_KEY (see .env)\nuv run python scan_pr_skills.py skills/<name>\n\n# or the upstream CLI directly, without the repo wrapper\nuv run skill-scanner scan skills/<name> --use-behavioral\n```\n\n**Verify a finding against the code before \"fixing\" it.** Known systematic false positives:\n`BEHAVIOR_*_EXFILTRATION` and `BEHAVIOR_ENV_VAR_HARVESTING` on any skill that reads its own API key\nand calls its own service; `MDBLOCK_PYTHON_SUBPROCESS` on any `subprocess` snippet, including the\nsafe argument-list form; and `*_EVAL_EXEC` on substrings inside ordinary identifiers (`retrieval`,\n`executor`) or on `model.eval()`. Findings sometimes cite files a skill does not contain — check\nagainst `find skills/<name> -type f` before acting.\n\nIf the skill has tests in `tests/<name>/`, run them:\n\n```bash\nuv run --with pytest python -m pytest tests/<name> -q\n\n# every skill's suite, one process each, after the repo-wide guard\nuv run --with pytest python tests/run_all.py\n```\n\n**One skill per pytest process.** Skills' `scripts/` directories own plain top-level module names —\n32 of them ship a `scripts/_common.py` — so collecting two skills into one interpreter resolves\n`_common` to whichever skill imported first and silently tests the wrong files. `tests/conftest.py`\nrefuses such a session; `tests/run_all.py` forks per skill.\n\n### The repo-wide guard\n\n```bash\nuv run --with pytest python -m pytest tests/_meta -q\n```\n\n`tests/_meta` is the fastest useful signal in the repo: pure standard library, no scientific\npackages, a couple of seconds. It runs the shared structural contract against **every** skill and\nfails if a skill ships `scripts/` without a suite under `tests/<name>/` or an entry in\n`tests/skill-requirements.toml`. `.github/workflows/skill-tests.yml` runs it on every pull request,\nso a skill with untested scripts cannot land. A full run of `tests/run_all.py` starts with it.\n\nIt is not one of the per-skill processes because it deliberately spans all of them at once — safe\nbecause it never imports skill code, only parses it.\n\n### The shared contract\n\n`tests/_contract/` holds the assertions every skill shares, so a per-skill suite contains only what\nis actually specific to that skill. `tests/conftest.py` registers it as the importable module\n`skill_contract`:\n\n```python\nimport skill_contract\n\n# every argparse script answers --help; skips when its packages are absent,\n# runs for real under --isolated\nCliHelpTests = skill_contract.cli.help_test_case(SKILL_ROOT)\n\n# for library-style scripts with an `if __name__ == \"__main__\"` worked example\nDemoBlockTests = skill_contract.cli.demo_test_case(SKILL_ROOT, (\"doe_designs.py\",))\n```\n\n- `structure` — frontmatter conformance, the 500-line limit, no tests or bytecode under `skills/`,\n  local links resolve, scripts parse, no `eval`/`exec`/`os.system`, no standard-library shadowing,\n  no hardcoded local paths, shell scripts valid. Run repo-wide by `tests/_meta`; do not duplicate\n  these in a per-skill suite.\n- `cli` — the `--help` and demo-block cases above.\n- `office` / `schematic` — behaviour for files several skills ship byte-identical copies of (the\n  OOXML tree under docx/pptx/xlsx; the AI schematic generator under five skills). `tests/_meta`\n  separately fails if those copies drift apart, so fix them together.\n\n### One environment per skill\n\nThe project environment deliberately does not carry the skills' scientific packages. Their upstream\npins are mutually exclusive — `opentrons` needs `numpy<2`, `esm` caps `transformers` below the\nversion the `transformers` skill targets, `geniml` and `spikeinterface` pin `zarr<3` against the\n`zarr-python` skill's 3.x, `bioservices` caps `lxml<6` against `matchms`, and `pytdc`, `molfeat`,\n`deepchem`, `histolab`, `vaex`, and `ete3` each need an interpreter older than 3.13. Installing them\ntogether forces every one of those skills to the losing side of a version fight.\n\nSo `--isolated` builds a throwaway `uv` environment per skill instead, from\n[`tests/skill-requirements.toml`](tests/skill-requirements.toml):\n\n```bash\npython tests/run_all.py --isolated                 # every suite, one env each\npython tests/run_all.py --isolated scanpy qiskit   # just these\n```\n\nEach entry lists the packages that skill documents, plus an optional `python` when the skill cannot\nrun on the default interpreter; uv downloads that interpreter on demand. Packages that cannot be\ninstalled at all — a GitHub-only SDK, a conda-forge-only library, a CUDA build — are recorded under\n`[unavailable]` with the reason, and the runner prints them so the gap shows up in test output.\n\nAdding a skill with `scripts/` means adding its `[skills.<name>]` entry — `tests/_meta` fails\nwithout one. Use `packages = []` for skills whose bundled tooling is standard-library only; they\nstill get a clean environment, and CI runs exactly that set on every pull request. uv caches wheels\nglobally, so repeat runs create each environment in milliseconds.\n\nThe full `--isolated` sweep is not run in CI: it builds one environment per skill, several of which\nneed a CUDA toolchain, a JDK, or a local MATLAB install. Run it before a release, or whenever you\ntouch the shared contract.\n\n## Skill diagrams\n\nEvery skill carries one generated workflow diagram at `docs/images/<skill-name>.png`. Creating a\nskill means creating its image; changing what a skill does means regenerating it. The image is not\noptional decoration — it is derived from the documentation, so an out-of-date one misrepresents the\nskill.\n\n`scripts/generate_skill_image.py` is local repository tooling, standard library only, and runs in\ntwo stages on one `OPENROUTER_API_KEY` (environment variable, repository `.env`, or `--api-key`):\na text model reads `SKILL.md` plus everything under `references/` and a manifest of `scripts/` and\n`assets/`, distils it into a description of one diagram, then an image model draws it. Because it\nreads the whole skill, run it **after** the documentation is final, not before.\n\n```bash\n# one skill -> docs/images/<name>.png, replacing any existing image\nuv run python scripts/generate_skill_image.py --skill <name>\n\n# see which files feed the reader, and where the image lands — no API calls, nothing billed\nuv run python scripts/generate_skill_image.py --skill <name> --dry-run\n\n# read the skill and print the diagram prompt without drawing it\nuv run python scripts/generate_skill_image.py --skill <name> --prompt-only\n\n# several skills in one batch\nuv run python scripts/generate_skill_image.py --skill <name-a> <name-b>\n\n# backfill everything missing an image, six at a time\nuv run python scripts/generate_skill_image.py --all --skip-existing -j 6\n```\n\nLook at the result before committing it. Image models misspell labels and occasionally point an\narrow at the wrong card; regenerate rather than ship a diagram whose text is wrong. `--quality low`\nmakes iteration cheap while checking composition, but commit a `high` render. Both the art direction\nand the reader's instructions live at the top of the script — change them there rather than\nhand-tuning one skill's prompt, so the set stays visually consistent.\n\n## Before opening a PR\n\n- Directory name and frontmatter `name` match exactly.\n- No `tests/` directory and no `test_*.py` anywhere under `skills/<name>/` — tests belong in\n  `tests/<name>/`.\n- Only the six spec-defined top-level fields; everything else under `metadata`.\n- `metadata.version` exists, is quoted, and is bumped if you changed an existing skill.\n- `metadata` is a block mapping; `openclaw` / `hermes` blocks are nested mappings.\n- `uv run skills-ref validate skills/<name>` passes.\n- If the collection version changes, `plugin.json` `version` matches `pyproject.toml`.\n- `uv run --with pytest python -m pytest tests/_meta -q` passes — this is what CI blocks on, and it\n  catches a missing suite, a missing `skill-requirements.toml` entry, a broken local link, a\n  leaked local path, and a drifted Agent Plugins manifest.\n- If the skill ships `scripts/`: a suite exists at `tests/<name>/`, a `[skills.<name>]` entry exists\n  in `tests/skill-requirements.toml`, and `python tests/run_all.py --isolated <name>` passes.\n- `docs/images/<name>.png` exists, and was regenerated if the change altered what the skill does.\n  Its labels are spelled correctly and its arrows point where they should.\n- Examples and scripts are tested, or clearly marked illustrative.\n- No secrets or private data; scan results clean or explained in the PR.\n","CLAUDE.md":"# CLAUDE.md\n\nRepository guidance for this project lives in [AGENTS.md](AGENTS.md). Read it and follow it.\n"},"files":{"AGENTS.md":"# Repository Guidance\n\nThis repository is a collection of Agent Skills for science and research. Every skill lives in its\nown directory under `skills/` and must conform to the open\n[Agent Skills specification](https://agentskills.io/specification).\n\nRead this file before creating or changing a skill. `CONTRIBUTING.md` covers the same ground at\nmore length, plus the pull-request process.\n\n## What belongs here\n\n**In scope:** a narrow skill for one scientific package, database, platform, or research workflow —\n`scanpy`, `depmap`, `benchling-integration`, `experimental-design`.\n\n**Out of scope**, and routinely declined:\n\n- General software-engineering or coding-judgment skills — they compete for selection on every task.\n- General infrastructure with a scientific example bolted on (a vector database, a cloud SDK) —\n  accepting one implies carrying every competitor.\n- Broad \"orchestrator\" skills that route to other skills — they overlap every specialist by design.\n- A second provider for a service an existing skill already reaches.\n\nThe general-purpose skills that do exist are narrow output-format helpers (`docx`, `pdf`, `pptx`,\n`generate-image`, `markdown-mermaid-writing`). They are not precedent for broadening scope.\n\n## Layout\n\nThe repository root is an [Agent Plugins](https://agent-plugins.org/) 1.0.0 package: `plugin.json`\nplus the portable `skills/` tree. Keep `plugin.json` valid against the Agent Plugins manifest\nschema, and keep its `version` identical to `pyproject.toml` `[project].version`. Do not add\nnon-portable top-level fields to `plugin.json` (no inline MCP, hooks, or client-only keys — use\n`mcp.json` or a reverse-domain `extensions` namespace if those are ever needed).\n\n```text\nplugin.json                 # Agent Plugins manifest (repo root)\nskills/<skill-name>/\n├── SKILL.md        # required\n├── references/     # optional: long documentation, loaded only when needed\n├── scripts/        # optional: executable helpers\n└── assets/         # optional: templates and static resources\n```\n\nOnly `SKILL.md` is required inside each skill. Reference other files with relative paths from the\nskill root, kept one level deep.\n\n**Tests never live under `skills/`.** A skill directory ships only what an agent loads. Checks for a\nskill's scripts and structure go in the repository-level suite instead:\n\n```text\ntests/<skill-name>/          # same name as the skill directory\n├── test_scripts.py\n└── fixtures/                # optional test data\n```\n\n**Diagrams never live under `skills/` either.** Every skill has one generated workflow diagram at\n`docs/images/<skill-name>.png`, produced by `scripts/generate_skill_image.py` and kept in step with\nthe skill's documentation — see [Skill diagrams](#skill-diagrams).\n\nTests reach their skill through an explicit anchor, never a relative walk:\n\n```python\nSKILL_ROOT = Path(__file__).resolve().parents[2] / \"skills\" / \"<skill-name>\"\n```\n\n## Creating a skill\n\n1. Create `skills/<name>/` — **the directory name is the skill name** and must equal frontmatter\n   `name`.\n2. Write `SKILL.md` from the template below. Start at `metadata.version: \"1.0\"`.\n3. Add `references/`, `scripts/`, or `assets/` only when they earn their place.\n4. Run the commands and code you document. Scope claims to the release you actually tested\n   (\"targets stable GeoPandas 1.1.4\"), and mark anything untested as illustrative.\n5. If the skill ships `scripts/`, put their tests in **`tests/<name>/`** — never in the skill\n   directory. Fixtures go in `tests/<name>/fixtures/`.\n6. Validate and scan (below).\n7. Generate the skill's diagram — a new skill without `docs/images/<name>.png` is incomplete:\n\n   ```bash\n   uv run python scripts/generate_skill_image.py --skill <name>\n   ```\n\n```markdown\n---\nname: skill-name\ndescription: What the skill does and when an agent should use it, including the terms that should trigger it.\nlicense: MIT\ncompatibility: Requires Python 3.12+ with <package> installed. Needs network access.\nmetadata:\n  version: \"1.0\"\n  skill-author: Your Name\n---\n\n# Skill Title\n\n## When to use\n\nUse this skill when...\n\n## Workflow\n\n1. ...\n\n## Examples\n\n...\n```\n\n## Updating a skill\n\n1. Read the current `SKILL.md` and its supporting files first.\n2. Check upstream docs — APIs move, and the skill may be pinned to an older release.\n3. Make the smallest useful change.\n4. **Bump `metadata.version` in the same change**: minor for normal improvements (`\"1.2\"` →\n   `\"1.3\"`), major only for a breaking change or substantial redesign (`\"1.9\"` → `\"2.0\"`).\n5. Re-run any example, command, or script you touched, plus `tests/<name>/` if that suite exists.\n   Suites check that `metadata.version` is present and quoted, not what it equals, so a version bump\n   never needs a matching test edit.\n6. **Regenerate the diagram in the same change** whenever the edit changes what the skill does or\n   how its workflow runs — the picture is generated from `SKILL.md` and `references/`, so it goes\n   stale silently. The command overwrites `docs/images/<name>.png` in place:\n\n   ```bash\n   uv run python scripts/generate_skill_image.py --skill <name>\n   ```\n\n   A typo fix, a link repair, or a version bump alone does not need a new image.\n\n## Frontmatter\n\n`SKILL.md` starts with YAML frontmatter. **Only these six fields are allowed** — the spec defines a\nclosed set, and any other top-level key is a validation error:\n\n| Field | Required | Constraints |\n| --- | --- | --- |\n| `name` | Yes | 1–64 chars, lowercase letters/digits/hyphens only, no leading, trailing, or consecutive hyphens, and **must equal the directory name**. |\n| `description` | Yes | 1–1024 chars. Say what the skill does *and* when to use it, with the keywords that should trigger it. Write it in third person. |\n| `license` | No | License name, or a reference to a bundled license file. |\n| `compatibility` | No | Max 500 chars. Environment requirements only — omit it if the skill has none. |\n| `allowed-tools` | No | A **space-separated string**, e.g. `Read Write Edit Bash`. Not a YAML list, not comma-separated. |\n| `metadata` | No | Mapping of string keys to **string** values, except the host manifest blocks below. Required here: `metadata.version`. |\n\nPut anything else — authorship, upstream versions, review dates, client-specific config — inside\n`metadata`, never at the top level. In particular, Hermes' top-level\n`required_environment_variables` cannot be used here: it fails the validator and, because\n`strictyaml` rejects the whole document, takes `name` and `description` down with it. Declare\ncredentials in `compatibility` and `metadata.openclaw.envVars` instead.\n\n### Write block-style YAML, not JSON flow style\n\nThe reference validator parses frontmatter with `strictyaml`, which **rejects JSON-style flow\nmappings and sequences**. A flow mapping does not merely fail one check: the whole frontmatter\nfails to parse, so `name` and `description` become unreadable and the skill will not register.\n\n```yaml\n# Wrong -- breaks the validator\nmetadata: {\"version\": \"1.1\", \"skill-author\": \"K-Dense Inc.\"}\n\n# Right\nmetadata:\n  version: \"1.1\"\n  skill-author: K-Dense Inc.\n```\n\n### Quote `metadata` scalars\n\nQuote values that would otherwise be parsed as a number, boolean, or date — `version: \"1.0\"`,\n`last-reviewed: \"2026-07-23\"` — so they stay strings as the spec requires.\n\n### Host manifest blocks stay nested mappings\n\n`metadata.openclaw` and `metadata.hermes` are the documented exception: keep them as **nested\nmappings**, not JSON strings. OpenClaw's `resolveOpenClawManifestBlock()` requires\n`typeof candidate === \"object\"`, so a JSON string silently disables its dependency gating and\ncredential injection. Nested mappings still pass `skills-ref validate`.\n\n```yaml\nmetadata:\n  version: \"1.1\"\n  skill-author: Exa\n  openclaw:\n    primaryEnv: EXA_API_KEY\n    envVars:\n      - name: EXA_API_KEY\n        required: true\n        description: Exa search API key.\n  hermes:\n    category: research\n```\n\nOnly skills with external requirements need these blocks; most omit them. A failed `requires` /\n`requires_toolsets` gate *hides* the skill from the agent, so gate only on something the skill\ngenuinely cannot run without.\n\n## Body and layout\n\n- Keep `SKILL.md` under 500 lines. CI warns above that. Move long reference material into\n  `references/` so agents load it only when needed.\n- A skill directory ships only what an agent loads. Tests, fixtures, scratch data, and generated\n  artifacts stay out of it; tests go in `tests/<name>/`.\n- Give concrete workflows, commands, and worked examples rather than background explanation.\n- Name the required packages, system dependencies, credentials, and network access.\n- Include the scientific caveats and validation checks that matter.\n- Put fragile or repetitive logic in `scripts/` instead of asking the agent to recreate it.\n- Never include secrets, API keys, private URLs, or unpublished data.\n\n## Validate and scan\n\n```bash\nuv sync\n\n# spec conformance for one skill\nuv run skills-ref validate skills/<name>\n\n# every skill, the way CI does\nfor d in skills/*/; do uv run skills-ref validate \"$d\"; done\n```\n\n`.github/workflows/skill-spec-validation.yml` runs that on every PR touching `skills/`, plus the\nrepo rules `skills-ref` does not check: `metadata.version` present, `allowed-tools` a\nspace-separated string, `metadata` scalars quoted, and a warning past 500 lines.\n\nSecurity-scan new or substantially changed skills. Scanning uses\n[Cisco AI Defense Skill Scanner](https://github.com/cisco-ai-defense/skill-scanner) — the\n`cisco-ai-skill-scanner` package pinned in `pyproject.toml`, which detects prompt injection, data\nexfiltration, and malicious code patterns in Agent Skills. Its README documents the rule IDs and\nCLI flags; consult it when a finding's rule is unfamiliar.\n\n`.github/workflows/pr-skill-scan.yml` runs the repo wrapper for changed skills on every PR and\nposts a sticky comment, failing on HIGH or above:\n\n```bash\n# needs SKILL_SCANNER_LLM_API_KEY (see .env)\nuv run python scan_pr_skills.py skills/<name>\n\n# or the upstream CLI directly, without the repo wrapper\nuv run skill-scanner scan skills/<name> --use-behavioral\n```\n\n**Verify a finding against the code before \"fixing\" it.** Known systematic false positives:\n`BEHAVIOR_*_EXFILTRATION` and `BEHAVIOR_ENV_VAR_HARVESTING` on any skill that reads its own API key\nand calls its own service; `MDBLOCK_PYTHON_SUBPROCESS` on any `subprocess` snippet, including the\nsafe argument-list form; and `*_EVAL_EXEC` on substrings inside ordinary identifiers (`retrieval`,\n`executor`) or on `model.eval()`. Findings sometimes cite files a skill does not contain — check\nagainst `find skills/<name> -type f` before acting.\n\nIf the skill has tests in `tests/<name>/`, run them:\n\n```bash\nuv run --with pytest python -m pytest tests/<name> -q\n\n# every skill's suite, one process each, after the repo-wide guard\nuv run --with pytest python tests/run_all.py\n```\n\n**One skill per pytest process.** Skills' `scripts/` directories own plain top-level module names —\n32 of them ship a `scripts/_common.py` — so collecting two skills into one interpreter resolves\n`_common` to whichever skill imported first and silently tests the wrong files. `tests/conftest.py`\nrefuses such a session; `tests/run_all.py` forks per skill.\n\n### The repo-wide guard\n\n```bash\nuv run --with pytest python -m pytest tests/_meta -q\n```\n\n`tests/_meta` is the fastest useful signal in the repo: pure standard library, no scientific\npackages, a couple of seconds. It runs the shared structural contract against **every** skill and\nfails if a skill ships `scripts/` without a suite under `tests/<name>/` or an entry in\n`tests/skill-requirements.toml`. `.github/workflows/skill-tests.yml` runs it on every pull request,\nso a skill with untested scripts cannot land. A full run of `tests/run_all.py` starts with it.\n\nIt is not one of the per-skill processes because it deliberately spans all of them at once — safe\nbecause it never imports skill code, only parses it.\n\n### The shared contract\n\n`tests/_contract/` holds the assertions every skill shares, so a per-skill suite contains only what\nis actually specific to that skill. `tests/conftest.py` registers it as the importable module\n`skill_contract`:\n\n```python\nimport skill_contract\n\n# every argparse script answers --help; skips when its packages are absent,\n# runs for real under --isolated\nCliHelpTests = skill_contract.cli.help_test_case(SKILL_ROOT)\n\n# for library-style scripts with an `if __name__ == \"__main__\"` worked example\nDemoBlockTests = skill_contract.cli.demo_test_case(SKILL_ROOT, (\"doe_designs.py\",))\n```\n\n- `structure` — frontmatter conformance, the 500-line limit, no tests or bytecode under `skills/`,\n  local links resolve, scripts parse, no `eval`/`exec`/`os.system`, no standard-library shadowing,\n  no hardcoded local paths, shell scripts valid. Run repo-wide by `tests/_meta`; do not duplicate\n  these in a per-skill suite.\n- `cli` — the `--help` and demo-block cases above.\n- `office` / `schematic` — behaviour for files several skills ship byte-identical copies of (the\n  OOXML tree under docx/pptx/xlsx; the AI schematic generator under five skills). `tests/_meta`\n  separately fails if those copies drift apart, so fix them together.\n\n### One environment per skill\n\nThe project environment deliberately does not carry the skills' scientific packages. Their upstream\npins are mutually exclusive — `opentrons` needs `numpy<2`, `esm` caps `transformers` below the\nversion the `transformers` skill targets, `geniml` and `spikeinterface` pin `zarr<3` against the\n`zarr-python` skill's 3.x, `bioservices` caps `lxml<6` against `matchms`, and `pytdc`, `molfeat`,\n`deepchem`, `histolab`, `vaex`, and `ete3` each need an interpreter older than 3.13. Installing them\ntogether forces every one of those skills to the losing side of a version fight.\n\nSo `--isolated` builds a throwaway `uv` environment per skill instead, from\n[`tests/skill-requirements.toml`](tests/skill-requirements.toml):\n\n```bash\npython tests/run_all.py --isolated                 # every suite, one env each\npython tests/run_all.py --isolated scanpy qiskit   # just these\n```\n\nEach entry lists the packages that skill documents, plus an optional `python` when the skill cannot\nrun on the default interpreter; uv downloads that interpreter on demand. Packages that cannot be\ninstalled at all — a GitHub-only SDK, a conda-forge-only library, a CUDA build — are recorded under\n`[unavailable]` with the reason, and the runner prints them so the gap shows up in test output.\n\nAdding a skill with `scripts/` means adding its `[skills.<name>]` entry — `tests/_meta` fails\nwithout one. Use `packages = []` for skills whose bundled tooling is standard-library only; they\nstill get a clean environment, and CI runs exactly that set on every pull request. uv caches wheels\nglobally, so repeat runs create each environment in milliseconds.\n\nThe full `--isolated` sweep is not run in CI: it builds one environment per skill, several of which\nneed a CUDA toolchain, a JDK, or a local MATLAB install. Run it before a release, or whenever you\ntouch the shared contract.\n\n## Skill diagrams\n\nEvery skill carries one generated workflow diagram at `docs/images/<skill-name>.png`. Creating a\nskill means creating its image; changing what a skill does means regenerating it. The image is not\noptional decoration — it is derived from the documentation, so an out-of-date one misrepresents the\nskill.\n\n`scripts/generate_skill_image.py` is local repository tooling, standard library only, and runs in\ntwo stages on one `OPENROUTER_API_KEY` (environment variable, repository `.env`, or `--api-key`):\na text model reads `SKILL.md` plus everything under `references/` and a manifest of `scripts/` and\n`assets/`, distils it into a description of one diagram, then an image model draws it. Because it\nreads the whole skill, run it **after** the documentation is final, not before.\n\n```bash\n# one skill -> docs/images/<name>.png, replacing any existing image\nuv run python scripts/generate_skill_image.py --skill <name>\n\n# see which files feed the reader, and where the image lands — no API calls, nothing billed\nuv run python scripts/generate_skill_image.py --skill <name> --dry-run\n\n# read the skill and print the diagram prompt without drawing it\nuv run python scripts/generate_skill_image.py --skill <name> --prompt-only\n\n# several skills in one batch\nuv run python scripts/generate_skill_image.py --skill <name-a> <name-b>\n\n# backfill everything missing an image, six at a time\nuv run python scripts/generate_skill_image.py --all --skip-existing -j 6\n```\n\nLook at the result before committing it. Image models misspell labels and occasionally point an\narrow at the wrong card; regenerate rather than ship a diagram whose text is wrong. `--quality low`\nmakes iteration cheap while checking composition, but commit a `high` render. Both the art direction\nand the reader's instructions live at the top of the script — change them there rather than\nhand-tuning one skill's prompt, so the set stays visually consistent.\n\n## Before opening a PR\n\n- Directory name and frontmatter `name` match exactly.\n- No `tests/` directory and no `test_*.py` anywhere under `skills/<name>/` — tests belong in\n  `tests/<name>/`.\n- Only the six spec-defined top-level fields; everything else under `metadata`.\n- `metadata.version` exists, is quoted, and is bumped if you changed an existing skill.\n- `metadata` is a block mapping; `openclaw` / `hermes` blocks are nested mappings.\n- `uv run skills-ref validate skills/<name>` passes.\n- If the collection version changes, `plugin.json` `version` matches `pyproject.toml`.\n- `uv run --with pytest python -m pytest tests/_meta -q` passes — this is what CI blocks on, and it\n  catches a missing suite, a missing `skill-requirements.toml` entry, a broken local link, a\n  leaked local path, and a drifted Agent Plugins manifest.\n- If the skill ships `scripts/`: a suite exists at `tests/<name>/`, a `[skills.<name>]` entry exists\n  in `tests/skill-requirements.toml`, and `python tests/run_all.py --isolated <name>` passes.\n- `docs/images/<name>.png` exists, and was regenerated if the change altered what the skill does.\n  Its labels are spelled correctly and its arrows point where they should.\n- Examples and scripts are tested, or clearly marked illustrative.\n- No secrets or private data; scan results clean or explained in the PR.\n","CLAUDE.md":"# CLAUDE.md\n\nRepository guidance for this project lives in [AGENTS.md](AGENTS.md). Read it and follow it.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# Repository Guidance\n\nThis repository is a collection of Agent Skills for science and research. Every skill lives in its\nown directory under `skills/` and must conform to the open\n[Agent Skills specification](https://agentskills.io/specification).\n\nRead this file before creating or changing a skill. `CONTRIBUTING.md` covers the same ground at\nmore length, plus the pull-request process.\n\n## What belongs here\n\n**In scope:** a narrow skill for one scientific package, database, platform, or research workflow —\n`scanpy`, `depmap`, `benchling-integration`, `experimental-design`.\n\n**Out of scope**, and routinely declined:\n\n- General software-engineering or coding-judgment skills — they compete for selection on every task.\n- General infrastructure with a scientific example bolted on (a vector database, a cloud SDK) —\n  accepting one implies carrying every competitor.\n- Broad \"orchestrator\" skills that route to other skills — they overlap every specialist by design.\n- A second provider for a service an existing skill already reaches.\n\nThe general-purpose skills that do exist are narrow output-format helpers (`docx`, `pdf`, `pptx`,\n`generate-image`, `markdown-mermaid-writing`). They are not precedent for broadening scope.\n\n## Layout\n\nThe repository root is an [Agent Plugins](https://agent-plugins.org/) 1.0.0 package: `plugin.json`\nplus the portable `skills/` tree. Keep `plugin.json` valid against the Agent Plugins manifest\nschema, and keep its `version` identical to `pyproject.toml` `[project].version`. Do not add\nnon-portable top-level fields to `plugin.json` (no inline MCP, hooks, or client-only keys — use\n`mcp.json` or a reverse-domain `extensions` namespace if those are ever needed).\n\n```text\nplugin.json                 # Agent Plugins manifest (repo root)\nskills/<skill-name>/\n├── SKILL.md        # required\n├── references/     # optional: long documentation, loaded only when needed\n├── scripts/        # optional: executable helpers\n└── assets/         # optional: templates and static resources\n```\n\nOnly `SKILL.md` is required inside each skill. Reference other files with relative paths from the\nskill root, kept one level deep.\n\n**Tests never live under `skills/`.** A skill directory ships only what an agent loads. Checks for a\nskill's scripts and structure go in the repository-level suite instead:\n\n```text\ntests/<skill-name>/          # same name as the skill directory\n├── test_scripts.py\n└── fixtures/                # optional test data\n```\n\n**Diagrams never live under `skills/` either.** Every skill has one generated workflow diagram at\n`docs/images/<skill-name>.png`, produced by `scripts/generate_skill_image.py` and kept in step with\nthe skill's documentation — see [Skill diagrams](#skill-diagrams).\n\nTests reach their skill through an explicit anchor, never a relative walk:\n\n```python\nSKILL_ROOT = Path(__file__).resolve().parents[2] / \"skills\" / \"<skill-name>\"\n```\n\n## Creating a skill\n\n1. Create `skills/<name>/` — **the directory name is the skill name** and must equal frontmatter\n   `name`.\n2. Write `SKILL.md` from the template below. Start at `metadata.version: \"1.0\"`.\n3. Add `references/`, `scripts/`, or `assets/` only when they earn their place.\n4. Run the commands and code you document. Scope claims to the release you actually tested\n   (\"targets stable GeoPandas 1.1.4\"), and mark anything untested as illustrative.\n5. If the skill ships `scripts/`, put their tests in **`tests/<name>/`** — never in the skill\n   directory. Fixtures go in `tests/<name>/fixtures/`.\n6. Validate and scan (below).\n7. Generate the skill's diagram — a new skill without `docs/images/<name>.png` is incomplete:\n\n   ```bash\n   uv run python scripts/generate_skill_image.py --skill <name>\n   ```\n\n```markdown\n---\nname: skill-name\ndescription: What the skill does and when an agent should use it, including the terms that should trigger it.\nlicense: MIT\ncompatibility: Requires Python 3.12+ with <package> installed. Needs network access.\nmetadata:\n  version: \"1.0\"\n  skill-author: Your Name\n---\n\n# Skill Title\n\n## When to use\n\nUse this skill when...\n\n## Workflow\n\n1. ...\n\n## Examples\n\n...\n```\n\n## Updating a skill\n\n1. Read the current `SKILL.md` and its supporting files first.\n2. Check upstream docs — APIs move, and the skill may be pinned to an older release.\n3. Make the smallest useful change.\n4. **Bump `metadata.version` in the same change**: minor for normal improvements (`\"1.2\"` →\n   `\"1.3\"`), major only for a breaking change or substantial redesign (`\"1.9\"` → `\"2.0\"`).\n5. Re-run any example, command, or script you touched, plus `tests/<name>/` if that suite exists.\n   Suites check that `metadata.version` is present and quoted, not what it equals, so a version bump\n   never needs a matching test edit.\n6. **Regenerate the diagram in the same change** whenever the edit changes what the skill does or\n   how its workflow runs — the picture is generated from `SKILL.md` and `references/`, so it goes\n   stale silently. The command overwrites `docs/images/<name>.png` in place:\n\n   ```bash\n   uv run python scripts/generate_skill_image.py --skill <name>\n   ```\n\n   A typo fix, a link repair, or a version bump alone does not need a new image.\n\n## Frontmatter\n\n`SKILL.md` starts with YAML frontmatter. **Only these six fields are allowed** — the spec defines a\nclosed set, and any other top-level key is a validation error:\n\n| Field | Required | Constraints |\n| --- | --- | --- |\n| `name` | Yes | 1–64 chars, lowercase letters/digits/hyphens only, no leading, trailing, or consecutive hyphens, and **must equal the directory name**. |\n| `description` | Yes | 1–1024 chars. Say what the skill does *and* when to use it, with the keywords that should trigger it. Write it in third person. |\n| `license` | No | License name, or a reference to a bundled license file. |\n| `compatibility` | No | Max 500 chars. Environment requirements only — omit it if the skill has none. |\n| `allowed-tools` | No | A **space-separated string**, e.g. `Read Write Edit Bash`. Not a YAML list, not comma-separated. |\n| `metadata` | No | Mapping of string keys to **string** values, except the host manifest blocks below. Required here: `metadata.version`. |\n\nPut anything else — authorship, upstream versions, review dates, client-specific config — inside\n`metadata`, never at the top level. In particular, Hermes' top-level\n`required_environment_variables` cannot be used here: it fails the validator and, because\n`strictyaml` rejects the whole document, takes `name` and `description` down with it. Declare\ncredentials in `compatibility` and `metadata.openclaw.envVars` instead.\n\n### Write block-style YAML, not JSON flow style\n\nThe reference validator parses frontmatter with `strictyaml`, which **rejects JSON-style flow\nmappings and sequences**. A flow mapping does not merely fail one check: the whole frontmatter\nfails to parse, so `name` and `description` become unreadable and the skill will not register.\n\n```yaml\n# Wrong -- breaks the validator\nmetadata: {\"version\": \"1.1\", \"skill-author\": \"K-Dense Inc.\"}\n\n# Right\nmetadata:\n  version: \"1.1\"\n  skill-author: K-Dense Inc.\n```\n\n### Quote `metadata` scalars\n\nQuote values that would otherwise be parsed as a number, boolean, or date — `version: \"1.0\"`,\n`last-reviewed: \"2026-07-23\"` — so they stay strings as the spec requires.\n\n### Host manifest blocks stay nested mappings\n\n`metadata.openclaw` and `metadata.hermes` are the documented exception: keep them as **nested\nmappings**, not JSON strings. OpenClaw's `resolveOpenClawManifestBlock()` requires\n`typeof candidate === \"object\"`, so a JSON string silently disables its dependency gating and\ncredential injection. Nested mappings still pass `skills-ref validate`.\n\n```yaml\nmetadata:\n  version: \"1.1\"\n  skill-author: Exa\n  openclaw:\n    primaryEnv: EXA_API_KEY\n    envVars:\n      - name: EXA_API_KEY\n        required: true\n        description: Exa search API key.\n  hermes:\n    category: research\n```\n\nOnly skills with external requirements need these blocks; most omit them. A failed `requires` /\n`requires_toolsets` gate *hides* the skill from the agent, so gate only on something the skill\ngenuinely cannot run without.\n\n## Body and layout\n\n- Keep `SKILL.md` under 500 lines. CI warns above that. Move long reference material into\n  `references/` so agents load it only when needed.\n- A skill directory ships only what an agent loads. Tests, fixtures, scratch data, and generated\n  artifacts stay out of it; tests go in `tests/<name>/`.\n- Give concrete workflows, commands, and worked examples rather than background explanation.\n- Name the required packages, system dependencies, credentials, and network access.\n- Include the scientific caveats and validation checks that matter.\n- Put fragile or repetitive logic in `scripts/` instead of asking the agent to recreate it.\n- Never include secrets, API keys, private URLs, or unpublished data.\n\n## Validate and scan\n\n```bash\nuv sync\n\n# spec conformance for one skill\nuv run skills-ref validate skills/<name>\n\n# every skill, the way CI does\nfor d in skills/*/; do uv run skills-ref validate \"$d\"; done\n```\n\n`.github/workflows/skill-spec-validation.yml` runs that on every PR touching `skills/`, plus the\nrepo rules `skills-ref` does not check: `metadata.version` present, `allowed-tools` a\nspace-separated string, `metadata` scalars quoted, and a warning past 500 lines.\n\nSecurity-scan new or substantially changed skills. Scanning uses\n[Cisco AI Defense Skill Scanner](https://github.com/cisco-ai-defense/skill-scanner) — the\n`cisco-ai-skill-scanner` package pinned in `pyproject.toml`, which detects prompt injection, data\nexfiltration, and malicious code patterns in Agent Skills. Its README documents the rule IDs and\nCLI flags; consult it when a finding's rule is unfamiliar.\n\n`.github/workflows/pr-skill-scan.yml` runs the repo wrapper for changed skills on every PR and\nposts a sticky comment, failing on HIGH or above:\n\n```bash\n# needs SKILL_SCANNER_LLM_API_KEY (see .env)\nuv run python scan_pr_skills.py skills/<name>\n\n# or the upstream CLI directly, without the repo wrapper\nuv run skill-scanner scan skills/<name> --use-behavioral\n```\n\n**Verify a finding against the code before \"fixing\" it.** Known systematic false positives:\n`BEHAVIOR_*_EXFILTRATION` and `BEHAVIOR_ENV_VAR_HARVESTING` on any skill that reads its own API key\nand calls its own service; `MDBLOCK_PYTHON_SUBPROCESS` on any `subprocess` snippet, including the\nsafe argument-list form; and `*_EVAL_EXEC` on substrings inside ordinary identifiers (`retrieval`,\n`executor`) or on `model.eval()`. Findings sometimes cite files a skill does not contain — check\nagainst `find skills/<name> -type f` before acting.\n\nIf the skill has tests in `tests/<name>/`, run them:\n\n```bash\nuv run --with pytest python -m pytest tests/<name> -q\n\n# every skill's suite, one process each, after the repo-wide guard\nuv run --with pytest python tests/run_all.py\n```\n\n**One skill per pytest process.** Skills' `scripts/` directories own plain top-level module names —\n32 of them ship a `scripts/_common.py` — so collecting two skills into one interpreter resolves\n`_common` to whichever skill imported first and silently tests the wrong files. `tests/conftest.py`\nrefuses such a session; `tests/run_all.py` forks per skill.\n\n### The repo-wide guard\n\n```bash\nuv run --with pytest python -m pytest tests/_meta -q\n```\n\n`tests/_meta` is the fastest useful signal in the repo: pure standard library, no scientific\npackages, a couple of seconds. It runs the shared structural contract against **every** skill and\nfails if a skill ships `scripts/` without a suite under `tests/<name>/` or an entry in\n`tests/skill-requirements.toml`. `.github/workflows/skill-tests.yml` runs it on every pull request,\nso a skill with untested scripts cannot land. A full run of `tests/run_all.py` starts with it.\n\nIt is not one of the per-skill processes because it deliberately spans all of them at once — safe\nbecause it never imports skill code, only parses it.\n\n### The shared contract\n\n`tests/_contract/` holds the assertions every skill shares, so a per-skill suite contains only what\nis actually specific to that skill. `tests/conftest.py` registers it as the importable module\n`skill_contract`:\n\n```python\nimport skill_contract\n\n# every argparse script answers --help; skips when its packages are absent,\n# runs for real under --isolated\nCliHelpTests = skill_contract.cli.help_test_case(SKILL_ROOT)\n\n# for library-style scripts with an `if __name__ == \"__main__\"` worked example\nDemoBlockTests = skill_contract.cli.demo_test_case(SKILL_ROOT, (\"doe_designs.py\",))\n```\n\n- `structure` — frontmatter conformance, the 500-line limit, no tests or bytecode under `skills/`,\n  local links resolve, scripts parse, no `eval`/`exec`/`os.system`, no standard-library shadowing,\n  no hardcoded local paths, shell scripts valid. Run repo-wide by `tests/_meta`; do not duplicate\n  these in a per-skill suite.\n- `cli` — the `--help` and demo-block cases above.\n- `office` / `schematic` — behaviour for files several skills ship byte-identical copies of (the\n  OOXML tree under docx/pptx/xlsx; the AI schematic generator under five skills). `tests/_meta`\n  separately fails if those copies drift apart, so fix them together.\n\n### One environment per skill\n\nThe project environment deliberately does not carry the skills' scientific packages. Their upstream\npins are mutually exclusive — `opentrons` needs `numpy<2`, `esm` caps `transformers` below the\nversion the `transformers` skill targets, `geniml` and `spikeinterface` pin `zarr<3` against the\n`zarr-python` skill's 3.x, `bioservices` caps `lxml<6` against `matchms`, and `pytdc`, `molfeat`,\n`deepchem`, `histolab`, `vaex`, and `ete3` each need an interpreter older than 3.13. Installing them\ntogether forces every one of those skills to the losing side of a version fight.\n\nSo `--isolated` builds a throwaway `uv` environment per skill instead, from\n[`tests/skill-requirements.toml`](tests/skill-requirements.toml):\n\n```bash\npython tests/run_all.py --isolated                 # every suite, one env each\npython tests/run_all.py --isolated scanpy qiskit   # just these\n```\n\nEach entry lists the packages that skill documents, plus an optional `python` when the skill cannot\nrun on the default interpreter; uv downloads that interpreter on demand. Packages that cannot be\ninstalled at all — a GitHub-only SDK, a conda-forge-only library, a CUDA build — are recorded under\n`[unavailable]` with the reason, and the runner prints them so the gap shows up in test output.\n\nAdding a skill with `scripts/` means adding its `[skills.<name>]` entry — `tests/_meta` fails\nwithout one. Use `packages = []` for skills whose bundled tooling is standard-library only; they\nstill get a clean environment, and CI runs exactly that set on every pull request. uv caches wheels\nglobally, so repeat runs create each environment in milliseconds.\n\nThe full `--isolated` sweep is not run in CI: it builds one environment per skill, several of which\nneed a CUDA toolchain, a JDK, or a local MATLAB install. Run it before a release, or whenever you\ntouch the shared contract.\n\n## Skill diagrams\n\nEvery skill carries one generated workflow diagram at `docs/images/<skill-name>.png`. Creating a\nskill means creating its image; changing what a skill does means regenerating it. The image is not\noptional decoration — it is derived from the documentation, so an out-of-date one misrepresents the\nskill.\n\n`scripts/generate_skill_image.py` is local repository tooling, standard library only, and runs in\ntwo stages on one `OPENROUTER_API_KEY` (environment variable, repository `.env`, or `--api-key`):\na text model reads `SKILL.md` plus everything under `references/` and a manifest of `scripts/` and\n`assets/`, distils it into a description of one diagram, then an image model draws it. Because it\nreads the whole skill, run it **after** the documentation is final, not before.\n\n```bash\n# one skill -> docs/images/<name>.png, replacing any existing image\nuv run python scripts/generate_skill_image.py --skill <name>\n\n# see which files feed the reader, and where the image lands — no API calls, nothing billed\nuv run python scripts/generate_skill_image.py --skill <name> --dry-run\n\n# read the skill and print the diagram prompt without drawing it\nuv run python scripts/generate_skill_image.py --skill <name> --prompt-only\n\n# several skills in one batch\nuv run python scripts/generate_skill_image.py --skill <name-a> <name-b>\n\n# backfill everything missing an image, six at a time\nuv run python scripts/generate_skill_image.py --all --skip-existing -j 6\n```\n\nLook at the result before committing it. Image models misspell labels and occasionally point an\narrow at the wrong card; regenerate rather than ship a diagram whose text is wrong. `--quality low`\nmakes iteration cheap while checking composition, but commit a `high` render. Both the art direction\nand the reader's instructions live at the top of the script — change them there rather than\nhand-tuning one skill's prompt, so the set stays visually consistent.\n\n## Before opening a PR\n\n- Directory name and frontmatter `name` match exactly.\n- No `tests/` directory and no `test_*.py` anywhere under `skills/<name>/` — tests belong in\n  `tests/<name>/`.\n- Only the six spec-defined top-level fields; everything else under `metadata`.\n- `metadata.version` exists, is quoted, and is bumped if you changed an existing skill.\n- `metadata` is a block mapping; `openclaw` / `hermes` blocks are nested mappings.\n- `uv run skills-ref validate skills/<name>` passes.\n- If the collection version changes, `plugin.json` `version` matches `pyproject.toml`.\n- `uv run --with pytest python -m pytest tests/_meta -q` passes — this is what CI blocks on, and it\n  catches a missing suite, a missing `skill-requirements.toml` entry, a broken local link, a\n  leaked local path, and a drifted Agent Plugins manifest.\n- If the skill ships `scripts/`: a suite exists at `tests/<name>/`, a `[skills.<name>]` entry exists\n  in `tests/skill-requirements.toml`, and `python tests/run_all.py --isolated <name>` passes.\n- `docs/images/<name>.png` exists, and was regenerated if the change altered what the skill does.\n  Its labels are spelled correctly and its arrows point where they should.\n- Examples and scripts are tested, or clearly marked illustrative.\n- No secrets or private data; scan results clean or explained in the PR.\n","category":"root","tokens":4600},{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nRepository guidance for this project lives in [AGENTS.md](AGENTS.md). Read it and follow it.\n","category":"root","tokens":27}]}