Skip to content

feat(skills): carry a category on every catalog skill - #425

Open
sleeyax wants to merge 5 commits into
OpenHands:mainfrom
sleeyax:feat/skill-categories-for-canvas-rail
Open

feat(skills): carry a category on every catalog skill#425
sleeyax wants to merge 5 commits into
OpenHands:mainfrom
sleeyax:feat/skill-categories-for-canvas-rail

Conversation

@sleeyax

@sleeyax sleeyax commented Jul 28, 2026

Copy link
Copy Markdown
  • A human has tested these changes.

Why

The OpenHands Skills page (/skills) shows every skill in one flat grid. With 58 public skills in
this repo alone, search is the only way to narrow it down, which only helps when you already know
what you're looking for. See OpenHands/OpenHands#16123 for the full motivation.

To group skills by topic, the UI needs a topical axis, and the catalog is the only place that can
own it. Marketplace entries already carry a category, but the values were ad-hoc and inconsistent
across manifests (development, productivity, integration, monitoring, documentation, …),
and they were never exported from the generated catalog, so the frontend could not read them.

Summary

  • Normalise category on every skill entry across marketplaces/*.json onto a fixed nine-value
    taxonomy: automations, environment, code-hosting, agent-authoring, code-quality,
    integrations, writing, design, other.
  • scripts/build-skills-catalog.mjs now reads those manifests, validates each skill's category
    against the taxonomy, fails the build on an unknown or conflicting value, and emits category
    on every SKILLS_CATALOG entry plus a SKILL_CATEGORY_IDS export. Skills with no marketplace
    entry fall back to other and are listed in a build warning.
  • Type the new field and export SkillCategoryId / SKILL_CATEGORY_IDS from skills/index.d.ts.
  • Document the category requirement in the "Adding a Skill" section of the README.

Issue Number

OpenHands/OpenHands#16123

How to Test

node scripts/build-skills-catalog.mjs   # regenerates skills/index.js; should be a no-op on this branch
python3 -m pytest tests/test_skills_catalog.py -q

Verify the generated catalog carries the field:

node -e "import('./skills/index.js').then(m => console.log(m.SKILLS_CATALOG.slice(0,3).map(s => [s.name, s.category])))"

Negative paths covered by the test suite: an unknown category value and the same skill given
conflicting categories in two manifests both throw at build time.

Result of python3 -m pytest tests/test_skills_catalog.py -q: 38 passed.

Video/Screenshots

n/a — build-time/catalog change with no UI of its own. The consuming UI is shown in
OpenHands/OpenHands#16124.

Notes

  • category on skill entries now means something different from category on plugin entries.
    Only entries whose source starts with ./skills/ are re-keyed onto this taxonomy; plugin
    entries keep their existing free-form values for Claude Code marketplace browsing. If anything
    downstream groups skill entries by the old values, this changes what it sees. Flagging for
    reviewers who know the marketplace consumers better than I do — happy to move this onto a
    separate skill_category field instead if the shared key is a problem.
  • Two skills (qa-changes, release-notes) have no marketplace entry and so land in other.
    Adding entries would mean creating .plugin/plugin.json plus vendor symlinks, i.e. publishing
    them as Codex/Claude Code plugins — out of scope here, and the fallback is deliberate rather
    than accidental.
  • buildCatalog() takes an optional marketplacesDir so fixture-based tests can point at an empty
    directory instead of inheriting categories from this repo's real manifests.
  • The consumer is feat(skills): filter the skills page with a faceted rail OpenHands#16124; it degrades to other for any category it doesn't
    know, so a newer catalog against an older frontend renders rather than breaking.

sleeyax added 4 commits July 26, 2026 22:01
The agent-canvas /skills page renders categories as a facet rail, and the previous values produced an unusable distribution: productivity held 16 skills, the top three buckets held 71% of all skills, and four buckets held exactly one skill.
Skill entries now use nine balanced categories; plugin entries keep their existing values because they serve Claude Code marketplace browsing.
buildCatalog now joins the `category` from marketplace skill entries onto the generated catalog, keyed by skill directory name.
Unknown values and cross-manifest disagreements fail the build; skills with no marketplace entry warn and fall back to "other".
SKILL_CATEGORY_IDS is exported so consumers drive their UI from the package instead of re-declaring the list.
TestCodegenEndToEnd builds catalogs from ephemeral tmp_path fixtures, but buildCatalog's marketplacesDir now defaults to the real marketplaces/ directory.
A future fixture named after a real skill (docker, github, linear, etc.) would silently pick up its real category or trip the real conflict/unknown-category checks.
Each fixture call site now passes an explicit empty marketplaces temp dir so fixtures stay isolated, and buildCatalog's doc comment calls out the need for callers to do this.
Also tightened test_conflicting_categories_across_manifests_throws to assert on the conflict message itself, not just that stderr happens to mention "docker".
The skill-category docblock was duplicated near-verbatim in the build script and index.d.ts, and its add-javadoc/github-pr-review rationale annotated marketplace JSON entries that live in neither file — 15c3dd3's message already carries it.
buildCatalog's six-line warning about fixture isolation restated 93e24ec's message, which the parameter itself and the tests already convey.
The four copies of the "empty marketplaces dir" comment collapse into an empty_marketplaces helper whose docstring states the isolation rule once.
Also adds the one comment that was genuinely missing: why flarglebargle is excluded from test_uncovered_skills_land_in_other.
@github-actions github-actions Bot added the type: feat A new feature label Jul 28, 2026
@sleeyax
sleeyax marked this pull request as ready for review July 28, 2026 11:58
Comment thread tests/test_skills_catalog.py
…-for-canvas-rail

# Conflicts:
#	skills/index.js
@enyst

enyst commented Jul 31, 2026

Copy link
Copy Markdown
Member

@OpenHands Do a /codereview on this PR please. Check out also the linked PR from openhands/openhands so you understand the whole, and review both. Post your feedback using gh api and enyst token.

@openhands-ai

openhands-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown

I'm on it! enyst can track my progress at all-hands.dev

@enyst enyst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Good taste

LGTM. I reviewed this together with OpenHands/OpenHands#16124. The catalog is the right owner for the taxonomy; the generator validates unknown and conflicting values, preserves the unrelated plugin-category semantics, gives every emitted skill a deterministic fallback, and keeps the generated JS/types/docs aligned. The generator was a clean no-op locally and the category smoke check passed.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟢 LOW
    The change extends a generated public data contract without changing runtime execution or security-sensitive paths. Validation and fallback behavior limit malformed-catalog risk.

VERDICT:
Worth merging: The producer contract is coherent and ready for the consumer PR.

KEY INSIGHT:
The taxonomy has one validated source of truth and reaches consumers through generated artifacts rather than duplicated frontend constants.

This review was generated by an AI agent (OpenHands) on behalf of enyst.

@openhands-ai

openhands-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Reviewed and posted feedback on both PRs using the enyst GitHub token:

  • OpenHands/extensions#425 reviewApproved. The producer taxonomy, validation, fallback, generated artifacts, and types are coherent.
  • OpenHands/OpenHands#16124 reviewCommented with four actionable findings:
    1. Cloud skills bypass category enrichment and collapse to other.
    2. Browser back/forward navigation cannot reliably restore the search query.
    3. Facet counts are not predictive once values in that group are selected.
    4. The 384px filters modal can overflow narrow mobile viewports.

Validation: extensions catalog generation was a clean no-op and the Node category smoke check passed. Local pytest execution was unavailable because pytest is not installed; no dependencies were installed. Both worktrees remain clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants